Compare commits

...

877 Commits

Author SHA1 Message Date
Andrey Prygunkov
5e26d52d70 #784: removed expired root certificate
from ca root certificate store: certificate “DST Root CA X3” used by
Lets Encrypt
2021-10-01 00:08:45 +02:00
Andrey Prygunkov
ae81c9403d updated version string to "21.2-testing" 2021-09-30 22:12:56 +02:00
Andrey Prygunkov
b0d35f9a09 updated version string to "21.1" 2021-06-03 16:55:57 +02:00
Andrey Prygunkov
ce7cd631c2 updated ChangeLog for v21.1 2021-06-03 14:20:06 +02:00
Andrey Prygunkov
0432cf13d3 #715: improved reporting for binding errors
on Windows
2021-04-23 20:24:42 +02:00
Andrey Prygunkov
799de88b3e #704: corrected line endings 2021-04-22 20:59:47 +02:00
Andrey Prygunkov
7ff3251dcf #682: allow special characters in URL for username and password 2021-04-21 20:20:21 +02:00
Andrey Prygunkov
97ae03bbd3 #704: corrected icon in Windows "uninstall program" list 2021-04-21 18:12:30 +02:00
Captain Trips
6bbfb6b7b7 #736: cast time_t to int for printf (#742)
This fixes crashes on systems with 64-bit time_t.
2021-04-20 23:56:23 +02:00
Andrey Prygunkov
f02bbbefd7 #725: set SameSite attribute for cooikes 2021-04-19 20:45:04 +02:00
Andrey Prygunkov
4d19c899bd #749, #688: fixed crash on windows 2021-04-18 21:36:53 +02:00
Andrey Prygunkov
1d008bd1f5 #748: removed outdated links from web interface
and merged Info and About tabs
2021-04-15 22:17:00 +02:00
Andrey Prygunkov
8c1e62ef49 fixed #731: file selector in WebKit based browsers doesn't allow to choose the same file again 2021-04-15 21:28:47 +02:00
Andrey Prygunkov
e18c25c231 #747: updated url of the global certificate storage file
in the build scripts
2021-04-15 20:55:20 +02:00
Lucas Held
6dbe6edbab #739: fixed processing of group command in nserv 2021-04-15 01:43:29 +02:00
Andrey Prygunkov
1ee8e02586 #745: fixed crash caused by malformed nzb files
: for file elements not having subject attribute a (somewhat) random
subject is generated and is used as file name; correct file names are
read from articles later anyway
2021-04-15 01:26:36 +02:00
Andrey Prygunkov
f8f9dd2b6d #720: fixed file allocating
on file systems where sparse files are not supported
2020-11-01 16:59:38 +01:00
Andrey Prygunkov
414ffcbc35 #688: always using dirbrowser snapshot
to fix issues with leftovers on cleanup
2020-05-21 18:42:42 +02:00
Andrey Prygunkov
0522b5f49d #694: support new error messages in unrar 5.80 2020-04-20 21:03:56 +02:00
Disconnect3d
575b823758 #679: fix strncasecmp size parameter off by ones 2020-04-20 19:36:01 +02:00
Andrey Prygunkov
a124a91a84 fixed #693: negative values for "FileSizeLo" in JSON-RPC 2020-04-20 19:32:15 +02:00
Sander
4546b0f368 #650, #651: corrected space characters in one js-file 2019-06-29 23:44:57 +02:00
Andrey Prygunkov
625e7a61e1 #648: update license text
because of change of Free Software Foundation address but also includes
minor formatting changes.
2019-06-22 22:01:13 +02:00
Andrey Prygunkov
f1c1373c7d #637: nzbget version on about page 2019-05-13 18:20:51 +02:00
pfidr34
5dda6b2e49 #634: correct typo in about dialog of web interface 2019-05-10 20:27:12 +02:00
Andrey Prygunkov
81aa56324f #635: fixed PC sleep mode not working (Windows only) 2019-05-09 23:20:00 +02:00
Andrey Prygunkov
a8533e7f0a updated version string to "21.1-testing" 2019-05-09 22:30:00 +02:00
Andrey Prygunkov
bbfcf07689 updated version string to "21.0" 2019-05-02 21:48:05 +02:00
Andrey Prygunkov
fd35e05b61 updated ChangeLog for v21.0 2019-05-02 21:45:50 +02:00
Andrey Prygunkov
3e0be12cb3 #629: added aarch64 binaries to Linux installer and Android installer 2019-04-20 23:11:14 +02:00
Andrey Prygunkov
d6e8f67927 #622, #135, ff69fbbeb9: fixed trimming of relative paths in config 2019-04-07 15:56:31 +02:00
Andrey Prygunkov
a159a1ff5a #612: better description of option UMask 2019-03-10 22:06:44 +01:00
Andrey Prygunkov
15f4955f38 #620: wildcards in option AuthorizedIP 2019-03-10 21:52:08 +01:00
Andrey Prygunkov
aac98b53ee #621: fixed: remote server could crash when feed with invalid api request 2019-03-10 13:54:53 +01:00
Andrey Prygunkov
fa4a5bb261 #618: 32-bit and 64-bit unrar and 7-zip on Windows 2019-03-04 01:11:44 +01:00
Andrey Prygunkov
d19c9b80e7 #611: removed suggestion of RC4 cipher 2019-02-28 21:31:08 +01:00
Andrey Prygunkov
c7716ae9b7 #351, #610, e3bd94189a: fixed: remote clients not displaying current download speed 2019-02-13 18:20:04 +01:00
Andrey Prygunkov
e07a6b9443 #351: sleep longer in frontend when console window is hidden
(only in Windows app)
2019-02-09 09:45:38 +01:00
Joe Groocock
fa57474d78 #608, #607: fix compilation with OpenSSL no-comp 2019-02-03 22:01:43 +01:00
Andrey Prygunkov
4299ac1354 #599: url encoding in macOS app
Fixed: macOS menubar widget can't connect if password contains special
characters.
2019-02-02 22:37:57 +01:00
Andrey Prygunkov
82dfec471b #351: sleep longer in curses frontend
This reduces CPU usage, especially in idle.
2019-02-02 14:55:08 +01:00
Andrey Prygunkov
3a5bc85962 #351: notify about url or nzb returned to queue
from history.
2019-01-26 21:09:48 +01:00
Andrey Prygunkov
25dc60e71f #351: sleep longer in queue coordinator
Up to 2 second when queue is empty or downloads are paused.
2019-01-26 18:58:09 +01:00
Andrey Prygunkov
855f3e8649 #351: sleep longer in feed coordintator
up to 60 seconds.
2019-01-26 18:54:52 +01:00
Andrey Prygunkov
bdc7ba38db #351: sleep longer in disk service
If there are no active downloads the disk service can now sleep for 10
seconds instead of 1.
2019-01-26 18:17:36 +01:00
Andrey Prygunkov
89427f42ce #351: "WorkState" is now observable 2019-01-26 18:16:17 +01:00
Andrey Prygunkov
a665dc5375 fixed compiler warning
'register' storage class specifier is deprecated and incompatible with
C++17
2019-01-26 16:33:30 +01:00
Andrey Prygunkov
adf3e05e1d #351: refactor: utility function "Sleep"
to replace direct calls to “usleep”, with parameter in milliseconds
instead of microseconds.
2019-01-22 22:23:40 +01:00
Andrey Prygunkov
e3bd94189a #351: refactor: moved changeable state into new Unit "WorkState.cpp"
from Unit “Options.cpp”. The latter now contains only program options
(which cannot be changed without reload).
2019-01-22 21:57:00 +01:00
Andrey Prygunkov
bb1cb68653 #351: sleep longer in queue coordinator
In idle sleeps for 0.5 sec. Wake up immediately when a new item is
added to queue. Waking up from paused queue can take longer (up to 0.5
sec).
2019-01-22 18:32:34 +01:00
Andrey Prygunkov
e91f37d566 #351: protect vars depended on condition
to avoid race conditions and lock ups
2019-01-22 00:11:13 +01:00
Andrey Prygunkov
57f4d2864b #351: refactor: use same name for cond var and mutex 2019-01-21 23:40:12 +01:00
Andrey Prygunkov
05c841880f #593: 794f240f48: fixed potential lock up
due to race condition
2019-01-21 21:39:58 +01:00
Andrey Prygunkov
92828acab0 #351: reworked timed services
Now sleeping much longer, up to next scheduled work, instead of often
wake ups to check if the work needs to be performed. This improves CPU
usage in idle.
2019-01-21 21:21:16 +01:00
Andrey Prygunkov
137c936830 #351: full pausing UrlCoordinator in idle
UrlCoordinator is now completely paused (waits without wake ups) when
there are no work for it.
2019-01-19 23:42:57 +01:00
Andrey Prygunkov
4826f04778 #351: corrected formatting 2019-01-19 23:40:26 +01:00
hugbug
15b4f55310 added new badges and updated texts in README 2019-01-19 14:11:53 +01:00
Andrey Prygunkov
0461f2ad55 #604: fixed an LGTM alert for Python 2019-01-19 13:44:09 +01:00
Andrey Prygunkov
67ca371c6b #604: suppress an LGTM alert
A false positive.
2019-01-19 13:19:45 +01:00
Andrey Prygunkov
a97a6d7c7f #604: fixed LGTM alerts for Python 2019-01-19 11:56:18 +01:00
Andrey Prygunkov
b6927e992e #604: fixed LGTM alerts for JavaScript 2019-01-19 11:55:59 +01:00
Andrey Prygunkov
59cae49344 #604: fixed LGTM alerts for C++ 2019-01-19 11:55:25 +01:00
Andrey Prygunkov
6bf097f1c3 #604: fine tune LGTM config 2019-01-19 11:23:09 +01:00
Xavier RENE-CORAIL
ad0592843c #582: add LGTM.com code quality badges 2019-01-18 19:32:15 +01:00
Andrey Prygunkov
8a09de775f #604: exclude third-party files from LGTM analysis 2019-01-18 00:45:00 +01:00
Andrey Prygunkov
adf7ec225b #362: save ParSetId into disk state
That’s needed to make direct rename work properly if the program was
reloaded in the middle of direct rename download process.
2019-01-17 19:45:59 +01:00
Andrey Prygunkov
d15722c72d #595: save original file name into disk state 2019-01-17 19:42:41 +01:00
Andrey Prygunkov
0776c6b057 #595: check original file names when looking for splitted fragments 2019-01-17 00:28:55 +01:00
Andrey Prygunkov
8f63eef312 #595: functional tests for par-join issue
Some tests are failing because the issue isn’t fixed yet.
2019-01-16 22:46:05 +01:00
fedux
8a59079627 #600: fixed deprecated OpenSSL calls
Since OpenSSL 1.1.0 we have:

 - ERR_remove_thread_state, ERR_remove_state: "They are now deprecated
   and do nothing".

 - ASN1_STRING_data: "This function is deprecated: applications should
   use ASN1_STRING_get0_data() instead".
2019-01-15 21:20:59 +01:00
Andrey Prygunkov
491d816bff #591: save only changed queue data during downloading
This is a speed optimisation for large queue.
2019-01-14 17:53:51 +01:00
Andrey Prygunkov
6dfe17c1d8 #597: c2b93c588b: slightly simplified code 2019-01-13 19:37:46 +01:00
Andrey Prygunkov
f3cf9317a6 #591: avoid superfluous savings of queue 2019-01-13 00:39:42 +01:00
Andrey Prygunkov
b0356d88d6 #591: use local buffer for formatting during saving disk state
This improves performance with large queue by avoiding many memory
allocations.
2019-01-12 21:47:56 +01:00
Andrey Prygunkov
c0d7a15afa #591: string format functions return new length 2019-01-12 21:43:33 +01:00
Andrey Prygunkov
fbfa793b20 #591: improved error reporting for queue disk state corruption 2019-01-12 19:48:44 +01:00
Andrey Prygunkov
a329c65eb3 #351: pause article cache loop when cache is empty
to improve CPU usage when idle
2019-01-11 21:52:45 +01:00
Andrey Prygunkov
b9c4c5b19e #597: static linking of std::thread in Linux installer 2019-01-07 19:11:52 +01:00
Andrey Prygunkov
a5f2c1c7c5 #597: 49e8fea0e2: use std::thread instead of platform implementation 2019-01-07 19:02:18 +01:00
Andrey Prygunkov
e2ea481799 #590: fixed compiler warning on MSVC 2019-01-07 18:53:49 +01:00
Andrey Prygunkov
c2b93c588b #597: use std::mutex and std::condition_variable on all platforms
wrapped them in custom classes for easier replacements, just in case.
2019-01-07 18:52:19 +01:00
Andrey Prygunkov
3934244a70 #597: use std::mutex and std::condition_variable on Windows
That’s the easiest way to get compatibility with Windows XP yet better
performance on Windows Vista and above.
2019-01-06 21:42:41 +01:00
Andrey Prygunkov
62ba9a5609 #597: implemented condition variable class for Windows
works on Windows Vista and newer.
2019-01-06 15:50:59 +01:00
Andrey Prygunkov
e7d4556f8b #590: 541a695e2f: fixed wrong type 2019-01-06 14:43:41 +01:00
Andrey Prygunkov
43c9bb78f3 #597: use ConditionVar instead of std:condition_variable 2019-01-06 13:14:56 +01:00
Andrey Prygunkov
e824c5b940 #597: implemented OS-specific condition variable class
Currently for POSIX only; Windows implementation follows.
2019-01-06 13:08:09 +01:00
Andrey Prygunkov
32a6bf18ad #597: reverted changes to Thread-unit
Due to compatibility issues on older platforms (issues discovered on
ARMv7 with GCC 5.2 but may not be limited to this platform) the usage
of C++11 thread- and synchronisation facilities has been reverted to
previous custom OS-specific implementation.
2019-01-06 12:50:28 +01:00
Andrey Prygunkov
2cb419691d #593: 794f240f48: fixed compiling error
in GCC 5 for armel. Promise/future are not supported there and were
replaced with condition_variable.
2019-01-04 19:38:46 +01:00
Andrey Prygunkov
a74722d8cc #590: 20036b73b8: fixed compiling error
when cross-compiling: PRId64 and others maybe undefined even if
<inttypes.h> exists
2019-01-03 23:17:20 +01:00
Andrey Prygunkov
0602e9d2f1 #593: use platform independent type 2019-01-03 15:54:02 +01:00
fedux
9713cbad5e #592: RemoteClient: Use strncpy instead of strcpy
Ensure that the aligned text is filled with zeroes to avoid any data
leak. Also fixed a typo.
2019-01-03 15:23:32 +01:00
hugbug
009cf9eee2 Merge pull request #593 from fedux/idle-pr
Idle CPU usage improvements
2019-01-03 15:20:29 +01:00
Federico Cuello
85995ad56f Pause FeedCoordination::Run thread for 1 sec using condition variables
Loop every second waiting on a condition variable to reduce the number
of CPU wake ups and keep responsiveness.
2019-01-03 12:30:34 +01:00
Federico Cuello
1f3067c1e3 Pause PrePostProcessor::Run thread using condition variables
Wait until new jobs or a Stop signal instead of looping, in a way that
doesn't reduce responsiveness.
2019-01-03 12:29:56 +01:00
Federico Cuello
794f240f48 Pause DoMainLoop until stop/reload signal in daemon mode
When in deamon mode, just wait for the Stop signal instead of looping
constantly, in a way that doesn't affect responsiveness.
2019-01-03 12:27:28 +01:00
Federico Cuello
49e8fea0e2 Use std::thread instead of platform implementation
Simplify thread handling by using std::thread.
2019-01-03 12:27:28 +01:00
Federico Cuello
fa8f8855f9 Use std::atomic for Thread class members
Before only m_threadCount was protected by a mutex but not the rest of
the bools that are read/written from different threads. This replaces
them by atomic values removing the need for the mutex and protecting the
previously unprotected bools, except for m_autoDestroy that it's only
set before starting the thread.
2019-01-03 12:27:26 +01:00
Federico Cuello
2c85def959 Replace custom Guard class by std::lock_guard and std::unique_lock
Use a typedef to maintain the name and use Guard for simple scoped lock
guards and UniqueLock when it needs to be movable.
2019-01-02 20:49:10 +01:00
Federico Cuello
fb3a27fde9 Replace m_threadMutex by static Mutex
Just use Mutex and remove the need to call Thread::Init()
2019-01-02 20:49:09 +01:00
Federico Cuello
b29131ffb8 Use std::mutex instead of custom class Mutex
Basically this is just removing the custom class and using a typedef to
keep the name. Most of the changes are just case for the lock/unlock
methods.
2019-01-02 20:46:48 +01:00
hugbug
31a34b58ea Merge pull request #590 from fedux/fix-warnings
Fix compile warnings
2018-12-29 14:00:53 +01:00
Federico Cuello
4a10fdb2df fix compile warning: -Wstringop-truncation / -Wstringop-overflow 2018-12-27 14:49:17 +01:00
Federico Cuello
541a695e2f fix compile warning: -Wsign-compare
Fix sign compare warning by improving casting choices.
2018-12-21 16:04:41 +01:00
Federico Cuello
07b7a766a2 fix compile warning: -Wclass-memaccess
Use value-initialization instead of clearing an object with memset.
2018-12-21 16:04:40 +01:00
Federico Cuello
1057e9194c fix compile warning: -Wmisleading-indentation 2018-12-21 15:12:34 +01:00
Federico Cuello
9eaf9fae9a fix compile warning: -Wmaybe-uninitialized
Initialize codepoint to avoid the warning.
2018-12-21 15:11:53 +01:00
Federico Cuello
34d157990d fix compile warning: -Wnonnull
Rewrite the intentional segafult to avoid the compile warning.
2018-12-21 15:07:43 +01:00
Federico Cuello
c93eb2087f fix compile warning: -Wreorder
Declare variables in the right order to avoid this warning.
2018-12-21 15:06:50 +01:00
Federico Cuello
1f89c037b9 fix compile warning: -Wunused-variable
Don't define variables only used for debuggin when debug is not enabled.
2018-12-21 15:01:00 +01:00
Federico Cuello
20036b73b8 fix compile warning: -Wformat
Use macros defined in inttypes.h for the proper format for 64 bit types
and fall-back to previously define format when inttypes.h is not
available.
2018-12-21 14:56:55 +01:00
Jurgen S
da3425af3f #585: proper UTF-8 encoding of email content
Updated script so the email content is encoded properly in UTF-8 to avoid "UnicodeEncodeErrors" when non ascii characters are used like german Umlauts etc.
2018-11-30 23:57:22 +01:00
Simon Chapman
4c482a91da #581: added python 3 compatibility to Logger.py script 2018-11-29 22:45:01 +01:00
Simon Chapman
458a1afb13 #578: added python 3 compatibility to EMail.py script 2018-11-19 08:20:57 +01:00
Andrey Prygunkov
3339a2c520 #538: android resolver workaround
isn’t necessary when building specifically for Android using Android
NDK.
2018-09-01 13:35:13 +02:00
Andrey Prygunkov
17c5a9cbc8 fixed #573: statistics for session download time and speed
may be way off on high load
2018-09-01 13:18:21 +02:00
Andrey Prygunkov
4db9ef2535 #541: fixed crash when adding many urls 2018-08-25 18:06:44 +02:00
Andrey Prygunkov
5a0eae7bf4 #541: fixed log messages printed twice 2018-08-25 17:54:28 +02:00
Sander
86ac23b6aa #567, #569: NextParamAsInt: Stop parsing at end-of-string
fixed potential crash in web-interface.
2018-07-30 19:18:22 +02:00
Andrey Prygunkov
fa1aa45fa7 #541: f3cb44e7b2: other case for DELETED/DUPE
queued URLs with lower dupescore have status DELETED/DUPE instead of
DELETED/MANUAL.
2018-07-27 17:19:16 +02:00
Andrey Prygunkov
f842a19544 #541: preserve logs for URL items
do not discard (cleanup) logs of URLs when loading disk-state
2018-07-24 22:47:39 +02:00
Andrey Prygunkov
f3cb44e7b2 #541: even better duplicate handling of urls
1) allow status DELETED/GOOD for URLs;
2) queued URLs with lower dupescore have status DELETED/DUPE instead of
DELETED/MANUAL.
2018-07-23 23:41:51 +02:00
Andrey Prygunkov
e54ffbaaaa #562: fixed: failures are being moved to DestDir
This only happenned for downloads without par2-files and without
archives.
2018-07-16 22:48:41 +02:00
Andrey Prygunkov
2d049f1904 #562: refactor: avoiding multiple dereferences 2018-07-16 21:22:58 +02:00
Andrey Prygunkov
5106979d5d #541: mark as good hides urls 2018-07-15 14:02:42 +02:00
Andrey Prygunkov
75d05bce4a #541: fixed url dupe handling for good/success status 2018-07-14 12:44:01 +02:00
Andrey Prygunkov
ea4ea2c901 #564: click on logo switches to downloads tab
Infos from about dialog moved onto settings page.
2018-07-13 17:17:50 +02:00
Andrey Prygunkov
5e15677218 #541: better duplicate handling of urls 2018-07-12 21:19:09 +02:00
Andrey Prygunkov
93ad31b9d8 #541: preserving age and size info for urls from feed
and showing it in history of webui
2018-07-10 20:12:51 +02:00
Andrey Prygunkov
14c5a1caf7 #541: delayed fetching of nzbs added via urls 2018-07-07 18:16:40 +02:00
Andrey Prygunkov
f52f5b5de9 #558: fixed test failures 2018-07-06 18:08:53 +02:00
Andrey Prygunkov
0916c2a908 #561: more deterministic cleanup of OpenSSL
to prevent crash when using OpenSSL-FIPS
2018-06-28 18:10:06 +02:00
Andrey Prygunkov
ab1238dde4 #558: added tests for unpack CRC error 2018-06-22 20:06:45 +02:00
Andrey Prygunkov
758ce4047b #525: force par-check for nzbs without archives 2018-06-19 22:37:45 +02:00
Andrey Prygunkov
c24bf0e8ce #538: added android toolchain script to distribution archive 2018-06-15 23:35:02 +02:00
Andrey Prygunkov
1264878a97 #538: added compatibility with Android Bionic C library 2018-06-13 21:03:34 +02:00
Andrey Prygunkov
a85ff314f3 #538: use precompiled headers for Android NDK builds 2018-06-13 21:01:34 +02:00
Andrey Prygunkov
a349ab08f7 #538: installer build script for Android NDK 2018-06-13 21:01:10 +02:00
Andrey Prygunkov
064de49edf #538: unpackers build script for Android NDK 2018-06-13 21:00:34 +02:00
Andrey Prygunkov
ae79c56c07 #538: toolchain build script for Android NDK 2018-06-13 21:00:11 +02:00
Andrey Prygunkov
d6353e9cee updated version string to "21.0-testing" 2018-06-12 23:09:04 +02:00
Andrey Prygunkov
d9d824631e updated version string to "20.0" 2018-06-06 21:34:34 +02:00
Andrey Prygunkov
2bd765b06f updated ChangeLog for v20.0 2018-06-06 20:09:37 +02:00
Andrey Prygunkov
f51c216417 #550: fixed SIMD status message for ARM CRC 2018-06-04 19:00:34 +02:00
Andrey Prygunkov
78b270d23e #550: workaround for GCC 7 bug on ARM
Fix for GCC 7 needing option “-fpermissive” to compile it’s own file
“arm_acle.h”.
2018-06-02 20:34:29 +02:00
Andrey Prygunkov
a4252a1e79 #549: force terminating remote processors
when terminating remote server to ensure all child threads are
terminated on reload/shutdown, even if connections were not closed as
expected
2018-06-01 23:38:10 +02:00
Andrey Prygunkov
1ac2be47d5 #549: force socket closing in remote server (Windows only)
to fix hanging connection to web-client
2018-06-01 23:27:55 +02:00
Andrey Prygunkov
9437a227ee #548: direct rename and direct unpack active by default
on new installations
2018-05-31 21:50:40 +02:00
Andrey Prygunkov
0d19722881 #547: improved duplicate detection for files with same subjects 2018-05-31 18:31:28 +02:00
Andrey Prygunkov
adfe5eef26 #542: fixed 7zip crashing
on newer Linux systems
2018-05-31 15:12:35 +02:00
Sander
321cddeeba #546: advice for letsencrypt in option descriptions 2018-05-30 23:49:50 +02:00
Andrey Prygunkov
44f08325f9 #438: proper program termination on Windows shutdown/logoff 2018-05-30 18:54:01 +02:00
Andrey Prygunkov
e601e77e5e #542: fixed unrar crashing
on newer Linux systems.
2018-05-28 21:38:10 +02:00
Sander
8e6ccfa8a7 #536: nshow IP address of incoming connection (#536) 2018-05-09 22:31:42 +02:00
Andrey Prygunkov
3eebee20aa #534: fixed logging of IPv6 addresses 2018-05-08 18:27:57 +02:00
Andrey Prygunkov
b83a9b9aff #533: detecting malformed articles and printing a warning 2018-05-06 22:46:25 +02:00
Andrey Prygunkov
05d7a8ede2 #533: fixed crash on malformed articles 2018-05-06 22:43:14 +02:00
Andrey Prygunkov
4d771036e2 #529: fixed missing file unlocking after direct rename
Also made locking more granular to avoid unnecessary locks for files
whose articles are not going to be discarded.
2018-05-01 21:22:34 +02:00
Andrey Prygunkov
7e659d8d97 #532: update make config 2018-04-15 18:34:14 +02:00
Andrey Prygunkov
137ac1a3ee fixed #532: wrong favicon used on Android
Dynamically activate icon targeted for iOS only when running on iOS.
2018-04-15 18:16:46 +02:00
Andrey Prygunkov
3a4e6623db fixed #529: crash when flushing article cache after direct rename 2018-04-03 13:02:57 +02:00
Andrey Prygunkov
c2669b359e fixed #527: deleting of active par-job may crash the program 2018-04-02 21:17:24 +02:00
Andrey Prygunkov
8bffb51974 fixed #528: tests may fails on Windows due to locked files 2018-04-02 20:12:54 +02:00
Andrey Prygunkov
81be21b540 #483: added new images to distribution archive 2018-03-25 18:14:57 +02:00
Andrey Prygunkov
222d6a1f6d #509: fixed incorrect renaming in rar-renamer
Further improved detection of rar-sets.
2018-02-22 00:19:51 +01:00
hatem zidi
cd6bf682f9 #483: optimized mobile touch icon (favicon)
Optimized for desktop and mobile platforms.
2018-02-02 18:40:45 +01:00
Andrey Prygunkov
d93769021a #501: fixed race condition in queue script coordinator 2018-01-29 23:54:30 +01:00
Andrey Prygunkov
cf0d086b57 #485: HttpOnly for cookies
to improve security
2018-01-26 00:08:51 +01:00
Andrey Prygunkov
bf53c6eaa6 #496: don't log passwords for incorrect login attempts 2018-01-26 00:00:41 +01:00
Andrey Prygunkov
9b50760006 #477: dupe check now case insensitive 2018-01-25 23:50:07 +01:00
Andrey Prygunkov
b7102894d7 #498: fixed pp-parameter initialization 2018-01-25 23:47:53 +01:00
Andrey Prygunkov
db102f5a15 #498: fixed wrong case in unpack password parameter 2018-01-25 17:39:18 +01:00
Andrey Prygunkov
d9cb0026bd #498: case insensitive pp-parameters 2018-01-25 17:38:35 +01:00
bket
5893d03f1b #497: added LibreSSL support 2018-01-20 18:17:38 +01:00
Andrey Prygunkov
18d138648b fixed #474: build fails on musl 2017-11-15 22:36:10 +01:00
Andrey Prygunkov
93a43e711f #471: more robust news server connection test
This fixes connection test errors with servers checking message id
format correctness.
2017-11-11 11:50:23 +01:00
Andrey Prygunkov
2b52dc5bfe #468: compatibility with Android 4 and older 2017-11-10 23:59:17 +01:00
Andrey Prygunkov
ce844367e7 #468: DNS resolving on Android 2017-11-07 00:21:58 +01:00
Andrey Prygunkov
64a5a78866 #467: print par2 creator packet
as INFO on par-check start and as part of ERROR message on par-repair
failure
2017-11-06 22:47:16 +01:00
Andrey Prygunkov
6f9fb29595 #466: removed less useful debug messages 2017-11-06 22:20:03 +01:00
Andrey Prygunkov
0ee60ab844 #466: keep log-file longer open
that improve logging performance especially in debug build
2017-11-06 22:18:40 +01:00
Andrey Prygunkov
8dfca2a542 #461, 7deb3c1b68: renamed "LogBuffer" in webui 2017-11-04 13:55:30 +01:00
Andrey Prygunkov
76bdd63e60 #465: fixed decoding errors on ARMv5 2017-11-03 17:37:10 +01:00
Andrey Prygunkov
ef78cbfc74 #464: fixed: x86_64 universal build fails on PaX kernel 2017-11-02 19:23:01 +01:00
Andrey Prygunkov
74768b2183 #454, 801bf1ae7c: reactivated simd-decoder
which got accidentally deactivated
2017-11-01 22:56:27 +01:00
Andrey Prygunkov
801bf1ae7c #454: fixed: missing data in raw article mode
- option “RawArticle”
2017-11-01 21:05:11 +01:00
Andrey Prygunkov
a901deff03 #463: stop static initialisation invasion 2017-10-31 11:30:54 +01:00
Andrey Prygunkov
67245d6ca8 #448, 186da63056: NServ memory cache switch
no longer has memory limit parameter. The parameter wasn’t respected
anyway.
2017-10-29 19:14:43 +01:00
Andrey Prygunkov
2d70e1de21 #462: fixed: backup servers not used on certain article decoding errors 2017-10-29 17:52:39 +01:00
Andrey Prygunkov
7deb3c1b68 #461: renamed option "LogBufferSize" to "LogBuffer"
and removed obsolete options “SaveQueue” and “ReloadQueue” from
config-file
2017-10-29 13:07:00 +01:00
Andrey Prygunkov
d4886ac7d1 #461: removed option "BrokenLog" 2017-10-29 12:51:49 +01:00
Andrey Prygunkov
5b3372107d #461: removed option "AccurateRate" 2017-10-29 12:43:06 +01:00
Andrey Prygunkov
07c54740a7 #461: removed option "TerminateTimeout"
No thread killing anymore. Hanging downloads are gracefully cancelled
after timeout set in “ArticleTimeout” or “UrlTimeout”.
2017-10-29 12:34:16 +01:00
Andrey Prygunkov
af111adbde #461: removed options "SaveQueue" and "ReloadQueue" 2017-10-28 16:17:45 +02:00
Andrey Prygunkov
d31a734a5c #460: better handling broken connections 2017-10-27 19:38:42 +02:00
Andrey Prygunkov
54f14f5efa #459: use glibc instead of uClibc
in universal installer builds for Linux
2017-10-27 00:40:12 +02:00
Andrey Prygunkov
18fbd12f2c #454: better target CPU detection in configure 2017-10-25 21:31:50 +02:00
Andrey Prygunkov
ff671e722d #455: changed default location of log-file 2017-10-23 23:29:40 +02:00
Andrey Prygunkov
15c292653e #458: compiling without libxml2 to test dev environment
new configure-parameter “--disable-libxml2”.
2017-10-22 23:52:13 +02:00
Andrey Prygunkov
c0aed9af48 #454: fixed compiling error on aarch64 2017-10-22 20:35:51 +02:00
Andrey Prygunkov
597e4fd034 #454: removed force-inline
since it’s no longer needed after moving loop into inner functions;
better compatibility with different compilers
2017-10-21 00:04:19 +02:00
Andrey Prygunkov
3c2575bc26 #454: removed option "RateBuffer"
since it’s no longer needed with raw decoder which works on 4KB buffer
already
2017-10-20 20:54:52 +02:00
Andrey Prygunkov
50c1ca588c #454: option "RawArticle" works again 2017-10-20 20:52:42 +02:00
Andrey Prygunkov
da9c8b1138 #454: fixed buffer overrun
and compiler warnings on VC++
2017-10-20 18:07:01 +02:00
Andrey Prygunkov
c59ab2d9dc #454: one-pass simd decoder
updated SIMD decoder, support for end-of-stream detection
2017-10-19 18:27:04 +02:00
Andrey Prygunkov
35fca1479c #454: fixed rare crash in stream end detection 2017-10-16 18:15:21 +02:00
Andrey Prygunkov
54c5a061c8 #454: fixed align issue on Windows 32 bit 2017-10-16 18:14:11 +02:00
Andrey Prygunkov
3a0489a4a9 #435: fixed warnings in 64 bit mode on Windows 2017-10-16 18:13:34 +02:00
Andrey Prygunkov
a31fb733a2 #454: SIMD CRC routines for Intel and ARM 2017-10-12 21:09:24 +02:00
Andrey Prygunkov
2691eff535 #448: speed optimisation in NServ
when using unlimited memory cache (command line switch “-m 0”)
2017-10-10 19:11:40 +02:00
Andrey Prygunkov
37b04c593a #448: don't try deleting files that don't exist
- a small optimisation to reduce disk activity
2017-10-08 21:11:05 +02:00
Andrey Prygunkov
b9b1c76ada #454: using raw-decoder from node-yencode library 2017-10-08 21:08:23 +02:00
Andrey Prygunkov
69a0db63f6 #454: integrated node-yencode library by Anime Tosho
1) integrated the library; 2) splitted units by CPU architecture; 3)
extended makefile and configure script to detect CPU architecture and
use appropriate compiler flags; 4) runtime CPU features detection for
x86 and ARM with dynamic code  dispatching; 5) temporary (for test
purposes) printing info about SIMD support to stdout on program
startup; 6) new SIMD routines are not yet used in the program
2017-10-08 20:49:13 +02:00
Andrey Prygunkov
e9926d92e0 fixed compiler warnings 2017-10-09 13:35:43 +02:00
Andrey Prygunkov
f5aa27979c #448, 186da63056: small speed optimisation in NServ 2017-10-09 13:35:10 +02:00
BernCarney
24a4542c14 #452, #453: proper URL encoding in example pp-scripts
Updated scripts to accept special characters in nzbget password and username.
2017-10-05 19:31:49 +02:00
Andrey Prygunkov
bb95e1f274 #448, 186da63056: corrected file mode 2017-09-29 21:45:52 +02:00
Andrey Prygunkov
186da63056 #448: memory cache in NServ
: new command line switch “-m”
2017-09-28 20:45:06 +02:00
Andrey Prygunkov
1facedb694 #451: speed control in NServ
: new command line switches “-w“ and “-r”
2017-09-28 17:45:14 +02:00
Andrey Prygunkov
54eb8e1291 #448: new option "SkipWrite"
replaces compiler define “SKIP_ARTICLE_WRITING”. 2) renamed option
“Decode” to “RawArticle”. 3) option “CrcCheck” moved from section
“Download Queue “ into section “Check and Repair”
2017-09-28 17:31:47 +02:00
Andrey Prygunkov
80b67383e3 #450: speed up yenc decoder
by optimising main decoding loop
2017-09-24 14:24:10 +02:00
Andrey Prygunkov
406a78218a #448, 71505340d0: allow CRC calculation even if
decoding is disabled via SKIP_ARTICLE_DECODING
2017-09-23 00:29:22 +02:00
Andrey Prygunkov
262df77f74 #449: new option "RateBuffer"
to configure speed meter update rate
2017-09-22 23:45:26 +02:00
Andrey Prygunkov
71505340d0 #448: disable article writing and decoding
Disabling is now possible for test purposes via defines
SKIP_ARTICLE_WRITING and SKIP_ARTICLE_DECODING (nzbget.h)
2017-09-22 20:25:05 +02:00
Andrey Prygunkov
bddb0bb26d #447: better optimisation settings (Windows)
Adjusted Visual Studio project to compile with more aggressive
optimisation settings.
2017-09-21 18:45:30 +02:00
Andrey Prygunkov
d90a40909b #446: faster CRC computation 2017-09-21 18:06:17 +02:00
Andrey Prygunkov
2bdc87c198 fixed #445: program hangs during update on Linux 2017-09-18 17:49:12 +02:00
Andrey Prygunkov
e97a0fde11 #443: functional tests on Travis CI 2017-09-16 09:54:41 +02:00
Andrey Prygunkov
eb18608522 #443: functional tests find 7z and par2 automatically 2017-09-15 20:25:40 +02:00
Andrey Prygunkov
481e7b3d2b refactor: removed updates from article writer 2017-09-13 19:19:01 +02:00
Andrey Prygunkov
8545cb3581 #426: show unpack password as plain field 2017-09-13 18:39:40 +02:00
Andrey Prygunkov
e422fea746 #440: merge branch '440-update-check' into develop 2017-09-11 21:18:53 +02:00
Andrey Prygunkov
2ce9f0df38 #440: notification control from main site
To not update all installations on release day but rather gradually
within several days (as defined in update-info file on web-site).
2017-09-11 21:17:51 +02:00
Andrey Prygunkov
36de095e51 #442: improved volume detection in rar-renamer 2017-09-11 20:56:03 +02:00
Andrey Prygunkov
9b05f779f6 #432, #421, b4bcc82abe: remote-server cleanup
Use “close(socket)” when “accept”-ing connections and use
“shutdown(socket)” otherwise.
2017-09-07 17:59:10 +02:00
Andrey Prygunkov
38efd4a4de #440: help screen for update dialog 2017-09-07 17:48:51 +02:00
Andrey Prygunkov
80b8ee8dfb #440: automatic update check
New option "UpdateCheck”.
2017-09-05 20:31:11 +02:00
Andrey Prygunkov
47b1c1a2dd #49, #260: 9dc2b8c71b: corrected formatting 2017-09-05 20:19:41 +02:00
Andrey Prygunkov
7417160da9 #435: thread memory cleanup when using OpenSSL 2017-09-05 19:51:17 +02:00
Andrey Prygunkov
a41e010165 #438: fixed propagation delay 2017-09-04 20:28:00 +02:00
Andrey Prygunkov
cbe7b1e051 #431: fixed broken SSL in built-in web-server 2017-09-04 20:26:19 +02:00
Andrey Prygunkov
00a5b68d84 #439: Authorized IP not working on IPv6 (fix for Windows) 2017-09-04 20:25:47 +02:00
Andrey Prygunkov
561713dbed #435: using "windows" instead of "win32" in setup file name 2017-09-03 11:27:55 +02:00
Andrey Prygunkov
cce9338909 #435: reorganised Windows setup files 2017-09-02 22:20:29 +02:00
Andrey Prygunkov
515fd9298d fixed #439: Authorized IP not working on IPv6 2017-09-02 19:29:33 +02:00
Andrey Prygunkov
0ee72d2dd7 #438: speed improvement in queue management
when downloading with very large queue (thousands of nzbs).
2017-09-02 11:37:57 +02:00
Andrey Prygunkov
57f932cfab #438: hibernating all file infos in one large disk state file
to greatly improve startup time
2017-09-02 11:36:38 +02:00
Andrey Prygunkov
8f803c2f21 #438: great speed optimization for queue-dir cleanup 2017-09-02 11:34:07 +02:00
Andrey Prygunkov
89bd5d6dfe #435: added build script for Windows into repository 2017-09-01 23:01:56 +02:00
Simon Nicolussi
49a0292053 #437: don't rely on sizes of externally generated files
Tests that try to match exact file sizes are prone to break for unexpected versions of p7zip and par2cmdline.
2017-08-31 22:38:42 +02:00
Andrey Prygunkov
a60d8d1273 #435: fixed compiling error with older OpenSSL versions 2017-08-31 21:03:58 +02:00
Andrey Prygunkov
44ea3d02ab #435: extended Windows setup for 64 bit binaries 2017-08-30 23:21:01 +02:00
Andrey Prygunkov
fe9f208f20 #435: better cleanup when using OpenSLL
To avoid memory leaks report when linking OpenSLL statically.
2017-08-30 23:20:09 +02:00
Andrey Prygunkov
20e8bb6ebc #435: project configuration for 64 bit (Windows)
Now using only static libraries.
2017-08-30 23:18:57 +02:00
Andrey Prygunkov
0709f248ee #435: fixed warnings in 64 bit mode on Windows 2017-08-30 22:22:29 +02:00
Andrey Prygunkov
35d8aa5fa7 #435: fixed compiling error if no regex.h 2017-08-28 21:06:45 +02:00
Andrey Prygunkov
9f80f45fb9 #435: compatibility with windows 64 bit 2017-08-28 21:05:52 +02:00
Andrey Prygunkov
763fe425d6 #433: better username/password validation
when testing connection on settings page
2017-08-27 20:28:15 +02:00
Andrey Prygunkov
9c86dc70bd #421, #434: fixed: filter buttons don't work in history 2017-08-27 17:37:44 +02:00
Andrey Prygunkov
1f6a360de5 #421: fixed status buttons on history tab 2017-08-26 18:10:54 +02:00
Andrey Prygunkov
dcdc41ca9a #421, #422: URL components should not be encoded as JSON 2017-08-26 11:34:36 +02:00
Andrey Prygunkov
6d307a05f8 #431: use remote address in error reporting
for incoming connections
2017-08-25 20:22:55 +02:00
Andrey Prygunkov
83c15b1f05 fixed #431: NServ terminates if client interrupts connection 2017-08-25 20:21:40 +02:00
Andrey Prygunkov
43fc121219 #430: article statistics for par-files after direct rename 2017-08-25 18:56:09 +02:00
Andrey Prygunkov
4b729eb0f0 fixed #430: pause and article statistics after direct rename 2017-08-24 14:53:25 +02:00
Andrey Prygunkov
0158614da2 #421: update downloads table even if no changes
when there are active downloads in order to recalculate estimated time
2017-08-24 13:46:37 +02:00
Andrey Prygunkov
ca7807fa92 #421: fixed missing semicolon in raphael.min.js 2017-08-23 18:07:59 +02:00
Andrey Prygunkov
97018ae102 #424: resume detected non-par2-files
when direct rename is active
2017-08-15 12:51:44 +03:00
Andrey Prygunkov
cbe6c6a340 fixed #426: options formatted as password fields when they shouldn't 2017-08-10 22:22:12 +02:00
Andrey Prygunkov
d84ec5685b #425: cross-compiling for FreeBSD using Clang 2017-08-07 12:31:44 +02:00
Andrey Prygunkov
557e0580a7 #421: 45b5727374: fixed messages filter buttons disappeared 2017-08-05 22:58:20 +02:00
Andrey Prygunkov
43c0bdd9d3 #423: Linux installer compatibility with FreeBSD 2017-08-04 23:57:57 +02:00
Andrey Prygunkov
86bcb7073c #420: support for redirect codes 303, 307 and 308
in web-client for fetching of rss feeds and nzb-files
2017-08-04 21:43:40 +02:00
Andrey Prygunkov
6cf0edd278 #421: added debug logging for etags 2017-08-01 21:36:10 +02:00
Andrey Prygunkov
b4bcc82abe #421: fixed crash when disconnecting web-clients on Windows 2017-08-01 21:35:48 +02:00
Andrey Prygunkov
6fb1ea1cff #421: support keep-alive in all responses 2017-07-31 22:50:26 +02:00
Andrey Prygunkov
3ee9125100 #421: better handling shutdown in remote server 2017-07-31 20:30:24 +02:00
Andrey Prygunkov
fad2be0e0f #421: new option "RemoteTimeout"
to define timeout for incoming connections including timeout for
keep-alive.
2017-07-31 20:24:02 +02:00
Andrey Prygunkov
2763f1a522 #421: support for keep-alive connections in built-in web-server 2017-07-31 19:47:17 +02:00
Andrey Prygunkov
1214c79eab correction in debug logging
which could cause crash on shutdown in debug mode
2017-07-31 18:02:24 +02:00
Andrey Prygunkov
7ee0b60361 #418: fixed variadic macros detection 2017-07-31 17:59:11 +02:00
Andrey Prygunkov
0135e605a8 #421, #422: do not parse json-response if it will not be used
… and small refactorings and fixes for error reporting
2017-07-30 23:40:54 +02:00
schnusch
546324d891 #421, #422: added detection of cached responses in WebUI's RPC 2017-07-30 23:40:47 +02:00
schnusch
43563e8dfb #421, #422: removed remnants of 412 error handling 2017-07-30 23:40:38 +02:00
schnusch
4f5d357e3c #421, #422: use GET requests for safe JsonRPC methods in WebUI 2017-07-30 23:40:30 +02:00
Andrey Prygunkov
a6c120bc82 #421, #422: avoid table updates if no changes 2017-07-30 23:40:29 +02:00
Andrey Prygunkov
18f673e6b3 #421, #422: allow caching for more API methods
1) All safe methods are now cacheable.
2) Corrected debug code, accidentally pushed in previous commit (#ifdef
DISABLE_PARCHECK).
2017-07-30 23:40:29 +02:00
Andrey Prygunkov
5ac7c0398e #421, #422: adjustments in ETag support
1) convert MD5 hash into string using standard method instead of base64;
2) if par2 isn’t available using another hash function from Util-unit;
3) avoid gzipping of response if it isn’t sent;
4) use BString class for header string formatting.
2017-07-30 23:40:29 +02:00
schnusch
0008f040b3 #421, 422: added support for Etag an If-None-Match HTTP headers
The web server now support Etag generation for static files and some RPC
methods. If If-None-Match is given in the request and matches with the Etag
generated for the response than no data is sent and 304 or 412 is returned.

The JavaScript RPC calls also support the new HTTP error code by buffering
Etags and responses and will reuse the previous response if 412 is returned.
2017-07-30 23:40:13 +02:00
Andrey Prygunkov
45b5727374 #421: call multiple API-methods simultaneously 2017-07-28 00:42:25 +02:00
Andrey Prygunkov
f001b0744b #421: reduce number of requests when loading webui
by combining all javascript-files into one and all css-files into one
2017-07-28 00:41:18 +02:00
Andrey Prygunkov
2124a886f8 #418: updated POSIX build files to newer autotools version
- compatibility with newer autotools;
- compatibility with newer platforms such as aarch64.
2017-07-26 23:40:09 +02:00
Andrey Prygunkov
7f4b15b4de #416: fixed wait interval 2017-07-26 18:50:07 +02:00
Andrey Prygunkov
68c74a5a30 #416: better error handling when fetching rss feeds 2017-07-24 21:33:48 +02:00
Stefaan Ghysels
01d4ebb800 #417: fixed linux installer failure on android emulator 2017-07-24 18:57:06 +02:00
Tobias Geerinckx-Rice
f56e01d200 #414: fixed compiler error when building using GnuTLS 2017-07-16 07:24:50 +02:00
Andrey Prygunkov
cdc5c5515f fixed #412: unpack using password file doesn't work on Windows
Also added more debug output for future use.
2017-07-12 20:48:59 +02:00
Andrey Prygunkov
67195e7683 #400: adjustments to unix domain sockets mode 2017-07-09 21:48:46 +02:00
Andrey Prygunkov
499e3d5d8f fixed typo in project file for Visual Studio 2017-07-09 20:11:12 +02:00
schnusch
0ee9083627 #400: support for file sockets (POSIX only)
Option "ControlIP" can be set to local file path to use file sockets instead of network sockets.
2017-07-09 19:52:22 +02:00
Andrey Prygunkov
726a6154be updated version string to "20.0-testing" 2017-07-09 19:53:09 +02:00
Andrey Prygunkov
c0eedc342b updated version string to "19.1" 2017-07-08 18:20:02 +02:00
Andrey Prygunkov
eb4b8b30e1 updated ChangeLog for v19.1 2017-07-08 18:13:06 +02:00
Andrey Prygunkov
593e29f163 updated version string to "19.1-testing" 2017-07-02 18:08:17 +02:00
Andrey Prygunkov
c4d29bc57f #408: abort direct unpack if destination path was changed
For example caused by a change of category during direct unpack.
2017-07-02 17:26:35 +02:00
Andrey Prygunkov
92db424ce0 #405: safety check for presence of unpacked files
Before trying to reuse them in default unpack module.
2017-07-01 22:32:11 +02:00
Andrey Prygunkov
17fbb795c8 #407: fixed: rar-rename may fail to read encrypted rar3-archives 2017-07-01 21:59:19 +02:00
Andrey Prygunkov
35e65e792b #406: fixed incorrect renaming in rar-renamer
Improved detection of rar-sets.
2017-06-30 19:09:34 +02:00
Andrey Prygunkov
486b9d7d2b #405: discard info about extracted archives
if found archive files not processed by direct unpack; to prevent
reusing on next unpack attempt
2017-06-30 17:36:19 +02:00
Andrey Prygunkov
3abaa0fb3f updated version string to "20.0-testing" 2017-06-29 21:53:50 +02:00
Andrey Prygunkov
810ddc8356 updated version string to "19.0" 2017-06-25 19:21:32 +02:00
Andrey Prygunkov
1a840f894e updated ChangeLog for v19.0 2017-06-25 19:05:04 +02:00
Andrey Prygunkov
928e0a6006 fixed #399: error when compiling without par-check 2017-06-23 23:22:49 +02:00
Andrey Prygunkov
a4cca673dc fixed #398: crash between post-processing steps 2017-06-16 17:51:28 +02:00
Andrey Prygunkov
fb9b84a23b #371: wait for direct rename completion before direct unpack
Redo accidentally undone 5df06a2626.
2017-06-16 16:45:55 +02:00
Andrey Prygunkov
bc2b9de6a9 #392: added link to wiki page 2017-06-10 18:07:31 +02:00
Simon Nicolussi
7793f64b77 #386: don't write beyond buffer when reading a signature 2017-06-10 18:04:30 +02:00
Andrey Prygunkov
cad3116b5b #392: Windows crash dump support
Also renamed option "DumpCore" to "CrashDump"; new option "CrashTrace".
2017-06-10 13:13:16 +02:00
Andrey Prygunkov
dd714355c4 nzbget/nzbget#388: updated wiki links to use new url format 2017-06-09 18:41:40 +02:00
Andrey Prygunkov
0a73a0c31f fixed #387: asterix passed as parameter to extension scripts
(Windows only)
2017-06-07 18:51:16 +02:00
Andrey Prygunkov
7f393d050c #362: removed unnecessary code 2017-06-07 17:25:24 +02:00
Andrey Prygunkov
5dcca7294f #371: respect PostStrategy when starting another direct unpack
to avoid running way too many direct unpack jobs at the same time.
2017-06-06 15:32:24 +02:00
Andrey Prygunkov
53da5725c2 #362, #382: articles yEncoded with less than 68 characters
are now correctly processed by direct rename.
2017-05-31 22:00:25 +02:00
Andrey Prygunkov
77cabd7bce #371: do not direct unpack if article decoding is disabled
via option Decode=no.
2017-05-30 21:19:38 +02:00
Andrey Prygunkov
2336d4bcfe #362: do not direct rename if article decoding is disabled
via option Decode=no.
2017-05-30 21:18:54 +02:00
Andrey Prygunkov
580e1974bc #362, #382: fixed crash during direct rename
which may happen if articles were yEncoded with less than 68 character
length.
2017-05-30 21:17:47 +02:00
Andrey Prygunkov
8790ee685f updated link to documentation 2017-05-26 00:36:51 +02:00
Andrey Prygunkov
32f0bbae58 #371: do not terminate unrar if it's not running
when cancelling direct unpack
2017-05-23 21:05:25 +02:00
Andrey Prygunkov
8ffd6c24fe #364: do not reorder files on nzb parse errors 2017-05-23 20:53:11 +02:00
Andrey Prygunkov
98cc4817fe #362, #382: fixed crash during direct rename
which may happen if download errors occurred.
2017-05-23 20:51:23 +02:00
Andrey Prygunkov
14b40d6712 #362: discard unneeded data after direct rename
Now also discarding data when download completes without direct rename
being able to process files (due to download errors).
2017-05-23 19:39:09 +02:00
Andrey Prygunkov
629640898d #362: prevent repeating of error messages
If a file got lost before cache flushing.
2017-05-23 19:21:07 +02:00
Andrey Prygunkov
5df06a2626 #371: wait for direct rename completion before direct unpack 2017-05-22 22:08:09 +02:00
Andrey Prygunkov
4ca95b2989 #371: reset direct unpack status on post-process again 2017-05-22 22:05:52 +02:00
Andrey Prygunkov
b3cc316092 #252: new option to force news servers to ipv4 or ipv6 2017-05-22 22:03:30 +02:00
Andrey Prygunkov
298fde6cd4 #379: compile unrar for Linux with fallocate 2017-05-20 14:05:24 +02:00
Andrey Prygunkov
015b3be461 #371: better cleanup on cancelling unpack
Deleting destination if it contains only hidden files.
2017-05-18 22:00:45 +02:00
Andrey Prygunkov
f6adbe848d #362: avoid unnecessary renaming of par-sets 2017-05-18 20:32:46 +02:00
Andrey Prygunkov
08de827d7b refactor: removed unneeded type casts 2017-05-18 18:43:08 +02:00
Andrey Prygunkov
d1bda91954 refactor: removed one override for FileExists 2017-05-17 21:26:27 +02:00
Andrey Prygunkov
e40e3178da #378: removed debug logging 2017-05-16 19:38:01 +02:00
Andrey Prygunkov
cf3985f228 #378: save nzb download statistics on idle or reload 2017-05-16 19:19:54 +02:00
Andrey Prygunkov
f2329dada4 #362: log statistics for direct rename 2017-05-16 18:57:32 +02:00
Andrey Prygunkov
a73d1ba56e #31: backup icon in history in phone theme 2017-05-12 19:14:02 +02:00
Andrey Prygunkov
8d5ce3ddc3 fixed #376: startup scheduler tasks can be executed again 2017-05-12 19:08:31 +02:00
Andrey Prygunkov
d3362f9280 #371: detect not unpacked archives
Detect not unpacked archives which may appear after rar-rename and
unpack them during post-processing.
2017-05-12 18:37:15 +02:00
Andrey Prygunkov
85dcde36c5 374: unrar tests for Travis CI 2017-05-09 21:46:15 +02:00
Andrey Prygunkov
716bb3130f #374: added gcc 4.8 to Travis CI test matrix 2017-05-09 21:45:32 +02:00
Andrey Prygunkov
725e2c7376 #371: fixed zombies after reload during unpack 2017-05-09 20:36:48 +02:00
Andrey Prygunkov
3a07dd378a #371: restart direct unpack after program reload
When program is reloaded during direct unpack the unpack restarts after
one (any) inner file is downloaded.
2017-05-08 23:27:49 +02:00
Andrey Prygunkov
ff02b53ed0 #371: better handling of cancelling direct unpack 2017-05-06 23:45:31 +02:00
Andrey Prygunkov
39089b6f2f #371: disable direct unpack tests in Travis CI
because it misses unrar 5 required for the tests
2017-05-05 22:49:03 +02:00
Andrey Prygunkov
61af2b3446 #371: integrated direct unpack into pp-workflow 2017-05-05 21:57:52 +02:00
Andrey Prygunkov
20f4f3020b #371: 6b546394b2: corrected stdin redirection 2017-05-05 21:56:54 +02:00
Andrey Prygunkov
df5a1fe959 #371: direct unpack volume handling
Archive parts are now unpacked as they arrive.
2017-05-03 21:42:59 +02:00
Andrey Prygunkov
6b546394b2 #371: added stdin-redirecting support 2017-05-03 21:39:57 +02:00
Andrey Prygunkov
160b274ce8 #371: new module "DirectUnpack"
without implementation and with a new failing unit test.
2017-05-03 21:30:35 +02:00
Andrey Prygunkov
4104a2357b #371: new option "DirectUnpack"
without implementation.
2017-05-03 21:08:21 +02:00
Andrey Prygunkov
ba1e51a8d8 #362: new queue event after direct rename
Queue-event NZB_NAMED, sent after the inner files are renamed
2017-05-01 17:48:20 +02:00
Andrey Prygunkov
9f7d6655b2 #364: logging for file reordering 2017-05-01 17:31:14 +02:00
Andrey Prygunkov
f6a9253a53 #364: fixed compiling error on Linux 2017-04-30 21:41:23 +02:00
Andrey Prygunkov
48020e8901 #361: fixed failing test 2017-04-30 21:36:35 +02:00
Andrey Prygunkov
5afa20d655 #364: better ordering for rar- and par-files 2017-04-30 21:27:51 +02:00
Andrey Prygunkov
197baf066a updated option descriptions in configuration file 2017-04-30 14:39:46 +02:00
Andrey Prygunkov
c873647aae #361: new option "FileNaming"
replace pp-parameter “*naming”.
2017-04-30 14:21:49 +02:00
Andrey Prygunkov
fc1847588d #362: save and restore disk state for direct rename 2017-04-30 00:10:17 +02:00
Andrey Prygunkov
62b3d47b43 #362: redesigned renaming
Spread RenameInfo into FileInfo and NzbInfo to make the state easier to
save on disk
2017-04-28 23:55:41 +02:00
Andrey Prygunkov
ddb9333ca6 #362: discard partially downloaded par2-files
to solve issues with par-checker, par-renamer and avoid saving state
for these files.
2017-04-28 18:36:27 +02:00
Andrey Prygunkov
830e0e4858 #362: proper rename on completion
Fixed: if the file was renamed during finalizing stage (completion) the
file may not be properly renamed.
2017-04-27 17:31:39 +02:00
Andrey Prygunkov
085c612f97 #24: fixed errors when adding many files at once 2017-04-25 21:36:53 +02:00
Andrey Prygunkov
dea9fb2090 #24: double click handler for windows 2017-04-25 19:33:31 +02:00
Andrey Prygunkov
5813c903eb #362: compatibility with gcc 4.8 2017-04-25 19:27:39 +02:00
Andrey Prygunkov
4f499e2c2e fixed #367: fatal-messages when compiling from sources 2017-04-25 19:26:53 +02:00
wtf911
3884328251 #365, #24: option for file association in windows installer
This will add a checkbox so after installation finishes there will be an option to associate .nzb files with NZBGet. It will also remove the association if NZBGet is uninstalled.
2017-04-25 00:40:19 +02:00
Andrey Prygunkov
7e9e2471ef #362: handling of download interruptions
if they happen during downloading of first articles
2017-04-24 23:10:14 +02:00
Andrey Prygunkov
2a433ee7fb #362: fixed inner sorting 2017-04-24 23:09:05 +02:00
Andrey Prygunkov
118f835385 #362: multiple par-sets and renaming par2-files 2017-04-23 20:20:59 +02:00
Andrey Prygunkov
9a6a42bd44 #362: unpause par2-file
as we need it to download first and ExtraPriority-flag doesn’t work on
paused files
2017-04-23 13:48:39 +02:00
Andrey Prygunkov
9434149842 #364: remote command for sorting of inner files
subcommand “SF” of remote command “-E“
2017-04-22 21:12:47 +02:00
Andrey Prygunkov
4107536c03 #364: implemented file reordering
- reordering inner files after adding nzb to queue;
- reordering inner files after adding direct renaming;
- new command “GroupSortFiles” in api-method “editqueue”.
2017-04-22 20:13:45 +02:00
Andrey Prygunkov
2aec782f58 #364: new option "ReorderFiles" 2017-04-22 20:10:00 +02:00
Andrey Prygunkov
eaaa943af3 #362: save new filenames into disk state 2017-04-21 20:33:23 +02:00
Andrey Prygunkov
964e8311a9 #362: renaming files 2017-04-20 21:28:06 +02:00
Andrey Prygunkov
895dd12e4d #362: another functional test for direct renamer 2017-04-19 18:59:09 +02:00
Andrey Prygunkov
d16036aa78 #362: loading hashes from par2-flle 2017-04-19 16:43:58 +02:00
Andrey Prygunkov
8b79a81eaf #362: functional tests for direct renamer
(new tests currently fail)
2017-04-18 19:58:12 +02:00
Andrey Prygunkov
231e94dd2e #362: detecting par2-files 2017-04-18 19:57:35 +02:00
Andrey Prygunkov
f7be22893d #362: computing 16k-hashes for downloaded files 2017-04-17 15:32:32 +02:00
Andrey Prygunkov
3ac91a4bb6 #362: fixed restoring of partial state
when direct write was disabled
2017-04-16 15:01:34 +02:00
Andrey Prygunkov
43441a8d55 #362: downloading first article of each file
, then other articles, when option “DirectRename” is active.
2017-04-16 14:58:54 +02:00
Andrey Prygunkov
b02059f196 #362: new option "DirectRename" 2017-04-16 14:57:10 +02:00
Andrey Prygunkov
f107802f0e #361: pp-param "*naming"
to define naming scheme for downloaded files: “nzb” - use file names
from nzb, “article” - use file names from article metadata (default).
2017-04-15 01:39:40 +02:00
Andrey Prygunkov
7faf1fe64b #361: prefer file names from nzb
to names from article body; to better handle obfuscated posts.
2017-04-14 20:36:00 +02:00
Andrey Prygunkov
bf2fea64e7 #360: removed unnecessary requests to news servers 2017-04-14 14:39:37 +02:00
Andrey Prygunkov
de3eb3de9d #352: parameters for api-method "servervolumes"
as a performance optimization measure to reduce amount of transferred
data.
2017-04-12 19:47:58 +02:00
Andrey Prygunkov
55d080ac96 #50: clear script execution log
before executing script
2017-04-12 19:41:28 +02:00
Andrey Prygunkov
795bacb4fe merge from master v18.1 2017-04-09 13:38:01 +02:00
Andrey Prygunkov
f7930b56a6 updated version string to "18.1" 2017-04-08 17:37:57 +02:00
Andrey Prygunkov
b2bf488d59 fixed #338: "undefined" in reorder extension scripts
When editing option “ScriptOrder” in web-interface.
2017-04-08 17:28:41 +02:00
Andrey Prygunkov
8e4b75b21e fixed #347: possible crash at the end of post-processing 2017-04-08 17:27:51 +02:00
Andrey Prygunkov
5b17bebbd6 fixed #348: queue was not saved after deleting of queued post-jobs 2017-04-08 17:27:22 +02:00
Andrey Prygunkov
bda1eaa192 fixed #350: sleep mode no longer works on Windows 2017-04-08 17:26:15 +02:00
Andrey Prygunkov
4bcdbbeb09 fixed #356: crash during download caused by a race condition 2017-04-08 17:25:29 +02:00
Andrey Prygunkov
f0ee12f92e #352: option "ServerX.Notes"
for user comments on news servers.
2017-04-07 20:43:06 +02:00
Andrey Prygunkov
0b14a2f869 #352: showing volume stats from settings page
New button “Volume Statistics” in section “News Servers” of settings
page. Shows the same volume data as in global statistics dialog.
2017-04-07 20:40:46 +02:00
Andrey Prygunkov
2579ce65b9 fixed #356: crash during download caused by a race condition 2017-03-31 17:46:02 +02:00
Andrey Prygunkov
77f86988cb #353: support for ECC certificates in built-in web-server 2017-03-21 23:29:48 +01:00
Andrey Prygunkov
b9b62dcd75 #353: better error reporting for TLS error in built-in web-server 2017-03-21 23:29:07 +01:00
Andrey Prygunkov
bfee7c55cd #50: confirmation for commands marked as dangerous 2017-03-21 23:22:54 +01:00
Andrey Prygunkov
34efa87699 #50: "send test e-mail" command in EMail.py 2017-03-20 21:03:40 +01:00
Andrey Prygunkov
e839db7107 #50: pass not yet saved options to script 2017-03-20 20:59:34 +01:00
Andrey Prygunkov
ffb16aa7bb #50: progress dialog for script execution log
New API-method “logscript”.
2017-03-20 20:30:19 +01:00
Andrey Prygunkov
a3b0b7675e #50: execute custom commands for scripts
New API-method “startscript”.
2017-03-17 20:24:14 +01:00
Andrey Prygunkov
602f62c17c #50: define custom buttons in extension scripts 2017-03-17 19:23:56 +01:00
Andrey Prygunkov
ace7a1968d #349: added host name to all error messages 2017-03-15 20:36:45 +01:00
Andrey Prygunkov
6efefe3780 fixed #350: sleep mode no longer works on Windows 2017-03-15 20:13:55 +01:00
Andrey Prygunkov
c02f708d74 #346: save changes before performing actions in history dialog 2017-03-13 19:53:54 +01:00
Andrey Prygunkov
e7c47f523a fixed #348: queue was not saved after deleting of queued post-jobs 2017-03-13 18:04:44 +01:00
Andrey Prygunkov
5c8be152f4 fixed #347: possible crash at the end of post-processing 2017-03-13 17:58:15 +01:00
Andrey Prygunkov
4cdbfc84dd #346: changing category via drop-down changes pp-parameters
accordingly to the new category.
2017-03-12 20:09:50 +01:00
Andrey Prygunkov
75ec856af3 #346: improved hover effect
Increased clickable area and made the carets (triangles) clickable too.
2017-03-11 23:21:50 +01:00
Andrey Prygunkov
88b5e16597 #346: multi-edit via drop-downs
If multiple items are selected and drop-down is shown for one of them
the menus work on all selected items.
2017-03-09 20:55:04 +01:00
Andrey Prygunkov
fff6fdd4eb #346: unified hover feedback for all drop-downs 2017-03-09 20:44:57 +01:00
Andrey Prygunkov
c4ff544459 #346: drop-down for status in downloads and history 2017-03-08 20:16:17 +01:00
Andrey Prygunkov
9c87942b43 #346: drop-down for category in history 2017-03-08 20:12:38 +01:00
Andrey Prygunkov
0e4c9275b0 #346: drop-down for category in downloads 2017-03-08 20:10:23 +01:00
Andrey Prygunkov
f11dce4269 #346: drop-down for priority in downloads 2017-03-08 20:07:25 +01:00
Andrey Prygunkov
f1340eb542 #263: improvements in RSS feed view in phone mode 2017-03-04 18:17:14 +01:00
Andrey Prygunkov
e89f5cbd8a #31: backup-badge for items in history
if backup news servers were used; that’s similar to backup-badge on
downloads tab.
2017-02-26 21:35:09 +01:00
Andrey Prygunkov
975016700e #23: proper exit code on client command success or failure 2017-02-26 20:40:55 +01:00
Andrey Prygunkov
a9017be606 #123: set name, password and dupe info when adding via URL
by click on a button near URL field in web-interface
2017-02-26 19:23:33 +01:00
Andrey Prygunkov
d81d6831dc #331: support for HTTP-header "X-Forwarded-For" in IP-logging 2017-02-26 12:40:37 +01:00
Andrey Prygunkov
1b94373c7e fixed #338: "undefined" in reorder extension scripts
When editing option “ScriptOrder” in web-interface.
2017-02-24 16:27:05 +01:00
Andrey Prygunkov
d5e881ce1a #330: optimisations for Safari 2017-02-24 14:55:05 +01:00
Andrey Prygunkov
a3f84aca0e #330: better session handling in form login 2017-02-23 20:49:50 +01:00
Andrey Prygunkov
0ab86b90f0 #330: authentication via form in web-interface
, new option “FormAuth”.
2017-02-22 17:41:25 +01:00
Andrey Prygunkov
b6a606db35 #339: extended error messages with a link to wiki-page 2017-02-20 18:24:30 +01:00
Andrey Prygunkov
578731f239 #339: integrated root certificate store file into Linux installer 2017-02-19 18:14:04 +01:00
Andrey Prygunkov
71db4ffe9c #192: adjusted linux build script 2017-02-19 18:12:23 +01:00
Andrey Prygunkov
7421ec7b1a #339: added missing "define" for Windows build 2017-02-19 15:29:14 +01:00
Andrey Prygunkov
958712663e fixed compile error under gcc 4.8 2017-02-18 20:14:48 +01:00
Andrey Prygunkov
d96fa66487 #339: 36ac548842: fixed compile error on gcc and clang 2017-02-18 20:12:53 +01:00
Andrey Prygunkov
36ac548842 #339: TLS certificate verification with GnuTLS 2017-02-18 19:47:20 +01:00
Andrey Prygunkov
fc44ab6128 #339: simplified verification code 2017-02-17 23:44:15 +01:00
Andrey Prygunkov
f0da3936e5 #339: prevent compilation failure on older OpenSSL versions 2017-02-17 13:22:07 +01:00
Andrey Prygunkov
04e694799d #339: TLS certificate verification with OpenSSL 2017-02-17 12:32:20 +01:00
Andrey Prygunkov
712cedb84f #339: new options "CertStore" and "CertCheck" 2017-02-17 11:27:49 +01:00
Andrey Prygunkov
4fb3ddcf84 updated version string to "19.0-testing" 2017-02-17 11:03:57 +01:00
Andrey Prygunkov
69d40c11fd Merge branch 'develop' for v18.0 2017-02-12 17:20:07 +01:00
Andrey Prygunkov
58893710d8 updated version string to "18.0" 2017-02-12 17:05:21 +01:00
Andrey Prygunkov
6c6f781510 updated ChangeLog for v18.0 2017-02-12 17:03:04 +01:00
Andrey Prygunkov
569ec22ee8 updated copyright string in Windows and OS X apps 2017-02-12 17:02:56 +01:00
Andrey Prygunkov
b06d3eca86 #327: printing only base file name in warnings 2017-02-12 14:35:44 +01:00
Andrey Prygunkov
dcf63b4db7 #277: allow control of what tab is shown when opening web-interface
Add “#downloads”, “#history”, “#messages” or “#settings” to the URL,
for example “http://localhost:6789/#history” or
“http://localhost:6789/index.html#history”
2017-01-27 20:10:57 +01:00
Andrey Prygunkov
06e7573572 #329: improved selecting of par2-file for repair 2017-01-19 21:48:35 +01:00
Andrey Prygunkov
639e0b6bfb #327: reverted non-strict par2-filename matching
To handle article subjects with non-parseable filenames. Also created a
functional test for this case.
2017-01-17 22:18:34 +01:00
Andrey Prygunkov
f2073ff920 #327: better handing of damaged par2-files in par-renamer
If par-renamer can’t load par2-file another par2-file is downloaded and
par-renamer tries again.
2017-01-17 00:23:29 +01:00
Sander
4ba2e07d94 #328: NServ: option -b to specify on which address to listen 2017-01-08 19:37:43 +01:00
Sander
91515b20e5 #324, #325: Nserv: Check if specified data-dir is an existing directory 2017-01-06 21:11:42 +01:00
Andrey Prygunkov
7226e1c186 #319: fixed automatic changing of pp-scripts when changing category in web-interface 2016-12-30 22:29:14 +01:00
Andrey Prygunkov
98c2dd46b7 #291: fixed "PostTotalTimeSec" in API-method "listgroups" 2016-12-27 17:21:06 +01:00
Andrey Prygunkov
f0b08dbd63 #315: reset feed search box 2016-12-22 20:24:00 +01:00
Sander
25ddfd5659 #320, #321: newlines added to 281 and 205 server answers 2016-12-21 19:46:55 +01:00
Andrey Prygunkov
7450b97871 #319: scheduler scripts in option "Extensions"
Scheduler scripts can now be selected in option “Extensions” if the
scripts provide default time definition.
2016-12-20 22:22:09 +01:00
Andrey Prygunkov
9f7e0ee972 #304: safer termination of scripts 2016-12-19 19:33:38 +01:00
Andrey Prygunkov
ae7719e948 #319: auto migration of old script settings 2016-12-16 21:03:59 +01:00
Andrey Prygunkov
9a92896678 #319: unified extension scripts settings
- new option “Extension” as replacement for options “PostScript”,
“QueueScript”, “ScanScript”, “FeedScript”;
- renamed “CategoryX.PostScript” to “CategoryX.Extensions”;
- renamed “FeedX.PostScript” to “FeedX.Extensions”.
2016-12-14 20:34:14 +01:00
Andrey Prygunkov
ab0723adda #288: rar-rename only if unpack is enabled 2016-12-14 18:50:14 +01:00
Andrey Prygunkov
13b98fca83 fixed incompatibility with macOS El Capitan and XCode 7/8 2016-12-13 21:52:32 +01:00
Andrey Prygunkov
5901998cb5 #242: fixed enter-key in feed filter dialog 2016-12-10 20:13:19 +01:00
Andrey Prygunkov
9ddd73368e #282: 0d67e322a3: fixed confirmation dialog 2016-12-07 21:02:55 +01:00
Andrey Prygunkov
12daa683e5 #313: 5f71c4a5a7: fixed test failure on Linux 2016-12-06 19:18:06 +01:00
Andrey Prygunkov
5f71c4a5a7 #313: better handling of renamed par-files 2016-12-06 18:49:12 +01:00
Andrey Prygunkov
13db817395 #313: better handling of obfuscated par-files in par-renamer
Avoid false detection of missing files.
2016-12-04 19:04:39 +01:00
Andrey Prygunkov
84f4cf2f33 #312: extended description of option "KeepHistory" 2016-12-03 12:54:49 +01:00
Andrey Prygunkov
0d67e322a3 #282: extra warning when deleting from history
An extra warning is shown when deleting 50 or more records from history
and there are selected records on other pages.
2016-11-29 18:56:00 +01:00
Andrey Prygunkov
374f706354 #282: visual indication of records selected on other pages 2016-11-22 21:36:20 +01:00
Andrey Prygunkov
f7cefadf33 use default SDK when building mac app 2016-11-20 19:31:06 +01:00
Andrey Prygunkov
c111a114b9 #242: keyboard support in web-interface 2016-11-20 14:24:27 +01:00
Andrey Prygunkov
2cd3f0fc68 #309: allow sending e-mails to multiple recipients
in the example pp-script “EMail.py”.
2016-11-20 13:12:50 +01:00
Andrey Prygunkov
92db59116e #301: showing drag grip
and extended the drag grip area to the full cell containing check box.
2016-11-18 19:59:33 +01:00
Andrey Prygunkov
760aed68fb #288: fixed incorrect renaming
of archives containing directory entries
2016-11-16 21:41:34 +01:00
Andrey Prygunkov
e612257c28 #304: graceful termination of scheduler scripts
If a script is running when the program must shutdown, the script
receives signal SIGINT (CTRL+BREAK on Windows) and has 10 seconds to
gracefully terminate until it is killed.
2016-11-15 19:22:52 +01:00
Andrey Prygunkov
31208a5816 #304: scheduler tasks can be started at program launch
Using asterisk as TaskX.Time.
2016-11-15 19:00:49 +01:00
Andrey Prygunkov
a03e7cd550 #295: disabled SSLv3 in built-in web-server 2016-11-15 18:47:30 +01:00
Andrey Prygunkov
02b33fb559 #306: splitted options "Retries" and "RetryInterval"
into four options: ArticleRetries, ArticleInterval, UrlRetries, and
UrlInterval.
2016-11-13 13:05:37 +01:00
Andrey Prygunkov
a9ed53daa8 fixed #303: rar tests fail with TLS disabled 2016-11-12 18:37:48 +01:00
Andrey Prygunkov
a3c460ed40 #301: showing row content while dragging
Implemented alternative UI for drag and drop.
2016-11-12 14:23:19 +01:00
Andrey Prygunkov
df11d1acb4 #301: functional tests for api-method "editqueue"
And a small fix in edit-commands GroupMoveAfter and GroupMoveBefore.
2016-11-08 21:02:36 +01:00
Andrey Prygunkov
e49d4c59af #301: auto scrolling during dragging 2016-11-06 21:16:19 +01:00
Andrey Prygunkov
5dc9c07a58 #301: moved drag-n-drop code into module fasttable.js
Ready for reuse on other places.
2016-11-04 22:02:26 +01:00
Andrey Prygunkov
3bb0751c86 #301: drag-n-drop for touch screens
; also no more dragging via priority-icon but only via check mark.
2016-11-03 19:20:03 +01:00
Andrey Prygunkov
a3b3921bea #301: API extension for drag-n-drop support
New actions “GroupMoveBefore” and “GroupMoveAfter” in API-method
“editqueue”; parameter “args” contains id of the target item to move
before/after.
2016-11-01 12:27:42 +01:00
Andrey Prygunkov
b19d26aee8 #301: drag and drop handling in web-interface 2016-11-01 12:23:33 +01:00
Andrey Prygunkov
7fae337360 fixed #300: sorting of selected items may give wrong results 2016-10-31 19:05:15 +01:00
Andrey Prygunkov
80debf521a #299: removed parameter "offset" from api-method "editqueue"
When needed the “offset” is now passed within parameter “Args” as
string.
2016-10-31 15:58:02 +01:00
Andrey Prygunkov
528133482e #298: added compatibility with openssl 1.1.0 2016-10-31 10:11:23 +01:00
Andrey Prygunkov
a98bbd7d0d #279: cache support in built-in nntp server 2016-10-29 16:55:12 +02:00
Andrey Prygunkov
2681fe187b #288: sooner detection of damaged rar-files 2016-10-22 15:05:19 +02:00
Andrey Prygunkov
e16cab67fb #288: ed4761db37: reverted std-input handling on Windows 2016-10-22 15:04:11 +02:00
Andrey Prygunkov
50e2e0cf37 #291: refactor: handling of shutdown/reload 2016-10-18 21:53:48 +02:00
Andrey Prygunkov
d2d20f29c1 #291: better handling of shutdown/reload
for post-processing jobs
2016-10-18 21:52:30 +02:00
Andrey Prygunkov
53f4992eeb #279: corrected generation of test files 2016-10-18 21:51:22 +02:00
Andrey Prygunkov
3ae1be1ca4 #294: handling of incomplete obfuscated rar-archives
and special file extensions; new option “UnpackIgnoreExt” honored by
rar-renamer and unpacker.
2016-10-16 13:21:58 +02:00
Andrey Prygunkov
ce1e1b61e7 #291: fixed status in api-method "listgroups" 2016-10-15 16:12:48 +02:00
Andrey Prygunkov
cac25ed290 #291: distinguishing pp-stages par-renaming and rar-renaming
to better handling temporary pause.
2016-10-15 13:37:34 +02:00
Andrey Prygunkov
8ed0b70df5 #291: eacfacc5a2: fixed compilation error on Windows 2016-10-15 13:11:54 +02:00
Andrey Prygunkov
d72071c8ed #291: temporary pause during post-processing
Options ParPauseQueue, UnpackPauseQueue, PostPauseQueue work properly.
2016-10-15 13:00:52 +02:00
Andrey Prygunkov
ba05dfc202 #291: tweaking balanced pp-startegy
Other job can be run only during repair stage but not during
verification stage.
2016-10-14 11:20:57 +02:00
Andrey Prygunkov
f3adab5690 #293: split section "Download Queue"
Many options moved into new section “Connection”.
2016-10-14 00:19:14 +02:00
Andrey Prygunkov
b1b5405809 #291: new option "PostStrategy" to configure multi-post-processing
instead of option “ParExclusive”.
2016-10-14 00:07:13 +02:00
Andrey Prygunkov
99fcd164ac #291: smoother transition between pp-stages
without intermediate “pp-queued” state shown in web-interface
2016-10-13 00:07:44 +02:00
Andrey Prygunkov
eacfacc5a2 #291: non-global cout/cerr in par2-module
Fixed global cout/cerr in par2-module to prevent crashes when executing
multiple par2-instances.
2016-10-12 16:00:08 +02:00
Andrey Prygunkov
3404b544de #291: removed not used unit of par2-module 2016-10-12 15:58:08 +02:00
Andrey Prygunkov
3604534850 #291: functional tests for multi-processing 2016-10-12 00:30:23 +02:00
Andrey Prygunkov
2e18021e08 #291: new option "ParExclusive"
to configure simultaneous post-processing of a second item if currently
performing par-check/repair for the first item in pp-queue
2016-10-12 00:22:34 +02:00
Andrey Prygunkov
2e19382ccc #291: multi post-processing
During par-check/repair another pp-item can be post-processed at the
same time, as long as it doesn’t require par-check/repair.
2016-10-12 00:13:50 +02:00
Andrey Prygunkov
a6dc20dc8e #291: refactor: made par-checker non-global
and more similar to other post-processing stages.
2016-10-11 20:43:11 +02:00
Andrey Prygunkov
a1bef9146e #286: corrected html in priority column 2016-10-08 15:33:04 +02:00
Andrey Prygunkov
61f18f81b7 #29, #278: small UI tweak 2016-10-08 15:32:26 +02:00
Andrey Prygunkov
129125faa1 #286: priority column and sorting
Also removed horizontal lines in tables, for better visuals.
2016-10-07 23:46:21 +02:00
Andrey Prygunkov
b97c987f66 #274: fixed error on history page
Fixed javascript error on history page when showing hidden items.
2016-10-04 22:11:32 +02:00
Andrey Prygunkov
53e504d974 fixed failing functional tests 2016-10-03 23:39:34 +02:00
Andrey Prygunkov
5885258c35 #288: reading encrypted archives using nettle library
when compiled with GnuTLS instead of OpenSSL.
2016-10-03 22:38:48 +02:00
Andrey Prygunkov
2034ea97d2 #288: fixed compilation error with GnuTLS
and added unit test for rar3 encrypted files
2016-10-03 15:05:29 +02:00
Andrey Prygunkov
2cc38a85df #288: fixed incompatibility with older compilers 2016-10-03 01:35:46 +02:00
Andrey Prygunkov
ccd509b70c #288: reading rar3 encrypted archives
Rar-renamer now supports renaming of rar3 archives with encrypted
headers (encrypted filenames). Only when compiled with OpenSSL as TLS
library.
2016-10-02 23:10:31 +02:00
Andrey Prygunkov
bab43d8d30 #288: reading rar5 encrypted archives
Rar-renamer now supports renaming of rar5 archives with encrypted
headers (encrypted filenames). Only when compiled with OpenSSL as TLS
library.
2016-09-30 21:39:35 +02:00
Andrey Prygunkov
ed4761db37 #288: unit and functional tests for encrypted rar files 2016-09-30 19:32:55 +02:00
Andrey Prygunkov
db7bc4314f #288: rar-rename as a separate post-processing stage
Made rar-rename a separate post-processing stage which runs after
par-repair, in order to restore archive file names after possible
repair stage (which could rename files back to obfuscated names if
rar-rename were run before par-repair).
2016-09-28 22:22:18 +02:00
Andrey Prygunkov
8a21626bf6 #288: sophisticated renaming for rar-files 2016-09-27 23:34:42 +02:00
Andrey Prygunkov
804f8ab085 #288: integrated rar-renamer into post-processing
also implemented initial simple renaming for rar-files
2016-09-26 21:55:41 +02:00
Andrey Prygunkov
3593990dd6 #288: new module "Rename" to handle par-rename and rar-rename 2016-09-26 21:16:27 +02:00
Andrey Prygunkov
3a38a2c7c6 #288: refactor: moved parts of module "RarRenamer" into new module "RarReader"
Also added new unit test for module “RarReader”.
2016-09-26 21:01:15 +02:00
Andrey Prygunkov
16aef2e7a8 #288: functional tests for par-renamer and rar-renamer
Rar-renamer tests are currently failing as the feature isn’t completed
yet.
2016-09-25 21:08:45 +02:00
Andrey Prygunkov
bf992195e8 #288: fixed unit test for rar-renamer 2016-09-25 21:07:23 +02:00
Andrey Prygunkov
2fb0fd1113 #288: reading rar5-files 2016-09-24 22:43:20 +02:00
Andrey Prygunkov
709c9856c9 #288: reading rar3-files 2016-09-24 22:39:45 +02:00
Andrey Prygunkov
9e9ef3120f #288: added module "RarRenamer" with unit tests
The tests are failing at the moment as the module is not fully
implemented yet.
2016-09-24 18:37:02 +02:00
guaguasi
a7525ae6f9 #274, #285: password-badge for nzbs with passwords 2016-09-23 21:28:12 +02:00
Andrey Prygunkov
5e0e91f671 #249: do not cleanup if unpack disabled
If parameter "unpack" is disabled for an nzb-file the cleanup isn't
performed for it.
2016-09-19 23:40:00 +02:00
Andrey Prygunkov
09f863f3af #275: nZEDb attributes in rss feeds
Added support for nZEDb attributes in rss feeds.
2016-09-19 23:37:51 +02:00
Andrey Prygunkov
87e8d479d8 #281: improved error reporting on feed parse errors 2016-09-19 22:22:29 +02:00
Andrey Prygunkov
fb8c6b9cd3 #279: size of test files
can now be adjusted via pytest.ini; reduced default sizes to speed up
tests.
2016-09-19 22:04:49 +02:00
Andrey Prygunkov
bc98b0582c #279: corrected nntp error reporting 2016-09-19 21:44:38 +02:00
guaguasi
84388785e7 #276 , #280: highlight selected rows with alternative colors 2016-09-19 06:43:07 +02:00
Andrey Prygunkov
4c519cf646 #279: fixed compilation error on Windows 2016-09-17 23:45:03 +02:00
Andrey Prygunkov
f28c049c12 fixed #284: API-method "append" and dupe check
method “append” returns nzb-id also for items added straight to history.
2016-09-17 20:38:57 +02:00
Andrey Prygunkov
33dbbcb0b5 Merge branch '279-nserv' into develop 2016-09-17 13:49:45 +02:00
Andrey Prygunkov
7f6cbd137f #279: updated .gitignore 2016-09-17 13:49:09 +02:00
Andrey Prygunkov
facd8cd41f #279: fixed file permissions 2016-09-17 13:37:56 +02:00
guaguasi
e269d8f062 #29, #278: additional options in "custom pause dialog" 2016-09-16 06:40:55 +02:00
Andrey Prygunkov
dd141e4cf5 #279: initial collection of functional tests 2016-09-13 22:05:10 +02:00
Andrey Prygunkov
f18ee92a23 #279: built-in nntp server for functional testing
- implemented nserv - built-in simple nntp server to be used for
functional testing;
- nserv is part of nzbget executable and can be easily used on all
platforms;
- to start nzbget in nserv mode use command “nzbget --nserv”.
2016-09-13 21:31:20 +02:00
Andrey Prygunkov
b25a88683b #271: do not close files on daemonizing 2016-09-09 20:12:10 +02:00
guaguasi
9dc2b8c71b #49, #260: fields containing passwords are now password fields
- fields containing passwords are now password fields with option to view;
- excluding post-processing/archive password.
2016-09-09 19:43:46 +02:00
Andrey Prygunkov
2ef393531a updated version string to 18.0-testing 2016-09-09 19:45:46 +02:00
Andrey Prygunkov
0c1fce27a2 update version string to "17.1" 2016-09-05 19:36:02 +02:00
Andrey Prygunkov
606cf56150 Merge branch 'develop' for v17.1 2016-09-05 19:19:34 +02:00
Andrey Prygunkov
af8451e16e updated ChangeLog for v17.1 2016-09-05 19:05:52 +02:00
Andrey Prygunkov
203a828bbd #224: corrected text in history delete confirmation dialog 2016-08-28 15:00:48 +02:00
Andrey Prygunkov
a990078884 fixed #265: compilation error if configured without TLS 2016-08-21 22:44:48 +02:00
Andrey Prygunkov
3e5bd203f3 fixed #86: javascript error on Chrome for Linux
Renamed “Notification” to “PopupNotification” to avoid conflict with
browsers built-in interface with the same name.
2016-08-21 22:34:42 +02:00
Andrey Prygunkov
042979d122 #245: removed debug logging 2016-08-16 20:43:13 +02:00
Andrey Prygunkov
11c464ed46 fixed #262: crash on malformed articles 2016-08-12 23:45:57 +02:00
Andrey Prygunkov
070054a814 fixed #261: hanging after marking as BAD from queue script 2016-08-12 22:28:37 +02:00
Andrey Prygunkov
5841cf5002 updated version string to 17.1-testing 2016-08-09 00:14:31 +02:00
Andrey Prygunkov
6dda360986 #253: reset stats if file on disk is missing on retry
Fixed: if by retrying download an included file is missing on disk it
was downloaded again but its stats (remaining size, etc.) were not
correctly reset.
2016-08-09 00:01:55 +02:00
Andrey Prygunkov
12ff14b397 #253: park active file too when deleting
If by deleting download only one file remains and it is partially
downloaded it should be counted as remaining (parked) file for command
“Download remaining files” be available (RemaingFileCount must be > 0).
2016-08-08 23:52:58 +02:00
Andrey Prygunkov
20aa83e0f7 #253: new field "RetryData" in RPC-method "history"
to more carefully indicate if “Retry failed articles” is possible.
2016-08-08 23:42:18 +02:00
Andrey Prygunkov
4c8c7ef46b #253: do not park when deleting without keeping files 2016-08-08 22:57:29 +02:00
Andrey Prygunkov
43a6717394 #253: pause-state when unparking par-files
if option ParCheck=Force then all par-files must be unpaused, otherwise
one par-file must be unpaused.
2016-08-06 22:09:08 +02:00
Andrey Prygunkov
b4fc57977b #253: do not park if all articles failed 2016-08-06 21:28:07 +02:00
Andrey Prygunkov
4f9dbdadc3 #253: fixed: not parking if all articles were successful 2016-08-06 21:16:13 +02:00
Chris
418acb052f #256: compatibility with gcc 4.8 2016-08-05 07:51:10 +02:00
Gokturk Yuksek
c741297d84 #255: do not compile par-tests if parcheck is disabled 2016-08-04 22:38:02 +02:00
Andrey Prygunkov
55fb4df411 fixed #254: installing multiple versions on Windows 2016-08-03 20:37:35 +02:00
Andrey Prygunkov
35643b0207 fixed #253: delete-with-parking sometimes didn't park 2016-08-03 19:55:07 +02:00
Andrey Prygunkov
45753410df fixed #251: performance issue on certain Windows systems 2016-08-02 20:03:41 +02:00
Andrey Prygunkov
b58de541b3 #162: corrected option description 2016-07-30 12:35:19 +02:00
Andrey Prygunkov
3d577777bb #136: improved error reporting on certain file operations 2016-07-29 16:45:15 +02:00
Andrey Prygunkov
a0e9c537a3 fixed #247: root drive paths on Windows
.. could not be used (for example “NzbDir=N:\”).
2016-07-29 16:22:11 +02:00
Andrey Prygunkov
a2d87de7b9 updated version string to 18.0-testing 2016-07-29 16:18:54 +02:00
Andrey Prygunkov
032a7a4770 update version string to "17.0" 2016-07-27 14:23:43 +02:00
Andrey Prygunkov
b2dcc59845 Merge branch 'develop' for v17.0 2016-07-27 13:40:50 +02:00
Andrey Prygunkov
2ad86ee738 updated ChangeLog for v17.0 2016-07-26 21:08:45 +02:00
Andrey Prygunkov
dcf0318bea fixed #245: activity animation gets re-activated 2016-07-24 18:22:43 +02:00
Andrey Prygunkov
9973ec5f6c fixed #237: prevent errors when pressing ENTER
in web-interface.
2016-07-10 13:53:58 +02:00
Andrey Prygunkov
2b43695a01 #231: path to original nzb-file passed to scripts
Passed to queue-scripts in env. var “NZBNA_QUEUEDFILE” and to
post-processing-scripts as “NZBPP_QUEUEDFILE”.
2016-07-07 21:14:40 +02:00
Nathan Lee
19dd39d5e6 #236: fixed typo in webui
On the "Loading configuration failed" message. automatically was spelt incorrectly.
2016-07-03 21:03:27 +02:00
Andrey Prygunkov
e5eddbe7ce #223: SNI support with GnuTLS 2016-07-03 17:07:03 +02:00
Andrey Prygunkov
0f279aaf6e #223: SNI support with OpenSSL 2016-07-03 17:06:43 +02:00
Andrey Prygunkov
62cd5c776c #227: fixed check for reserved characters in file names (Windows) 2016-07-03 12:47:53 +02:00
Andrey Prygunkov
8c8f41ac33 #230: moved option "FeedX.Interval" upwards 2016-07-03 12:44:38 +02:00
Andrey Prygunkov
51880a60e9 fixed #235: very long log messages were not read properly from disk 2016-07-02 21:01:36 +02:00
Andrey Prygunkov
7dffe6f502 #210, #234: corrected file name cleanup 2016-06-28 21:00:43 +02:00
Andrey Prygunkov
f591e1680d #225: corrected event cleanup
events “NZB_MARKED” could be ignored when they shouldn’t
2016-06-02 19:40:05 +02:00
Andrey Prygunkov
0e0d7be16b #225: new queue script event NZB_MARKED
; new env var NZBNA_MARKSTATUS passed to queue scripts.
2016-05-30 23:46:37 +02:00
Andrey Prygunkov
8ad12646fb Merge branch '224-delete-files' into develop 2016-05-30 20:49:49 +02:00
Andrey Prygunkov
92ae10e338 #148: fixed crash in queue script handling
If multiple queue scripts were queued and the nzb-file was deleted
before all scripts were executed the program may crash.
2016-05-29 16:02:40 +02:00
Andrey Prygunkov
b2b3a2e281 #224: fixed: delete without history tracking did not work 2016-05-29 14:55:44 +02:00
Andrey Prygunkov
355e7a5ab1 #224: options to delete already downloaded files 2016-05-29 12:28:00 +02:00
Andrey Prygunkov
6f7be71a93 #205: fixed crash when "Download Again" URL 2016-05-28 20:06:45 +02:00
Andrey Prygunkov
4d5a4bb428 #206: fixed: string settings could not be saved empty 2016-05-26 19:36:04 +02:00
Andrey Prygunkov
717db2486a #206: fixed default settings were not properly set
All boolean settings were set to “off”.
2016-05-26 19:33:23 +02:00
JackDandy
43d450b8ae #222: the switch to disable animation on play/pause button didn't work 2016-05-25 23:56:26 +02:00
Travis La Marr
4501d129c2 #218: updated config description text 2016-05-21 20:45:35 +02:00
Andrey Prygunkov
d14dc599d9 #66: improved tray icon (Windows)
… to look better on a dark background.
2016-05-17 18:39:57 +02:00
Andrey Prygunkov
18d89435e4 #205: fixed: nzb-log was deleted
… just before adding to history
2016-05-16 12:41:57 +02:00
Andrey Prygunkov
a1e945661b #205: improved handling of queue cleanup
Also removed option “ParCleanupQueue” as it is not needed anymore with
reworked state handling.
2016-05-15 19:42:54 +02:00
Andrey Prygunkov
150857816d #197: various status info in browser window title
New option “WindowTitle” under web-interface settings.
2016-05-14 23:56:04 +02:00
Andrey Prygunkov
2190eec25a #121: do not delete completed state files 2016-05-14 13:00:32 +02:00
Andrey Prygunkov
b6dc2b6be9 Merge branch '205-retry-failed' into develop 2016-05-14 12:03:20 +02:00
Andrey Prygunkov
1809fa9228 #205: fixed state reset for completely failed files 2016-05-13 23:52:47 +02:00
Andrey Prygunkov
864fb92bc7 #205: new option "HistoryCleanupDisk"
… extracted from “DeleteCleanupDisk”.
2016-05-12 23:34:08 +02:00
Andrey Prygunkov
43f25587d2 #205: improved file state handling
Better handling of all cases: manual delete, health delete, retry
failed, download remaining with ContinuePartial=on/off and
DirectWrite=on/off.
2016-05-12 23:22:06 +02:00
Andrey Prygunkov
f08d3918dc #205: retry for deleted downloads too
Commands “Retry failed articles” and “Download remaining files“ now
work for deleted downloads too (including deleted by health check).
2016-05-09 19:17:17 +02:00
Will
89143bc19f #217: corrected option descriptions 2016-05-08 19:04:26 +02:00
Luis Flores III
7c2cac135d #212: corrected option descriptions 2016-05-04 20:05:27 +02:00
Andrey Prygunkov
11fc72e763 #206: 5376abc717: fixed edit-buttons in web-interface 2016-05-02 22:38:13 +02:00
Andrey Prygunkov
8c4d8cef1a #121: queue directory cleanup on program start
Automatically removing orphaned diskstate files from QueueDir:
- file info state;
- file progress state;
- file completion state;
- nzb-log.
2016-04-28 22:09:56 +02:00
Andrey Prygunkov
788d8d1f42 #210: validate and adjust file names after unpack
… and when moving from intermediate to final directory.
2016-04-26 23:56:50 +02:00
Andrey Prygunkov
a59b29c731 #210: auto adjusting file names with reserved words
… by prepending an underscore; with special support in par2-module.
2016-04-26 23:24:40 +02:00
Andrey Prygunkov
65398ac55f #210: support file names with reserved words on Windows
… using extra long path notation.
2016-04-26 19:47:50 +02:00
Andrey Prygunkov
8c3e70b1de #209: replace invalid characters in file names
- replace invalid characters in file names which were read from article
data;
- make necessary changes in par2-module to ensure par-repair works
correctly with changed file names;
- make necessary changes in par-renamer.
2016-04-25 19:11:14 +02:00
Andrey Prygunkov
8de5461759 #207: separate disk state files for queue and history 2016-04-23 21:33:06 +02:00
Andrey Prygunkov
cb20dfb547 #205: corrected file time info for retried files 2016-04-23 21:13:33 +02:00
Andrey Prygunkov
b82cc06789 #205: retry failed articles
- new action in history dialog: retry failed articles;
- new command “HistoryRetryFailed” of RPC-method “editqueue”;
- new subcommand “F” of command line switch “-E/--edit” for history.
2016-04-21 22:14:12 +02:00
Andrey Prygunkov
5376abc717 #206: UI for hidden web-interface settings 2016-04-21 21:37:58 +02:00
Andrey Prygunkov
6826410c6a #136: fixed crash on feed fetch failure (Windows)
Crash caused by nullptr passed to “FileSystem::DeleteFile”.
2016-04-18 20:41:09 +02:00
Andrey Prygunkov
85340831cc #203: exit code for feed scripts 2016-04-18 19:49:35 +02:00
Andrey Prygunkov
dd4df8b734 #99: separate quota reached flag
- using separate “quota reached”-flag instead of changing global pause
state;
 - new field “QuotaReached” in RPC-method “Status”.
2016-04-15 20:47:04 +02:00
Andrey Prygunkov
a4a3d1bc7a #99: indication of reached quota in web-interface
- remaining time is shown in orange when the quota is reached;
 - dialog “statistics and status” may show extra row “Download quota:
reached”.
2016-04-13 22:22:01 +02:00
Andrey Prygunkov
4f29804602 #136: fixed possible crash when canceling download
and having option “DirectWrite” disabled.
2016-04-12 19:03:47 +02:00
Andrey Prygunkov
a97cb4c61e #99: download volume quota
- new options “MonthlyQuota”, “QuotaStartDay”, “DailyQuota”;
- downloading is paused when quota is reached;
- new fields in RPC-method “status”: MonthSizeLo, MonthSizeHi,
MonthSizeMB, DaySizeLo, DaySizeHi, DaySizeMB. MonthSizes are related to
current billing month taking option “QuotaStartDay” into account;
- download volume for “this month” shown in web-interface in statistics
dialog shows data for current billing month (taking option
“QuotaStartDay” into account);
- scheduler task “UnpauseDownload” is ignored if the download was
paused due to quota.
2016-04-12 18:52:09 +02:00
Andrey Prygunkov
680171bd88 fixed #201: splitted files were not always joined 2016-04-12 18:30:25 +02:00
Andrey Prygunkov
e3c976406d #145: install included scripts into default MainDir on Windows
… instead of program’s directory.
2016-04-10 11:50:03 +02:00
Andrey Prygunkov
17dda0b0fc #155: unit tests for ServerPool 2016-04-02 17:43:47 +02:00
Andrey Prygunkov
298178b2dc #155: added handling of optional news servers 2016-03-30 22:10:57 +02:00
Andrey Prygunkov
3e919c0cb4 #196: badges in readme 2016-03-29 22:50:04 +02:00
Andrey Prygunkov
f3814e4e48 #196: continuous integration via Travis CI 2016-03-29 22:26:52 +02:00
Andrey Prygunkov
3278544d46 #195: preallocate files without sparse on Windows
Reduce disk fragmentation in direct write mode on Windows when using
article cache.
2016-03-29 18:51:08 +02:00
Andrey Prygunkov
44cf69b093 107: refactor: removed function "SetScript()" from Script-class
use SetArgs() instead.
2016-03-27 18:24:13 +02:00
Andrey Prygunkov
e6344d36a7 #107: new option "ShellOverride"
allows to configure path to python (bash, etc.); useful on systems with
non-standard paths; eliminating the need to change shebang for every
script; also makes it possible to put scripts on non-exec file systems.
2016-03-27 16:19:08 +02:00
Andrey Prygunkov
6b879e0c75 #172: 232c1a5597: fixed: segment alloc not always properly handled
and may cause program hanging on shutdown, with high cpu usage.
2016-03-26 18:41:27 +01:00
Andrey Prygunkov
0b5168ed23 #194: reworked script startup sequence
to use only async-signal-safe functions during forking.
2016-03-26 13:03:49 +01:00
Andrey Prygunkov
698edf1185 improved one info-message useful for troubleshooting 2016-03-25 21:53:19 +01:00
Andrey Prygunkov
737f059b3a #193: fixed for-range loops on guarded pointers
Range expression must be GuardedPtr. Dereferencing this object means
loosing the lock on pointer and therefore can’t be part of range
expression.
2016-03-25 20:29:25 +01:00
Andrey Prygunkov
cb8df8495b #172: 232c1a5597: fixed: segment realloc not properly handled
and may cause program hanging on shutdown, with high cpu usage.
2016-03-24 21:34:36 +01:00
Andrey Prygunkov
d348929ff0 #162: fixed: unpack destination path were not correct
if multiple unpack attempts were needed.
2016-03-24 21:16:42 +01:00
Andrey Prygunkov
c0d29d88b9 #193: corrected lock scope 2016-03-24 17:47:42 +01:00
Gunnar Lilleaasen
181a395515 Updated names of guarded pointers used directly in foreach loops. 2016-03-24 13:01:15 +01:00
Gunnar Lilleaasen
4f7849fbc1 Fixed guard usage in foreach loops, where the guards would unlock before the iteration was done. 2016-03-24 02:44:12 +01:00
Andrey Prygunkov
857ada54ea #121: unified disk state management
… to use StateDiskFile for all operations.
2016-03-23 19:29:57 +01:00
Andrey Prygunkov
01ef1c4024 #192: use pkg-config for all required libraries 2016-03-23 00:08:20 +01:00
Andrey Prygunkov
1e59b9976a #136, #127: forcing extended paths with unrar (Windows)
Instead of using extended (extra long) path notation only if the path
is really long, we now always use extended notation since we don’t know
how long the paths inside archive are.
2016-03-22 18:25:42 +01:00
Andrey Prygunkov
7d36881b29 Merge pull request #190 from heksesang/develop
merge pull request #190 from heksesang/develop
2016-03-20 22:58:53 +01:00
Gunnar Lilleaasen
8378ef8437 Replaced semantically incorrect in-class member initializers with correct
ones.
2016-03-20 21:33:17 +01:00
Andrey Prygunkov
1f83fb8a39 #189: added include for new macro file 2016-03-20 20:01:55 +01:00
Louis Sautier
67612d8dd5 Add the ax_cxx_compile_stdcxx macro locally 2016-03-20 18:13:44 +01:00
Andrey Prygunkov
a42df761fb #135: don't install doc-files coming with par2-module 2016-03-20 12:19:59 +01:00
Andrey Prygunkov
d37a9364e0 #125: linux build script works without system wide C++14 compiler
Since the script uses cross-compiling tools, it’s now possible to build
the targets without having a C++14 compiler installed on the host
system.
2016-03-20 12:17:52 +01:00
Andrey Prygunkov
caca0abb75 #168: 9da07e1e54: fixed: daemon lock-file was not properly created 2016-03-19 20:36:50 +01:00
Andrey Prygunkov
b71c990c2e updated copyright string in about box 2016-03-19 19:09:20 +01:00
Andrey Prygunkov
5948729b87 #185: guarded containers
Using guarded lists to automatically unlock containers.
2016-03-18 21:45:29 +01:00
Andrey Prygunkov
f973388879 #185: use guards with private mutexes
Use guard objects to automatically unlock private mutexes when leaving
current scope.
2016-03-16 22:37:19 +01:00
Andrey Prygunkov
65e0238aa2 #181: show first script directory in Windows app
If option “ScriptDir” contains multiple paths the menu command “Show in
Windows Explorer -> Scripts” of tray icon shows the first path
contained in option (instead of failing).
2016-03-14 18:43:27 +01:00
Andrey Prygunkov
ca088ef2e6 #163: also saving messages for nzbs in history
If an nzb was already removed from queue (put into history), the
messages were not added to it’s log. Fixed.
2016-03-13 19:42:05 +01:00
Andrey Prygunkov
1a48feba72 #178: 1d728996b2: fixed button "Choose" in settings 2016-03-13 18:18:57 +01:00
Andrey Prygunkov
1d728996b2 closes #178: workaround for a bug in Chrome on iOS 2016-03-13 00:24:45 +01:00
Andrey Prygunkov
22b1ae55e5 #148: unique smart pointers for queue scripts 2016-03-13 00:08:31 +01:00
Andrey Prygunkov
d7f5d8dea2 #168: smart pointers in par-checker 2016-03-13 00:08:31 +01:00
Andrey Prygunkov
39cf412938 #184: easier use of Debuggable class 2016-03-13 00:08:31 +01:00
Andrey Prygunkov
a5ac6d594f #148: unique smart pointers for other containers
: feeds, servers, scheduler.
2016-03-13 00:08:16 +01:00
Andrey Prygunkov
1a74695126 #148: unique smart pointers for download containers 2016-03-11 22:49:14 +01:00
Andrey Prygunkov
35049d436e #183: dropped support for old queue disk state formats
(v12 or older)
2016-03-10 21:47:38 +01:00
Andrey Prygunkov
069350eaa3 #168: unique smart pointers for download objects
… before they are put into containers.
2016-03-09 23:28:37 +01:00
Andrey Prygunkov
e3cce71329 #175: in-class member initializers (more) 2016-03-06 15:37:21 +01:00
Andrey Prygunkov
6bae67412a #168: unique smart pointers in download-info
… only a little bit yet.
2016-03-06 15:10:57 +01:00
Andrey Prygunkov
9461678a19 #168: unique smart pointers in remote server 2016-03-06 15:10:24 +01:00
Andrey Prygunkov
c22c5f6c7b #168: unique smart pointers in feed filter helper 2016-03-06 14:13:17 +01:00
Andrey Prygunkov
e5501cfa15 #182: use std::shared_ptr for feed items cache
… instead of a custom reference counting cache.
2016-03-05 19:59:33 +01:00
Andrey Prygunkov
14d1906320 #175: bfa5027bf9: fixed curses-mode initialization 2016-03-04 21:29:40 +01:00
Andrey Prygunkov
82ca298902 #181: show first script directory in OS X app
If option “ScriptDir” contains multiple paths the menu command “Show in
Finder -> Scripts” of OS X application shows the first path contained
in option (instead of failing).
2016-03-03 23:38:34 +01:00
Andrey Prygunkov
27fc3b594b #181: multiple script directories
Option “ScriptDir” now accepts multiple directories (separated with
comma or semicolon).
2016-03-03 19:09:22 +01:00
Andrey Prygunkov
b4a6b3954d fixed #180: could not delete intermediate directory (Windows) 2016-03-03 17:51:03 +01:00
Andrey Prygunkov
b6f4c9f704 #172: improved memory management in TlsSocket 2016-03-02 19:04:28 +01:00
Andrey Prygunkov
ee5cec4b16 #176: fixed compilation error with Visual Studio
A workaround for a bug in Visual Studio actually.
2016-03-01 19:49:46 +01:00
Andrey Prygunkov
f3f7fbd0de #176: updated copyright notice in source files
- added link to http://nzbget.net;
- replaced FSF Post address with a web link;
- removed unusable subversion-tags;
- updated year.
2016-03-01 19:45:07 +01:00
Andrey Prygunkov
8a344c97c9 #176: changed order of member declarations 2016-03-01 00:20:14 +01:00
Andrey Prygunkov
831c73d28d #176: removed aligning of class member names 2016-02-29 19:31:40 +01:00
Andrey Prygunkov
bfa5027bf9 #175: in-class member initializers 2016-02-28 19:53:37 +01:00
Andrey Prygunkov
194a4a66e5 closes #158: corrected option descriptions 2016-02-27 14:24:21 +01:00
Andrey Prygunkov
ceb66387eb #172: b7f01fc58c: fixed compilation error with GCC 2016-02-27 14:01:45 +01:00
Andrey Prygunkov
b18bc04606 #172: use vector of Mutex in TlsSocket
instead of dynamic array of raw pointers.
2016-02-26 23:40:47 +01:00
Andrey Prygunkov
b7f01fc58c #172: use CString in Tokenzier
instead of raw C-style string.
2016-02-26 23:34:54 +01:00
Andrey Prygunkov
7fc6d9e99e #172: moved class WString to NString.cpp 2016-02-26 23:18:35 +01:00
Andrey Prygunkov
232c1a5597 #172: new class for cached segments
1) improve memory management; 2) removed dependency of module
“DownloadInfo.cpp” from article cache module “ArticleWriter.h”
2016-02-26 22:59:00 +01:00
Andrey Prygunkov
1414a4976e fixed #174: command "--printconfig" segfaults 2016-02-26 22:30:00 +01:00
Andrey Prygunkov
048add1dcf #172: vectors and smart pointers in script controller
instead of raw pointers
2016-02-25 22:44:33 +01:00
Andrey Prygunkov
1ae8132be2 #168: unique smart pointers in gzip; #172: use system objects directly 2016-02-25 21:52:39 +01:00
Andrey Prygunkov
72cd89d5f4 #172: use vectors in curses frontend on Windows
Replaced raw dynamic arrays with vectors in curses frontend on Windows.
2016-02-25 19:35:21 +01:00
Andrey Prygunkov
b414526d02 #172: using vector of strings in script controller
instead of dynamic array of c-style strings
2016-02-25 19:27:24 +01:00
Andrey Prygunkov
27eb78faab #168: unique smart pointers in regex
instead of new/delete.
2016-02-25 18:50:00 +01:00
Andrey Prygunkov
6bd5223b92 fixed: not all unit tests were active on POSIX 2016-02-25 18:34:57 +01:00
Andrey Prygunkov
e8afb4e331 #172: use system objects directly
instead of dynamic creation, which were used to avoid system includes;
that’s become unimportant after using of precompiled headers.
2016-02-23 22:31:27 +01:00
Andrey Prygunkov
390481f814 #172: using CString in Maintenance-class
instead of c-style string.
2016-02-23 22:09:11 +01:00
Andrey Prygunkov
4eb0e82f75 #172: using vector of strings in command line parser
instead of dynamic array of c-style strings.
2016-02-23 22:08:20 +01:00
Andrey Prygunkov
548753aa69 #172: using vector of strings in SplitCommandLine
instead of dynamic array of c-style strings.
2016-02-23 22:07:30 +01:00
Andrey Prygunkov
cdf6068db5 #172: using vector instead of raw dynamic array in queue manager 2016-02-23 22:03:30 +01:00
Andrey Prygunkov
6aead41e6f #126: using CharBuffer instead of raw pointers in LoadFileIntoBuffer 2016-02-23 21:59:10 +01:00
Andrey Prygunkov
e48cc0257b #126: 9e2d8544da: fixed cleanup in par-renamer 2016-02-20 21:20:27 +01:00
Andrey Prygunkov
1d05477729 #168: c96d2259ce: fixed: remote mode didn't work 2016-02-19 22:47:03 +01:00
Andrey Prygunkov
2b840315b0 #126: d87d6ac2ac: fixed: exe path could be determined incorrectly 2016-02-19 22:17:12 +01:00
Andrey Prygunkov
177be1571e #168: c96d2259ce: fixed feed parse error 2016-02-19 22:11:44 +01:00
Andrey Prygunkov
b9076fc21d #168: 9da07e1e54: adjusted finalization
of global objects
2016-02-19 19:08:00 +01:00
Andrey Prygunkov
9dfa66ac3e fixed #171: queue-scripts not called for failed URLs
if the scripts were set in category’s option “PostScript”.
2016-02-18 23:03:52 +01:00
Andrey Prygunkov
b5dae56c7e #168: 9da07e1e54: fixed segfault
when starting daemon (nzbget -D).
2016-02-18 22:28:02 +01:00
Andrey Prygunkov
4a08fdb586 #168: c96d2259ce: fixed compiling error on Windows 2016-02-18 22:11:28 +01:00
Andrey Prygunkov
b910d123eb #143: store EditItem directly in container
instead of pointer to object.
2016-02-17 22:03:33 +01:00
Andrey Prygunkov
9da07e1e54 #168: unique smart pointers for global variables
and restructured main module “nzbget.cpp”
2016-02-17 22:03:33 +01:00
Andrey Prygunkov
c96d2259ce #168: unique smart pointers for local variables 2016-02-17 22:03:33 +01:00
Andrey Prygunkov
36fe905bd9 #168: unique smart pointers for member fields (2) 2016-02-17 22:03:33 +01:00
Andrey Prygunkov
40eb5c4e1e #168: unique smart pointers for static members 2016-02-17 22:03:32 +01:00
Andrey Prygunkov
c9b883f909 #168: unique smart pointers for member fields 2016-02-17 22:03:32 +01:00
Andrey Prygunkov
d803330e3f #167: more stack objects 2016-02-17 22:03:20 +01:00
Andrey Prygunkov
8f4718fb9d #167: prefer stack objects in tests 2016-02-17 22:03:06 +01:00
Andrey Prygunkov
1162c89e77 #167: prefer stack objects 2016-02-17 22:02:41 +01:00
Andrey Prygunkov
b55190e52f #162: change destination directory from queue-script
Queue-scripts can now change destination after download is completed
and before unpack.
2016-02-02 23:31:36 +01:00
Andrey Prygunkov
74f2ab8963 #162: removed support for multiple paths
in options “DestDir” and “CategoryX.DestDir”.
2016-02-02 21:38:45 +01:00
Andrey Prygunkov
4fbff8cd25 closes #163: queue scripts save messages into nzb-log 2016-02-01 22:22:56 +01:00
Andrey Prygunkov
c1f58220c2 #126: 6d33d83d20: removed incorrect attribute 2016-02-01 22:19:22 +01:00
Andrey Prygunkov
624944b82a #162: fixed compilation error in VC++ 2016-02-01 19:18:55 +01:00
Andrey Prygunkov
00d81795e9 #162: combining free space on all paths of "DestDir"
1) When checking free space (option “DiskSpace”) the total free size of
directories in “DestDir” is checked;
2) Web-interface shows the total free size of all directories.
2016-02-01 00:36:59 +01:00
Andrey Prygunkov
ec87c3c1f8 #162: multiple paths in global option "DestDir"
are now supported too.
2016-02-01 00:21:36 +01:00
Andrey Prygunkov
c1ac38d10b #162: multiple destination paths for categories
in option “CategoryX.DestDir”.
2016-01-30 18:32:29 +01:00
Andrey Prygunkov
b7d785797f #136: fixed: root drive paths sometimes unusable on Windows
Paths like “C:\” were sometimes not usable.
2016-01-29 22:34:37 +01:00
Andrey Prygunkov
2b2b1e1539 213eb2c7c1, 0c3ce58ffa: restored target detection in configure script
which is required to proper configure on OS X.
2016-01-25 19:15:40 +01:00
Andrey Prygunkov
3ae8b5ac95 #136: fixed: scripts were not found 2016-01-24 19:07:25 +01:00
Andrey Prygunkov
05a1926ebc #161: printing low-level messages from par2-module 2016-01-24 17:58:04 +01:00
Andrey Prygunkov
ce64eeeb1d #160: status "FAILURE" for failed par-repairs
If par-repair fails the par-status is now set to “FAILURE” (instead of
“REPAIRABLE”).
2016-01-23 22:56:10 +01:00
Andrey Prygunkov
6e763f3573 #126: small optimization 2016-01-23 18:41:58 +01:00
Andrey Prygunkov
b448fddfe6 #136: corrected slashes and simpilified code 2016-01-23 18:29:55 +01:00
Andrey Prygunkov
69eb079851 #136: ff69fbbeb9: fixed compilation error under GCC 2016-01-23 16:58:37 +01:00
Andrey Prygunkov
ff69fbbeb9 #136: avoid double slashes in paths
Extra long path names are not normalized automatically by Windows and
therefore must contain paths in canonical form.
2016-01-23 14:23:53 +01:00
Andrey Prygunkov
26fea301e0 #136: fixed crash on Windows 2016-01-22 00:37:49 +01:00
Andrey Prygunkov
d95e389583 #156: fixed memory access error 2016-01-22 00:36:40 +01:00
Andrey Prygunkov
72370c9c71 #126: fixed memory access error 2016-01-22 00:36:04 +01:00
Andrey Prygunkov
d04e6a53da #156: fixed compilation error with certain compilers 2016-01-20 20:46:39 +01:00
Andrey Prygunkov
1aa59ded15 #126: another place for CString 2016-01-20 20:45:36 +01:00
Andrey Prygunkov
080bd22d77 #141: ignore hidden files and directories in NzbDir
The files and directories whose names start with dot are now ignored by
the scanner of incoming nzb directory.
2016-01-17 12:58:43 +01:00
Andrey Prygunkov
8f84132218 #156: new class "CharBuffer" for temporary buffers
Replaced everywhere plain “char”-buffers with new class. Avoid using
“malloc/free”.
2016-01-17 00:06:27 +01:00
Andrey Prygunkov
34771792ac #126: removed "CString::Capacity()" as it was error source 2016-01-16 19:02:27 +01:00
Andrey Prygunkov
17024eb0e5 #126: replaced "char*" with "CString" at few more places 2016-01-16 16:31:45 +01:00
Andrey Prygunkov
da0a0f3105 refactor: removed unneeded code 2016-01-14 19:47:12 +01:00
Andrey Prygunkov
98cd5a8dbc refactor: removed unneeded checks 2016-01-14 19:46:49 +01:00
Andrey Prygunkov
421de1013f #154: use lambdas when searching in containers 2016-01-14 19:43:33 +01:00
Andrey Prygunkov
0ee644d252 #154: use lambdas when deleting elements from containers 2016-01-14 19:42:49 +01:00
Andrey Prygunkov
ac1bd3d07c #154: use lambdas when sorting containers 2016-01-13 20:09:36 +01:00
Andrey Prygunkov
ef4a72d383 #152: eliminated dereferences in for-range loops using “begin()” and “end()” template functions in “nzbget.h” 2016-01-13 19:44:40 +01:00
Andrey Prygunkov
b32b4c0691 #152: for-range loops with iterators 2016-01-12 00:30:17 +01:00
Andrey Prygunkov
0732cb4b8b closes #147: par-check doesn't ignore files from option "ExtCleanupDisk"
Only files listed in option ParIgnoreExt are ignored.
2016-01-09 21:44:01 +01:00
Andrey Prygunkov
362ee700c5 #125: check for C++14 features in configure-script
- configure now requires a C++14 compliant compiler;
- special exception: GCC 4.9 is also recognized and supported (it
offers only partial support for C++14);
- an error message and a link to wiki-page is printed if no C++14
compiler was found.
2016-01-08 22:49:00 +01:00
Andrey Prygunkov
93835ea2af #146: improved error reporting on DNS lookup errors
Previously printed error messages were not correct as errors must be
read via special function, not via default “errno”.
2016-01-08 22:45:34 +01:00
Andrey Prygunkov
32400a810f #143: store download objects directly in containers
…where possible.
2016-01-08 22:29:44 +01:00
Andrey Prygunkov
17999fb96d #143: store feed cache objects directly in container 2016-01-08 21:52:16 +01:00
Andrey Prygunkov
4f8370400f #143: store Segment and DupeSource directly in containers 2016-01-07 00:35:05 +01:00
Andrey Prygunkov
09dde2b82f #143: store Scanner objects directly in container 2016-01-07 00:07:06 +01:00
Andrey Prygunkov
f6587d3299 #143: store Script objects and templates directly in container 2016-01-05 23:47:59 +01:00
Andrey Prygunkov
a70b86abd0 #143: store Block directly in container 2016-01-05 22:15:26 +01:00
Andrey Prygunkov
6885082299 #143: store volume stat objects directly in container 2016-01-05 18:57:15 +01:00
Andrey Prygunkov
37c126e24d #143: fixed file permissions in repository 2016-01-04 22:44:34 +01:00
Andrey Prygunkov
476dae8c1e #143: store feed objects directly in containers 2016-01-04 22:43:56 +01:00
Andrey Prygunkov
c949b27468 #143: store Term and Rule directly in containers 2016-01-04 19:54:51 +01:00
Andrey Prygunkov
28897e4e79 #143: store OptEntry and Category directly in containers 2016-01-04 19:42:47 +01:00
Andrey Prygunkov
81f2c7825d #143: store Message directly in container
…instead of pointer to object.
2016-01-03 20:49:30 +01:00
Andrey Prygunkov
5d4b56b40b #143: store FileHash directly in container
instead of pointer to object.
2016-01-03 20:40:01 +01:00
Andrey Prygunkov
dc8803d6a3 #143: passing ids to "editqueue" as vector
… instead of pointer to array. And names as vector<CString> instead of
vector<char*>.
2016-01-03 20:38:52 +01:00
Andrey Prygunkov
be852d0a9b #143: using <CString> instead of <char*> in containers 2016-01-03 19:58:11 +01:00
Andrey Prygunkov
c8c86f00ef #142: deactivated timestamp check of autotools files
This makes it easier for users to build from git, eliminating the need
for “touch <certain files>“-command.
2016-01-03 16:19:43 +01:00
Andrey Prygunkov
13fef5ce96 Merge pull request #140 from parnic/develop
Fixed compilation error when using GnuTLS
2016-01-03 00:53:36 +01:00
Andrey Prygunkov
69c995359b #126: time formatting functions 2016-01-01 15:56:53 +01:00
Andrey Prygunkov
3e89638b39 refactor: replaced "time(NULL)" with an utility function 2016-01-01 14:57:48 +01:00
parnic
8dc9b4e396 Fixed compilation error with g++ 5.3
daemon/connect/TlsSocket.cpp: In member function ‘bool TlsSocket::Start()’:
daemon/connect/TlsSocket.cpp:322:56: error: use of deleted function ‘CString::CString(CString&)’
  const char* priority = !m_cipher.Empty() ? m_cipher : "NORMAL";
                                                        ^
In file included from daemon/connect/TlsSocket.h:30:0,
                 from daemon/connect/TlsSocket.cpp:30:
./daemon/util/NString.h:73:2: note: declared here
  CString(CString& other) = delete;
  ^
2015-12-31 22:39:50 -06:00
Andrey Prygunkov
28ec8a8ec3 #132: tvdbid and tvmazeid in web-interface
updated descriptions and other references.
2015-12-31 15:42:11 +01:00
Andrey Prygunkov
1b457bceb7 #132: tvdbid and tvmazeid in rss feeds
- new fields “tvdbid” and “tvmazeid” in filter;
- new fields are used when automatically generating dupe-keys;
- new options “tvdbid:xxx” and “tvmazeid:xxx” of command “Accept” to
generate dupe-keys based on these fields.
2015-12-31 15:35:35 +01:00
Andrey Prygunkov
2c152b51b6 #126: fixed memory leak 2015-12-30 23:23:33 +01:00
Andrey Prygunkov
9771d7f53f #125: check for C++11 features in configure-script 2015-12-30 17:26:14 +01:00
Andrey Prygunkov
94b7ef8a37 #138: fixed compilation error on Windows 2015-12-30 16:41:28 +01:00
Andrey Prygunkov
04c3e0d263 #138: use "nullptr" instead of "NULL" 2015-12-30 16:35:07 +01:00
Andrey Prygunkov
74c0e8804f #136: transcoding of names in par2-module
when running on POSIX the file names must be transcoded as well, not
only on Windows.
2015-12-29 23:49:37 +01:00
Andrey Prygunkov
3aa97e44e7 #136: adjustment in par2-module
using built-in latin1-to-utf8 conversion routine instead of Windows
function (which depend on current code page).
2015-12-29 23:44:22 +01:00
Andrey Prygunkov
5df8f73b20 #137: transcode file names read from articles
File names which were read from downloaded articles (yEnc) are now
transcoded from Latin1 to UTF8.
2015-12-29 23:33:39 +01:00
Andrey Prygunkov
a9181d8a56 #136: support for extra long UNC paths 2015-12-29 20:47:30 +01:00
Andrey Prygunkov
df68fd52c8 #131: better support for UNC paths on Windows
fixed: automatic creation of directories didn't work if the last existed
directory in the path was the network share itself.
2015-12-29 20:34:14 +01:00
Andrey Prygunkov
65f2a0afe3 #136: support for Unicode and extra long paths in par2-module
- par-renamer and par-checker both support Unicode paths and extra long
paths;
2015-12-29 01:54:19 +01:00
Andrey Prygunkov
6292ad5394 #131: fixed incorrect return value of "Seek" 2015-12-29 01:17:12 +01:00
Andrey Prygunkov
b8e14faefe #136: removed unneeded function
deleted functions for converting ANSI <-> UTF which are not used
anymore.
2015-12-28 18:35:24 +01:00
Andrey Prygunkov
d8a2d79240 #136: handling of Unicode paths with MSXML
- when parsing nzb-files (option NzbDir);
- when parsing rss feeds (option TempDir).
2015-12-28 18:34:47 +01:00
Andrey Prygunkov
449a048304 #136: made class WString public for use from other modules 2015-12-28 14:45:01 +01:00
Andrey Prygunkov
f347f1aed1 #136: avoid crash in par-renamer
if the directory content could not be read.
2015-12-28 11:05:52 +01:00
Andrey Prygunkov
03ba670d36 #136: Unicode in script environment
- pass all data to scripts in Unicode environment;
- script which doesn’t support Unicode can access ANSI-version of
environment, which is provided by Windows automatically from the
Unicode-version.
2015-12-28 11:04:59 +01:00
Andrey Prygunkov
a9847831a5 #136: extra long paths with unrar
- pass extra long paths to unrar using “\\?\” prefix (and in Unicode).
2015-12-28 11:02:20 +01:00
Andrey Prygunkov
920507c163 #136: Unicode Windows-API when calling other programs
- use CreateProcessW;
- pass command-line in Unicode;
- pass environment in Unicode;
- if current directory is too long convert it to short path (8.3
notation); because CreateProcessW doesn’t support extra long path
(prefixed with “\\?\”) for current directory.
2015-12-28 11:00:32 +01:00
Andrey Prygunkov
e11dfb62d0 #136: Unicode-Windows-API for file operations
- internally all paths are handled in UTF8;
- all paths are stored in config-file in UTF8;
- when calling file access Windows API functions the paths are
converted to wide-chars and Unicode-API is used;
- extra long paths are prefixed with “\\?\” (extended path format).
2015-12-28 10:56:11 +01:00
Andrey Prygunkov
c68ba306fe #131: using enum for file mode in function DiskFile::Open 2015-12-26 17:19:12 +01:00
Andrey Prygunkov
6840ef4439 #131: routing a few more remaining function calls through module "FileSystem"
“rename”, “remove” and “rmdir”.
2015-12-25 22:55:46 +01:00
Andrey Prygunkov
dce6f0c9a3 #131: new class DiskFile to wrap "FILE*"-functions 2015-12-25 18:11:50 +01:00
Andrey Prygunkov
8fb9fb7b2d #133: put all par2-modules into namespace "Par2"
To avoid conflicts with similarly named classes of NZBGet.
2015-12-24 21:25:38 +01:00
Andrey Prygunkov
eae6fd5d91 #131: wrapped function "remove" into module FileSystem
under name “DeleteFile”.
2015-12-24 18:34:15 +01:00
Andrey Prygunkov
321c7efa41 #130: moved parts from module "Util" into new module "FileSystem" 2015-12-22 22:01:03 +01:00
Andrey Prygunkov
19ce3bf69b #130: improved class DirBrowser
It doesn’t return filenames ‘.’ and ‘..’ (for current and parent
directories) anymore, eliminating the need to check and ignore these
names on each usage of the class.
2015-12-21 20:54:21 +01:00
Andrey Prygunkov
c170d6a180 Merge branch 'develop' into 126-managed-strings 2015-12-21 19:12:16 +01:00
Andrey Prygunkov
80f614a54c #126: fixed compilation errors under GCC 2015-12-21 18:58:35 +01:00
Andrey Prygunkov
99d8cee0db #126: class BString supports initialization via assignment
which makes the code clearer.
2015-12-20 23:46:22 +01:00
Andrey Prygunkov
d87d6ac2ac #126: using CString for ref-parameters and return values
1) for parameters use references to CString instead of pointers to
buffers;
2) when returning strings use CString instead of pointer to char buffer
which caller needs to deallocate;
3) use BString even more.
2015-12-20 16:27:01 +01:00
Andrey Prygunkov
4f4a289309 Merge pull request #129 from fjeldsoe/patch-2
removing leading and trailing spaces from URL when adding from web-interface.
2015-12-19 22:44:25 +01:00
fjeldsoe
44aaf77e5d Added trim method to url string value
Adding a NZB URL fails if there is a space at the start of the string, this should fix it. Often when you copy and paste a link, spaces in front of the string can occur.
2015-12-19 22:37:42 +01:00
Andrey Prygunkov
9e2d8544da #126: full use of class BString
1) replaced characters arrays with class BString through the whole
program. The string formatting code has become much cleaner.
2) class Util returns error message via CString instead of character
buffers.
3) few more places to use CString.
2015-12-19 18:43:52 +01:00
Andrey Prygunkov
8394759527 #126: reworked string classes
1) classes CString and BString now have no memory overhead compared to
C-style null-terminated strings and character arrays respectively.
2) class StringBuilder is back and should be used when often Append’s
are needed.
2015-12-19 00:09:12 +01:00
Andrey Prygunkov
4ba432dee3 #126: fixed: corrupted files when article cache was active 2015-12-18 23:47:18 +01:00
Andrey Prygunkov
5ea439c8a0 #125: 94e55e1b5a: fixed accidental deactivation of compiler optimization 2015-12-13 23:39:55 +01:00
Andrey Prygunkov
6d33d83d20 #126: using GCC attributes to detect formatting errors
also eliminated warnings found with activated detection.
2015-12-13 18:28:48 +01:00
Andrey Prygunkov
17d3e05e16 #126: update project file for Windows
and fixed a few warnings.
2015-12-13 17:09:52 +01:00
Andrey Prygunkov
ecc7fc9695 #126: extended string classes
- Append/AppendFmt-methods;
- CString stores length internally for faster appends;
- removed class StringBuilder, replaced usages with CString;
- binding to existing C-style null-terminated strings with methods
Bind/Unbind.
2015-12-13 16:02:57 +01:00
Andrey Prygunkov
558fce9b47 #126: replaced C-style strings with class "CString"
: replaced all data members.
2015-12-12 16:36:25 +01:00
Andrey Prygunkov
6b0fdc881e #126: new string classes
which can be easily used as replacement for C-style null-terminated
strings and C-style char arrays.
2015-12-12 16:34:45 +01:00
Andrey Prygunkov
94e55e1b5a #125: activated C++14 in Linux build script 2015-12-10 19:59:43 +01:00
Andrey Prygunkov
d6e05430bf #124: updated build instructions for Linux and FreeBSD toolchains 2015-12-08 18:50:13 +01:00
Andrey Prygunkov
fc50dc871a #124: added FreebSD toolchain script to Makefile 2015-12-08 18:34:09 +01:00
Andrey Prygunkov
7e6c3f435a #124: program update script works on FreeBSD too 2015-12-08 00:09:38 +01:00
Andrey Prygunkov
9512ec7d74 #124: installer-script now supports FreeBSD 2015-12-08 00:09:02 +01:00
Andrey Prygunkov
202200c74a #124: unpack build scripts support FreeBSD toolchain 2015-12-08 00:08:20 +01:00
Andrey Prygunkov
fe4e9d1a94 #124: supporting FreeBSD in the installer build script 2015-12-08 00:07:52 +01:00
Andrey Prygunkov
c330704c9f #124: toolchain creation script for FreeBSD
the script creates a cross-compiling toolchain, which works on Linux
and produces binaries for FreeBSD.
2015-12-08 00:04:14 +01:00
Andrey Prygunkov
1d77852bea updated version string to "16.4" 2015-12-05 12:24:20 +01:00
Andrey Prygunkov
15a5d056ed #118: merge 80653a8dad: fixed resource (socket) leak
, which may cause “too many open files” errors with a possible crash.
2015-12-05 11:51:30 +01:00
Andrey Prygunkov
32009c5691 closes #120: better error reporting when using GnuTLS 2015-11-30 19:44:53 +01:00
Andrey Prygunkov
41c62dc413 #117: better handling of command line when calling external programs (Windows)
Trailing slashes must be doubled. This in particular improves
compatibility with user-compiled “unrar”.
2015-11-30 19:38:59 +01:00
Andrey Prygunkov
9c3d6fadca #117: refactor: restructured Script-module 2015-11-30 19:33:13 +01:00
Andrey Prygunkov
726542b698 #108: 51172e1c96: fixed file attributes 2015-11-30 19:32:00 +01:00
Andrey Prygunkov
51172e1c96 closes #108: save nzb-log into a file directly from web-ui
In download details dialog or history details dialog -> button “Log” ->
button “Save to file”.
2015-11-29 23:41:41 +01:00
Andrey Prygunkov
f8049a81e1 #119: do not print warnings for certain missing web files
Built-in web-server doesn’t print warnings to log for certain files
which are or can be missing but that’s OK for them:
- package-info.json - update information file, which is available only
with binary packages supporting automatic updates;
- favicon.ico - the file is located in img-subdirectory; web-clients
requesting the file from the root directory are doing this wrong;
- apple-touch-icon*.png - iOS safari asks for these files, but we don’t
have nice icons.
2015-11-28 21:50:44 +01:00
Andrey Prygunkov
80653a8dad closes #118: fixed resource (socket) leak,
which may cause “too many open files” errors with a possible crash
2015-11-27 22:45:58 +01:00
Andrey Prygunkov
4e4816c3c8 #116: use size specific integer types 2015-11-21 00:02:22 +01:00
Andrey Prygunkov
b5dd49dbc4 fixed a compiler warning in VS2015 (Windows only) 2015-11-20 23:17:56 +01:00
Andrey Prygunkov
1f406a391a #115: using precompiled header in Linux build script
… to speed up compilation. Another new feature in the build script -
multicore compilation via new option “corex”.
2015-11-20 00:02:09 +01:00
Andrey Prygunkov
87d2c1a7f4 #115: using precompiled header on Windows 2015-11-19 23:54:38 +01:00
Andrey Prygunkov
ec17d119a1 #115: put all external headers together
into “nzbget.h”
2015-11-19 23:51:02 +01:00
Andrey Prygunkov
8630f1365c merged #100: fixed: downloads may sometimes fail due to incorrect decoding of articles 2015-11-16 22:56:44 +01:00
Andrey Prygunkov
322b08e4b2 #98: showing number of selected items in confirmation box
…when deleting or performing other actions on multiple items in
web-interface.
2015-11-15 00:42:09 +01:00
Andrey Prygunkov
fc3c90605c #110: accepting certificate chains in option SecureCert
The built-in web-server can now use certificate chain files through
option “SecureCert”, when compiled using OpenSSL.
2015-11-07 00:03:02 +01:00
hugbug
afb310ab9b a72f787a40: renamed missing file 2015-11-06 23:57:40 +01:00
Andrey Prygunkov
580df4d361 closes #97: cleanup of dupe-badges
allowing character “=“ in dupe-badges.
2015-11-06 00:07:08 +01:00
Andrey Prygunkov
70ccfd9802 normalized whitespace formatting
1) removed trailing spaces and tabs;
2) replaced occasional leading spaces with tabs.
2015-11-05 23:45:19 +01:00
Andrey Prygunkov
98bc1ebd37 #103: corrected whitespace formatting 2015-11-04 00:14:58 +01:00
Andrey Prygunkov
a9a6f1e2d4 #103: manual corrections of variable names 2015-11-03 23:33:21 +01:00
Andrey Prygunkov
bf49f16d7c #103: renamed global variables 2015-11-01 21:42:35 +01:00
Andrey Prygunkov
c46e6953e1 #103: updated references for renamed modules 2015-10-31 19:57:41 +01:00
Andrey Prygunkov
a72f787a40 #103: renamed modules to normalize acronyms 2015-10-31 19:15:54 +01:00
Andrey Prygunkov
1fb21b330e #103: normalized (renamed) acronyms 2015-10-30 23:54:37 +01:00
Andrey Prygunkov
af85bb91fa #103: few manual renames 2015-10-30 22:08:21 +01:00
Andrey Prygunkov
57bee2447e #103: in "tests": renamed local, member variables and function parameters 2015-10-30 21:50:26 +01:00
Andrey Prygunkov
5adb50274e #103: renamed local, member variables and function parameters 2015-10-27 22:37:23 +01:00
Andrey Prygunkov
739925ecc8 #104: improved error reporting when creating sparse files 2015-10-22 21:36:23 +02:00
Andrey Prygunkov
c8f4712e77 #101: upgraded Linux build machine to use GCC 5.2 2015-10-21 23:35:12 +02:00
Andrey Prygunkov
13f5ab7388 #102: updated compiling instructions (Windows only)
…for Visual Studio 2015.
2015-10-21 23:03:17 +02:00
Andrey Prygunkov
7d2ee607a1 #102: upgraded VC++ project to Visual Studio 2015 2015-10-21 22:57:18 +02:00
Andrey Prygunkov
68a87c775c fixed #96: failed to load nzb if path contains certain characters (Windows only)
although the path is URL-encoded the MSXML still fails to parse it
properly sometimes.
2015-10-20 23:20:08 +02:00
Andrey Prygunkov
50d344cb91 fixed #100: workaround to deal with malformed responses
…which still may contain useful data.
2015-10-20 21:09:01 +02:00
Andrey Prygunkov
0c5dc22a93 fixed #95: starting nzbget from setup (Windows)
When launching NZBGet at the end of setup the program is now started
with regular user permissions.
2015-10-18 19:14:45 +02:00
Andrey Prygunkov
e81b42f8dc #77: fixed issues with reverse proxies (3)
when very long headers were sent from the proxy, in particular if
htdigest authorization were used.
2015-10-17 01:01:06 +02:00
Andrey Prygunkov
2a302b3f0d #77: reverted e7562b6470
restored header name back to “X-Auth-Token” since it wasn’t the source
of the problem.
2015-10-17 00:54:20 +02:00
Andrey Prygunkov
eb78bdcf06 #77: fixed issues with reverse proxies (again)
removing of authorization-header wasn’t such a good idea.
2015-10-14 08:05:17 +02:00
Andrey Prygunkov
e7562b6470 #77: fixed issues with reverse proxies
renamed header “X-Auth-Token” to “X-Private-Auth-Token” to avoid
conflicts with apache and nginx.
2015-10-14 00:00:59 +02:00
Andrey Prygunkov
01e672c7e2 #89: fixed unpack failure on certain CPUs
unrar shipped with nzbget was compiled in optimization mode O3. In that
mode GCC activates loop vectorization. On x86_64 architecture that
caused the usage of CPU commands from extended set SSSE3. Some older
AMD processors doesn’t support SSSE3, which lead to abortion with
“illegal instruction”-message. Now using O2-mode; that solves the
issues.
2015-10-13 23:29:15 +02:00
Andrey Prygunkov
3c638c5ca2 fixed file permissions in repository 2015-10-13 23:22:32 +02:00
Andrey Prygunkov
5356f5aafc updated version string to 17.0-testing 2015-10-13 23:22:19 +02:00
370 changed files with 60850 additions and 46866 deletions

11
.gitignore vendored Executable file → Normal file
View File

@@ -26,11 +26,14 @@
# GNU Autotools
.deps/
config.h
config.h.in~
config.log
config.status
Makefile
stamp-h1
autom4te.cache/
.dirstamp
*.o-*
# Visual Studio User-specific files
*.suo
@@ -57,7 +60,15 @@ ipch/
*.svclog
*.scc
*.sln
.vscode/
# macOS
.DS_Store
# NZBGet specific
nzbget
code_revision.cpp
*.temp
*.pyc
pytest.ini
.cache

17
.lgtm.yml Normal file
View File

@@ -0,0 +1,17 @@
# Configuration file for integration with http://lgtm.com
path_classifiers:
library:
# exclude these directories from default alerts report:
- lib
- webui/lib
extraction:
cpp:
configure:
command:
# compile with tests to activate scanning of C++ sources for tests
- ./configure --enable-tests
queries:
- exclude: js/incomplete-sanitization # this one gives false positives only and nothing useful

67
.travis.yml Normal file
View File

@@ -0,0 +1,67 @@
sudo: required
dist: trusty
language: cpp
matrix:
include:
- compiler: gcc
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-5
- unrar
- p7zip-full
- par2
env:
- COMPILER=g++-5
- compiler: gcc
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.9
- unrar
- p7zip-full
- par2
env:
- COMPILER=g++-4.9
- compiler: gcc
addons:
apt:
packages:
- unrar
- p7zip-full
- par2
env:
- COMPILER=g++-4.8
- CXXFLAGS="-std=c++11 -O2 -s"
- CONFIGUREOPTS="--disable-cpp-check"
- compiler: clang
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-precise-3.6
packages:
- clang-3.6
- unrar
- p7zip-full
- par2
env:
- COMPILER=clang++-3.6
install:
- sudo pip install -U pytest
script:
- $COMPILER --version
- CXX=$COMPILER ./configure $CONFIGUREOPTS --enable-tests && make
- ./nzbget --tests
- cd tests/functional && pytest -v

41
COPYING
View File

@@ -1,12 +1,12 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
@@ -15,7 +15,7 @@ software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
@@ -55,8 +55,8 @@ patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
@@ -110,7 +110,7 @@ above, provided that you also meet all of these conditions:
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
@@ -168,7 +168,7 @@ access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
@@ -225,7 +225,7 @@ impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
@@ -255,7 +255,7 @@ make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
@@ -277,9 +277,9 @@ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
@@ -303,10 +303,9 @@ the "copyright" line and a pointer to where the full notice is found.
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
@@ -336,5 +335,5 @@ necessary. Here is a sample; alter the names:
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

543
ChangeLog
View File

@@ -1,3 +1,546 @@
nzbget-21.2-testing:
- please see repository change log at
https://github.com/nzbget/nzbget/commits/develop
nzbget-21.1:
- fixed crash on systems with 64-bit time;
- corrected icon in Windows "uninstall program" list;
- allow special characters in URL for username and password;
- improved reporting for binding errors on Windows;
- fixed unicode space characters in javascript files, which could cause issues
with nginx proxy;
- fixed negative values for "FileSizeLo" in json-rpc;
- corrected url detection in rpc-method "append";
- added support for new error messages in unrar 5.80;
- now always using snapshots when reading directory contents:
- in previous versions snapshots were used on macOS only;
- now they are used on all OSes;
- this solves issue with leftovers during directory cleanup, which could
happen on certain OSes when working with network drives;
- fixed file allocating on file systems where sparse files are not supported:
- the issue could happen when InterDir was located on a network drive;
- fixed crash caused by malformed nzb files;
- fixed GROUP command in nserv;
- updated url of the global certificate storage file in the build scripts;
- fixed: file selector in WebKit based browsers doesn't allow to choose the
same file again;
- removed outdated links from web interface;
- fixed PC sleep mode not working (Windows only);
- set "SameSite" attribute for cookies;
- corrected typo in about dialog of web interface;
- updated license text: changed address of Free Software Foundation and minor
formatting changes.
nzbget-21.0:
- reworked duplicate handling to support URLs, especially when using RSS
feeds:
- queue items added via URLs (to be fetched by nzbget) are no longer
immediately fetched;
- instead url-items are handled by duplicate check similar to nzb-items
and may be placed into history as duplicate backups;
- if an url-item needs to be downloaded as backup for a failed other item
the nzb-file is fetched via provided URL;
- this greatly reduces the number of nzbs fetched from indexers when using
RSS feeds and duplicate handling;
- improved support for Android devices:
- now providing a separate installer package for Android;
- the package contains binaries built using Android NDK;
- this improves compatibility with Android, in particular with Android 8,
where general Linux installer version of NZBGet didn't work anymore due
to security changes in Android;
- android installer app is updated to use the new android installer package
instead of general Linux package;
- thoroughly optimised the program to reduce power consumption in idle state:
- number of CPU wake ups in idle state has been reduced from hundreds times
per second to about only one per second;
- optimisations for large queues with thousands of items:
- speed up saving of queue state and reduced number of queue state savings;
- improved queue state format to reduce amount of state data saved during
downloading;
- in tests download speed for very large queue (16000 items) has been
increased from 45 MB/s to 300 MB/s (comparing to 400 MB/s with small
queue);
- added native support for aarch64 architecture (ARM 64 Bit CPU) in Linux and
Android installers;
- force par-check for nzbs without archives;
- added functional tests for unpack CRC error;
- click on nzbget logo in web-interface now switches to downloads tab instead
of showing "About dialog" which has been moved into settings;
- improved handling of files splitted via par2;
- added python 3 compatibility to EMail.py script;
- added python 3 compatibility to Logger.py script;
- proper UTF-8 encoding of email content in EMail.py script;
- improved error reporting for queue disk state corruption;
- updated unrar to 5.7 and 7-zip to 19.0;
- Windows installer now includes unrar in 32 bit and 64 bit variants;
- allowing wildcards in option AuthorizedIP;
- removed suggestion of RC4 cipher;
- better description of option UMask;
- integrated LGTM code analyser tool into project;
- fixed: failed downloads not having any par2- or archive- files were moved to
DestDir instead of remaining in InterDir;
- fixed crash when using FIPS version of OpenSSL;
- fixed compatibility issue with OpenSSL compiled without compression support;
- fixed deprecated OpenSSL calls;
- fixed potential crash in built-in web-server;
- fixed: statistics for session download time and speed may be way off on high
load;
- fixed many compilation warnings in GCC;
- fixed: macOS menubar widget could not connect if password contained special
characters;
- fixed: remote clients not displaying current download speed;
- fixed: remote server could crash when feed with invalid api request;
- fixed trimming of relative paths in config.
nzbget-20.0:
- massive performance optimisations in downloader:
- improved yEnc decoder;
- improved CRC32 calculation;
- processing data in one pass;
- SIMD implementation of decoder and CRC functions on x86 and ARM CPUs;
SIMD code relies on node-yencode library by Anime Tosho
(https://github.com/animetosho/node-yencode);
- overall performance improvement up to +500% on x86 and +250% on ARM
(better speed or less CPU usage);
- using glibc instead of uClibc in universal installer builds for Linux:
- this significantly improves performance;
- compatibility with Android and other systems is hopefully also improved;
- in universal installer glibc is used on x86 and ARM;
- uClibc is still used on MIPS and PPC;
- performance optimisations in web-interface:
- reduced number of requests when loading webui by combining all
javascript-files into one and all css-files into one;
- reduced load time by calling multiple API-methods simultaneously;
- extensive use of browser caching for static files significantly
reduces the amount of data transferred on webui initialisation;
- extensive use of browser caching for API requests reduces the amount
of data transferred during webui status updates, especially when
nzbget is in idle state and there are no changes in download queue or
history;
- avoid work in browser on status updates if there are no changes in
download queue or history;
- support for keep alive connections significantly reduces overhead for
establishing connections on webui status updates, especially when
connecting to nzbget via TLS/SSL (avoiding TLS handshakes);
- a number of performance optimisations for large download queue with
thousands of items:
- much faster loading of queue from disk greatly improves program start
up time;
- improved queue management for faster download speed;
- now offering 64 bit binaries for Windows:
- installer includes 32 and 64 bit nzbget binaries;
- when updating from older versions the 64 bit binary is installed
automatically, although into the old location to keep all your
shortcuts intact;
- using word "windows" instead of "win32" in the setup file name;
- automatic update check:
- new option "UpdateCheck" to check for stable or testing versions (or
disable);
- when a new version is found a notification is shown;
- the update check is enabled by default for stable versions;
- significantly improved logging performance, especially in debug builds;
- par-check prints par2 creator application to help identify creator app
issues;
- added support for Unix domain sockets (POSIX only);
- better error handling when fetching rss feeds;
- updated POSIX build files to newer autotools version:
- compatibility with newer autotools;
- compatibility with newer platforms such as aarch64;
- better username/password validation when testing connection on settings
page;
- improved rar-renamer to better handle certain cases;
- new option "SkipWrite" for easier speed tests;
- support for redirect codes 303, 307 and 308 in web-client for fetching of
rss feeds and nzb-files;
- installer for FreeBSD is now built using Clang instead of GCC; this fixes
incompatibility with FreeBSD 11;
- universal Linux installer can now be used on FreeBSD (via Linux
compatibility mode);
- updated unrar to v5.50;
- more robust news server connection test;
- enhancements in NServ:
- memory cache to reduce disk load during speed tests - new command line
switch "-m";
- speed control - new command line switches "-w" and "-r";
- show IP address of incoming connection;
- changed default location of log-file;
- better handling of broken connections;
- removed obsolete or less useful options "SaveQueue", "ReloadQueue",
"TerminateTimeout", "AccurateRate", "BrokenLog";
- renamed option "LogBufferSize" to "LogBuffer";
- passwords of failed login attempts are no longer printed to log to improve
security;
- cookies in web interface are now saved with "httpOnly" attribute to improve
security;
- titles and duplicate keys in duplicate check are now case insensitive;
- added LibreSSL support;
- web interface now has a better icon for favorites or home screen of mobile
devices;
- improved duplicate detection for obfuscated downloads having files with
same subjects;
- direct rename and direct unpack are now active by default on new
installations, except for slow Linux systems;
- added advice for letsencrypt in option descriptions;
- fixed incorrect renaming in rar-renamer which could cause some downloads to
fail;
- fixed race condition in queue script coordinator which could cause crashes;
- fixed: post-processing parameters were sometimes case sensitive causing
issues;
- fixed DNS resolving issues on Android;
- fixed: backup servers not used on certain article decoding errors;
- fixed: when direct rename was active certain downloads with damaged
par2-files become paused at near completion and required manual resuming;
- fixed: crash when flushing article cache after direct rename;
- fixed: deleting active par-job may crash the program;
- fixed: functional tests may fail on Windows due to locked files;
- fixed: unpack using password file doesn't work on Windows;
- fixed: compiler error when building using GnuTLS;
- fixed: Linux installer failure on android emulator;
- fixed: options formatted as password fields when they shouldn't;
- fixed: slightly off article statistics after direct rename;
- fixed: NServ terminated if client interrupted connection;
- fixed: example pp-scripts may not work properly if nzbget password or
username contained special characters;
- fix in functional tests to not rely on sizes of externally generated files;
- fixed: option AuthorizedIP did not work with IPv6 addresses;
- fixed crash on certain malformed articles;
- fixed crash which could happen on Windows when reloading or terminating the
program;
- fixed logging of IPv6 addresses.
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;
- new button "Volume Statistics" in section "News Servers" of settings page;
shows the same volume data as in global statistics dialog;
- new option "ServerX.Notes" for user comments on news servers;
- new parameters for api-method "servervolumes" as a performance optimization
measure to reduce amount of transferred data;
- new option to force connection to news servers via ipv4 or ipv6;
- removed unnecessary requests to news servers;
- updated unrar to v5.40;
- clear script execution log before executing script;
- added support for crash dumps on Windows:
- renamed option "DumpCore" to "CrashDump";
- new option "CrashTrace" to make it possible to disable default
printing off call stack in order to produce more relevant crash dumps;
- fixed: startup scheduler tasks can be executed again;
- fixed: "fatal" messages when compiling from sources.
- fixed: per-nzb download statistics could be wrong if the program was
reloaded during downloading.
- fixed crash which may happen between post-processing steps;
- fixed: asterix (*) was sometimes passed as parameter to extension scripts
(Windows only);
- fixed potential crash during update via web-interface.
nzbget-18.1:
- fixed: crash during download caused by a race condition;
- fixed: sleep mode did not work on Windows;
- fixed: queue was not saved after deleting of queued post-jobs;
- fixed: possible crash at the end of post-processing;
- fixed: "undefined" in reorder extension scripts.
nzbget-18.0:
- automatic deobfuscation of rar-archives without par-files:
- obfuscated downloads not having par-files can now be successfully
unpacked;
- also helps with downloads where rar-files were obfuscated before
creating par-files;
- new options "RarRename" and "UnpackIgnoreExt";
- multi post-processing:
- in addition to classic post-processing strategy where items are
processed one after another it is now possible to post-process
multiple items at the same time;
- new option "PostStrategy" to choose from four: sequential, balanced,
aggressive, rocket;
- in "balanced" strategy downloads needing repair do not block other
items which are processed sequentially but simultaneously with
repairing item;
- in "aggressive" mode up to three items are post-processed at the same
time and in "rocket" mode up to six items (including up to two repair
tasks);
- unified extension scripts settings:
- options "PostScript", "QueueScript", "ScanScript" and "FeedScript"
were replaced with one option "Extensions";
- users don't need to know the technical details os extension scripts as
all scripts are now can be selected at one place;
- easier activation of complex extension scripts which previously needed
to be selected in multiple options for their proper work;
- reordering download queue with drag and drop in web-interface:
- new actions "GroupMoveBefore" and "GroupMoveAfter" in API-method
"editqueue";
- priorities are now displayed as a column instead of badge; that makes it
possible to manually sort on priority;
- removed vertical lines in tables; looks better in combination with new
priority column;
- keyboard shortcuts in web-interface;
- improved UI to prevent accidental deletion of many items:
- visual indication of records selected on other pages;
- extra warning when deleting many records from history;
- additional options in "custom pause dialog";
- better handing of damaged par2-files in par-renamer:
- if par-renamer can't load a (damaged) par2-file then another par2-file
is downloaded and par-renamer tries again;
- reverted non-strict par2-filename matching to handle article subjects
with non-parseable filenames;
- better handling of obfuscated par-files;
- splitted option "Retries" into "ArticleRetries" and "UrlRetries"; option
"RetryInterval" into "ArticleInterval" and "UrlInterval";
- scheduler tasks can be started at program launch:
- use asterisk as TaskX.Time;
- graceful termination of scheduler scripts:
- scripts receive signal SIGINT (CTRL+BREAK on Windows) before
termination;
- added support for nZEDb attributes in rss feeds;
- better cleanup handling: if parameter "unpack" is disabled for an nzb-file
the cleanup isn't performed for it;
- fields containing passwords are now displayed as protected fields;
- showing password-badge for nzbs with passwords;
- allow control of what tab is shown when opening web-interface:
add "#downloads", "#history", "#messages" or "#settings" to the URL,
for example "http://localhost:6789/#history" or
"http://localhost:6789/index.html#history";
- functional testing to ensure program quality:
- implemented built-in simple nntp server to be used for functional
testing;
- created a number of tests;
- new features come with additional tests;
- improved API-method "append" in combination with duplicate check; method
returns nzb-id also for items added straight to history;
- removed parameter "offset" from api-method "editqueue":
- when needed the "offset" is now passed within parameter "Args" as
string;
- old method signature is supported for compatibility;
- improved error reporting on feed parse errors;
- highlighting selected rows with alternative colors;
- improved selecting of par2-file for repair;
- splitted config section "Download Queue" and moved many options into new
section "Connection";
- disabled SSLv3 in built-in web-server;
- multiple recipients in the example pp-script "EMail.py";
- added compatibility with openssl 1.1.0;
- fixed TLS handshake error when using GnuTLS;
- fixed: sorting of selected items may give wrong results;
- fixed: search box filter in feed view were not reset.
nzbget-17.1:
- adjustments and fixes for "Retry failed articles" function, better handling
of certain corner cases;
- partial compatibility with gcc 4.8;
- removed unnecessary debug logging to javascript console;
- improved error reporting on certain file operations;
- corrected option description;
- corrected text in history delete confirmation dialog;
- fixed performance issue on certain Windows systems;
- fixed: root drive paths on Windows could not be used (for example
"NzbDir=N:\");
- fixed hanging after marking as BAD from queue script;
- fixed: old nzbget.exe was deleted even when installing into a new directory
(Windows only);
- fixed: compilation error if configured with unit tests but without par-module;
- fixed crash on malformed articles;
- fixed javascript error on Chrome for Linux;
- fixed compilation error if configured without TLS.
nzbget-17.0:
- reworked the full source code base to utilize modern C++ features:
- with the main motivation to make the code nicer and more fun to work
with;
- to compile NZBGet from source a compiler supporting C++14 is required;
- most users don't have to compile on their own and can use official
installers offered on download page;
- for a detailed list of internal changes see Milestone "Modern C++" on
GitHub;
- now offering an official installer for FreeBSD:
- automatic installation;
- built-in update via web-interface;
- currently supporting only x86_64 CPU architecture;
- full support for Unicode and extra long file paths (more than 260 characters)
on Windows including:
- downloading;
- par-verification and -repair;
- par-renaming (deobfuscation);
- unpacking;
- post-processing;
- added download volume quota management:
- new options "MonthlyQuota", "QuotaStartDay", "DailyQuota";
- downloading is suspended when the quota is reached and automatically
resumed when the next billing period starts (month or day);
- new fields in RPC-method "status": MonthSizeLo, MonthSizeHi,
MonthSizeMB, DaySizeLo, DaySizeHi, DaySizeMB, QuotaReached.
MonthSizes are related to current billing month taking option
"QuotaStartDay" into account;
- download volume for "this month" shown in web-interface in statistics
dialog shows data for current billing month (taking option
"QuotaStartDay" into account);
- remaining time is shown in orange when the quota is reached;
- dialog "statistics and status" may show extra row "Download quota:
reached";
- new function "Retry failed articles" in history:
- failed downloads can be now tried again but in contrast to command
"Download again" only failed articles are downloaded whereas
successfully downloaded pieces are reused;
- new command "HistoryRetryFailed" of RPC-method "editqueue";
- new subcommand "F" of command line switch "-E/--edit" for history;
- reworked options to delete already downloaded files when deleting downloads
from queue:
- removed option "DeleteCleanupDisk";
- in the "Delete downloads confirmation dialog" allowing user to decide
if the already downloaded files must be deleted or not;
- option "HealthCheck" extended with new possible value "Park"; Health
check now offers:
- "Delete" - to move download into history and delete already
downloaded files;
- "Park" - to move download into history and keep already downloaded
files;
- remote command "GroupDelete" now always delete already downloaded files;
- new remote command "GroupParkDelete" keeps already downloaded files;
- new subcommand "DP" of console command "--edit/-E" to delete download
from queue and keep already downloaded files;
- new queue script event "NZB_MARKED"; new env var "NZBNA_MARKSTATUS" is
passed to queue scripts:
- new option "ShellOverride" allows to configure path to python, bash, etc.;
useful on systems with non-standard paths; eliminating the need to change
shebang for every script; also makes it possible to put scripts on non-exec
file systems;
- reduced disk fragmentation in direct write mode on Windows; this improves
unpack speed;
- news servers can now be configured as optional; marking server as optional
tells NZBGet to ignore this server if a connection to this server cannot be
established;
- added support for tvdbid and tvmazeid in rss feeds;
- added button to save nzb-log into a file directly from web-ui;
- queue-scripts can now change destination after download is completed and
before unpack;
- queue-scripts save messages into nzb-log;
- showing number of selected items in confirmation box when deleting or
performing other actions on multiple items in web-interface;
- built-in web-server can now use certificate chain files through option
"SecureCert", when compiled using OpenSSL;
- added support for SNI in TLS/SSL;
- better error reporting when using GnuTLS;
- allowing character "=" in dupe-badges;
- par-check doesn't ignore files from option "ExtCleanupDisk" anymore; only
files listed in option "ParIgnoreExt" are ignored;
- low-level messages from par2-module are now added to log (as DETAIL);
- option "ScriptDir" now accepts multiple directories;
- path to original queued nzb-file is now passed to scripts;
- hidden files and directories are now ignored by the scanner of incoming nzb
directory;
- separated disk state files for queue and history for better performance;
- improved replacing of invalid characters in file names in certain cases;
- automatically removing orphaned diskstate files from QueueDir;
- added support for file names with reserved words on Windows;
- added several settings to change behavior of web-interface, new section
"WEB-INTERFACE" on settings page;
- showing various status info in browser window title:
- number of downloads, current speed, remaining time, pause state;
- new option "WindowTitle";
- improved tray icon (Windows) to look better on a dark background;
- feed scripts must now return exit codes; this is to prevent queueing of
all files from the feed if the feed script crashes;
- improved error reporting on DNS lookup errors;
- fixed: splitted files were not always joined;
- fixed: wrong encoding in file names of downloaded files;
- fixed: queue-scripts not called for failed URLs if the scripts were set in
categorys option "PostScript";
- fixed: crash when executing command "--printconfig";
- fixed: error messages when trying to delete intermediate directory on Windows;
- fixed: web-ui didn't load in Chrome on iOS;
- improved POSIX configure script - now using pkg-config for all required
libraries;
- improved Windows installer - scripts are now installed into a subdirectory
of default "MainDir" (C:\ProgramData\NZBGet\scripts) instead of program's
directory;
- updated and corrected option descriptions.
nzbget-16.4:
- fixed resource (socket) leak which may cause "too many open files"
errors with a possible crash.
nzbget-16.3:
- fixed: certain downloads may fail due to a bug in the workaround
implemented in v16.2.

View File

@@ -1,7 +1,7 @@
#
# This file is part of nzbget
# This file is part of nzbget. See <http://nzbget.net>.
#
# Copyright (C) 2008-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
# Copyright (C) 2008-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -14,9 +14,7 @@
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
bin_PROGRAMS = nzbget
@@ -24,12 +22,14 @@ bin_PROGRAMS = nzbget
nzbget_SOURCES = \
daemon/connect/Connection.cpp \
daemon/connect/Connection.h \
daemon/connect/TLS.cpp \
daemon/connect/TLS.h \
daemon/connect/TlsSocket.cpp \
daemon/connect/TlsSocket.h \
daemon/connect/WebDownloader.cpp \
daemon/connect/WebDownloader.h \
daemon/extension/FeedScript.cpp \
daemon/extension/FeedScript.h \
daemon/extension/CommandScript.cpp \
daemon/extension/CommandScript.h \
daemon/extension/NzbScript.cpp \
daemon/extension/NzbScript.h \
daemon/extension/PostScript.cpp \
@@ -58,8 +58,8 @@ nzbget_SOURCES = \
daemon/frontend/LoggableFrontend.h \
daemon/frontend/NCursesFrontend.cpp \
daemon/frontend/NCursesFrontend.h \
daemon/main/CommandLineParser.cpp \
daemon/main/CommandLineParser.h \
daemon/main/CommandLineParser.cpp \
daemon/main/CommandLineParser.h \
daemon/main/DiskService.cpp \
daemon/main/DiskService.h \
daemon/main/Maintenance.cpp \
@@ -68,6 +68,8 @@ nzbget_SOURCES = \
daemon/main/nzbget.h \
daemon/main/Options.cpp \
daemon/main/Options.h \
daemon/main/WorkState.cpp \
daemon/main/WorkState.h \
daemon/main/Scheduler.cpp \
daemon/main/Scheduler.h \
daemon/main/StackTrace.cpp \
@@ -80,8 +82,8 @@ nzbget_SOURCES = \
daemon/nntp/Decoder.h \
daemon/nntp/NewsServer.cpp \
daemon/nntp/NewsServer.h \
daemon/nntp/NNTPConnection.cpp \
daemon/nntp/NNTPConnection.h \
daemon/nntp/NntpConnection.cpp \
daemon/nntp/NntpConnection.h \
daemon/nntp/ServerPool.cpp \
daemon/nntp/ServerPool.h \
daemon/nntp/StatMeter.cpp \
@@ -92,16 +94,26 @@ nzbget_SOURCES = \
daemon/postprocess/DupeMatcher.h \
daemon/postprocess/ParChecker.cpp \
daemon/postprocess/ParChecker.h \
daemon/postprocess/ParCoordinator.cpp \
daemon/postprocess/ParCoordinator.h \
daemon/postprocess/ParParser.cpp \
daemon/postprocess/ParParser.h \
daemon/postprocess/ParRenamer.cpp \
daemon/postprocess/ParRenamer.h \
daemon/postprocess/PrePostProcessor.cpp \
daemon/postprocess/PrePostProcessor.h \
daemon/postprocess/RarRenamer.cpp \
daemon/postprocess/RarRenamer.h \
daemon/postprocess/RarReader.cpp \
daemon/postprocess/RarReader.h \
daemon/postprocess/Rename.cpp \
daemon/postprocess/Rename.h \
daemon/postprocess/Repair.cpp \
daemon/postprocess/Repair.h \
daemon/postprocess/Unpack.cpp \
daemon/postprocess/Unpack.h \
daemon/postprocess/DirectUnpack.cpp \
daemon/postprocess/DirectUnpack.h \
daemon/queue/DirectRenamer.cpp \
daemon/queue/DirectRenamer.h \
daemon/queue/DiskState.cpp \
daemon/queue/DiskState.h \
daemon/queue/DownloadInfo.cpp \
@@ -110,8 +122,8 @@ nzbget_SOURCES = \
daemon/queue/DupeCoordinator.h \
daemon/queue/HistoryCoordinator.cpp \
daemon/queue/HistoryCoordinator.h \
daemon/queue/NZBFile.cpp \
daemon/queue/NZBFile.h \
daemon/queue/NzbFile.cpp \
daemon/queue/NzbFile.h \
daemon/queue/QueueCoordinator.cpp \
daemon/queue/QueueCoordinator.h \
daemon/queue/QueueEditor.cpp \
@@ -133,6 +145,9 @@ nzbget_SOURCES = \
daemon/remote/XmlRpc.h \
daemon/util/Log.cpp \
daemon/util/Log.h \
daemon/util/NString.cpp \
daemon/util/NString.h \
daemon/util/Container.h \
daemon/util/Observer.cpp \
daemon/util/Observer.h \
daemon/util/Script.cpp \
@@ -141,8 +156,20 @@ nzbget_SOURCES = \
daemon/util/Thread.h \
daemon/util/Service.cpp \
daemon/util/Service.h \
daemon/util/FileSystem.cpp \
daemon/util/FileSystem.h \
daemon/util/Util.cpp \
daemon/util/Util.h \
daemon/nserv/NServMain.h \
daemon/nserv/NServMain.cpp \
daemon/nserv/NServFrontend.h \
daemon/nserv/NServFrontend.cpp \
daemon/nserv/NntpServer.h \
daemon/nserv/NntpServer.cpp \
daemon/nserv/NzbGenerator.h \
daemon/nserv/NzbGenerator.cpp \
daemon/nserv/YEncoder.h \
daemon/nserv/YEncoder.cpp \
code_revision.cpp
if WITH_PAR2
@@ -171,8 +198,6 @@ nzbget_SOURCES += \
lib/par2/md5.cpp \
lib/par2/md5.h \
lib/par2/par2cmdline.h \
lib/par2/par2creatorsourcefile.cpp \
lib/par2/par2creatorsourcefile.h \
lib/par2/par2fileformat.cpp \
lib/par2/par2fileformat.h \
lib/par2/par2repairer.cpp \
@@ -191,6 +216,25 @@ nzbget_SOURCES += \
lib/par2/verificationpacket.h
endif
# Simd decoder and Crc32
nzbget_SOURCES += \
lib/yencode/YEncode.h \
lib/yencode/SimdInit.cpp \
lib/yencode/SimdDecoder.cpp \
lib/yencode/ScalarDecoder.cpp \
lib/yencode/Sse2Decoder.cpp \
lib/yencode/Ssse3Decoder.cpp \
lib/yencode/PclmulCrc.cpp \
lib/yencode/NeonDecoder.cpp \
lib/yencode/AcleCrc.cpp \
lib/yencode/SliceCrc.cpp
lib/yencode/Sse2Decoder.$(OBJEXT) : CXXFLAGS+=$(SSE2_CXXFLAGS)
lib/yencode/Ssse3Decoder.$(OBJEXT) : CXXFLAGS+=$(SSSE3_CXXFLAGS)
lib/yencode/PclmulCrc.$(OBJEXT) : CXXFLAGS+=$(PCLMUL_CXXFLAGS)
lib/yencode/NeonDecoder.$(OBJEXT) : CXXFLAGS+=$(NEON_CXXFLAGS)
lib/yencode/AcleCrc.$(OBJEXT) : CXXFLAGS+=$(ACLECRC_CXXFLAGS)
AM_CPPFLAGS = \
-I$(srcdir)/daemon/connect \
-I$(srcdir)/daemon/extension \
@@ -202,7 +246,9 @@ AM_CPPFLAGS = \
-I$(srcdir)/daemon/queue \
-I$(srcdir)/daemon/remote \
-I$(srcdir)/daemon/util \
-I$(srcdir)/lib/par2
-I$(srcdir)/daemon/nserv \
-I$(srcdir)/lib/par2 \
-I$(srcdir)/lib/yencode
if WITH_TESTS
nzbget_SOURCES += \
@@ -214,11 +260,22 @@ nzbget_SOURCES += \
tests/main/CommandLineParserTest.cpp \
tests/main/OptionsTest.cpp \
tests/feed/FeedFilterTest.cpp \
tests/postprocess/ParCheckerTest.cpp \
tests/postprocess/ParRenamerTest.cpp \
tests/queue/NZBFileTest.cpp \
tests/postprocess/DupeMatcherTest.cpp \
tests/postprocess/RarRenamerTest.cpp \
tests/postprocess/RarReaderTest.cpp \
tests/postprocess/DirectUnpackTest.cpp \
tests/queue/NzbFileTest.cpp \
tests/nntp/ServerPoolTest.cpp \
tests/util/FileSystemTest.cpp \
tests/util/NStringTest.cpp \
tests/util/UtilTest.cpp
if WITH_PAR2
nzbget_SOURCES += \
tests/postprocess/ParCheckerTest.cpp \
tests/postprocess/ParRenamerTest.cpp
endif
AM_CPPFLAGS += \
-I$(srcdir)/lib/catch \
-I$(srcdir)/tests/suite
@@ -228,15 +285,16 @@ EXTRA_DIST = \
$(windows_FILES) \
$(osx_FILES) \
$(linux_FILES) \
$(testdata_FILES)
$(testdata_FILES) \
$(par2doc_FILES)
windows_FILES = \
daemon/windows/NTService.cpp \
daemon/windows/NTService.h \
daemon/windows/win32.h \
daemon/windows/StdAfx.cpp \
daemon/windows/WinService.cpp \
daemon/windows/WinService.h \
daemon/windows/WinConsole.cpp \
daemon/windows/WinConsole.h \
nzbget.vcproj \
nzbget.vcxproj \
windows/nzbget-command-shell.bat \
windows/install-update.bat \
windows/README-WINDOWS.txt \
@@ -247,9 +305,9 @@ windows_FILES = \
windows/resources/trayicon_idle.ico \
windows/resources/trayicon_paused.ico \
windows/resources/trayicon_working.ico \
windows/setup/nzbget-setup.nsi \
windows/setup/install.bmp \
windows/setup/uninstall.bmp
windows/resources/install.bmp \
windows/resources/uninstall.bmp \
windows/nzbget-setup.nsi
osx_FILES = \
osx/App_Prefix.pch \
@@ -288,15 +346,19 @@ linux_FILES = \
linux/package-info.json \
linux/build-info.txt \
linux/build-nzbget \
linux/build-unpack
linux/build-unpack \
linux/build-toolchain-android \
linux/build-toolchain-freebsd
doc_FILES = \
lib/par2/AUTHORS \
lib/par2/README \
README \
ChangeLog \
COPYING
par2doc_FILES = \
lib/par2/AUTHORS \
lib/par2/README
exampleconf_FILES = \
nzbget.conf
@@ -330,7 +392,9 @@ webui_FILES = \
webui/img/favicon.ico \
webui/img/download-anim-green-2x.png \
webui/img/download-anim-orange-2x.png \
webui/img/transmit-reload-2x.gif
webui/img/transmit-reload-2x.gif \
webui/img/favicon-256x256-opaque.png \
webui/img/favicon-256x256.png
scripts_FILES = \
scripts/EMail.py \
@@ -351,7 +415,36 @@ testdata_FILES = \
tests/testdata/parchecker/testfile.par2 \
tests/testdata/parchecker/testfile.vol00+1.PAR2 \
tests/testdata/parchecker/testfile.vol01+2.PAR2 \
tests/testdata/parchecker/testfile.vol03+3.PAR2
tests/testdata/parchecker/testfile.vol03+3.PAR2 \
tests/testdata/parchecker2/crc.txt \
tests/testdata/parchecker2/testfile.7z.001 \
tests/testdata/parchecker2/testfile.7z.002 \
tests/testdata/parchecker2/testfile.7z.003 \
tests/testdata/parchecker2/testfile.7z.par2 \
tests/testdata/parchecker2/testfile.7z.vol0+1.PAR2 \
tests/testdata/parchecker2/testfile.7z.vol1+2.PAR2 \
tests/testdata/parchecker2/testfile.7z.vol3+3.PAR2 \
tests/testdata/rarrenamer/testfile3.part01.rar \
tests/testdata/rarrenamer/testfile3.part02.rar \
tests/testdata/rarrenamer/testfile3.part03.rar \
tests/testdata/rarrenamer/testfile5.part01.rar \
tests/testdata/rarrenamer/testfile5.part02.rar \
tests/testdata/rarrenamer/testfile5.part03.rar \
tests/testdata/rarrenamer/testfile3oldnam.rar \
tests/testdata/rarrenamer/testfile3oldnam.r00 \
tests/testdata/rarrenamer/testfile3oldnam.r01 \
tests/testdata/rarrenamer/testfile3encdata.part01.rar \
tests/testdata/rarrenamer/testfile3encdata.part02.rar \
tests/testdata/rarrenamer/testfile3encdata.part03.rar \
tests/testdata/rarrenamer/testfile3encnam.part01.rar \
tests/testdata/rarrenamer/testfile3encnam.part02.rar \
tests/testdata/rarrenamer/testfile3encnam.part03.rar \
tests/testdata/rarrenamer/testfile5encdata.part01.rar \
tests/testdata/rarrenamer/testfile5encdata.part02.rar \
tests/testdata/rarrenamer/testfile5encdata.part03.rar \
tests/testdata/rarrenamer/testfile5encnam.part01.rar \
tests/testdata/rarrenamer/testfile5encnam.part02.rar \
tests/testdata/rarrenamer/testfile5encnam.part03.rar
# Install
dist_doc_DATA = $(doc_FILES)
@@ -402,25 +495,26 @@ 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 */" ;\
echo "/* This file is automatically regenerated on each build. Do not edit it. */" ;\
echo "#include \"nzbget.h\"" ;\
echo "const char* code_revision(void)" ;\
echo "{" ;\
echo " const char* revision = \"$$V\";" ;\
@@ -434,6 +528,7 @@ code_revision.cpp: FORCE
( \
echo "/* */" ;\
echo "/* This file is automatically regenerated on each build. Do not edit it. */" ;\
echo "#include \"nzbget.h\"" ;\
echo "const char* code_revision(void)" ;\
echo "{" ;\
echo " const char* revision = \"\";" ;\

3077
Makefile.in vendored
View File

File diff suppressed because it is too large Load Diff

5
README
View File

@@ -206,9 +206,8 @@ TLS/SSL support using option "--disable-tls":
5. Compiling on Windows
=====================================
NZBGet is developed using MS Visual C++ 2005. The project file and solution
are provided. If you use MS Visual C++ 2005 Express you need to download
and install Platform SDK.
NZBGet is developed using MS Visual Studio 2015 (Community Edition). The project
file is provided.
To compile the program with TLS/SSL support you need either OpenSSL or GnuTLS:
- OpenSSL (http://www.openssl.org)

View File

@@ -1,15 +1,19 @@
# NZBGet #
[![License](https://img.shields.io/badge/license-GPL-blue.svg)](http://www.gnu.org/licenses/)
[![Build Status](https://img.shields.io/travis/nzbget/nzbget/develop.svg)](https://travis-ci.org/nzbget/nzbget)
[![Code Quality: Cpp](https://img.shields.io/lgtm/grade/cpp/g/nzbget/nzbget.svg?label=code%20quality:%20c%2b%2b)](https://lgtm.com/projects/g/nzbget/nzbget/context:cpp)
[![Code Quality: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/nzbget/nzbget.svg?label=code%20quality:%20js)](https://lgtm.com/projects/g/nzbget/nzbget/context:javascript)
[![Total Alerts](https://img.shields.io/lgtm/alerts/g/nzbget/nzbget.svg)](https://lgtm.com/projects/g/nzbget/nzbget/alerts)
[![Total downloads](https://img.shields.io/github/downloads/nzbget/nzbget/total.svg)](https://github.com/nzbget/nzbget/releases)
[![Downloads (latest release)](https://img.shields.io/github/downloads/nzbget/nzbget/latest/total.svg?label=latest%20release)](https://github.com/nzbget/nzbget/releases/latest)
NZBGet is a binary downloader, which downloads files from Usenet
based on information given in nzb-files.
NZBGet is written in C++ and is known for its extraordinary performance and efficiency.
NZBGet is written in C++ and is known for its performance and efficiency.
NZBGet can be run at almost every platform - classic PCs, NAS, media players, SAT-receivers, WLAN-routers, etc.
The download area provides precompiled binaries
for Windows, Mac OS X and Linux (compatible with many CPUs and platform variants). For other platforms
the program can be compiled from sources.
- [Home page (nzbget.net)](http://nzbget.net) - for first time visitors, learn more about NZBGet;
- [Downloads](http://nzbget.net/download) - get the binaries and sources;
- [Documentation](https://github.com/nzbget/nzbget/wiki) - installation manuals, HOW-TOs, API;
- [Forum](http://forum.nzbget.net) - get support, share your ideas, scripts, add-ons.
NZBGet can run on almost any device - classic PC, NAS, media player, SAT-receiver, WLAN-router, etc.
The download area provides precompiled binaries for Windows, macOS, Linux (compatible with
many CPUs and platform variants), FreeBSD and Android. For other platforms
the program can be compiled from sources.

1220
aclocal.m4 vendored
View File

File diff suppressed because it is too large Load Diff

View File

@@ -3,16 +3,15 @@
/* Define to 1 to include debug-code */
#undef DEBUG
/* Define to 1 if deleting of files during reading of directory is not
properly supported by OS */
#undef DIRBROWSER_SNAPSHOT
/* Define to 1 to not use curses */
#undef DISABLE_CURSES
/* Define to 1 to disable gzip-support */
#undef DISABLE_GZIP
/* Define to 1 to not use libxml2, only for development purposes */
#undef DISABLE_LIBXML2
/* Define to 1 to disable par-verification and repair */
#undef DISABLE_PARCHECK
@@ -38,9 +37,8 @@
/* Define to 1 if you have the <curses.h> header file. */
#undef HAVE_CURSES_H
/* Define to 1 if you have the <dirent.h> header file, and it defines `DIR'.
*/
#undef HAVE_DIRENT_H
/* define if the compiler supports basic C++14 syntax */
#undef HAVE_CXX14
/* Define to 1 if you have the <endian.h> header file. */
#undef HAVE_ENDIAN_H
@@ -84,8 +82,8 @@
/* Define to 1 to use GnuTLS library for TLS/SSL-support. */
#undef HAVE_LIBGNUTLS
/* Define to 1 if you have the `memcpy' function. */
#undef HAVE_MEMCPY
/* Define to 1 if lockf is supported */
#undef HAVE_LOCKF
/* Define to 1 if you have the <memory.h> header file. */
#undef HAVE_MEMORY_H
@@ -96,36 +94,27 @@
/* Define to 1 if you have the <ncurses/ncurses.h> header file. */
#undef HAVE_NCURSES_NCURSES_H
/* Define to 1 if you have the <ndir.h> header file, and it defines `DIR'. */
#undef HAVE_NDIR_H
/* Define to 1 to use Nettle library for decryption. */
#undef HAVE_NETTLE
/* Define to 1 to use OpenSSL library for TLS/SSL-support. */
/* Define to 1 to use OpenSSL library for TLS/SSL-support and decryption. */
#undef HAVE_OPENSSL
/* Define to 1 if pthread_cancel is supported */
#undef HAVE_PTHREAD_CANCEL
/* Define to 1 if you have the <regex.h> header file. */
#undef HAVE_REGEX_H
/* Define to 1 if _SC_NPROCESSORS_ONLN is present in unistd.h */
#undef HAVE_SC_NPROCESSORS_ONLN
/* Define to 1 if stdbool.h conforms to C99. */
#undef HAVE_STDBOOL_H
/* Define to 1 if you have the <stdint.h> header file. */
#undef HAVE_STDINT_H
/* Define to 1 if you have the <stdio.h> header file. */
#undef HAVE_STDIO_H
/* Define to 1 if you have the <stdlib.h> header file. */
#undef HAVE_STDLIB_H
/* Define to 1 if you have the `strcasecmp' function. */
#undef HAVE_STRCASECMP
/* Define to 1 if you have the `strchr' function. */
#undef HAVE_STRCHR
/* Define to 1 if you have the `stricmp' function. */
#undef HAVE_STRICMP
@@ -135,14 +124,6 @@
/* Define to 1 if you have the <string.h> header file. */
#undef HAVE_STRING_H
/* Define to 1 if you have the <sys/dir.h> header file, and it defines `DIR'.
*/
#undef HAVE_SYS_DIR_H
/* Define to 1 if you have the <sys/ndir.h> header file, and it defines `DIR'.
*/
#undef HAVE_SYS_NDIR_H
/* Define to 1 if you have the <sys/prctl.h> header file. */
#undef HAVE_SYS_PRCTL_H
@@ -158,8 +139,8 @@
/* Define to 1 if variadic macros are supported */
#undef HAVE_VARIADIC_MACROS
/* Define to 1 if the system has the type `_Bool'. */
#undef HAVE__BOOL
/* Define to 1 if OpenSSL supports function "X509_check_host". */
#undef HAVE_X509_CHECK_HOST
/* Define to 1 to exclude debug-code */
#undef NDEBUG
@@ -179,6 +160,9 @@
/* Define to the one symbol short name of this package. */
#undef PACKAGE_TARNAME
/* Define to the home page for this package. */
#undef PACKAGE_URL
/* Define to the version of this package. */
#undef PACKAGE_VERSION
@@ -194,9 +178,10 @@
/* Version number of package */
#undef VERSION
/* Define to 1 if your processor stores words with the most significant byte
first (like Motorola and SPARC, unlike Intel and VAX). */
#undef WORDS_BIGENDIAN
/* Enable large inode numbers on Mac OS X 10.5. */
#ifndef _DARWIN_USE_64_BIT_INODE
# define _DARWIN_USE_64_BIT_INODE 1
#endif
/* Number of bits in a file offset, on hosts where this is settable. */
#undef _FILE_OFFSET_BITS
@@ -207,14 +192,5 @@
/* Define for large files, on AIX-style hosts. */
#undef _LARGE_FILES
/* Define to empty if `const' does not conform to ANSI C. */
#undef const
/* Define to `__inline__' or `__inline' if that's what the C compiler
calls it, or to nothing if 'inline' is not supported under any name. */
#ifndef __cplusplus
#undef inline
#endif
/* Define to `unsigned int' if <sys/types.h> does not define. */
#undef size_t

12849
configure vendored
View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
#
# This file is part of nzbget
# This file is part of nzbget. See <http://nzbget.net>.
#
# Copyright (C) 2008-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
# Copyright (C) 2008-2021 Andrey Prygunkov <hugbug@users.sourceforge.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -14,28 +14,22 @@
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.59)
AC_INIT(nzbget, 16.3, hugbug@users.sourceforge.net)
AC_PREREQ(2.65)
AC_INIT(nzbget, 21.2-testing, hugbug@users.sourceforge.net)
AC_CONFIG_AUX_DIR(posix)
AM_INIT_AUTOMAKE([foreign])
AC_CANONICAL_TARGET
AM_INIT_AUTOMAKE([foreign subdir-objects])
AC_CONFIG_SRCDIR([daemon/main/nzbget.cpp])
AC_CONFIG_HEADERS([config.h])
AM_MAINTAINER_MODE
dnl
dnl Set default library path, if not specified in environment variable "LIBPREF".
dnl
if test "$LIBPREF" = ""; then
LIBPREF="/usr"
fi
m4_include([posix/ax_cxx_compile_stdcxx.m4])
dnl
@@ -52,12 +46,26 @@ dnl Do all tests with c++ compiler.
dnl
AC_LANG(C++)
dnl
dnl Determine compiler switches to support C++14 standard.
dnl
AC_MSG_CHECKING(whether to test compiler features)
AC_ARG_ENABLE(cpp-check,
[AS_HELP_STRING([--disable-cpp-check], [disable check for C++14 compiler features])],
[ ENABLECPPCHECK=$enableval ],
[ ENABLECPPCHECK=yes] )
AC_MSG_RESULT($ENABLECPPCHECK)
if test "$ENABLECPPCHECK" = "yes"; then
AX_CXX_COMPILE_STDCXX(14,,[optional])
if test "$HAVE_CXX14" != "1"; then
AC_MSG_ERROR("A compiler with support for C++14 language features is required. For details visit http://nzbget.net/cpp14")
fi
fi
dnl
dnl Checks for header files.
dnl
AC_CHECK_HEADERS(sys/prctl.h)
AC_CHECK_HEADERS(regex.h)
AC_CHECK_HEADERS(sys/prctl.h regex.h endian.h getopt.h)
dnl
@@ -69,6 +77,19 @@ AC_SEARCH_LIBS([inet_addr], [nsl])
AC_SEARCH_LIBS([hstrerror], [resolv])
dnl
dnl Android NDK restrictions
dnl
AC_CHECK_FUNC(lockf,
[AC_CHECK_DECL(lockf,
[AC_DEFINE([HAVE_LOCKF], 1, [Define to 1 if lockf is supported])],,
[#include <unistd.h>])])
AC_CHECK_FUNC(pthread_cancel,
[AC_CHECK_DECL(pthread_cancel,
[AC_DEFINE([HAVE_PTHREAD_CANCEL], 1, [Define to 1 if pthread_cancel is supported])],,
[#include <pthread.h>])])
dnl
dnl Getopt
dnl
@@ -137,7 +158,7 @@ if test "$FOUND" = "no"; then
[ char* szHost; struct hostent hinfobuf; char* strbuf; int h_errnop;
struct hostent* hinfo = gethostbyname_r(szHost, &hinfobuf, strbuf, 1024, &h_errnop); ],
AC_MSG_RESULT([[yes, and it takes 5 arguments]])
FOUND="yes"
FOUND="yes"
AC_DEFINE([HAVE_GETHOSTBYNAME_R_5], 1, [Define to 1 if gethostbyname_r takes 5 arguments]),
FOUND="no")
@@ -187,32 +208,20 @@ AC_TRY_COMPILE([
#include <sys/types.h>
#include <sys/socket.h>],[
(void)getsockopt (1, 1, 1, NULL, (size_t*)NULL)],[
AC_MSG_RESULT(size_t)
SOCKLEN_T=size_t],[
AC_TRY_COMPILE([
AC_MSG_RESULT(size_t)
SOCKLEN_T=size_t],[
AC_TRY_COMPILE([
#include <stddef.h>
#include <sys/types.h>
#include <sys/socket.h>],[
(void)getsockopt (1, 1, 1, NULL, (int*)NULL)],[
AC_MSG_RESULT(int)
SOCKLEN_T=int],[
AC_MSG_WARN(could not determine)
SOCKLEN_T=int])])])
AC_MSG_RESULT(int)
SOCKLEN_T=int],[
AC_MSG_WARN(could not determine)
SOCKLEN_T=int])])])
AC_DEFINE_UNQUOTED(SOCKLEN_T, $SOCKLEN_T, [Determine what socket length (socklen_t) data type is])
dnl
dnl Dir-browser's snapshot
dnl
AC_MSG_CHECKING(whether dir-browser snapshot workaround is needed)
if test "$target_vendor" == "apple"; then
AC_MSG_RESULT([[yes]])
AC_DEFINE([DIRBROWSER_SNAPSHOT], 1, [Define to 1 if deleting of files during reading of directory is not properly supported by OS])
else
AC_MSG_RESULT([[no]])
fi
dnl
dnl check cpu cores via sysconf
dnl
@@ -229,26 +238,36 @@ AC_TRY_COMPILE(
dnl
dnl checks for libxml2 includes and libraries.
dnl
AC_ARG_WITH(libxml2_includes,
[AS_HELP_STRING([--with-libxml2-includes=DIR], [libxml2 include directory])],
[CPPFLAGS="${CPPFLAGS} -I${withval}"]
[INCVAL="yes"],
[INCVAL="no"])
AC_ARG_WITH(libxml2_libraries,
[AS_HELP_STRING([--with-libxml2-libraries=DIR], [libxml2 library directory])],
[LDFLAGS="${LDFLAGS} -L${withval}"]
[LIBVAL="yes"],
[LIBVAL="no"])
if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then
PKG_CHECK_MODULES(libxml2, libxml-2.0,
[LIBS="${LIBS} $libxml2_LIBS"]
[CPPFLAGS="${CPPFLAGS} $libxml2_CFLAGS"],
AC_MSG_CHECKING(whether to use libxml2)
AC_ARG_ENABLE(libxml2,
[AS_HELP_STRING([--disable-libxml2], [do not use libxml2 (removes dependency from libxml2-library, only for development purposes)])],
[USELIBXML2=$enableval],
[USELIBXML2=yes] )
AC_MSG_RESULT($USELIBXML2)
if test "$USELIBXML2" = "yes"; then
AC_ARG_WITH(libxml2_includes,
[AS_HELP_STRING([--with-libxml2-includes=DIR], [libxml2 include directory])],
[CPPFLAGS="${CPPFLAGS} -I${withval}"]
[INCVAL="yes"],
[INCVAL="no"])
AC_ARG_WITH(libxml2_libraries,
[AS_HELP_STRING([--with-libxml2-libraries=DIR], [libxml2 library directory])],
[LDFLAGS="${LDFLAGS} -L${withval}"]
[LIBVAL="yes"],
[LIBVAL="no"])
if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then
PKG_CHECK_MODULES(libxml2, libxml-2.0,
[LIBS="${LIBS} $libxml2_LIBS"]
[CPPFLAGS="${CPPFLAGS} $libxml2_CFLAGS"],
AC_MSG_ERROR("libxml2 library not found"))
fi
AC_CHECK_HEADER(libxml/tree.h,,
AC_MSG_ERROR("libxml2 header files not found"))
AC_SEARCH_LIBS([xmlNewNode], [xml2], ,
AC_MSG_ERROR("libxml2 library not found"))
else
AC_DEFINE([DISABLE_LIBXML2],1,[Define to 1 to not use libxml2, only for development purposes])
fi
AC_CHECK_HEADER(libxml/tree.h,,
AC_MSG_ERROR("libxml2 header files not found"))
AC_SEARCH_LIBS([xmlNewNode], [xml2], ,
AC_MSG_ERROR("libxml2 library not found"))
dnl
@@ -261,17 +280,23 @@ AC_ARG_ENABLE(curses,
[USECURSES=yes] )
AC_MSG_RESULT($USECURSES)
if test "$USECURSES" = "yes"; then
INCVAL="${LIBPREF}/include"
LIBVAL="${LIBPREF}/lib"
AC_ARG_WITH(libcurses_includes,
[AS_HELP_STRING([--with-libcurses-includes=DIR], [libcurses include directory])],
[INCVAL="$withval"])
CPPFLAGS="${CPPFLAGS} -I${INCVAL}"
[CPPFLAGS="${CPPFLAGS} -I${withval}"]
[INCVAL="yes"],
[INCVAL="no"])
AC_ARG_WITH(libcurses_libraries,
[AS_HELP_STRING([--with-libcurses-libraries=DIR], [libcurses library directory])],
[LIBVAL="$withval"])
LDFLAGS="${LDFLAGS} -L${LIBVAL}"
[LDFLAGS="${LDFLAGS} -L${withval}"]
[LIBVAL="yes"],
[LIBVAL="no"])
if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then
PKG_CHECK_MODULES(ncurses, ncurses,
[LIBS="${LIBS} $ncurses_LIBS"]
[CPPFLAGS="${CPPFLAGS} $ncurses_CFLAGS"],
AC_MSG_ERROR("ncurses library not found"))
fi
AC_CHECK_HEADER(ncurses.h,
FOUND=yes
AC_DEFINE([HAVE_NCURSES_H],1,[Define to 1 if you have the <ncurses.h> header file.]),
@@ -293,6 +318,8 @@ if test "$USECURSES" = "yes"; then
fi
AC_SEARCH_LIBS([refresh], [ncurses curses],,
AC_ERROR([Couldn't find curses library]))
AC_SEARCH_LIBS([nodelay], [ncurses curses tinfo],,
AC_ERROR([Couldn't find curses library]))
else
AC_DEFINE([DISABLE_CURSES],1,[Define to 1 to not use curses])
fi
@@ -310,21 +337,11 @@ AC_MSG_RESULT($ENABLEPARCHECK)
if test "$ENABLEPARCHECK" = "yes"; then
dnl PAR2 checks.
dnl
dnl Checks for header files.
AC_HEADER_DIRENT
AC_HEADER_STDBOOL
AC_HEADER_STDC
AC_CHECK_HEADERS([stdio.h] [endian.h] [getopt.h])
dnl Checks for typedefs, structures, and compiler characteristics.
AC_TYPE_SIZE_T
AC_C_BIGENDIAN
AC_C_CONST
AC_C_INLINE
AC_FUNC_FSEEKO
dnl Checks for library functions.
AC_FUNC_MEMCMP
AC_CHECK_FUNCS([stricmp] [strcasecmp])
AC_CHECK_FUNCS([strchr] [memcpy])
AC_CHECK_FUNCS([stricmp])
AC_CHECK_FUNCS([getopt])
AM_CONDITIONAL(WITH_PAR2, true)
else
@@ -364,8 +381,7 @@ if test "$USETLS" = "yes"; then
if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then
PKG_CHECK_MODULES([openssl], [openssl],
[LIBS="${LIBS} $openssl_LIBS"]
[CPPFLAGS="${CPPFLAGS} $openssl_CFLAGS"],
FOUND=no)
[CPPFLAGS="${CPPFLAGS} $openssl_CFLAGS"])
fi
AC_CHECK_HEADER(openssl/ssl.h,
@@ -376,8 +392,8 @@ if test "$USETLS" = "yes"; then
AC_MSG_ERROR([Couldn't find OpenSSL headers (ssl.h)])
fi
if test "$FOUND" = "yes"; then
AC_SEARCH_LIBS([CRYPTO_set_locking_callback], [crypto],
AC_SEARCH_LIBS([SSL_library_init], [ssl],
AC_SEARCH_LIBS([ASN1_OBJECT_free], [crypto],
AC_SEARCH_LIBS([SSL_CTX_new], [ssl],
FOUND=yes,
FOUND=no),
FOUND=no)
@@ -386,22 +402,29 @@ if test "$USETLS" = "yes"; then
fi
if test "$FOUND" = "yes"; then
TLSLIB="OpenSSL"
AC_DEFINE([HAVE_OPENSSL],1,[Define to 1 to use OpenSSL library for TLS/SSL-support.])
AC_DEFINE([HAVE_OPENSSL],1,[Define to 1 to use OpenSSL library for TLS/SSL-support and decryption.])
AC_SEARCH_LIBS([X509_check_host], [crypto],
AC_DEFINE([HAVE_X509_CHECK_HOST],1,[Define to 1 if OpenSSL supports function "X509_check_host".]))
fi
fi
fi
if test "$TLSLIB" = "GnuTLS" -o "$TLSLIB" = ""; then
INCVAL="${LIBPREF}/include"
LIBVAL="${LIBPREF}/lib"
AC_ARG_WITH(libgnutls_includes,
[AS_HELP_STRING([--with-libgnutls-includes=DIR], [GnuTLS include directory])],
[INCVAL="$withval"])
CPPFLAGS="${CPPFLAGS} -I${INCVAL}"
[CPPFLAGS="${CPPFLAGS} -I${withval}"]
[INCVAL="yes"],
[INCVAL="no"])
AC_ARG_WITH(libgnutls_libraries,
[AS_HELP_STRING([--with-libgnutls-libraries=DIR], [GnuTLS library directory])],
[LIBVAL="$withval"])
LDFLAGS="${LDFLAGS} -L${LIBVAL}"
[LDFLAGS="${LDFLAGS} -L${withval}"]
[LIBVAL="yes"],
[LIBVAL="no"])
if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then
PKG_CHECK_MODULES([gnutls], [gnutls],
[LIBS="${LIBS} $gnutls_LIBS"]
[CPPFLAGS="${CPPFLAGS} $gnutls_CFLAGS"])
fi
AC_CHECK_HEADER(gnutls/gnutls.h,
FOUND=yes
@@ -443,6 +466,39 @@ if test "$USETLS" = "yes"; then
AC_DEFINE([HAVE_LIBGNUTLS],1,[Define to 1 to use GnuTLS library for TLS/SSL-support.])
fi
fi
if test "$TLSLIB" = "GnuTLS"; then
AC_ARG_WITH(libnettle_includes,
[AS_HELP_STRING([--with-libnettle-includes=DIR], [Nettle include directory])],
[CPPFLAGS="${CPPFLAGS} -I${withval}"]
[INCVAL="yes"],
[INCVAL="no"])
AC_ARG_WITH(libnettle_libraries,
[AS_HELP_STRING([--with-libnettle-libraries=DIR], [Nettle library directory])],
[LDFLAGS="${LDFLAGS} -L${withval}"]
[LIBVAL="yes"],
[LIBVAL="no"])
if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then
PKG_CHECK_MODULES([nettle], [nettle],
[LIBS="${LIBS} $nettle_LIBS"]
[CPPFLAGS="${CPPFLAGS} $nettle_CFLAGS"])
fi
AC_CHECK_HEADER(nettle/sha.h,
FOUND=yes,
FOUND=no)
if test "$FOUND" = "no"; then
AC_MSG_ERROR([Couldn't find Nettle headers (sha.h)])
fi
AC_SEARCH_LIBS([nettle_pbkdf2_hmac_sha256], [nettle],
FOUND=yes,
FOUND=no)
if test "$FOUND" = "no"; then
AC_MSG_ERROR([Couldn't find Nettle library, required when using GnuTLS])
fi
if test "$FOUND" = "yes"; then
AC_DEFINE([HAVE_NETTLE],1,[Define to 1 to use Nettle library for decryption.])
fi
fi
fi
if test "$TLSLIB" = ""; then
@@ -467,16 +523,21 @@ AC_ARG_ENABLE(gzip,
[USEZLIB=yes] )
AC_MSG_RESULT($USEZLIB)
if test "$USEZLIB" = "yes"; then
INCVAL="${LIBPREF}/include"
LIBVAL="${LIBPREF}/lib"
AC_ARG_WITH(zlib_includes,
[AS_HELP_STRING([--with-zlib-includes=DIR], [zlib include directory])],
[INCVAL="$withval"])
CPPFLAGS="${CPPFLAGS} -I${INCVAL}"
[CPPFLAGS="${CPPFLAGS} -I${withval}"]
[INCVAL="yes"],
[INCVAL="no"])
AC_ARG_WITH(zlib_libraries,
[AS_HELP_STRING([--with-zlib-libraries=DIR], [zlib library directory])],
[LIBVAL="$withval"])
LDFLAGS="${LDFLAGS} -L${LIBVAL}"
[LDFLAGS="${LDFLAGS} -L${withval}"]
[LIBVAL="yes"],
[LIBVAL="no"])
if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then
PKG_CHECK_MODULES([zlib], [zlib],
[LIBS="${LIBS} $zlib_LIBS"]
[CPPFLAGS="${CPPFLAGS} $zlib_CFLAGS"])
fi
AC_CHECK_HEADER(zlib.h,,
AC_MSG_ERROR("zlib header files not found"))
@@ -487,6 +548,36 @@ else
fi
dnl
dnl Determine if CPU supports SIMD instructions
dnl
AC_MSG_CHECKING(whether to use SIMD-optimized routines)
USE_SIMD=no
case $host_cpu in
i?86|x86_64)
SSE2_CXXFLAGS="-msse2"
SSSE3_CXXFLAGS="-mssse3"
PCLMUL_CXXFLAGS="-msse4.1 -mpclmul"
USE_SIMD=yes
;;
arm*)
NEON_CXXFLAGS="-mfpu=neon"
ACLECRC_CXXFLAGS="-march=armv8-a+crc -fpermissive"
USE_SIMD=yes
;;
aarch64)
ACLECRC_CXXFLAGS="-march=armv8-a+crc -fpermissive"
USE_SIMD=yes
;;
esac
AC_MSG_RESULT($USE_SIMD)
AC_SUBST([SSE2_CXXFLAGS])
AC_SUBST([SSSE3_CXXFLAGS])
AC_SUBST([PCLMUL_CXXFLAGS])
AC_SUBST([NEON_CXXFLAGS])
AC_SUBST([ACLECRC_CXXFLAGS])
dnl
dnl Some Linux systems require an empty signal handler for SIGCHLD
dnl in order for exit codes to be correctly delivered to parent process.
@@ -545,11 +636,10 @@ dnl
dnl variadic macros
dnl
AC_MSG_CHECKING(for variadic macros)
AC_COMPILE_IFELSE([
#define macro(...) macrofunc(__VA_ARGS__)
int macrofunc(int a, int b) { return a + b; }
int test() { return macro(1, 2); }
],
AC_TRY_COMPILE(
[ #define macro(...) macrofunc(__VA_ARGS__) ]
[ int macrofunc(int a, int b) { return a + b; } ],
[ int a=macro(1, 2); ],
AC_MSG_RESULT([yes])
AC_DEFINE([HAVE_VARIADIC_MACROS], 1, Define to 1 if variadic macros are supported),
AC_MSG_RESULT([no]))

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,25 +15,22 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CONNECTION_H
#define CONNECTION_H
#include "NString.h"
#ifndef HAVE_GETADDRINFO
#ifndef HAVE_GETHOSTBYNAME_R
#include "Thread.h"
#endif
#endif
#ifndef DISABLE_TLS
#include "TLS.h"
#include "TlsSocket.h"
#endif
class Connection
@@ -44,105 +41,117 @@ public:
csConnected,
csDisconnected,
csListening,
csCancelled
csCancelled,
csBroken
};
enum EIPVersion
{
ipAuto,
ipV4,
ipV6
};
Connection(const char* host, int port, bool tls);
Connection(SOCKET socket, bool tls);
virtual ~Connection();
static void Init();
static void Final();
virtual bool Connect();
virtual bool Disconnect();
bool Bind();
bool Send(const char* buffer, int size);
bool Recv(char* buffer, int size);
int TryRecv(char* buffer, int size);
char* ReadLine(char* buffer, int size, int* bytesRead);
void ReadBuffer(char** buffer, int *bufLen);
int WriteLine(const char* buffer);
std::unique_ptr<Connection> Accept();
void Cancel();
const char* GetHost() { return m_host; }
int GetPort() { return m_port; }
bool GetTls() { return m_tls; }
const char* GetCipher() { return m_cipher; }
void SetCipher(const char* cipher) { m_cipher = cipher; }
void SetTimeout(int timeout) { m_timeout = timeout; }
void SetIPVersion(EIPVersion ipVersion) { m_ipVersion = ipVersion; }
EStatus GetStatus() { return m_status; }
void SetSuppressErrors(bool suppressErrors);
bool GetSuppressErrors() { return m_suppressErrors; }
const char* GetRemoteAddr();
bool GetGracefull() { return m_gracefull; }
void SetGracefull(bool gracefull) { m_gracefull = gracefull; }
void SetForceClose(bool forceClose) { m_forceClose = forceClose; }
#ifndef DISABLE_TLS
bool StartTls(bool isClient, const char* certFile, const char* keyFile);
#endif
int FetchTotalBytesRead();
protected:
char* m_szHost;
int m_iPort;
SOCKET m_iSocket;
bool m_bTLS;
char* m_szCipher;
char* m_szReadBuf;
int m_iBufAvail;
char* m_szBufPtr;
EStatus m_eStatus;
int m_iTimeout;
bool m_bSuppressErrors;
char m_szRemoteAddr[20];
int m_iTotalBytesRead;
bool m_bBroken;
bool m_bGracefull;
CString m_host;
int m_port;
bool m_tls;
EIPVersion m_ipVersion = ipAuto;
SOCKET m_socket = INVALID_SOCKET;
CString m_cipher;
CharBuffer m_readBuf;
int m_bufAvail = 0;
char* m_bufPtr = nullptr;
EStatus m_status = csDisconnected;
int m_timeout = 60;
bool m_suppressErrors = true;
BString<100> m_remoteAddr;
int m_totalBytesRead = 0;
bool m_gracefull = false;
bool m_forceClose = false;
struct SockAddr
{
int ai_family;
int ai_socktype;
int ai_protocol;
bool operator==(const SockAddr& rhs) const
{ return memcmp(this, &rhs, sizeof(SockAddr)) == 0; }
int ai_family;
int ai_socktype;
int ai_protocol;
bool operator==(const SockAddr& rhs) const
{ return memcmp(this, &rhs, sizeof(SockAddr)) == 0; }
};
#ifndef DISABLE_TLS
class ConTLSSocket: public TLSSocket
class ConTlsSocket: public TlsSocket
{
private:
Connection* m_pOwner;
protected:
virtual void PrintError(const char* szErrMsg) { m_pOwner->PrintError(szErrMsg); }
public:
ConTLSSocket(SOCKET iSocket, bool bIsClient, const char* szCertFile,
const char* szKeyFile, const char* szCipher, Connection* pOwner):
TLSSocket(iSocket, bIsClient, szCertFile, szKeyFile, szCipher), m_pOwner(pOwner) {}
ConTlsSocket(SOCKET socket, bool isClient, const char* host,
const char* certFile, const char* keyFile, const char* cipher, Connection* owner) :
TlsSocket(socket, isClient, host, certFile, keyFile, cipher), m_owner(owner) {}
protected:
virtual void PrintError(const char* errMsg) { m_owner->PrintError(errMsg); }
private:
Connection* m_owner;
};
ConTLSSocket* m_pTLSSocket;
bool m_bTLSError;
std::unique_ptr<ConTlsSocket> m_tlsSocket;
bool m_tlsError = false;
#endif
#ifndef HAVE_GETADDRINFO
#ifndef HAVE_GETHOSTBYNAME_R
static Mutex* m_pMutexGetHostByName;
static std::unique_ptr<Mutex> m_getHostByNameMutex;
#endif
#endif
Connection(SOCKET iSocket, bool bTLS);
void ReportError(const char* szMsgPrefix, const char* szMsgArg, bool PrintErrCode, int herrno);
virtual void PrintError(const char* szErrMsg);
bool DoConnect();
bool DoDisconnect();
bool InitSocketOpts();
bool ConnectWithTimeout(void* address, int address_len);
void ReportError(const char* msgPrefix, const char* msgArg, bool printErrCode, int errCode = 0,
const char* errMsg = nullptr);
virtual void PrintError(const char* errMsg);
int GetLastNetworkError();
bool DoConnect();
bool DoDisconnect();
bool InitSocketOpts(SOCKET socket);
bool ConnectWithTimeout(void* address, int address_len);
#ifndef HAVE_GETADDRINFO
unsigned int ResolveHostAddr(const char* szHost);
in_addr_t ResolveHostAddr(const char* host);
#endif
#ifndef DISABLE_TLS
int recv(SOCKET s, char* buf, int len, int flags);
int send(SOCKET s, const char* buf, int len, int flags);
void CloseTLS();
int recv(SOCKET s, char* buf, int len, int flags);
int send(SOCKET s, const char* buf, int len, int flags);
void CloseTls();
#endif
public:
Connection(const char* szHost, int iPort, bool bTLS);
virtual ~Connection();
static void Init();
static void Final();
virtual bool Connect();
virtual bool Disconnect();
bool Bind();
bool Send(const char* pBuffer, int iSize);
bool Recv(char* pBuffer, int iSize);
int TryRecv(char* pBuffer, int iSize);
char* ReadLine(char* pBuffer, int iSize, int* pBytesRead);
void ReadBuffer(char** pBuffer, int *iBufLen);
int WriteLine(const char* pBuffer);
Connection* Accept();
void Cancel();
const char* GetHost() { return m_szHost; }
int GetPort() { return m_iPort; }
bool GetTLS() { return m_bTLS; }
const char* GetCipher() { return m_szCipher; }
void SetCipher(const char* szCipher);
void SetTimeout(int iTimeout) { m_iTimeout = iTimeout; }
EStatus GetStatus() { return m_eStatus; }
void SetSuppressErrors(bool bSuppressErrors);
bool GetSuppressErrors() { return m_bSuppressErrors; }
const char* GetRemoteAddr();
bool GetGracefull() { return m_bGracefull; }
void SetGracefull(bool bGracefull) { m_bGracefull = bGracefull; }
#ifndef DISABLE_TLS
bool StartTLS(bool bIsClient, const char* szCertFile, const char* szKeyFile);
#endif
int FetchTotalBytesRead();
};
#endif

View File

@@ -1,565 +0,0 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2008-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef WIN32
#define SKIP_DEFAULT_WINDOWS_HEADERS
#include "win32.h"
#endif
#ifndef DISABLE_TLS
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#ifdef WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <strings.h>
#endif
#include <ctype.h>
#include <limits.h>
#include <time.h>
#include <errno.h>
#include <list>
#ifdef WIN32
#include "nzbget.h"
#endif
#ifdef HAVE_LIBGNUTLS
#include <gnutls/gnutls.h>
#if GNUTLS_VERSION_NUMBER <= 0x020b00
#define NEED_GCRYPT_LOCKING
#endif
#ifdef NEED_GCRYPT_LOCKING
#include <gcrypt.h>
#endif /* NEED_GCRYPT_LOCKING */
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
#endif /* HAVE_OPENSSL */
#ifndef WIN32
#include "nzbget.h"
#endif
#include "TLS.h"
#include "Thread.h"
#include "Log.h"
#ifdef HAVE_LIBGNUTLS
#ifdef NEED_GCRYPT_LOCKING
/**
* Mutexes for gcryptlib
*/
typedef std::list<Mutex*> Mutexes;
Mutexes* g_pGCryptLibMutexes;
static int gcry_mutex_init(void **priv)
{
Mutex* pMutex = new Mutex();
g_pGCryptLibMutexes->push_back(pMutex);
*priv = pMutex;
return 0;
}
static int gcry_mutex_destroy(void **lock)
{
Mutex* pMutex = ((Mutex*)*lock);
g_pGCryptLibMutexes->remove(pMutex);
delete pMutex;
return 0;
}
static int gcry_mutex_lock(void **lock)
{
((Mutex*)*lock)->Lock();
return 0;
}
static int gcry_mutex_unlock(void **lock)
{
((Mutex*)*lock)->Unlock();
return 0;
}
static struct gcry_thread_cbs gcry_threads_Mutex =
{ GCRY_THREAD_OPTION_USER, NULL,
gcry_mutex_init, gcry_mutex_destroy,
gcry_mutex_lock, gcry_mutex_unlock,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
};
#endif /* NEED_GCRYPT_LOCKING */
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
/**
* Mutexes for OpenSSL
*/
Mutex* *g_pOpenSSLMutexes;
static void openssl_locking(int mode, int n, const char *file, int line)
{
Mutex* mutex = g_pOpenSSLMutexes[n];
if (mode & CRYPTO_LOCK)
{
mutex->Lock();
}
else
{
mutex->Unlock();
}
}
/*
static unsigned long openssl_thread_id(void)
{
#ifdef WIN32
return (unsigned long)GetCurrentThreadId();
#else
return (unsigned long)pthread_self();
#endif
}
*/
static struct CRYPTO_dynlock_value* openssl_dynlock_create(const char *file, int line)
{
return (CRYPTO_dynlock_value*)new Mutex();
}
static void openssl_dynlock_destroy(struct CRYPTO_dynlock_value *l, const char *file, int line)
{
Mutex* mutex = (Mutex*)l;
delete mutex;
}
static void openssl_dynlock_lock(int mode, struct CRYPTO_dynlock_value *l, const char *file, int line)
{
Mutex* mutex = (Mutex*)l;
if (mode & CRYPTO_LOCK)
{
mutex->Lock();
}
else
{
mutex->Unlock();
}
}
#endif /* HAVE_OPENSSL */
void TLSSocket::Init()
{
debug("Initializing TLS library");
#ifdef HAVE_LIBGNUTLS
#ifdef NEED_GCRYPT_LOCKING
g_pGCryptLibMutexes = new Mutexes();
#endif /* NEED_GCRYPT_LOCKING */
int error_code;
#ifdef NEED_GCRYPT_LOCKING
error_code = gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_Mutex);
if (error_code != 0)
{
error("Could not initialize libcrypt");
return;
}
#endif /* NEED_GCRYPT_LOCKING */
error_code = gnutls_global_init();
if (error_code != 0)
{
error("Could not initialize libgnutls");
return;
}
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
int iMaxMutexes = CRYPTO_num_locks();
g_pOpenSSLMutexes = (Mutex**)malloc(sizeof(Mutex*)*iMaxMutexes);
for (int i=0; i < iMaxMutexes; i++)
{
g_pOpenSSLMutexes[i] = new Mutex();
}
SSL_load_error_strings();
SSL_library_init();
OpenSSL_add_all_algorithms();
CRYPTO_set_locking_callback(openssl_locking);
//CRYPTO_set_id_callback(openssl_thread_id);
CRYPTO_set_dynlock_create_callback(openssl_dynlock_create);
CRYPTO_set_dynlock_destroy_callback(openssl_dynlock_destroy);
CRYPTO_set_dynlock_lock_callback(openssl_dynlock_lock);
#endif /* HAVE_OPENSSL */
}
void TLSSocket::Final()
{
debug("Finalizing TLS library");
#ifdef HAVE_LIBGNUTLS
gnutls_global_deinit();
#ifdef NEED_GCRYPT_LOCKING
// fixing memory leak in gcryptlib
for (Mutexes::iterator it = g_pGCryptLibMutexes->begin(); it != g_pGCryptLibMutexes->end(); it++)
{
delete *it;
}
delete g_pGCryptLibMutexes;
#endif /* NEED_GCRYPT_LOCKING */
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
int iMaxMutexes = CRYPTO_num_locks();
for (int i=0; i < iMaxMutexes; i++)
{
delete g_pOpenSSLMutexes[i];
}
free(g_pOpenSSLMutexes);
#endif /* HAVE_OPENSSL */
}
TLSSocket::TLSSocket(SOCKET iSocket, bool bIsClient, const char* szCertFile, const char* szKeyFile, const char* szCipher)
{
m_iSocket = iSocket;
m_bIsClient = bIsClient;
m_szCertFile = szCertFile ? strdup(szCertFile) : NULL;
m_szKeyFile = szKeyFile ? strdup(szKeyFile) : NULL;
m_szCipher = szCipher && strlen(szCipher) > 0 ? strdup(szCipher) : NULL;
m_pContext = NULL;
m_pSession = NULL;
m_bSuppressErrors = false;
m_bInitialized = false;
m_bConnected = false;
}
TLSSocket::~TLSSocket()
{
free(m_szCertFile);
free(m_szKeyFile);
free(m_szCipher);
Close();
}
void TLSSocket::ReportError(const char* szErrMsg)
{
char szMessage[1024];
#ifdef HAVE_LIBGNUTLS
const char* errstr = gnutls_strerror(m_iRetCode);
if (m_bSuppressErrors)
{
debug("%s: %s", szErrMsg, errstr);
}
else
{
snprintf(szMessage, sizeof(szMessage), "%s: %s", szErrMsg, errstr);
szMessage[sizeof(szMessage) - 1] = '\0';
PrintError(szMessage);
}
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
int errcode;
do
{
errcode = ERR_get_error();
char errstr[1024];
ERR_error_string_n(errcode, errstr, sizeof(errstr));
errstr[1024-1] = '\0';
if (m_bSuppressErrors)
{
debug("%s: %s", szErrMsg, errstr);
}
else if (errcode != 0)
{
snprintf(szMessage, sizeof(szMessage), "%s: %s", szErrMsg, errstr);
szMessage[sizeof(szMessage) - 1] = '\0';
PrintError(szMessage);
}
else
{
PrintError(szErrMsg);
}
} while (errcode);
#endif /* HAVE_OPENSSL */
}
void TLSSocket::PrintError(const char* szErrMsg)
{
error("%s", szErrMsg);
}
bool TLSSocket::Start()
{
#ifdef HAVE_LIBGNUTLS
gnutls_certificate_credentials_t cred;
m_iRetCode = gnutls_certificate_allocate_credentials(&cred);
if (m_iRetCode != 0)
{
ReportError("Could not create TLS context");
return false;
}
m_pContext = cred;
if (m_szCertFile && m_szKeyFile)
{
m_iRetCode = gnutls_certificate_set_x509_key_file((gnutls_certificate_credentials_t)m_pContext,
m_szCertFile, m_szKeyFile, GNUTLS_X509_FMT_PEM);
if (m_iRetCode != 0)
{
ReportError("Could not load certificate or key file");
Close();
return false;
}
}
gnutls_session_t sess;
m_iRetCode = gnutls_init(&sess, m_bIsClient ? GNUTLS_CLIENT : GNUTLS_SERVER);
if (m_iRetCode != 0)
{
ReportError("Could not create TLS session");
Close();
return false;
}
m_pSession = sess;
m_bInitialized = true;
const char* szPriority = m_szCipher ? m_szCipher : "NORMAL";
m_iRetCode = gnutls_priority_set_direct((gnutls_session_t)m_pSession, szPriority, NULL);
if (m_iRetCode != 0)
{
ReportError("Could not select cipher for TLS session");
Close();
return false;
}
m_iRetCode = gnutls_credentials_set((gnutls_session_t)m_pSession, GNUTLS_CRD_CERTIFICATE,
(gnutls_certificate_credentials_t*)m_pContext);
if (m_iRetCode != 0)
{
ReportError("Could not initialize TLS session");
Close();
return false;
}
gnutls_transport_set_ptr((gnutls_session_t)m_pSession, (gnutls_transport_ptr_t)(size_t)m_iSocket);
m_iRetCode = gnutls_handshake((gnutls_session_t)m_pSession);
if (m_iRetCode != 0)
{
ReportError("TLS handshake failed");
Close();
return false;
}
m_bConnected = true;
return true;
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
m_pContext = SSL_CTX_new(SSLv23_method());
if (!m_pContext)
{
ReportError("Could not create TLS context");
return false;
}
if (m_szCertFile && m_szKeyFile)
{
if (SSL_CTX_use_certificate_file((SSL_CTX*)m_pContext, m_szCertFile, SSL_FILETYPE_PEM) != 1)
{
ReportError("Could not load certificate file");
Close();
return false;
}
if (SSL_CTX_use_PrivateKey_file((SSL_CTX*)m_pContext, m_szKeyFile, SSL_FILETYPE_PEM) != 1)
{
ReportError("Could not load key file");
Close();
return false;
}
}
m_pSession = SSL_new((SSL_CTX*)m_pContext);
if (!m_pSession)
{
ReportError("Could not create TLS session");
Close();
return false;
}
if (m_szCipher && !SSL_set_cipher_list((SSL*)m_pSession, m_szCipher))
{
ReportError("Could not select cipher for TLS");
Close();
return false;
}
if (!SSL_set_fd((SSL*)m_pSession, m_iSocket))
{
ReportError("Could not set the file descriptor for TLS");
Close();
return false;
}
int error_code = m_bIsClient ? SSL_connect((SSL*)m_pSession) : SSL_accept((SSL*)m_pSession);
if (error_code < 1)
{
ReportError("TLS handshake failed");
Close();
return false;
}
m_bConnected = true;
return true;
#endif /* HAVE_OPENSSL */
}
void TLSSocket::Close()
{
if (m_pSession)
{
#ifdef HAVE_LIBGNUTLS
if (m_bConnected)
{
gnutls_bye((gnutls_session_t)m_pSession, GNUTLS_SHUT_WR);
}
if (m_bInitialized)
{
gnutls_deinit((gnutls_session_t)m_pSession);
}
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
if (m_bConnected)
{
SSL_shutdown((SSL*)m_pSession);
}
SSL_free((SSL*)m_pSession);
#endif /* HAVE_OPENSSL */
m_pSession = NULL;
}
if (m_pContext)
{
#ifdef HAVE_LIBGNUTLS
gnutls_certificate_free_credentials((gnutls_certificate_credentials_t)m_pContext);
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
SSL_CTX_free((SSL_CTX*)m_pContext);
#endif /* HAVE_OPENSSL */
m_pContext = NULL;
}
}
int TLSSocket::Send(const char* pBuffer, int iSize)
{
int ret;
#ifdef HAVE_LIBGNUTLS
ret = gnutls_record_send((gnutls_session_t)m_pSession, pBuffer, iSize);
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
ret = SSL_write((SSL*)m_pSession, pBuffer, iSize);
#endif /* HAVE_OPENSSL */
if (ret < 0)
{
#ifdef HAVE_OPENSSL
if (ERR_peek_error() == 0)
{
ReportError("Could not write to TLS-Socket: Connection closed by remote host");
}
else
#endif /* HAVE_OPENSSL */
ReportError("Could not write to TLS-Socket");
return -1;
}
return ret;
}
int TLSSocket::Recv(char* pBuffer, int iSize)
{
int ret;
#ifdef HAVE_LIBGNUTLS
ret = gnutls_record_recv((gnutls_session_t)m_pSession, pBuffer, iSize);
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
ret = SSL_read((SSL*)m_pSession, pBuffer, iSize);
#endif /* HAVE_OPENSSL */
if (ret < 0)
{
#ifdef HAVE_OPENSSL
if (ERR_peek_error() == 0)
{
ReportError("Could not read from TLS-Socket: Connection closed by remote host");
}
else
#endif /* HAVE_OPENSSL */
{
ReportError("Could not read from TLS-Socket");
}
return -1;
}
return ret;
}
#endif

View File

@@ -1,65 +0,0 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2008-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
*/
#ifndef TLS_H
#define TLS_H
#ifndef DISABLE_TLS
class TLSSocket
{
private:
bool m_bIsClient;
char* m_szCertFile;
char* m_szKeyFile;
char* m_szCipher;
SOCKET m_iSocket;
bool m_bSuppressErrors;
int m_iRetCode;
bool m_bInitialized;
bool m_bConnected;
// using "void*" to prevent the including of GnuTLS/OpenSSL header files into TLS.h
void* m_pContext;
void* m_pSession;
void ReportError(const char* szErrMsg);
protected:
virtual void PrintError(const char* szErrMsg);
public:
TLSSocket(SOCKET iSocket, bool bIsClient, const char* szCertFile, const char* szKeyFile, const char* szCipher);
virtual ~TLSSocket();
static void Init();
static void Final();
bool Start();
void Close();
int Send(const char* pBuffer, int iSize);
int Recv(char* pBuffer, int iSize);
void SetSuppressErrors(bool bSuppressErrors) { m_bSuppressErrors = bSuppressErrors; }
};
#endif
#endif

View File

@@ -0,0 +1,696 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2008-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "nzbget.h"
#ifndef DISABLE_TLS
#include "TlsSocket.h"
#include "Thread.h"
#include "Log.h"
#include "Util.h"
#include "FileSystem.h"
CString TlsSocket::m_certStore;
#ifdef HAVE_LIBGNUTLS
#ifdef NEED_GCRYPT_LOCKING
/**
* Mutexes for gcryptlib
*/
std::vector<std::unique_ptr<Mutex>> g_GCryptLibMutexes;
static int gcry_mutex_init(void **priv)
{
g_GCryptLibMutexes.emplace_back(std::make_unique<Mutex>());
*priv = g_GCryptLibMutexes.back().get();
return 0;
}
static int gcry_mutex_destroy(void **lock)
{
Mutex* mutex = ((Mutex*)*lock);
g_GCryptLibMutexes.erase(std::find_if(g_GCryptLibMutexes.begin(), g_GCryptLibMutexes.end(),
[mutex](std::unique_ptr<Mutex>& itMutex)
{
return itMutex.get() == mutex;
}));
return 0;
}
static int gcry_mutex_lock(void **lock)
{
((Mutex*)*lock)->Lock();
return 0;
}
static int gcry_mutex_unlock(void **lock)
{
((Mutex*)*lock)->Unlock();
return 0;
}
static struct gcry_thread_cbs gcry_threads_Mutex =
{ GCRY_THREAD_OPTION_USER, nullptr,
gcry_mutex_init, gcry_mutex_destroy,
gcry_mutex_lock, gcry_mutex_unlock,
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr
};
#endif /* NEED_GCRYPT_LOCKING */
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
#ifndef CRYPTO_set_locking_callback
#define NEED_CRYPTO_LOCKING
#endif
#ifdef NEED_CRYPTO_LOCKING
/**
* Mutexes for OpenSSL
*/
std::vector<std::unique_ptr<Mutex>> g_OpenSSLMutexes;
static void openssl_locking(int mode, int n, const char* file, int line)
{
Mutex* mutex = g_OpenSSLMutexes[n].get();
if (mode & CRYPTO_LOCK)
{
mutex->Lock();
}
else
{
mutex->Unlock();
}
}
static struct CRYPTO_dynlock_value* openssl_dynlock_create(const char *file, int line)
{
return (CRYPTO_dynlock_value*)new Mutex();
}
static void openssl_dynlock_destroy(struct CRYPTO_dynlock_value *l, const char *file, int line)
{
Mutex* mutex = (Mutex*)l;
delete mutex;
}
static void openssl_dynlock_lock(int mode, struct CRYPTO_dynlock_value *l, const char *file, int line)
{
Mutex* mutex = (Mutex*)l;
if (mode & CRYPTO_LOCK)
{
mutex->Lock();
}
else
{
mutex->Unlock();
}
}
#endif /* NEED_CRYPTO_LOCKING */
#endif /* HAVE_OPENSSL */
void TlsSocket::Init()
{
debug("Initializing TLS library");
#ifdef HAVE_LIBGNUTLS
int error_code;
#ifdef NEED_GCRYPT_LOCKING
error_code = gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_Mutex);
if (error_code != 0)
{
error("Could not initialize libcrypt");
return;
}
#endif /* NEED_GCRYPT_LOCKING */
error_code = gnutls_global_init();
if (error_code != 0)
{
error("Could not initialize libgnutls");
return;
}
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
#ifdef NEED_CRYPTO_LOCKING
for (int i = 0, num = CRYPTO_num_locks(); i < num; i++)
{
g_OpenSSLMutexes.emplace_back(std::make_unique<Mutex>());
}
CRYPTO_set_locking_callback(openssl_locking);
CRYPTO_set_dynlock_create_callback(openssl_dynlock_create);
CRYPTO_set_dynlock_destroy_callback(openssl_dynlock_destroy);
CRYPTO_set_dynlock_lock_callback(openssl_dynlock_lock);
#endif /* NEED_CRYPTO_LOCKING */
SSL_load_error_strings();
SSL_library_init();
OpenSSL_add_all_algorithms();
#endif /* HAVE_OPENSSL */
}
void TlsSocket::Final()
{
#ifdef HAVE_LIBGNUTLS
gnutls_global_deinit();
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
#ifndef LIBRESSL_VERSION_NUMBER
FIPS_mode_set(0);
#endif
#ifdef NEED_CRYPTO_LOCKING
CRYPTO_set_locking_callback(nullptr);
CRYPTO_set_id_callback(nullptr);
#endif
#if OPENSSL_VERSION_NUMBER < 0x10100000L
ERR_remove_state(0);
#endif
#if OPENSSL_VERSION_NUMBER >= 0x10002000L && ! defined (LIBRESSL_VERSION_NUMBER)
SSL_COMP_free_compression_methods();
#endif
//ENGINE_cleanup();
CONF_modules_free();
CONF_modules_unload(1);
#ifndef OPENSSL_NO_COMP
COMP_zlib_cleanup();
#endif
ERR_free_strings();
EVP_cleanup();
CRYPTO_cleanup_all_ex_data();
#endif /* HAVE_OPENSSL */
}
TlsSocket::~TlsSocket()
{
Close();
#ifdef HAVE_OPENSSL
#if OPENSSL_VERSION_NUMBER < 0x10100000L
ERR_remove_state(0);
#endif
#endif
}
void TlsSocket::ReportError(const char* errMsg, bool suppressable)
{
#ifdef HAVE_LIBGNUTLS
const char* errstr = gnutls_strerror(m_retCode);
if (suppressable && m_suppressErrors)
{
debug("%s: %s", errMsg, errstr);
}
else
{
PrintError(BString<1024>("%s: %s", errMsg, errstr));
}
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
int errcode = ERR_get_error();
do
{
char errstr[1024];
ERR_error_string_n(errcode, errstr, sizeof(errstr));
errstr[1024-1] = '\0';
if (suppressable && m_suppressErrors)
{
debug("%s: %s", errMsg, errstr);
}
else if (errcode != 0)
{
PrintError(BString<1024>("%s: %s", errMsg, errstr));
}
else
{
PrintError(errMsg);
}
errcode = ERR_get_error();
} while (errcode);
#endif /* HAVE_OPENSSL */
}
void TlsSocket::PrintError(const char* errMsg)
{
error("%s", errMsg);
}
bool TlsSocket::Start()
{
#ifdef HAVE_LIBGNUTLS
gnutls_certificate_credentials_t cred;
m_retCode = gnutls_certificate_allocate_credentials(&cred);
if (m_retCode != 0)
{
ReportError("Could not create TLS context", false);
return false;
}
m_context = cred;
if (m_certFile && m_keyFile)
{
m_retCode = gnutls_certificate_set_x509_key_file((gnutls_certificate_credentials_t)m_context,
m_certFile, m_keyFile, GNUTLS_X509_FMT_PEM);
if (m_retCode != 0)
{
ReportError("Could not load certificate or key file", false);
Close();
return false;
}
}
gnutls_session_t sess;
m_retCode = gnutls_init(&sess, m_isClient ? GNUTLS_CLIENT : GNUTLS_SERVER);
if (m_retCode != 0)
{
ReportError("Could not create TLS session", false);
Close();
return false;
}
m_session = sess;
m_initialized = true;
const char* priority = !m_cipher.Empty() ? m_cipher.Str() :
(m_certFile && m_keyFile ? "NORMAL:!VERS-SSL3.0" : "NORMAL");
m_retCode = gnutls_priority_set_direct((gnutls_session_t)m_session, priority, nullptr);
if (m_retCode != 0)
{
ReportError("Could not select cipher for TLS", false);
Close();
return false;
}
if (m_host)
{
m_retCode = gnutls_server_name_set((gnutls_session_t)m_session, GNUTLS_NAME_DNS, m_host, m_host.Length());
if (m_retCode != 0)
{
ReportError("Could not set hostname for TLS");
Close();
return false;
}
}
m_retCode = gnutls_credentials_set((gnutls_session_t)m_session, GNUTLS_CRD_CERTIFICATE,
(gnutls_certificate_credentials_t*)m_context);
if (m_retCode != 0)
{
ReportError("Could not initialize TLS session", false);
Close();
return false;
}
gnutls_transport_set_ptr((gnutls_session_t)m_session, (gnutls_transport_ptr_t)(size_t)m_socket);
m_retCode = gnutls_handshake((gnutls_session_t)m_session);
if (m_retCode != 0)
{
ReportError(BString<1024>("TLS handshake failed for %s", *m_host));
Close();
return false;
}
if (m_isClient && !m_certStore.Empty() && !ValidateCert())
{
Close();
return false;
}
m_connected = true;
return true;
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
m_context = SSL_CTX_new(SSLv23_method());
if (!m_context)
{
ReportError("Could not create TLS context", false);
return false;
}
if (m_certFile && m_keyFile)
{
if (SSL_CTX_use_certificate_chain_file((SSL_CTX*)m_context, m_certFile) != 1)
{
ReportError("Could not load certificate file", false);
Close();
return false;
}
if (SSL_CTX_use_PrivateKey_file((SSL_CTX*)m_context, m_keyFile, SSL_FILETYPE_PEM) != 1)
{
ReportError("Could not load key file", false);
Close();
return false;
}
if (!SSL_CTX_set_options((SSL_CTX*)m_context, SSL_OP_NO_SSLv3))
{
ReportError("Could not select minimum protocol version for TLS", false);
Close();
return false;
}
// For ECC certificates
EC_KEY* ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
if (!ecdh)
{
ReportError("Could not generate ecdh parameters for TLS", false);
Close();
return false;
}
if (!SSL_CTX_set_tmp_ecdh((SSL_CTX*)m_context, ecdh))
{
ReportError("Could not set ecdh parameters for TLS", false);
EC_KEY_free(ecdh);
Close();
return false;
}
EC_KEY_free(ecdh);
}
if (m_isClient && !m_certStore.Empty())
{
// Enable certificate validation
if (SSL_CTX_load_verify_locations((SSL_CTX*)m_context, m_certStore, nullptr) != 1)
{
ReportError("Could not set certificate store location", false);
Close();
return false;
}
SSL_CTX_set_verify((SSL_CTX*)m_context, SSL_VERIFY_PEER, nullptr);
}
m_session = SSL_new((SSL_CTX*)m_context);
if (!m_session)
{
ReportError("Could not create TLS session", false);
Close();
return false;
}
if (!m_cipher.Empty() && !SSL_set_cipher_list((SSL*)m_session, m_cipher))
{
ReportError("Could not select cipher for TLS", false);
Close();
return false;
}
if (m_isClient && m_host && !SSL_set_tlsext_host_name((SSL*)m_session, m_host))
{
ReportError("Could not set host name for TLS");
Close();
return false;
}
if (!SSL_set_fd((SSL*)m_session, (int)m_socket))
{
ReportError("Could not set the file descriptor for TLS");
Close();
return false;
}
int error_code = m_isClient ? SSL_connect((SSL*)m_session) : SSL_accept((SSL*)m_session);
if (error_code < 1)
{
long verifyRes = SSL_get_verify_result((SSL*)m_session);
if (verifyRes != X509_V_OK)
{
PrintError(BString<1024>("TLS certificate verification failed for %s: %s."
" For more info visit http://nzbget.net/certificate-verification",
*m_host, X509_verify_cert_error_string(verifyRes)));
}
else
{
ReportError(BString<1024>("TLS handshake failed for %s", *m_host));
}
Close();
return false;
}
if (m_isClient && !m_certStore.Empty() && !ValidateCert())
{
Close();
return false;
}
m_connected = true;
return true;
#endif /* HAVE_OPENSSL */
}
bool TlsSocket::ValidateCert()
{
#ifdef HAVE_LIBGNUTLS
#if GNUTLS_VERSION_NUMBER >= 0x030104
#if GNUTLS_VERSION_NUMBER >= 0x030306
if (FileSystem::DirectoryExists(m_certStore))
{
if (gnutls_certificate_set_x509_trust_dir((gnutls_certificate_credentials_t)m_context, m_certStore, GNUTLS_X509_FMT_PEM) < 0)
{
ReportError("Could not set certificate store location");
return false;
}
}
else
#endif
{
if (gnutls_certificate_set_x509_trust_file((gnutls_certificate_credentials_t)m_context, m_certStore, GNUTLS_X509_FMT_PEM) < 0)
{
ReportError("Could not set certificate store location");
return false;
}
}
unsigned int status = 0;
if (gnutls_certificate_verify_peers3((gnutls_session_t)m_session, m_host, &status) != 0 ||
gnutls_certificate_type_get((gnutls_session_t)m_session) != GNUTLS_CRT_X509)
{
ReportError("Could not verify TLS certificate");
return false;
}
if (status != 0)
{
if (status & GNUTLS_CERT_UNEXPECTED_OWNER)
{
// Extracting hostname from the certificate
unsigned int cert_list_size = 0;
const gnutls_datum_t* cert_list = gnutls_certificate_get_peers((gnutls_session_t)m_session, &cert_list_size);
if (cert_list_size > 0)
{
gnutls_x509_crt_t cert;
gnutls_x509_crt_init(&cert);
gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER);
char dn[256];
size_t size = sizeof(dn);
if (gnutls_x509_crt_get_dn_by_oid(cert, GNUTLS_OID_X520_COMMON_NAME, 0, 0, dn, &size) == 0)
{
PrintError(BString<1024>("TLS certificate verification failed for %s: certificate hostname mismatch (%s)."
" For more info visit http://nzbget.net/certificate-verification", *m_host, dn));
gnutls_x509_crt_deinit(cert);
return false;
}
gnutls_x509_crt_deinit(cert);
}
}
gnutls_datum_t msgdata;
if (gnutls_certificate_verification_status_print(status, GNUTLS_CRT_X509, &msgdata, 0) == 0)
{
PrintError(BString<1024>("TLS certificate verification failed for %s: %s."
" For more info visit http://nzbget.net/certificate-verification", *m_host, msgdata.data));
gnutls_free(&msgdata);
}
else
{
ReportError(BString<1024>("TLS certificate verification failed for %s."
" For more info visit http://nzbget.net/certificate-verification", *m_host));
}
return false;
}
#endif
return true;
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
// verify a server certificate was presented during the negotiation
X509* cert = SSL_get_peer_certificate((SSL*)m_session);
if (!cert)
{
PrintError(BString<1024>("TLS certificate verification failed for %s: no certificate provided by server."
" For more info visit http://nzbget.net/certificate-verification", *m_host));
return false;
}
#ifdef HAVE_X509_CHECK_HOST
// hostname verification
if (!m_host.Empty() && X509_check_host(cert, m_host, m_host.Length(), 0, nullptr) != 1)
{
const unsigned char* certHost = nullptr;
// Find the position of the CN field in the Subject field of the certificate
int common_name_loc = X509_NAME_get_index_by_NID(X509_get_subject_name(cert), NID_commonName, -1);
if (common_name_loc >= 0)
{
// Extract the CN field
X509_NAME_ENTRY* common_name_entry = X509_NAME_get_entry(X509_get_subject_name(cert), common_name_loc);
if (common_name_entry != nullptr)
{
// Convert the CN field to a C string
ASN1_STRING* common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry);
if (common_name_asn1 != nullptr)
{
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
certHost = ASN1_STRING_get0_data(common_name_asn1);
#else
certHost = ASN1_STRING_data(common_name_asn1);
#endif
}
}
}
PrintError(BString<1024>("TLS certificate verification failed for %s: certificate hostname mismatch (%s)."
" For more info visit http://nzbget.net/certificate-verification", *m_host, certHost));
X509_free(cert);
return false;
}
#endif
X509_free(cert);
return true;
#endif /* HAVE_OPENSSL */
}
void TlsSocket::Close()
{
if (m_session)
{
#ifdef HAVE_LIBGNUTLS
if (m_connected)
{
gnutls_bye((gnutls_session_t)m_session, GNUTLS_SHUT_WR);
}
if (m_initialized)
{
gnutls_deinit((gnutls_session_t)m_session);
}
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
if (m_connected)
{
SSL_shutdown((SSL*)m_session);
}
SSL_free((SSL*)m_session);
#endif /* HAVE_OPENSSL */
m_session = nullptr;
}
if (m_context)
{
#ifdef HAVE_LIBGNUTLS
gnutls_certificate_free_credentials((gnutls_certificate_credentials_t)m_context);
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
SSL_CTX_free((SSL_CTX*)m_context);
#endif /* HAVE_OPENSSL */
m_context = nullptr;
}
}
int TlsSocket::Send(const char* buffer, int size)
{
#ifdef HAVE_LIBGNUTLS
m_retCode = gnutls_record_send((gnutls_session_t)m_session, buffer, size);
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
m_retCode = SSL_write((SSL*)m_session, buffer, size);
#endif /* HAVE_OPENSSL */
if (m_retCode < 0)
{
#ifdef HAVE_OPENSSL
if (ERR_peek_error() == 0)
{
ReportError("Could not write to TLS-Socket: Connection closed by remote host");
}
else
#endif /* HAVE_OPENSSL */
ReportError("Could not write to TLS-Socket");
return -1;
}
return m_retCode;
}
int TlsSocket::Recv(char* buffer, int size)
{
#ifdef HAVE_LIBGNUTLS
m_retCode = gnutls_record_recv((gnutls_session_t)m_session, buffer, size);
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
m_retCode = SSL_read((SSL*)m_session, buffer, size);
#endif /* HAVE_OPENSSL */
if (m_retCode < 0)
{
#ifdef HAVE_OPENSSL
if (ERR_peek_error() == 0)
{
ReportError("Could not read from TLS-Socket: Connection closed by remote host");
}
else
#endif /* HAVE_OPENSSL */
{
ReportError("Could not read from TLS-Socket");
}
return -1;
}
return m_retCode;
}
#endif

View File

@@ -0,0 +1,69 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2008-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TLSSOCKET_H
#define TLSSOCKET_H
#ifndef DISABLE_TLS
#include "NString.h"
class TlsSocket
{
public:
TlsSocket(SOCKET socket, bool isClient, const char* host,
const char* certFile, const char* keyFile, const char* cipher) :
m_socket(socket), m_isClient(isClient), m_host(host),
m_certFile(certFile), m_keyFile(keyFile), m_cipher(cipher) {}
virtual ~TlsSocket();
static void Init();
static void InitOptions(const char* certStore) { m_certStore = certStore; }
static void Final();
bool Start();
void Close();
int Send(const char* buffer, int size);
int Recv(char* buffer, int size);
void SetSuppressErrors(bool suppressErrors) { m_suppressErrors = suppressErrors; }
protected:
virtual void PrintError(const char* errMsg);
private:
SOCKET m_socket;
bool m_isClient;
CString m_host;
CString m_certFile;
CString m_keyFile;
CString m_cipher;
bool m_suppressErrors = false;
bool m_initialized = false;
bool m_connected = false;
int m_retCode;
static CString m_certStore;
// using "void*" to prevent the including of GnuTLS/OpenSSL header files into TlsSocket.h
void* m_context = nullptr;
void* m_session = nullptr;
void ReportError(const char* errMsg, bool suppressable = true);
bool ValidateCert();
};
#endif
#endif

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2012-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2012-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,87 +14,39 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#ifdef WIN32
#include <direct.h>
#else
#include <unistd.h>
#include <sys/time.h>
#endif
#include <sys/stat.h>
#include <errno.h>
#include "nzbget.h"
#include "WebDownloader.h"
#include "Log.h"
#include "Options.h"
#include "WorkState.h"
#include "Util.h"
#include "FileSystem.h"
WebDownloader::WebDownloader()
{
debug("Creating WebDownloader");
m_szURL = NULL;
m_szOutputFilename = NULL;
m_pConnection = NULL;
m_szInfoName = NULL;
m_bConfirmedLength = false;
m_eStatus = adUndefined;
m_szOriginalFilename = NULL;
m_bForce = false;
m_bRetry = true;
SetLastUpdateTimeNow();
}
WebDownloader::~WebDownloader()
void WebDownloader::SetUrl(const char* url)
{
debug("Destroying WebDownloader");
free(m_szURL);
free(m_szInfoName);
free(m_szOutputFilename);
free(m_szOriginalFilename);
m_url = WebUtil::UrlEncode(url);
}
void WebDownloader::SetOutputFilename(const char* v)
void WebDownloader::SetStatus(EStatus status)
{
m_szOutputFilename = strdup(v);
m_status = status;
Notify(nullptr);
}
void WebDownloader::SetInfoName(const char* v)
void WebDownloader::SetLastUpdateTimeNow()
{
m_szInfoName = strdup(v);
}
void WebDownloader::SetURL(const char * szURL)
{
free(m_szURL);
m_szURL = WebUtil::URLEncode(szURL);
}
void WebDownloader::SetStatus(EStatus eStatus)
{
m_eStatus = eStatus;
Notify(NULL);
m_lastUpdateTime = Util::CurrentTime();
}
void WebDownloader::Run()
@@ -103,37 +55,37 @@ void WebDownloader::Run()
SetStatus(adRunning);
int iRemainedDownloadRetries = g_pOptions->GetRetries() > 0 ? g_pOptions->GetRetries() : 1;
int iRemainedConnectRetries = iRemainedDownloadRetries > 10 ? iRemainedDownloadRetries : 10;
if (!m_bRetry)
int remainedDownloadRetries = g_Options->GetUrlRetries() > 0 ? g_Options->GetUrlRetries() : 1;
int remainedConnectRetries = remainedDownloadRetries > 10 ? remainedDownloadRetries : 10;
if (!m_retry)
{
iRemainedDownloadRetries = 1;
iRemainedConnectRetries = 1;
remainedDownloadRetries = 1;
remainedConnectRetries = 1;
}
EStatus Status = adFailed;
while (!IsStopped() && iRemainedDownloadRetries > 0 && iRemainedConnectRetries > 0)
while (!IsStopped() && remainedDownloadRetries > 0 && remainedConnectRetries > 0)
{
SetLastUpdateTimeNow();
Status = DownloadWithRedirects(5);
if ((((Status == adFailed) && (iRemainedDownloadRetries > 1)) ||
((Status == adConnectError) && (iRemainedConnectRetries > 1)))
&& !IsStopped() && !(!m_bForce && g_pOptions->GetPauseDownload()))
if ((((Status == adFailed) && (remainedDownloadRetries > 1)) ||
((Status == adConnectError) && (remainedConnectRetries > 1)))
&& !IsStopped() && !(!m_force && g_WorkState->GetPauseDownload()))
{
detail("Waiting %i sec to retry", g_pOptions->GetRetryInterval());
detail("Waiting %i sec to retry", g_Options->GetUrlInterval());
int msec = 0;
while (!IsStopped() && (msec < g_pOptions->GetRetryInterval() * 1000) &&
!(!m_bForce && g_pOptions->GetPauseDownload()))
while (!IsStopped() && (msec < g_Options->GetUrlInterval() * 1000) &&
!(!m_force && g_WorkState->GetPauseDownload()))
{
usleep(100 * 1000);
Util::Sleep(100);
msec += 100;
}
}
if (IsStopped() || (!m_bForce && g_pOptions->GetPauseDownload()))
if (IsStopped() || (!m_force && g_WorkState->GetPauseDownload()))
{
Status = adRetry;
break;
@@ -146,11 +98,11 @@ void WebDownloader::Run()
if (Status != adConnectError)
{
iRemainedDownloadRetries--;
remainedDownloadRetries--;
}
else
{
iRemainedConnectRetries--;
remainedConnectRetries--;
}
}
@@ -163,17 +115,17 @@ void WebDownloader::Run()
{
if (IsStopped())
{
detail("Download %s cancelled", m_szInfoName);
detail("Download %s cancelled", *m_infoName);
}
else
{
error("Download %s failed", m_szInfoName);
error("Download %s failed", *m_infoName);
}
}
if (Status == adFinished)
{
detail("Download %s completed", m_szInfoName);
detail("Download %s completed", *m_infoName);
}
SetStatus(Status);
@@ -185,7 +137,7 @@ WebDownloader::EStatus WebDownloader::Download()
{
EStatus Status = adRunning;
URL url(m_szURL);
URL url(m_url);
Status = CreateConnection(&url);
if (Status != adRunning)
@@ -193,19 +145,19 @@ WebDownloader::EStatus WebDownloader::Download()
return Status;
}
m_pConnection->SetTimeout(g_pOptions->GetUrlTimeout());
m_pConnection->SetSuppressErrors(false);
m_connection->SetTimeout(g_Options->GetUrlTimeout());
m_connection->SetSuppressErrors(false);
// connection
bool bConnected = m_pConnection->Connect();
if (!bConnected || IsStopped())
bool connected = m_connection->Connect();
if (!connected || IsStopped())
{
FreeConnection();
return adConnectError;
}
// Okay, we got a Connection. Now start downloading.
detail("Downloading %s", m_szInfoName);
detail("Downloading %s", *m_infoName);
SendHeaders(&url);
@@ -226,132 +178,122 @@ WebDownloader::EStatus WebDownloader::Download()
if (Status != adFinished)
{
// Download failed, delete broken output file
remove(m_szOutputFilename);
FileSystem::DeleteFile(m_outputFilename);
}
return Status;
}
WebDownloader::EStatus WebDownloader::DownloadWithRedirects(int iMaxRedirects)
WebDownloader::EStatus WebDownloader::DownloadWithRedirects(int maxRedirects)
{
// do sync download, following redirects
EStatus eStatus = adRedirect;
while (eStatus == adRedirect && iMaxRedirects >= 0)
EStatus status = adRedirect;
while (status == adRedirect && maxRedirects >= 0)
{
iMaxRedirects--;
eStatus = Download();
maxRedirects--;
status = Download();
}
if (eStatus == adRedirect && iMaxRedirects < 0)
if (status == adRedirect && maxRedirects < 0)
{
warn("Too many redirects for %s", m_szInfoName);
eStatus = adFailed;
warn("Too many redirects for %s", *m_infoName);
status = adFailed;
}
return eStatus;
return status;
}
WebDownloader::EStatus WebDownloader::CreateConnection(URL *pUrl)
WebDownloader::EStatus WebDownloader::CreateConnection(URL *url)
{
if (!pUrl->IsValid())
if (!url->IsValid())
{
error("URL is not valid: %s", pUrl->GetAddress());
error("URL is not valid: %s", url->GetAddress());
return adFatalError;
}
int iPort = pUrl->GetPort();
if (iPort == 0 && !strcasecmp(pUrl->GetProtocol(), "http"))
int port = url->GetPort();
if (port == 0 && !strcasecmp(url->GetProtocol(), "http"))
{
iPort = 80;
port = 80;
}
if (iPort == 0 && !strcasecmp(pUrl->GetProtocol(), "https"))
if (port == 0 && !strcasecmp(url->GetProtocol(), "https"))
{
iPort = 443;
port = 443;
}
if (strcasecmp(pUrl->GetProtocol(), "http") && strcasecmp(pUrl->GetProtocol(), "https"))
if (strcasecmp(url->GetProtocol(), "http") && strcasecmp(url->GetProtocol(), "https"))
{
error("Unsupported protocol in URL: %s", pUrl->GetAddress());
error("Unsupported protocol in URL: %s", url->GetAddress());
return adFatalError;
}
#ifdef DISABLE_TLS
if (!strcasecmp(pUrl->GetProtocol(), "https"))
if (!strcasecmp(url->GetProtocol(), "https"))
{
error("Program was compiled without TLS/SSL-support. Cannot download using https protocol. URL: %s", pUrl->GetAddress());
error("Program was compiled without TLS/SSL-support. Cannot download using https protocol. URL: %s", url->GetAddress());
return adFatalError;
}
#endif
bool bTLS = !strcasecmp(pUrl->GetProtocol(), "https");
bool tls = !strcasecmp(url->GetProtocol(), "https");
m_pConnection = new Connection(pUrl->GetHost(), iPort, bTLS);
m_connection = std::make_unique<Connection>(url->GetHost(), port, tls);
return adRunning;
}
void WebDownloader::SendHeaders(URL *pUrl)
void WebDownloader::SendHeaders(URL *url)
{
char tmp[1024];
// retrieve file
snprintf(tmp, 1024, "GET %s HTTP/1.0\r\n", pUrl->GetResource());
tmp[1024-1] = '\0';
m_pConnection->WriteLine(tmp);
m_connection->WriteLine(BString<1024>("GET %s HTTP/1.0\r\n", url->GetResource()));
m_connection->WriteLine(BString<1024>("User-Agent: nzbget/%s\r\n", Util::VersionRevision()));
snprintf(tmp, 1024, "User-Agent: nzbget/%s\r\n", Util::VersionRevision());
tmp[1024-1] = '\0';
m_pConnection->WriteLine(tmp);
if ((!strcasecmp(pUrl->GetProtocol(), "http") && (pUrl->GetPort() == 80 || pUrl->GetPort() == 0)) ||
(!strcasecmp(pUrl->GetProtocol(), "https") && (pUrl->GetPort() == 443 || pUrl->GetPort() == 0)))
if ((!strcasecmp(url->GetProtocol(), "http") && (url->GetPort() == 80 || url->GetPort() == 0)) ||
(!strcasecmp(url->GetProtocol(), "https") && (url->GetPort() == 443 || url->GetPort() == 0)))
{
snprintf(tmp, 1024, "Host: %s\r\n", pUrl->GetHost());
m_connection->WriteLine(BString<1024>("Host: %s\r\n", url->GetHost()));
}
else
{
snprintf(tmp, 1024, "Host: %s:%i\r\n", pUrl->GetHost(), pUrl->GetPort());
m_connection->WriteLine(BString<1024>("Host: %s:%i\r\n", url->GetHost(), url->GetPort()));
}
tmp[1024-1] = '\0';
m_pConnection->WriteLine(tmp);
m_pConnection->WriteLine("Accept: */*\r\n");
m_connection->WriteLine("Accept: */*\r\n");
#ifndef DISABLE_GZIP
m_pConnection->WriteLine("Accept-Encoding: gzip\r\n");
m_connection->WriteLine("Accept-Encoding: gzip\r\n");
#endif
m_pConnection->WriteLine("Connection: close\r\n");
m_pConnection->WriteLine("\r\n");
m_connection->WriteLine("Connection: close\r\n");
m_connection->WriteLine("\r\n");
}
WebDownloader::EStatus WebDownloader::DownloadHeaders()
{
EStatus Status = adRunning;
m_bConfirmedLength = false;
const int LineBufSize = 1024*10;
char* szLineBuf = (char*)malloc(LineBufSize);
m_iContentLen = -1;
bool bFirstLine = true;
m_bGZip = false;
m_bRedirecting = false;
m_bRedirected = false;
m_confirmedLength = false;
CharBuffer lineBuf(1024*10);
m_contentLen = -1;
bool firstLine = true;
m_gzip = false;
m_redirecting = false;
m_redirected = false;
// Headers
while (!IsStopped())
{
SetLastUpdateTimeNow();
int iLen = 0;
char* line = m_pConnection->ReadLine(szLineBuf, LineBufSize, &iLen);
int len = 0;
char* line = m_connection->ReadLine(lineBuf, lineBuf.Size(), &len);
if (bFirstLine)
if (firstLine)
{
Status = CheckResponse(szLineBuf);
Status = CheckResponse(lineBuf);
if (Status != adRunning)
{
break;
}
bFirstLine = false;
firstLine = false;
}
// Have we encountered a timeout?
@@ -359,7 +301,7 @@ WebDownloader::EStatus WebDownloader::DownloadHeaders()
{
if (!IsStopped())
{
warn("URL %s failed: Unexpected end of file", m_szInfoName);
warn("URL %s failed: Unexpected end of file", *m_infoName);
}
Status = adFailed;
break;
@@ -376,15 +318,13 @@ WebDownloader::EStatus WebDownloader::DownloadHeaders()
Util::TrimRight(line);
ProcessHeader(line);
if (m_bRedirected)
if (m_redirected)
{
Status = adRedirect;
break;
}
}
free(szLineBuf);
return Status;
}
@@ -392,17 +332,16 @@ WebDownloader::EStatus WebDownloader::DownloadBody()
{
EStatus Status = adRunning;
m_pOutFile = NULL;
bool bEnd = false;
const int LineBufSize = 1024*10;
char* szLineBuf = (char*)malloc(LineBufSize);
int iWrittenLen = 0;
m_outFile.Close();
bool end = false;
CharBuffer lineBuf(1024*10);
int writtenLen = 0;
#ifndef DISABLE_GZIP
m_pGUnzipStream = NULL;
if (m_bGZip)
m_gUnzipStream.reset();
if (m_gzip)
{
m_pGUnzipStream = new GUnzipStream(1024*10);
m_gUnzipStream = std::make_unique<GUnzipStream>(1024*10);
}
#endif
@@ -411,66 +350,61 @@ WebDownloader::EStatus WebDownloader::DownloadBody()
{
SetLastUpdateTimeNow();
char* szBuffer;
int iLen;
m_pConnection->ReadBuffer(&szBuffer, &iLen);
if (iLen == 0)
char* buffer;
int len;
m_connection->ReadBuffer(&buffer, &len);
if (len == 0)
{
iLen = m_pConnection->TryRecv(szLineBuf, LineBufSize);
szBuffer = szLineBuf;
len = m_connection->TryRecv(lineBuf, lineBuf.Size());
buffer = lineBuf;
}
// Connection closed or timeout?
if (iLen <= 0)
if (len <= 0)
{
if (iLen == 0 && m_iContentLen == -1 && iWrittenLen > 0)
if (len == 0 && m_contentLen == -1 && writtenLen > 0)
{
bEnd = true;
end = true;
break;
}
if (!IsStopped())
{
warn("URL %s failed: Unexpected end of file", m_szInfoName);
warn("URL %s failed: Unexpected end of file", *m_infoName);
}
Status = adFailed;
break;
}
// write to output file
if (!Write(szBuffer, iLen))
if (!Write(buffer, len))
{
Status = adFatalError;
break;
}
iWrittenLen += iLen;
writtenLen += len;
//detect end of file
if (iWrittenLen == m_iContentLen || (m_iContentLen == -1 && m_bGZip && m_bConfirmedLength))
if (writtenLen == m_contentLen || (m_contentLen == -1 && m_gzip && m_confirmedLength))
{
bEnd = true;
end = true;
break;
}
}
free(szLineBuf);
#ifndef DISABLE_GZIP
delete m_pGUnzipStream;
m_gUnzipStream.reset();
#endif
if (m_pOutFile)
{
fclose(m_pOutFile);
}
m_outFile.Close();
if (!bEnd && Status == adRunning && !IsStopped())
if (!end && Status == adRunning && !IsStopped())
{
warn("URL %s failed: file incomplete", m_szInfoName);
warn("URL %s failed: file incomplete", *m_infoName);
Status = adFailed;
}
if (bEnd)
if (end)
{
Status = adFinished;
}
@@ -478,83 +412,85 @@ WebDownloader::EStatus WebDownloader::DownloadBody()
return Status;
}
WebDownloader::EStatus WebDownloader::CheckResponse(const char* szResponse)
WebDownloader::EStatus WebDownloader::CheckResponse(const char* response)
{
if (!szResponse)
if (!response)
{
if (!IsStopped())
{
warn("URL %s: Connection closed by remote host", m_szInfoName);
warn("URL %s: Connection closed by remote host", *m_infoName);
}
return adConnectError;
}
const char* szHTTPResponse = strchr(szResponse, ' ');
if (strncmp(szResponse, "HTTP", 4) || !szHTTPResponse)
const char* hTTPResponse = strchr(response, ' ');
if (strncmp(response, "HTTP", 4) || !hTTPResponse)
{
warn("URL %s failed: %s", m_szInfoName, szResponse);
warn("URL %s failed: %s", *m_infoName, response);
return adFailed;
}
szHTTPResponse++;
hTTPResponse++;
if (!strncmp(szHTTPResponse, "400", 3) || !strncmp(szHTTPResponse, "499", 3))
if (!strncmp(hTTPResponse, "400", 3) || !strncmp(hTTPResponse, "499", 3))
{
warn("URL %s failed: %s", m_szInfoName, szHTTPResponse);
warn("URL %s failed: %s", *m_infoName, hTTPResponse);
return adConnectError;
}
else if (!strncmp(szHTTPResponse, "404", 3))
else if (!strncmp(hTTPResponse, "404", 3))
{
warn("URL %s failed: %s", m_szInfoName, szHTTPResponse);
warn("URL %s failed: %s", *m_infoName, hTTPResponse);
return adNotFound;
}
else if (!strncmp(szHTTPResponse, "301", 3) || !strncmp(szHTTPResponse, "302", 3))
else if (!strncmp(hTTPResponse, "301", 3) || !strncmp(hTTPResponse, "302", 3) ||
!strncmp(hTTPResponse, "303", 3) || !strncmp(hTTPResponse, "307", 3) ||
!strncmp(hTTPResponse, "308", 3))
{
m_bRedirecting = true;
m_redirecting = true;
return adRunning;
}
else if (!strncmp(szHTTPResponse, "200", 3))
else if (!strncmp(hTTPResponse, "200", 3))
{
// OK
return adRunning;
}
else
else
{
// unknown error, no special handling
warn("URL %s failed: %s", m_szInfoName, szResponse);
warn("URL %s failed: %s", *m_infoName, response);
return adFailed;
}
}
void WebDownloader::ProcessHeader(const char* szLine)
void WebDownloader::ProcessHeader(const char* line)
{
if (!strncasecmp(szLine, "Content-Length: ", 16))
if (!strncasecmp(line, "Content-Length: ", 16))
{
m_iContentLen = atoi(szLine + 16);
m_bConfirmedLength = true;
m_contentLen = atoi(line + 16);
m_confirmedLength = true;
}
else if (!strncasecmp(szLine, "Content-Encoding: gzip", 22))
else if (!strncasecmp(line, "Content-Encoding: gzip", 22))
{
m_bGZip = true;
m_gzip = true;
}
else if (!strncasecmp(szLine, "Content-Disposition: ", 21))
else if (!strncasecmp(line, "Content-Disposition: ", 21))
{
ParseFilename(szLine);
ParseFilename(line);
}
else if (m_bRedirecting && !strncasecmp(szLine, "Location: ", 10))
else if (m_redirecting && !strncasecmp(line, "Location: ", 10))
{
ParseRedirect(szLine + 10);
m_bRedirected = true;
ParseRedirect(line + 10);
m_redirected = true;
}
}
void WebDownloader::ParseFilename(const char* szContentDisposition)
void WebDownloader::ParseFilename(const char* contentDisposition)
{
// Examples:
// Content-Disposition: attachment; filename="fname.ext"
// Content-Disposition: attachement;filename=fname.ext
// Content-Disposition: attachement;filename=fname.ext;
const char *p = strstr(szContentDisposition, "filename");
const char *p = strstr(contentDisposition, "filename");
if (!p)
{
return;
@@ -570,9 +506,7 @@ void WebDownloader::ParseFilename(const char* szContentDisposition)
while (*p == ' ') p++;
char fname[1024];
strncpy(fname, p, 1024);
fname[1024-1] = '\0';
BString<1024> fname = p;
char *pe = fname + strlen(fname) - 1;
while ((*pe == ' ' || *pe == '\n' || *pe == '\r' || *pe == ';') && pe > fname) {
@@ -582,98 +516,93 @@ void WebDownloader::ParseFilename(const char* szContentDisposition)
WebUtil::HttpUnquote(fname);
free(m_szOriginalFilename);
m_szOriginalFilename = strdup(Util::BaseFileName(fname));
m_originalFilename = FileSystem::BaseFileName(fname);
debug("OriginalFilename: %s", m_szOriginalFilename);
debug("OriginalFilename: %s", *m_originalFilename);
}
void WebDownloader::ParseRedirect(const char* szLocation)
void WebDownloader::ParseRedirect(const char* location)
{
const char* szNewURL = szLocation;
char szUrlBuf[1024];
URL newUrl(szNewURL);
const char* newLocation = location;
BString<1024> urlBuf;
URL newUrl(newLocation);
if (!newUrl.IsValid())
{
// redirect within host
char szResource[1024];
URL oldUrl(m_szURL);
BString<1024> resource;
URL oldUrl(m_url);
if (*szLocation == '/')
if (*location == '/')
{
// absolute path within host
strncpy(szResource, szLocation, 1024);
szResource[1024-1] = '\0';
resource = location;
}
else
{
// relative path within host
strncpy(szResource, oldUrl.GetResource(), 1024);
szResource[1024-1] = '\0';
resource = oldUrl.GetResource();
char* p = strchr(szResource, '?');
char* p = strchr(resource, '?');
if (p)
{
*p = '\0';
}
p = strrchr(szResource, '/');
p = strrchr(resource, '/');
if (p)
{
p[1] = '\0';
}
strncat(szResource, szLocation, 1024 - strlen(szResource));
szResource[1024-1] = '\0';
resource.Append(location);
}
if (oldUrl.GetPort() > 0)
{
snprintf(szUrlBuf, 1024, "%s://%s:%i%s", oldUrl.GetProtocol(), oldUrl.GetHost(), oldUrl.GetPort(), szResource);
urlBuf.Format("%s://%s:%i%s", oldUrl.GetProtocol(), oldUrl.GetHost(), oldUrl.GetPort(), *resource);
}
else
{
snprintf(szUrlBuf, 1024, "%s://%s%s", oldUrl.GetProtocol(), oldUrl.GetHost(), szResource);
urlBuf.Format("%s://%s%s", oldUrl.GetProtocol(), oldUrl.GetHost(), *resource);
}
szUrlBuf[1024-1] = '\0';
szNewURL = szUrlBuf;
newLocation = urlBuf;
}
detail("URL %s redirected to %s", m_szURL, szNewURL);
SetURL(szNewURL);
detail("URL %s redirected to %s", *m_url, newLocation);
SetUrl(newLocation);
}
bool WebDownloader::Write(void* pBuffer, int iLen)
bool WebDownloader::Write(void* buffer, int len)
{
if (!m_pOutFile && !PrepareFile())
if (!m_outFile.Active() && !PrepareFile())
{
return false;
}
#ifndef DISABLE_GZIP
if (m_bGZip)
if (m_gzip)
{
m_pGUnzipStream->Write(pBuffer, iLen);
const void *pOutBuf;
int iOutLen = 1;
while (iOutLen > 0)
m_gUnzipStream->Write(buffer, len);
const void *outBuf;
int outLen = 1;
while (outLen > 0)
{
GUnzipStream::EStatus eGZStatus = m_pGUnzipStream->Read(&pOutBuf, &iOutLen);
GUnzipStream::EStatus gZStatus = m_gUnzipStream->Read(&outBuf, &outLen);
if (eGZStatus == GUnzipStream::zlError)
if (gZStatus == GUnzipStream::zlError)
{
error("URL %s: GUnzip failed", m_szInfoName);
error("URL %s: GUnzip failed", *m_infoName);
return false;
}
if (iOutLen > 0 && fwrite(pOutBuf, 1, iOutLen, m_pOutFile) <= 0)
if (outLen > 0 && m_outFile.Write(outBuf, outLen) <= 0)
{
return false;
}
if (eGZStatus == GUnzipStream::zlFinished)
if (gZStatus == GUnzipStream::zlFinished)
{
m_bConfirmedLength = true;
m_confirmedLength = true;
return true;
}
}
@@ -682,23 +611,22 @@ bool WebDownloader::Write(void* pBuffer, int iLen)
else
#endif
return fwrite(pBuffer, 1, iLen, m_pOutFile) > 0;
return m_outFile.Write(buffer, len) > 0;
}
bool WebDownloader::PrepareFile()
{
// prepare file for writing
const char* szFilename = m_szOutputFilename;
m_pOutFile = fopen(szFilename, FOPEN_WB);
if (!m_pOutFile)
const char* filename = m_outputFilename;
if (!m_outFile.Open(filename, DiskFile::omWrite))
{
error("Could not %s file %s", "create", szFilename);
error("Could not %s file %s", "create", filename);
return false;
}
if (g_pOptions->GetWriteBuffer() > 0)
if (g_Options->GetWriteBuffer() > 0)
{
setvbuf(m_pOutFile, NULL, _IOFBF, g_pOptions->GetWriteBuffer() * 1024);
m_outFile.SetWriteBuffer(g_Options->GetWriteBuffer() * 1024);
}
return true;
@@ -706,57 +634,33 @@ bool WebDownloader::PrepareFile()
void WebDownloader::LogDebugInfo()
{
char szTime[50];
#ifdef HAVE_CTIME_R_3
ctime_r(&m_tLastUpdateTime, szTime, 50);
#else
ctime_r(&m_tLastUpdateTime, szTime);
#endif
info(" Web-Download: status=%i, LastUpdateTime=%s, filename=%s", m_eStatus, szTime, Util::BaseFileName(m_szOutputFilename));
info(" Web-Download: status=%i, LastUpdateTime=%s, filename=%s", m_status,
*Util::FormatTime(m_lastUpdateTime), FileSystem::BaseFileName(m_outputFilename));
}
void WebDownloader::Stop()
{
debug("Trying to stop WebDownloader");
Thread::Stop();
m_mutexConnection.Lock();
if (m_pConnection)
Guard guard(m_connectionMutex);
if (m_connection)
{
m_pConnection->SetSuppressErrors(true);
m_pConnection->Cancel();
m_connection->SetSuppressErrors(true);
m_connection->Cancel();
}
m_mutexConnection.Unlock();
debug("WebDownloader stopped successfully");
}
bool WebDownloader::Terminate()
{
Connection* pConnection = m_pConnection;
bool terminated = Kill();
if (terminated && pConnection)
{
debug("Terminating connection");
pConnection->SetSuppressErrors(true);
pConnection->Cancel();
pConnection->Disconnect();
delete pConnection;
}
return terminated;
}
void WebDownloader::FreeConnection()
{
if (m_pConnection)
if (m_connection)
{
debug("Releasing connection");
m_mutexConnection.Lock();
if (m_pConnection->GetStatus() == Connection::csCancelled)
Guard guard(m_connectionMutex);
if (m_connection->GetStatus() == Connection::csCancelled)
{
m_pConnection->Disconnect();
m_connection->Disconnect();
}
delete m_pConnection;
m_pConnection = NULL;
m_mutexConnection.Unlock();
m_connection.reset();
}
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2012-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2012-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,23 +14,18 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef WEBDOWNLOADER_H
#define WEBDOWNLOADER_H
#include <time.h>
#include "NString.h"
#include "Observer.h"
#include "Thread.h"
#include "Connection.h"
#include "FileSystem.h"
#include "Util.h"
class WebDownloader : public Thread, public Subject
@@ -48,65 +43,62 @@ public:
adConnectError,
adFatalError
};
private:
char* m_szURL;
char* m_szOutputFilename;
Connection* m_pConnection;
Mutex m_mutexConnection;
EStatus m_eStatus;
time_t m_tLastUpdateTime;
char* m_szInfoName;
FILE* m_pOutFile;
int m_iContentLen;
bool m_bConfirmedLength;
char* m_szOriginalFilename;
bool m_bForce;
bool m_bRedirecting;
bool m_bRedirected;
bool m_bGZip;
bool m_bRetry;
#ifndef DISABLE_GZIP
GUnzipStream* m_pGUnzipStream;
#endif
void SetStatus(EStatus eStatus);
bool Write(void* pBuffer, int iLen);
bool PrepareFile();
void FreeConnection();
EStatus CheckResponse(const char* szResponse);
EStatus CreateConnection(URL *pUrl);
void ParseFilename(const char* szContentDisposition);
void SendHeaders(URL *pUrl);
EStatus DownloadHeaders();
EStatus DownloadBody();
void ParseRedirect(const char* szLocation);
WebDownloader();
EStatus GetStatus() { return m_status; }
virtual void Run();
virtual void Stop();
EStatus Download();
EStatus DownloadWithRedirects(int maxRedirects);
void SetInfoName(const char* infoName) { m_infoName = infoName; }
const char* GetInfoName() { return m_infoName; }
void SetUrl(const char* url);
const char* GetOutputFilename() { return m_outputFilename; }
void SetOutputFilename(const char* outputFilename) { m_outputFilename = outputFilename; }
time_t GetLastUpdateTime() { return m_lastUpdateTime; }
void SetLastUpdateTimeNow();
bool GetConfirmedLength() { return m_confirmedLength; }
const char* GetOriginalFilename() { return m_originalFilename; }
void SetForce(bool force) { m_force = force; }
void SetRetry(bool retry) { m_retry = retry; }
void LogDebugInfo();
protected:
virtual void ProcessHeader(const char* szLine);
virtual void ProcessHeader(const char* line);
public:
WebDownloader();
virtual ~WebDownloader();
EStatus GetStatus() { return m_eStatus; }
virtual void Run();
virtual void Stop();
EStatus Download();
EStatus DownloadWithRedirects(int iMaxRedirects);
bool Terminate();
void SetInfoName(const char* v);
const char* GetInfoName() { return m_szInfoName; }
void SetURL(const char* szURL);
const char* GetOutputFilename() { return m_szOutputFilename; }
void SetOutputFilename(const char* v);
time_t GetLastUpdateTime() { return m_tLastUpdateTime; }
void SetLastUpdateTimeNow() { m_tLastUpdateTime = ::time(NULL); }
bool GetConfirmedLength() { return m_bConfirmedLength; }
const char* GetOriginalFilename() { return m_szOriginalFilename; }
void SetForce(bool bForce) { m_bForce = bForce; }
void SetRetry(bool bRetry) { m_bRetry = bRetry; }
private:
CString m_url;
CString m_outputFilename;
std::unique_ptr<Connection> m_connection;
Mutex m_connectionMutex;
EStatus m_status = adUndefined;
time_t m_lastUpdateTime;
CString m_infoName;
DiskFile m_outFile;
int m_contentLen;
bool m_confirmedLength = false;
CString m_originalFilename;
bool m_force = false;
bool m_redirecting;
bool m_redirected;
bool m_gzip;
bool m_retry = true;
#ifndef DISABLE_GZIP
std::unique_ptr<GUnzipStream> m_gUnzipStream;
#endif
void LogDebugInfo();
void SetStatus(EStatus status);
bool Write(void* buffer, int len);
bool PrepareFile();
void FreeConnection();
EStatus CheckResponse(const char* response);
EStatus CreateConnection(URL *url);
void ParseFilename(const char* contentDisposition);
void SendHeaders(URL *url);
EStatus DownloadHeaders();
EStatus DownloadBody();
void ParseRedirect(const char* location);
};
#endif

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2015-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,82 +14,67 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include <sys/stat.h>
#include <stdio.h>
#include "nzbget.h"
#include "FeedScript.h"
#include "Options.h"
#include "Log.h"
#include "Util.h"
static const int FEED_SUCCESS = 93;
void FeedScriptController::ExecuteScripts(const char* szFeedScript, const char* szFeedFile, int iFeedID)
void FeedScriptController::ExecuteScripts(const char* feedScript, const char* feedFile, int feedId, bool* success)
{
FeedScriptController* pScriptController = new FeedScriptController();
FeedScriptController scriptController;
scriptController.m_feedFile = feedFile;
scriptController.m_feedId = feedId;
pScriptController->m_szFeedFile = szFeedFile;
pScriptController->m_iFeedID = iFeedID;
scriptController.ExecuteScriptList(feedScript);
pScriptController->ExecuteScriptList(szFeedScript);
delete pScriptController;
if (success)
{
*success = scriptController.m_success;
}
}
void FeedScriptController::ExecuteScript(ScriptConfig::Script* pScript)
void FeedScriptController::ExecuteScript(ScriptConfig::Script* script)
{
if (!pScript->GetFeedScript())
if (!script->GetFeedScript())
{
return;
}
PrintMessage(Message::mkInfo, "Executing feed-script %s for Feed%i", pScript->GetName(), m_iFeedID);
PrintMessage(Message::mkInfo, "Executing feed-script %s for Feed%i", script->GetName(), m_feedId);
SetScript(pScript->GetLocation());
SetArgs(NULL, false);
SetArgs({script->GetLocation()});
char szInfoName[1024];
snprintf(szInfoName, 1024, "feed-script %s for Feed%i", pScript->GetName(), m_iFeedID);
szInfoName[1024-1] = '\0';
SetInfoName(szInfoName);
BString<1024> infoName("feed-script %s for Feed%i", script->GetName(), m_feedId);
SetInfoName(infoName);
SetLogPrefix(pScript->GetDisplayName());
PrepareParams(pScript->GetName());
SetLogPrefix(script->GetDisplayName());
PrepareParams(script->GetName());
Execute();
int exitCode = Execute();
SetLogPrefix(NULL);
if (exitCode != FEED_SUCCESS)
{
infoName[0] = 'F'; // uppercase
PrintMessage(Message::mkError, "%s failed", *infoName);
m_success = false;
}
SetLogPrefix(nullptr);
}
void FeedScriptController::PrepareParams(const char* szScriptName)
void FeedScriptController::PrepareParams(const char* scriptName)
{
ResetEnv();
SetEnvVar("NZBFP_FILENAME", m_szFeedFile);
SetIntEnvVar("NZBFP_FEEDID", m_iFeedID);
SetEnvVar("NZBFP_FILENAME", m_feedFile);
SetIntEnvVar("NZBFP_FEEDID", m_feedId);
PrepareEnvScript(NULL, szScriptName);
PrepareEnvScript(nullptr, scriptName);
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2015-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,12 +14,7 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
@@ -28,19 +23,20 @@
#include "NzbScript.h"
class FeedScriptController : public NZBScriptController
class FeedScriptController : public NzbScriptController
{
private:
const char* m_szFeedFile;
int m_iFeedID;
void PrepareParams(const char* szScriptName);
public:
static void ExecuteScripts(const char* feedScript, const char* feedFile, int feedId, bool* success);
protected:
virtual void ExecuteScript(ScriptConfig::Script* pScript);
virtual void ExecuteScript(ScriptConfig::Script* script);
public:
static void ExecuteScripts(const char* szFeedScript, const char* szFeedFile, int iFeedID);
private:
const char* m_feedFile;
int m_feedId;
bool m_success = true;
void PrepareParams(const char* scriptName);
};
#endif

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,109 +14,70 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include <sys/stat.h>
#include <stdio.h>
#include <algorithm>
#include "nzbget.h"
#include "NzbScript.h"
#include "Options.h"
#include "Log.h"
#include "Util.h"
#include "FileSystem.h"
/**
* If szStripPrefix is not NULL, only pp-parameters, whose names start with the prefix
* If szStripPrefix is not nullptr, only pp-parameters, whose names start with the prefix
* are processed. The prefix is then stripped from the names.
* If szStripPrefix is NULL, all pp-parameters are processed; without stripping.
* If szStripPrefix is nullptr, all pp-parameters are processed; without stripping.
*/
void NZBScriptController::PrepareEnvParameters(NZBParameterList* pParameters, const char* szStripPrefix)
void NzbScriptController::PrepareEnvParameters(NzbParameterList* parameters, const char* stripPrefix)
{
int iPrefixLen = szStripPrefix ? strlen(szStripPrefix) : 0;
int prefixLen = stripPrefix ? strlen(stripPrefix) : 0;
for (NZBParameterList::iterator it = pParameters->begin(); it != pParameters->end(); it++)
for (NzbParameter& parameter : parameters)
{
NZBParameter* pParameter = *it;
const char* szValue = pParameter->GetValue();
#ifdef WIN32
char* szAnsiValue = strdup(szValue);
WebUtil::Utf8ToAnsi(szAnsiValue, strlen(szAnsiValue) + 1);
szValue = szAnsiValue;
#endif
const char* value = parameter.GetValue();
if (szStripPrefix && !strncmp(pParameter->GetName(), szStripPrefix, iPrefixLen) && (int)strlen(pParameter->GetName()) > iPrefixLen)
if (stripPrefix && !strncmp(parameter.GetName(), stripPrefix, prefixLen) && (int)strlen(parameter.GetName()) > prefixLen)
{
SetEnvVarSpecial("NZBPR", pParameter->GetName() + iPrefixLen, szValue);
SetEnvVarSpecial("NZBPR", parameter.GetName() + prefixLen, value);
}
else if (!szStripPrefix)
else if (!stripPrefix)
{
SetEnvVarSpecial("NZBPR", pParameter->GetName(), szValue);
SetEnvVarSpecial("NZBPR", parameter.GetName(), value);
}
#ifdef WIN32
free(szAnsiValue);
#endif
}
}
void NZBScriptController::PrepareEnvScript(NZBParameterList* pParameters, const char* szScriptName)
void NzbScriptController::PrepareEnvScript(NzbParameterList* parameters, const char* scriptName)
{
if (pParameters)
if (parameters)
{
PrepareEnvParameters(pParameters, NULL);
PrepareEnvParameters(parameters, nullptr);
}
char szParamPrefix[1024];
snprintf(szParamPrefix, 1024, "%s:", szScriptName);
szParamPrefix[1024-1] = '\0';
BString<1024> paramPrefix("%s:", scriptName);
if (pParameters)
if (parameters)
{
PrepareEnvParameters(pParameters, szParamPrefix);
PrepareEnvParameters(parameters, paramPrefix);
}
PrepareEnvOptions(szParamPrefix);
PrepareEnvOptions(paramPrefix);
}
void NZBScriptController::ExecuteScriptList(const char* szScriptList)
void NzbScriptController::ExecuteScriptList(const char* scriptList)
{
for (ScriptConfig::Scripts::iterator it = g_pScriptConfig->GetScripts()->begin(); it != g_pScriptConfig->GetScripts()->end(); it++)
for (ScriptConfig::Script& script : g_ScriptConfig->GetScripts())
{
ScriptConfig::Script* pScript = *it;
if (szScriptList && *szScriptList)
if (scriptList && *scriptList)
{
// split szScriptList into tokens
Tokenizer tok(szScriptList, ",;");
while (const char* szScriptName = tok.Next())
Tokenizer tok(scriptList, ",;");
while (const char* scriptName = tok.Next())
{
if (Util::SameFilename(szScriptName, pScript->GetName()))
if (FileSystem::SameFilename(scriptName, script.GetName()))
{
ExecuteScript(pScript);
ExecuteScript(&script);
break;
}
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,12 +14,7 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
@@ -30,13 +25,13 @@
#include "DownloadInfo.h"
#include "ScriptConfig.h"
class NZBScriptController : public ScriptController
class NzbScriptController : public ScriptController
{
protected:
void PrepareEnvParameters(NZBParameterList* pParameters, const char* szStripPrefix);
void PrepareEnvScript(NZBParameterList* pParameters, const char* szScriptName);
void ExecuteScriptList(const char* szScriptList);
virtual void ExecuteScript(ScriptConfig::Script* pScript) = 0;
void PrepareEnvParameters(NzbParameterList* parameters, const char* stripPrefix);
void PrepareEnvScript(NzbParameterList* parameters, const char* scriptName);
void ExecuteScriptList(const char* scriptList);
virtual void ExecuteScript(ScriptConfig::Script* script) = 0;
};
#endif

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,330 +14,288 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include <stdio.h>
#include "nzbget.h"
#include "PostScript.h"
#include "Log.h"
#include "Util.h"
#include "Options.h"
#include "WorkState.h"
static const int POSTPROCESS_PARCHECK = 92;
static const int POSTPROCESS_SUCCESS = 93;
static const int POSTPROCESS_ERROR = 94;
static const int POSTPROCESS_NONE = 95;
void PostScriptController::StartJob(PostInfo* pPostInfo)
void PostScriptController::StartJob(PostInfo* postInfo)
{
PostScriptController* pScriptController = new PostScriptController();
pScriptController->m_pPostInfo = pPostInfo;
pScriptController->SetWorkingDir(g_pOptions->GetDestDir());
pScriptController->SetAutoDestroy(false);
pScriptController->m_iPrefixLen = 0;
PostScriptController* scriptController = new PostScriptController();
scriptController->m_postInfo = postInfo;
scriptController->SetAutoDestroy(false);
scriptController->m_prefixLen = 0;
pPostInfo->SetPostThread(pScriptController);
postInfo->SetPostThread(scriptController);
pScriptController->Start();
scriptController->Start();
}
void PostScriptController::Run()
{
StringBuilder scriptCommaList;
// the locking is needed for accessing the members of NZBInfo
DownloadQueue::Lock();
for (NZBParameterList::iterator it = m_pPostInfo->GetNZBInfo()->GetParameters()->begin(); it != m_pPostInfo->GetNZBInfo()->GetParameters()->end(); it++)
{
NZBParameter* pParameter = *it;
const char* szVarname = pParameter->GetName();
if (strlen(szVarname) > 0 && szVarname[0] != '*' && szVarname[strlen(szVarname)-1] == ':' &&
(!strcasecmp(pParameter->GetValue(), "yes") || !strcasecmp(pParameter->GetValue(), "on") || !strcasecmp(pParameter->GetValue(), "1")))
GuardedDownloadQueue guard = DownloadQueue::Guard();
for (NzbParameter& parameter : m_postInfo->GetNzbInfo()->GetParameters())
{
char* szScriptName = strdup(szVarname);
szScriptName[strlen(szScriptName)-1] = '\0'; // remove trailing ':'
scriptCommaList.Append(szScriptName);
scriptCommaList.Append(",");
free(szScriptName);
const char* varname = parameter.GetName();
if (strlen(varname) > 0 && varname[0] != '*' && varname[strlen(varname) - 1] == ':' &&
(!strcasecmp(parameter.GetValue(), "yes") || !strcasecmp(parameter.GetValue(), "on") || !strcasecmp(parameter.GetValue(), "1")))
{
CString scriptName(varname);
scriptName[strlen(scriptName) - 1] = '\0'; // remove trailing ':'
scriptCommaList.Append(scriptName);
scriptCommaList.Append(",");
}
}
m_postInfo->GetNzbInfo()->GetScriptStatuses()->clear();
}
m_pPostInfo->GetNZBInfo()->GetScriptStatuses()->Clear();
DownloadQueue::Unlock();
ExecuteScriptList(scriptCommaList.GetBuffer());
ExecuteScriptList(scriptCommaList);
m_pPostInfo->SetStage(PostInfo::ptFinished);
m_pPostInfo->SetWorking(false);
m_postInfo->SetStage(PostInfo::ptFinished);
m_postInfo->SetWorking(false);
}
void PostScriptController::ExecuteScript(ScriptConfig::Script* pScript)
void PostScriptController::ExecuteScript(ScriptConfig::Script* script)
{
// if any script has requested par-check, do not execute other scripts
if (!pScript->GetPostScript() || m_pPostInfo->GetRequestParCheck())
if (!script->GetPostScript() || m_postInfo->GetRequestParCheck())
{
return;
}
PrintMessage(Message::mkInfo, "Executing post-process-script %s for %s", pScript->GetName(), m_pPostInfo->GetNZBInfo()->GetName());
PrintMessage(Message::mkInfo, "Executing post-process-script %s for %s", script->GetName(), m_postInfo->GetNzbInfo()->GetName());
char szProgressLabel[1024];
snprintf(szProgressLabel, 1024, "Executing post-process-script %s", pScript->GetName());
szProgressLabel[1024-1] = '\0';
BString<1024> progressLabel("Executing post-process-script %s", script->GetName());
DownloadQueue::Lock();
m_pPostInfo->SetProgressLabel(szProgressLabel);
DownloadQueue::Unlock();
{
GuardedDownloadQueue guard = DownloadQueue::Guard();
m_postInfo->SetProgressLabel(progressLabel);
}
SetScript(pScript->GetLocation());
SetArgs(NULL, false);
SetArgs({script->GetLocation()});
char szInfoName[1024];
snprintf(szInfoName, 1024, "post-process-script %s for %s", pScript->GetName(), m_pPostInfo->GetNZBInfo()->GetName());
szInfoName[1024-1] = '\0';
SetInfoName(szInfoName);
BString<1024> infoName("post-process-script %s for %s", script->GetName(), m_postInfo->GetNzbInfo()->GetName());
SetInfoName(infoName);
m_pScript = pScript;
SetLogPrefix(pScript->GetDisplayName());
m_iPrefixLen = strlen(pScript->GetDisplayName()) + 2; // 2 = strlen(": ");
PrepareParams(pScript->GetName());
m_script = script;
SetLogPrefix(script->GetDisplayName());
m_prefixLen = strlen(script->GetDisplayName()) + 2; // 2 = strlen(": ");
PrepareParams(script->GetName());
int iExitCode = Execute();
int exitCode = Execute();
szInfoName[0] = 'P'; // uppercase
infoName[0] = 'P'; // uppercase
SetLogPrefix(NULL);
ScriptStatus::EStatus eStatus = AnalyseExitCode(iExitCode);
// the locking is needed for accessing the members of NZBInfo
DownloadQueue::Lock();
m_pPostInfo->GetNZBInfo()->GetScriptStatuses()->Add(pScript->GetName(), eStatus);
DownloadQueue::Unlock();
SetLogPrefix(nullptr);
ScriptStatus::EStatus status = AnalyseExitCode(exitCode, infoName);
{
GuardedDownloadQueue guard = DownloadQueue::Guard();
m_postInfo->GetNzbInfo()->GetScriptStatuses()->emplace_back(script->GetName(), status);
}
}
void PostScriptController::PrepareParams(const char* szScriptName)
void PostScriptController::PrepareParams(const char* scriptName)
{
// the locking is needed for accessing the members of NZBInfo
DownloadQueue::Lock();
GuardedDownloadQueue guard = DownloadQueue::Guard();
ResetEnv();
SetIntEnvVar("NZBPP_NZBID", m_pPostInfo->GetNZBInfo()->GetID());
SetEnvVar("NZBPP_NZBNAME", m_pPostInfo->GetNZBInfo()->GetName());
SetEnvVar("NZBPP_DIRECTORY", m_pPostInfo->GetNZBInfo()->GetDestDir());
SetEnvVar("NZBPP_NZBFILENAME", m_pPostInfo->GetNZBInfo()->GetFilename());
SetEnvVar("NZBPP_URL", m_pPostInfo->GetNZBInfo()->GetURL());
SetEnvVar("NZBPP_FINALDIR", m_pPostInfo->GetNZBInfo()->GetFinalDir());
SetEnvVar("NZBPP_CATEGORY", m_pPostInfo->GetNZBInfo()->GetCategory());
SetIntEnvVar("NZBPP_HEALTH", m_pPostInfo->GetNZBInfo()->CalcHealth());
SetIntEnvVar("NZBPP_CRITICALHEALTH", m_pPostInfo->GetNZBInfo()->CalcCriticalHealth(false));
SetIntEnvVar("NZBPP_NZBID", m_postInfo->GetNzbInfo()->GetId());
SetEnvVar("NZBPP_NZBNAME", m_postInfo->GetNzbInfo()->GetName());
SetEnvVar("NZBPP_DIRECTORY", m_postInfo->GetNzbInfo()->GetDestDir());
SetEnvVar("NZBPP_NZBFILENAME", m_postInfo->GetNzbInfo()->GetFilename());
SetEnvVar("NZBPP_QUEUEDFILE", m_postInfo->GetNzbInfo()->GetQueuedFilename());
SetEnvVar("NZBPP_URL", m_postInfo->GetNzbInfo()->GetUrl());
SetEnvVar("NZBPP_FINALDIR", m_postInfo->GetNzbInfo()->GetFinalDir());
SetEnvVar("NZBPP_CATEGORY", m_postInfo->GetNzbInfo()->GetCategory());
SetIntEnvVar("NZBPP_HEALTH", m_postInfo->GetNzbInfo()->CalcHealth());
SetIntEnvVar("NZBPP_CRITICALHEALTH", m_postInfo->GetNzbInfo()->CalcCriticalHealth(false));
SetEnvVar("NZBPP_DUPEKEY", m_pPostInfo->GetNZBInfo()->GetDupeKey());
SetIntEnvVar("NZBPP_DUPESCORE", m_pPostInfo->GetNZBInfo()->GetDupeScore());
SetEnvVar("NZBPP_DUPEKEY", m_postInfo->GetNzbInfo()->GetDupeKey());
SetIntEnvVar("NZBPP_DUPESCORE", m_postInfo->GetNzbInfo()->GetDupeScore());
const char* szDupeModeName[] = { "SCORE", "ALL", "FORCE" };
SetEnvVar("NZBPP_DUPEMODE", szDupeModeName[m_pPostInfo->GetNZBInfo()->GetDupeMode()]);
const char* dupeModeName[] = { "SCORE", "ALL", "FORCE" };
SetEnvVar("NZBPP_DUPEMODE", dupeModeName[m_postInfo->GetNzbInfo()->GetDupeMode()]);
char szStatus[256];
strncpy(szStatus, m_pPostInfo->GetNZBInfo()->MakeTextStatus(true), sizeof(szStatus));
szStatus[256-1] = '\0';
SetEnvVar("NZBPP_STATUS", szStatus);
BString<1024> status = m_postInfo->GetNzbInfo()->MakeTextStatus(true);
SetEnvVar("NZBPP_STATUS", status);
char* szDetail = strchr(szStatus, '/');
if (szDetail) *szDetail = '\0';
SetEnvVar("NZBPP_TOTALSTATUS", szStatus);
char* detail = strchr(status, '/');
if (detail) *detail = '\0';
SetEnvVar("NZBPP_TOTALSTATUS", status);
const char* szScriptStatusName[] = { "NONE", "FAILURE", "SUCCESS" };
SetEnvVar("NZBPP_SCRIPTSTATUS", szScriptStatusName[m_pPostInfo->GetNZBInfo()->GetScriptStatuses()->CalcTotalStatus()]);
const char* scriptStatusName[] = { "NONE", "FAILURE", "SUCCESS" };
SetEnvVar("NZBPP_SCRIPTSTATUS", scriptStatusName[m_postInfo->GetNzbInfo()->GetScriptStatuses()->CalcTotalStatus()]);
// deprecated
int iParStatus[] = { 0, 0, 1, 2, 3, 4 };
NZBInfo::EParStatus eParStatus = m_pPostInfo->GetNZBInfo()->GetParStatus();
int parStatusCodes[] = { 0, 0, 1, 2, 3, 4 };
NzbInfo::EParStatus parStatus = m_postInfo->GetNzbInfo()->GetParStatus();
// for downloads marked as bad and for deleted downloads pass par status "Failure"
// for compatibility with older scripts which don't check "NZBPP_TOTALSTATUS"
if (m_pPostInfo->GetNZBInfo()->GetDeleteStatus() != NZBInfo::dsNone ||
m_pPostInfo->GetNZBInfo()->GetMarkStatus() == NZBInfo::ksBad)
if (m_postInfo->GetNzbInfo()->GetDeleteStatus() != NzbInfo::dsNone ||
m_postInfo->GetNzbInfo()->GetMarkStatus() == NzbInfo::ksBad)
{
eParStatus = NZBInfo::psFailure;
parStatus = NzbInfo::psFailure;
}
SetIntEnvVar("NZBPP_PARSTATUS", iParStatus[eParStatus]);
SetIntEnvVar("NZBPP_PARSTATUS", parStatusCodes[parStatus]);
// deprecated
int iUnpackStatus[] = { 0, 0, 1, 2, 3, 4 };
SetIntEnvVar("NZBPP_UNPACKSTATUS", iUnpackStatus[m_pPostInfo->GetNZBInfo()->GetUnpackStatus()]);
int unpackStatus[] = { 0, 0, 1, 2, 3, 4 };
SetIntEnvVar("NZBPP_UNPACKSTATUS", unpackStatus[m_postInfo->GetNzbInfo()->GetUnpackStatus()]);
// deprecated
SetIntEnvVar("NZBPP_HEALTHDELETED", (int)m_pPostInfo->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsHealth);
SetIntEnvVar("NZBPP_HEALTHDELETED", (int)m_postInfo->GetNzbInfo()->GetDeleteStatus() == NzbInfo::dsHealth);
SetIntEnvVar("NZBPP_TOTALARTICLES", (int)m_pPostInfo->GetNZBInfo()->GetTotalArticles());
SetIntEnvVar("NZBPP_SUCCESSARTICLES", (int)m_pPostInfo->GetNZBInfo()->GetSuccessArticles());
SetIntEnvVar("NZBPP_FAILEDARTICLES", (int)m_pPostInfo->GetNZBInfo()->GetFailedArticles());
SetIntEnvVar("NZBPP_TOTALARTICLES", (int)m_postInfo->GetNzbInfo()->GetTotalArticles());
SetIntEnvVar("NZBPP_SUCCESSARTICLES", (int)m_postInfo->GetNzbInfo()->GetSuccessArticles());
SetIntEnvVar("NZBPP_FAILEDARTICLES", (int)m_postInfo->GetNzbInfo()->GetFailedArticles());
for (ServerStatList::iterator it = m_pPostInfo->GetNZBInfo()->GetServerStats()->begin(); it != m_pPostInfo->GetNZBInfo()->GetServerStats()->end(); it++)
for (ServerStat& serverStat : m_postInfo->GetNzbInfo()->GetServerStats())
{
ServerStat* pServerStat = *it;
SetIntEnvVar(BString<1024>("NZBPP_SERVER%i_SUCCESSARTICLES", serverStat.GetServerId()),
serverStat.GetSuccessArticles());
char szName[50];
snprintf(szName, 50, "NZBPP_SERVER%i_SUCCESSARTICLES", pServerStat->GetServerID());
szName[50-1] = '\0';
SetIntEnvVar(szName, pServerStat->GetSuccessArticles());
snprintf(szName, 50, "NZBPP_SERVER%i_FAILEDARTICLES", pServerStat->GetServerID());
szName[50-1] = '\0';
SetIntEnvVar(szName, pServerStat->GetFailedArticles());
SetIntEnvVar(BString<1024>("NZBPP_SERVER%i_FAILEDARTICLES", serverStat.GetServerId()),
serverStat.GetFailedArticles());
}
PrepareEnvScript(m_pPostInfo->GetNZBInfo()->GetParameters(), szScriptName);
DownloadQueue::Unlock();
PrepareEnvScript(m_postInfo->GetNzbInfo()->GetParameters(), scriptName);
}
ScriptStatus::EStatus PostScriptController::AnalyseExitCode(int iExitCode)
ScriptStatus::EStatus PostScriptController::AnalyseExitCode(int exitCode, const char* upInfoName)
{
// The ScriptStatus is accumulated for all scripts:
// If any script has failed the status is "failure", etc.
switch (iExitCode)
switch (exitCode)
{
case POSTPROCESS_SUCCESS:
PrintMessage(Message::mkInfo, "%s successful", GetInfoName());
PrintMessage(Message::mkInfo, "%s successful", upInfoName);
return ScriptStatus::srSuccess;
case POSTPROCESS_ERROR:
case -1: // Execute() returns -1 if the process could not be started (file not found or other problem)
PrintMessage(Message::mkError, "%s failed", GetInfoName());
PrintMessage(Message::mkError, "%s failed", upInfoName);
return ScriptStatus::srFailure;
case POSTPROCESS_NONE:
PrintMessage(Message::mkInfo, "%s skipped", GetInfoName());
PrintMessage(Message::mkInfo, "%s skipped", upInfoName);
return ScriptStatus::srNone;
#ifndef DISABLE_PARCHECK
case POSTPROCESS_PARCHECK:
if (m_pPostInfo->GetNZBInfo()->GetParStatus() > NZBInfo::psSkipped)
if (m_postInfo->GetNzbInfo()->GetParStatus() > NzbInfo::psSkipped)
{
PrintMessage(Message::mkError, "%s requested par-check/repair, but the collection was already checked", GetInfoName());
PrintMessage(Message::mkError, "%s requested par-check/repair, but the collection was already checked", upInfoName);
return ScriptStatus::srFailure;
}
else
{
PrintMessage(Message::mkInfo, "%s requested par-check/repair", GetInfoName());
m_pPostInfo->SetRequestParCheck(true);
m_pPostInfo->SetForceRepair(true);
PrintMessage(Message::mkInfo, "%s requested par-check/repair", upInfoName);
m_postInfo->SetRequestParCheck(true);
m_postInfo->SetForceRepair(true);
return ScriptStatus::srSuccess;
}
break;
#endif
default:
PrintMessage(Message::mkError, "%s failed (terminated with unknown status)", GetInfoName());
PrintMessage(Message::mkError, "%s failed (terminated with unknown status)", upInfoName);
return ScriptStatus::srFailure;
}
}
void PostScriptController::AddMessage(Message::EKind eKind, const char* szText)
void PostScriptController::AddMessage(Message::EKind kind, const char* text)
{
const char* szMsgText = szText + m_iPrefixLen;
const char* msgText = text + m_prefixLen;
if (!strncmp(szMsgText, "[NZB] ", 6))
if (!strncmp(msgText, "[NZB] ", 6))
{
debug("Command %s detected", szMsgText + 6);
if (!strncmp(szMsgText + 6, "FINALDIR=", 9))
debug("Command %s detected", msgText + 6);
if (!strncmp(msgText + 6, "FINALDIR=", 9))
{
DownloadQueue::Lock();
m_pPostInfo->GetNZBInfo()->SetFinalDir(szMsgText + 6 + 9);
DownloadQueue::Unlock();
GuardedDownloadQueue guard = DownloadQueue::Guard();
m_postInfo->GetNzbInfo()->SetFinalDir(msgText + 6 + 9);
}
else if (!strncmp(szMsgText + 6, "DIRECTORY=", 10))
else if (!strncmp(msgText + 6, "DIRECTORY=", 10))
{
DownloadQueue::Lock();
m_pPostInfo->GetNZBInfo()->SetDestDir(szMsgText + 6 + 10);
DownloadQueue::Unlock();
GuardedDownloadQueue guard = DownloadQueue::Guard();
m_postInfo->GetNzbInfo()->SetDestDir(msgText + 6 + 10);
}
else if (!strncmp(szMsgText + 6, "NZBPR_", 6))
else if (!strncmp(msgText + 6, "NZBPR_", 6))
{
char* szParam = strdup(szMsgText + 6 + 6);
char* szValue = strchr(szParam, '=');
if (szValue)
CString param = msgText + 6 + 6;
char* value = strchr(param, '=');
if (value)
{
*szValue = '\0';
DownloadQueue::Lock();
m_pPostInfo->GetNZBInfo()->GetParameters()->SetParameter(szParam, szValue + 1);
DownloadQueue::Unlock();
*value = '\0';
GuardedDownloadQueue guard = DownloadQueue::Guard();
m_postInfo->GetNzbInfo()->GetParameters()->SetParameter(param, value + 1);
}
else
{
m_pPostInfo->GetNZBInfo()->PrintMessage(Message::mkError,
"Invalid command \"%s\" received from %s", szMsgText, GetInfoName());
m_postInfo->GetNzbInfo()->PrintMessage(Message::mkError,
"Invalid command \"%s\" received from %s", msgText, GetInfoName());
}
free(szParam);
}
else if (!strncmp(szMsgText + 6, "MARK=BAD", 8))
else if (!strncmp(msgText + 6, "MARK=BAD", 8))
{
SetLogPrefix(NULL);
PrintMessage(Message::mkWarning, "Marking %s as bad", m_pPostInfo->GetNZBInfo()->GetName());
SetLogPrefix(m_pScript->GetDisplayName());
m_pPostInfo->GetNZBInfo()->SetMarkStatus(NZBInfo::ksBad);
SetLogPrefix(nullptr);
PrintMessage(Message::mkWarning, "Marking %s as bad", m_postInfo->GetNzbInfo()->GetName());
SetLogPrefix(m_script->GetDisplayName());
m_postInfo->GetNzbInfo()->SetMarkStatus(NzbInfo::ksBad);
}
else
{
m_pPostInfo->GetNZBInfo()->PrintMessage(Message::mkError,
"Invalid command \"%s\" received from %s", szMsgText, GetInfoName());
m_postInfo->GetNzbInfo()->PrintMessage(Message::mkError,
"Invalid command \"%s\" received from %s", msgText, GetInfoName());
}
}
else
{
m_pPostInfo->GetNZBInfo()->AddMessage(eKind, szText);
DownloadQueue::Lock();
m_pPostInfo->SetProgressLabel(szText);
DownloadQueue::Unlock();
m_postInfo->GetNzbInfo()->AddMessage(kind, text);
GuardedDownloadQueue guard = DownloadQueue::Guard();
m_postInfo->SetProgressLabel(text);
}
if (g_pOptions->GetPausePostProcess() && !m_pPostInfo->GetNZBInfo()->GetForcePriority())
if (g_WorkState->GetPausePostProcess() && !m_postInfo->GetNzbInfo()->GetForcePriority())
{
time_t tStageTime = m_pPostInfo->GetStageTime();
time_t tStartTime = m_pPostInfo->GetStartTime();
time_t tWaitTime = time(NULL);
time_t stageTime = m_postInfo->GetStageTime();
time_t startTime = m_postInfo->GetStartTime();
time_t waitTime = Util::CurrentTime();
// wait until Post-processor is unpaused
while (g_pOptions->GetPausePostProcess() && !m_pPostInfo->GetNZBInfo()->GetForcePriority() && !IsStopped())
while (g_WorkState->GetPausePostProcess() && !m_postInfo->GetNzbInfo()->GetForcePriority() && !IsStopped())
{
usleep(100 * 1000);
Util::Sleep(100);
// update time stamps
time_t tDelta = time(NULL) - tWaitTime;
time_t delta = Util::CurrentTime() - waitTime;
if (tStageTime > 0)
if (stageTime > 0)
{
m_pPostInfo->SetStageTime(tStageTime + tDelta);
m_postInfo->SetStageTime(stageTime + delta);
}
if (tStartTime > 0)
if (startTime > 0)
{
m_pPostInfo->SetStartTime(tStartTime + tDelta);
m_postInfo->SetStartTime(startTime + delta);
}
}
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,12 +14,7 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
@@ -29,24 +24,24 @@
#include "Thread.h"
#include "NzbScript.h"
class PostScriptController : public Thread, public NZBScriptController
class PostScriptController : public Thread, public NzbScriptController
{
private:
PostInfo* m_pPostInfo;
int m_iPrefixLen;
ScriptConfig::Script* m_pScript;
void PrepareParams(const char* szScriptName);
ScriptStatus::EStatus AnalyseExitCode(int iExitCode);
public:
virtual void Run();
virtual void Stop();
static void StartJob(PostInfo* postInfo);
protected:
virtual void ExecuteScript(ScriptConfig::Script* pScript);
virtual void AddMessage(Message::EKind eKind, const char* szText);
virtual void ExecuteScript(ScriptConfig::Script* script);
virtual void AddMessage(Message::EKind kind, const char* text);
public:
virtual void Run();
virtual void Stop();
static void StartJob(PostInfo* pPostInfo);
private:
PostInfo* m_postInfo;
int m_prefixLen;
ScriptConfig::Script* m_script;
void PrepareParams(const char* scriptName);
ScriptStatus::EStatus AnalyseExitCode(int exitCode, const char* upInfoName);
};
#endif

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,512 +14,482 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include <sys/stat.h>
#include <stdio.h>
#include "nzbget.h"
#include "NString.h"
#include "QueueScript.h"
#include "NzbScript.h"
#include "Options.h"
#include "Log.h"
#include "Util.h"
#include "FileSystem.h"
static const char* QUEUE_EVENT_NAMES[] = { "FILE_DOWNLOADED", "URL_COMPLETED", "NZB_ADDED", "NZB_DOWNLOADED", "NZB_DELETED" };
static const char* QUEUE_EVENT_NAMES[] = {
"FILE_DOWNLOADED",
"URL_COMPLETED",
"NZB_MARKED",
"NZB_ADDED",
"NZB_NAMED",
"NZB_DOWNLOADED",
"NZB_DELETED" };
class QueueScriptController : public Thread, public NZBScriptController
class QueueScriptController : public Thread, public NzbScriptController
{
private:
char* m_szNZBName;
char* m_szNZBFilename;
char* m_szUrl;
char* m_szCategory;
char* m_szDestDir;
int m_iID;
int m_iPriority;
char* m_szDupeKey;
EDupeMode m_eDupeMode;
int m_iDupeScore;
NZBParameterList m_Parameters;
int m_iPrefixLen;
ScriptConfig::Script* m_pScript;
QueueScriptCoordinator::EEvent m_eEvent;
bool m_bMarkBad;
NZBInfo::EDeleteStatus m_eDeleteStatus;
NZBInfo::EUrlStatus m_eUrlStatus;
void PrepareParams(const char* szScriptName);
public:
virtual void Run();
static void StartScript(NzbInfo* nzbInfo, ScriptConfig::Script* script, QueueScriptCoordinator::EEvent event);
protected:
virtual void ExecuteScript(ScriptConfig::Script* pScript);
virtual void AddMessage(Message::EKind eKind, const char* szText);
virtual void ExecuteScript(ScriptConfig::Script* script);
virtual void AddMessage(Message::EKind kind, const char* text);
public:
virtual ~QueueScriptController();
virtual void Run();
static void StartScript(NZBInfo* pNZBInfo, ScriptConfig::Script* pScript, QueueScriptCoordinator::EEvent eEvent);
private:
CString m_nzbName;
CString m_nzbFilename;
CString m_url;
CString m_category;
CString m_destDir;
CString m_queuedFilename;
int m_id;
int m_priority;
CString m_dupeKey;
EDupeMode m_dupeMode;
int m_dupeScore;
NzbParameterList m_parameters;
int m_prefixLen;
ScriptConfig::Script* m_script;
QueueScriptCoordinator::EEvent m_event;
bool m_markBad;
NzbInfo::EDeleteStatus m_deleteStatus;
NzbInfo::EUrlStatus m_urlStatus;
NzbInfo::EMarkStatus m_markStatus;
void PrepareParams(const char* scriptName);
};
QueueScriptController::~QueueScriptController()
void QueueScriptController::StartScript(NzbInfo* nzbInfo, ScriptConfig::Script* script, QueueScriptCoordinator::EEvent event)
{
free(m_szNZBName);
free(m_szNZBFilename);
free(m_szUrl);
free(m_szCategory);
free(m_szDestDir);
free(m_szDupeKey);
}
QueueScriptController* scriptController = new QueueScriptController();
void QueueScriptController::StartScript(NZBInfo* pNZBInfo, ScriptConfig::Script* pScript, QueueScriptCoordinator::EEvent eEvent)
{
QueueScriptController* pScriptController = new QueueScriptController();
scriptController->m_nzbName = nzbInfo->GetName();
scriptController->m_nzbFilename = nzbInfo->GetFilename();
scriptController->m_url = nzbInfo->GetUrl();
scriptController->m_category = nzbInfo->GetCategory();
scriptController->m_destDir = nzbInfo->GetDestDir();
scriptController->m_queuedFilename = nzbInfo->GetQueuedFilename();
scriptController->m_id = nzbInfo->GetId();
scriptController->m_priority = nzbInfo->GetPriority();
scriptController->m_dupeKey = nzbInfo->GetDupeKey();
scriptController->m_dupeMode = nzbInfo->GetDupeMode();
scriptController->m_dupeScore = nzbInfo->GetDupeScore();
scriptController->m_parameters.CopyFrom(nzbInfo->GetParameters());
scriptController->m_script = script;
scriptController->m_event = event;
scriptController->m_prefixLen = 0;
scriptController->m_markBad = false;
scriptController->m_deleteStatus = nzbInfo->GetDeleteStatus();
scriptController->m_urlStatus = nzbInfo->GetUrlStatus();
scriptController->m_markStatus = nzbInfo->GetMarkStatus();
scriptController->SetAutoDestroy(true);
pScriptController->m_szNZBName = strdup(pNZBInfo->GetName());
pScriptController->m_szNZBFilename = strdup(pNZBInfo->GetFilename());
pScriptController->m_szUrl = strdup(pNZBInfo->GetURL());
pScriptController->m_szCategory = strdup(pNZBInfo->GetCategory());
pScriptController->m_szDestDir = strdup(pNZBInfo->GetDestDir());
pScriptController->m_iID = pNZBInfo->GetID();
pScriptController->m_iPriority = pNZBInfo->GetPriority();
pScriptController->m_szDupeKey = strdup(pNZBInfo->GetDupeKey());
pScriptController->m_eDupeMode = pNZBInfo->GetDupeMode();
pScriptController->m_iDupeScore = pNZBInfo->GetDupeScore();
pScriptController->m_Parameters.CopyFrom(pNZBInfo->GetParameters());
pScriptController->m_pScript = pScript;
pScriptController->m_eEvent = eEvent;
pScriptController->m_iPrefixLen = 0;
pScriptController->m_bMarkBad = false;
pScriptController->m_eDeleteStatus = pNZBInfo->GetDeleteStatus();
pScriptController->m_eUrlStatus = pNZBInfo->GetUrlStatus();
pScriptController->SetAutoDestroy(true);
pScriptController->Start();
scriptController->Start();
}
void QueueScriptController::Run()
{
ExecuteScript(m_pScript);
ExecuteScript(m_script);
SetLogPrefix(NULL);
SetLogPrefix(nullptr);
if (m_bMarkBad)
if (m_markBad)
{
DownloadQueue* pDownloadQueue = DownloadQueue::Lock();
NZBInfo* pNZBInfo = pDownloadQueue->GetQueue()->Find(m_iID);
if (pNZBInfo)
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
NzbInfo* nzbInfo = downloadQueue->GetQueue()->Find(m_id);
if (nzbInfo)
{
PrintMessage(Message::mkWarning, "Cancelling download and deleting %s", m_szNZBName);
pNZBInfo->SetDeleteStatus(NZBInfo::dsBad);
pDownloadQueue->EditEntry(m_iID, DownloadQueue::eaGroupDelete, 0, NULL);
nzbInfo->PrintMessage(Message::mkWarning, "Cancelling download and deleting %s", *m_nzbName);
nzbInfo->SetDeleteStatus(NzbInfo::dsBad);
downloadQueue->EditEntry(m_id, DownloadQueue::eaGroupDelete, nullptr);
}
DownloadQueue::Unlock();
}
g_pQueueScriptCoordinator->CheckQueue();
g_QueueScriptCoordinator->CheckQueue();
}
void QueueScriptController::ExecuteScript(ScriptConfig::Script* pScript)
void QueueScriptController::ExecuteScript(ScriptConfig::Script* script)
{
PrintMessage(m_eEvent == QueueScriptCoordinator::qeFileDownloaded ? Message::mkDetail : Message::mkInfo,
"Executing queue-script %s for %s", pScript->GetName(), Util::BaseFileName(m_szNZBName));
PrintMessage(m_event == QueueScriptCoordinator::qeFileDownloaded ? Message::mkDetail : Message::mkInfo,
"Executing queue-script %s for %s", script->GetName(), FileSystem::BaseFileName(m_nzbName));
SetScript(pScript->GetLocation());
SetArgs(NULL, false);
SetArgs({script->GetLocation()});
char szInfoName[1024];
snprintf(szInfoName, 1024, "queue-script %s for %s", pScript->GetName(), Util::BaseFileName(m_szNZBName));
szInfoName[1024-1] = '\0';
SetInfoName(szInfoName);
BString<1024> infoName("queue-script %s for %s", script->GetName(), FileSystem::BaseFileName(m_nzbName));
SetInfoName(infoName);
SetLogPrefix(pScript->GetDisplayName());
m_iPrefixLen = strlen(pScript->GetDisplayName()) + 2; // 2 = strlen(": ");
PrepareParams(pScript->GetName());
SetLogPrefix(script->GetDisplayName());
m_prefixLen = strlen(script->GetDisplayName()) + 2; // 2 = strlen(": ");
PrepareParams(script->GetName());
Execute();
SetLogPrefix(NULL);
SetLogPrefix(nullptr);
}
void QueueScriptController::PrepareParams(const char* szScriptName)
void QueueScriptController::PrepareParams(const char* scriptName)
{
ResetEnv();
SetEnvVar("NZBNA_NZBNAME", m_szNZBName);
SetIntEnvVar("NZBNA_NZBID", m_iID);
SetEnvVar("NZBNA_FILENAME", m_szNZBFilename);
SetEnvVar("NZBNA_DIRECTORY", m_szDestDir);
SetEnvVar("NZBNA_URL", m_szUrl);
SetEnvVar("NZBNA_CATEGORY", m_szCategory);
SetIntEnvVar("NZBNA_PRIORITY", m_iPriority);
SetIntEnvVar("NZBNA_LASTID", m_iID); // deprecated
SetEnvVar("NZBNA_NZBNAME", m_nzbName);
SetIntEnvVar("NZBNA_NZBID", m_id);
SetEnvVar("NZBNA_FILENAME", m_nzbFilename);
SetEnvVar("NZBNA_DIRECTORY", m_destDir);
SetEnvVar("NZBNA_QUEUEDFILE", m_queuedFilename);
SetEnvVar("NZBNA_URL", m_url);
SetEnvVar("NZBNA_CATEGORY", m_category);
SetIntEnvVar("NZBNA_PRIORITY", m_priority);
SetIntEnvVar("NZBNA_LASTID", m_id); // deprecated
SetEnvVar("NZBNA_DUPEKEY", m_szDupeKey);
SetIntEnvVar("NZBNA_DUPESCORE", m_iDupeScore);
SetEnvVar("NZBNA_DUPEKEY", m_dupeKey);
SetIntEnvVar("NZBNA_DUPESCORE", m_dupeScore);
const char* szDupeModeName[] = { "SCORE", "ALL", "FORCE" };
SetEnvVar("NZBNA_DUPEMODE", szDupeModeName[m_eDupeMode]);
const char* dupeModeName[] = { "SCORE", "ALL", "FORCE" };
SetEnvVar("NZBNA_DUPEMODE", dupeModeName[m_dupeMode]);
SetEnvVar("NZBNA_EVENT", QUEUE_EVENT_NAMES[m_eEvent]);
SetEnvVar("NZBNA_EVENT", QUEUE_EVENT_NAMES[m_event]);
const char* szDeleteStatusName[] = { "NONE", "MANUAL", "HEALTH", "DUPE", "BAD", "GOOD", "COPY", "SCAN" };
SetEnvVar("NZBNA_DELETESTATUS", szDeleteStatusName[m_eDeleteStatus]);
const char* deleteStatusName[] = { "NONE", "MANUAL", "HEALTH", "DUPE", "BAD", "GOOD", "COPY", "SCAN" };
SetEnvVar("NZBNA_DELETESTATUS", deleteStatusName[m_deleteStatus]);
const char* szUrlStatusName[] = { "NONE", "UNKNOWN", "SUCCESS", "FAILURE", "UNKNOWN", "SCAN_SKIPPED", "SCAN_FAILURE" };
SetEnvVar("NZBNA_URLSTATUS", szUrlStatusName[m_eUrlStatus]);
const char* urlStatusName[] = { "NONE", "UNKNOWN", "SUCCESS", "FAILURE", "UNKNOWN", "SCAN_SKIPPED", "SCAN_FAILURE" };
SetEnvVar("NZBNA_URLSTATUS", urlStatusName[m_urlStatus]);
PrepareEnvScript(&m_Parameters, szScriptName);
const char* markStatusName[] = { "NONE", "BAD", "GOOD", "SUCCESS" };
SetEnvVar("NZBNA_MARKSTATUS", markStatusName[m_markStatus]);
PrepareEnvScript(&m_parameters, scriptName);
}
void QueueScriptController::AddMessage(Message::EKind eKind, const char* szText)
void QueueScriptController::AddMessage(Message::EKind kind, const char* text)
{
const char* szMsgText = szText + m_iPrefixLen;
const char* msgText = text + m_prefixLen;
if (!strncmp(szMsgText, "[NZB] ", 6))
if (!strncmp(msgText, "[NZB] ", 6))
{
debug("Command %s detected", szMsgText + 6);
if (!strncmp(szMsgText + 6, "NZBPR_", 6))
debug("Command %s detected", msgText + 6);
if (!strncmp(msgText + 6, "NZBPR_", 6))
{
char* szParam = strdup(szMsgText + 6 + 6);
char* szValue = strchr(szParam, '=');
if (szValue)
CString param = msgText + 6 + 6;
char* value = strchr(param, '=');
if (value)
{
*szValue = '\0';
DownloadQueue* pDownloadQueue = DownloadQueue::Lock();
NZBInfo* pNZBInfo = pDownloadQueue->GetQueue()->Find(m_iID);
if (pNZBInfo)
*value = '\0';
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
NzbInfo* nzbInfo = QueueScriptCoordinator::FindNzbInfo(downloadQueue, m_id);
if (nzbInfo)
{
pNZBInfo->GetParameters()->SetParameter(szParam, szValue + 1);
nzbInfo->GetParameters()->SetParameter(param, value + 1);
}
DownloadQueue::Unlock();
}
else
{
error("Invalid command \"%s\" received from %s", szMsgText, GetInfoName());
error("Invalid command \"%s\" received from %s", msgText, GetInfoName());
}
free(szParam);
}
else if (!strncmp(szMsgText + 6, "MARK=BAD", 8))
else if (!strncmp(msgText + 6, "DIRECTORY=", 10) &&
m_event == QueueScriptCoordinator::qeNzbDownloaded)
{
m_bMarkBad = true;
DownloadQueue* pDownloadQueue = DownloadQueue::Lock();
NZBInfo* pNZBInfo = pDownloadQueue->GetQueue()->Find(m_iID);
if (pNZBInfo)
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
NzbInfo* nzbInfo = QueueScriptCoordinator::FindNzbInfo(downloadQueue, m_id);
if (nzbInfo)
{
SetLogPrefix(NULL);
PrintMessage(Message::mkWarning, "Marking %s as bad", m_szNZBName);
SetLogPrefix(m_pScript->GetDisplayName());
pNZBInfo->SetMarkStatus(NZBInfo::ksBad);
nzbInfo->SetFinalDir(msgText + 6 + 10);
}
}
else if (!strncmp(msgText + 6, "MARK=BAD", 8))
{
m_markBad = true;
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
NzbInfo* nzbInfo = QueueScriptCoordinator::FindNzbInfo(downloadQueue, m_id);
if (nzbInfo)
{
nzbInfo->PrintMessage(Message::mkWarning, "Marking %s as bad", *m_nzbName);
nzbInfo->SetMarkStatus(NzbInfo::ksBad);
}
DownloadQueue::Unlock();
}
else
{
error("Invalid command \"%s\" received from %s", szMsgText, GetInfoName());
error("Invalid command \"%s\" received from %s", msgText, GetInfoName());
}
}
else
{
ScriptController::AddMessage(eKind, szText);
NzbInfo* nzbInfo = nullptr;
{
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
nzbInfo = QueueScriptCoordinator::FindNzbInfo(downloadQueue, m_id);
if (nzbInfo)
{
nzbInfo->AddMessage(kind, text);
}
}
if (!nzbInfo)
{
ScriptController::AddMessage(kind, text);
}
}
}
QueueScriptCoordinator::QueueItem::QueueItem(int iNZBID, ScriptConfig::Script* pScript, EEvent eEvent)
{
m_iNZBID = iNZBID;
m_pScript = pScript;
m_eEvent = eEvent;
}
QueueScriptCoordinator::QueueScriptCoordinator()
{
m_pCurItem = NULL;
m_bStopped = false;
}
QueueScriptCoordinator::~QueueScriptCoordinator()
{
delete m_pCurItem;
for (Queue::iterator it = m_Queue.begin(); it != m_Queue.end(); it++ )
{
delete *it;
}
}
void QueueScriptCoordinator::InitOptions()
{
m_bHasQueueScripts = false;
for (ScriptConfig::Scripts::iterator it = g_pScriptConfig->GetScripts()->begin(); it != g_pScriptConfig->GetScripts()->end(); it++)
m_hasQueueScripts = false;
for (ScriptConfig::Script& script : g_ScriptConfig->GetScripts())
{
ScriptConfig::Script* pScript = *it;
if (pScript->GetQueueScript())
if (script.GetQueueScript())
{
m_bHasQueueScripts = true;
m_hasQueueScripts = true;
break;
}
}
}
void QueueScriptCoordinator::EnqueueScript(NZBInfo* pNZBInfo, EEvent eEvent)
void QueueScriptCoordinator::EnqueueScript(NzbInfo* nzbInfo, EEvent event)
{
if (!m_bHasQueueScripts)
if (!m_hasQueueScripts)
{
return;
}
m_mutexQueue.Lock();
Guard guard(m_queueMutex);
if (eEvent == qeNzbDownloaded)
if (event == qeNzbDownloaded)
{
// delete all other queued scripts for this nzb
for (Queue::iterator it = m_Queue.begin(); it != m_Queue.end(); )
{
QueueItem* pQueueItem = *it;
if (pQueueItem->GetNZBID() == pNZBInfo->GetID())
m_queue.erase(std::remove_if(m_queue.begin(), m_queue.end(),
[nzbInfo](std::unique_ptr<QueueItem>& queueItem)
{
delete pQueueItem;
it = m_Queue.erase(it);
continue;
}
it++;
}
return queueItem->GetNzbId() == nzbInfo->GetId();
}),
m_queue.end());
}
// respect option "EventInterval"
time_t tCurTime = time(NULL);
if (eEvent == qeFileDownloaded &&
(g_pOptions->GetEventInterval() == -1 ||
(g_pOptions->GetEventInterval() > 0 && tCurTime - pNZBInfo->GetQueueScriptTime() > 0 &&
(int)(tCurTime - pNZBInfo->GetQueueScriptTime()) < g_pOptions->GetEventInterval())))
time_t curTime = Util::CurrentTime();
if (event == qeFileDownloaded &&
(g_Options->GetEventInterval() == -1 ||
(g_Options->GetEventInterval() > 0 && curTime - nzbInfo->GetQueueScriptTime() > 0 &&
(int)(curTime - nzbInfo->GetQueueScriptTime()) < g_Options->GetEventInterval())))
{
m_mutexQueue.Unlock();
return;
}
for (ScriptConfig::Scripts::iterator it = g_pScriptConfig->GetScripts()->begin(); it != g_pScriptConfig->GetScripts()->end(); it++)
for (ScriptConfig::Script& script : g_ScriptConfig->GetScripts())
{
ScriptConfig::Script* pScript = *it;
if (!pScript->GetQueueScript())
if (UsableScript(script, nzbInfo, event))
{
continue;
}
bool bUseScript = false;
// check queue-scripts
const char* szQueueScript = g_pOptions->GetQueueScript();
if (!Util::EmptyStr(szQueueScript))
{
// split szQueueScript into tokens
Tokenizer tok(szQueueScript, ",;");
while (const char* szScriptName = tok.Next())
{
if (Util::SameFilename(szScriptName, pScript->GetName()))
{
bUseScript = true;
break;
}
}
}
// check post-processing-scripts
if (!bUseScript)
{
for (NZBParameterList::iterator it = pNZBInfo->GetParameters()->begin(); it != pNZBInfo->GetParameters()->end(); it++)
{
NZBParameter* pParameter = *it;
const char* szVarname = pParameter->GetName();
if (strlen(szVarname) > 0 && szVarname[0] != '*' && szVarname[strlen(szVarname)-1] == ':' &&
(!strcasecmp(pParameter->GetValue(), "yes") ||
!strcasecmp(pParameter->GetValue(), "on") ||
!strcasecmp(pParameter->GetValue(), "1")))
{
char szScriptName[1024];
strncpy(szScriptName, szVarname, 1024);
szScriptName[1024-1] = '\0';
szScriptName[strlen(szScriptName)-1] = '\0'; // remove trailing ':'
if (Util::SameFilename(szScriptName, pScript->GetName()))
{
bUseScript = true;
break;
}
}
}
}
bUseScript &= Util::EmptyStr(pScript->GetQueueEvents()) || strstr(pScript->GetQueueEvents(), QUEUE_EVENT_NAMES[eEvent]);
if (bUseScript)
{
bool bAlreadyQueued = false;
if (eEvent == qeFileDownloaded)
bool alreadyQueued = false;
if (event == qeFileDownloaded)
{
// check if this script is already queued for this nzb
for (Queue::iterator it2 = m_Queue.begin(); it2 != m_Queue.end(); it2++)
for (QueueItem* queueItem : &m_queue)
{
QueueItem* pQueueItem = *it2;
if (pQueueItem->GetNZBID() == pNZBInfo->GetID() && pQueueItem->GetScript() == pScript)
if (queueItem->GetNzbId() == nzbInfo->GetId() && queueItem->GetScript() == &script)
{
bAlreadyQueued = true;
alreadyQueued = true;
break;
}
}
}
if (!bAlreadyQueued)
if (!alreadyQueued)
{
QueueItem* pQueueItem = new QueueItem(pNZBInfo->GetID(), pScript, eEvent);
if (m_pCurItem)
std::unique_ptr<QueueItem> queueItem = std::make_unique<QueueItem>(nzbInfo->GetId(), &script, event);
if (m_curItem)
{
m_Queue.push_back(pQueueItem);
m_queue.push_back(std::move(queueItem));
}
else
{
StartScript(pNZBInfo, pQueueItem);
m_curItem = std::move(queueItem);
QueueScriptController::StartScript(nzbInfo, m_curItem->GetScript(), m_curItem->GetEvent());
}
}
pNZBInfo->SetQueueScriptTime(time(NULL));
nzbInfo->SetQueueScriptTime(Util::CurrentTime());
}
}
m_mutexQueue.Unlock();
}
NZBInfo* QueueScriptCoordinator::FindNZBInfo(DownloadQueue* pDownloadQueue, int iNZBID)
bool QueueScriptCoordinator::UsableScript(ScriptConfig::Script& script, NzbInfo* nzbInfo, EEvent event)
{
NZBInfo* pNZBInfo = pDownloadQueue->GetQueue()->Find(iNZBID);
if (!pNZBInfo)
if (!script.GetQueueScript())
{
for (HistoryList::iterator it = pDownloadQueue->GetHistory()->begin(); it != pDownloadQueue->GetHistory()->end(); it++)
return false;
}
if (!Util::EmptyStr(script.GetQueueEvents()) && !strstr(script.GetQueueEvents(), QUEUE_EVENT_NAMES[event]))
{
return false;
}
// check extension scripts assigned for that nzb
for (NzbParameter& parameter : nzbInfo->GetParameters())
{
const char* varname = parameter.GetName();
if (strlen(varname) > 0 && varname[0] != '*' && varname[strlen(varname)-1] == ':' &&
(!strcasecmp(parameter.GetValue(), "yes") ||
!strcasecmp(parameter.GetValue(), "on") ||
!strcasecmp(parameter.GetValue(), "1")))
{
HistoryInfo* pHistoryInfo = *it;
if (pHistoryInfo->GetNZBInfo() && pHistoryInfo->GetNZBInfo()->GetID() == iNZBID)
BString<1024> scriptName = varname;
scriptName[strlen(scriptName)-1] = '\0'; // remove trailing ':'
if (FileSystem::SameFilename(scriptName, script.GetName()))
{
pNZBInfo = pHistoryInfo->GetNZBInfo();
break;
return true;
}
}
}
return pNZBInfo;
// for URL-events the extension scripts are not assigned yet;
// instead we take the default extension scripts for the category (or global)
if (event == qeUrlCompleted)
{
const char* postScript = g_Options->GetExtensions();
if (!Util::EmptyStr(nzbInfo->GetCategory()))
{
Options::Category* categoryObj = g_Options->FindCategory(nzbInfo->GetCategory(), false);
if (categoryObj && !Util::EmptyStr(categoryObj->GetExtensions()))
{
postScript = categoryObj->GetExtensions();
}
}
if (!Util::EmptyStr(postScript))
{
Tokenizer tok(postScript, ",;");
while (const char* scriptName = tok.Next())
{
if (FileSystem::SameFilename(scriptName, script.GetName()))
{
return true;
}
}
}
}
return false;
}
NzbInfo* QueueScriptCoordinator::FindNzbInfo(DownloadQueue* downloadQueue, int nzbId)
{
NzbInfo* nzbInfo = downloadQueue->GetQueue()->Find(nzbId);
if (nzbInfo)
{
return nzbInfo;
}
HistoryInfo* historyInfo = downloadQueue->GetHistory()->Find(nzbId);
if (historyInfo)
{
return historyInfo->GetNzbInfo();
}
return nullptr;
}
void QueueScriptCoordinator::CheckQueue()
{
if (m_bStopped)
if (m_stopped)
{
return;
}
DownloadQueue* pDownloadQueue = DownloadQueue::Lock();
m_mutexQueue.Lock();
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
Guard guard(m_queueMutex);
delete m_pCurItem;
m_curItem.reset();
NzbInfo* curNzbInfo = nullptr;
Queue::iterator itCurItem;
m_pCurItem = NULL;
NZBInfo* pCurNZBInfo = NULL;
Queue::iterator itCurItem = m_Queue.end();
for (Queue::iterator it = m_Queue.begin(); it != m_Queue.end(); )
for (Queue::iterator it = m_queue.begin(); it != m_queue.end(); )
{
QueueItem* pQueueItem = *it;
std::unique_ptr<QueueItem>& queueItem = *it;
NZBInfo* pNZBInfo = FindNZBInfo(pDownloadQueue, pQueueItem->GetNZBID());
NzbInfo* nzbInfo = FindNzbInfo(downloadQueue, queueItem->GetNzbId());
// in a case this nzb must not be processed further - delete queue script from queue
if (!pNZBInfo ||
(pNZBInfo->GetDeleteStatus() != NZBInfo::dsNone && pQueueItem->GetEvent() != qeNzbDeleted) ||
pNZBInfo->GetMarkStatus() == NZBInfo::ksBad)
EEvent event = queueItem->GetEvent();
bool ignoreEvent = !nzbInfo ||
(nzbInfo->GetDeleteStatus() != NzbInfo::dsNone && event != qeNzbDeleted && event != qeNzbMarked) ||
(nzbInfo->GetMarkStatus() == NzbInfo::ksBad && event != qeNzbMarked);
if (ignoreEvent)
{
delete pQueueItem;
it = m_Queue.erase(it);
it = m_queue.erase(it);
if (curNzbInfo)
{
// process from the beginning, while "erase" invalidated "itCurItem"
curNzbInfo = nullptr;
it = m_queue.begin();
}
continue;
}
if (!m_pCurItem || pQueueItem->GetEvent() > m_pCurItem->GetEvent())
if (!m_curItem || queueItem->GetEvent() > m_curItem->GetEvent())
{
m_pCurItem = pQueueItem;
itCurItem = it;
pCurNZBInfo = pNZBInfo;
curNzbInfo = nzbInfo;
}
it++;
}
if (m_pCurItem)
if (curNzbInfo)
{
m_Queue.erase(itCurItem);
StartScript(pCurNZBInfo, m_pCurItem);
m_curItem = std::move(*itCurItem);
m_queue.erase(itCurItem);
QueueScriptController::StartScript(curNzbInfo, m_curItem->GetScript(), m_curItem->GetEvent());
}
m_mutexQueue.Unlock();
DownloadQueue::Unlock();
}
void QueueScriptCoordinator::StartScript(NZBInfo* pNZBInfo, QueueItem* pQueueItem)
bool QueueScriptCoordinator::HasJob(int nzbId, bool* active)
{
m_pCurItem = pQueueItem;
QueueScriptController::StartScript(pNZBInfo, pQueueItem->GetScript(), pQueueItem->GetEvent());
}
Guard guard(m_queueMutex);
bool QueueScriptCoordinator::HasJob(int iNZBID, bool* pActive)
{
m_mutexQueue.Lock();
bool bWorking = m_pCurItem && m_pCurItem->GetNZBID() == iNZBID;
if (pActive)
bool working = m_curItem && m_curItem->GetNzbId() == nzbId;
if (active)
{
*pActive = bWorking;
*active = working;
}
if (!bWorking)
if (!working)
{
for (Queue::iterator it = m_Queue.begin(); it != m_Queue.end(); it++)
for (QueueItem* queueItem : &m_queue)
{
QueueItem* pQueueItem = *it;
bWorking = pQueueItem->GetNZBID() == iNZBID;
if (bWorking)
working = queueItem->GetNzbId() == nzbId;
if (working)
{
break;
}
}
}
m_mutexQueue.Unlock();
return bWorking;
return working;
}
int QueueScriptCoordinator::GetQueueSize()
{
m_mutexQueue.Lock();
int iQueuedCount = m_Queue.size();
if (m_pCurItem)
{
iQueuedCount++;
}
m_mutexQueue.Unlock();
Guard guard(m_queueMutex);
return iQueuedCount;
int queuedCount = m_queue.size();
if (m_curItem)
{
queuedCount++;
}
return queuedCount;
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,20 +14,13 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef QUEUESCRIPT_H
#define QUEUESCRIPT_H
#include <list>
#include "DownloadInfo.h"
#include "ScriptConfig.h"
@@ -36,49 +29,49 @@ class QueueScriptCoordinator
public:
enum EEvent
{
qeFileDownloaded, // lowest priority
qeFileDownloaded, // lowest priority
qeUrlCompleted,
qeNzbMarked,
qeNzbAdded,
qeNzbNamed,
qeNzbDownloaded,
qeNzbDeleted // highest priority
qeNzbDeleted // highest priority
};
void Stop() { m_stopped = true; }
void InitOptions();
void EnqueueScript(NzbInfo* nzbInfo, EEvent event);
void CheckQueue();
bool HasJob(int nzbId, bool* active);
int GetQueueSize();
static NzbInfo* FindNzbInfo(DownloadQueue* downloadQueue, int nzbId);
private:
class QueueItem
{
private:
int m_iNZBID;
ScriptConfig::Script* m_pScript;
EEvent m_eEvent;
public:
QueueItem(int iNZBID, ScriptConfig::Script* pScript, EEvent eEvent);
int GetNZBID() { return m_iNZBID; }
ScriptConfig::Script* GetScript() { return m_pScript; }
EEvent GetEvent() { return m_eEvent; }
QueueItem(int nzbId, ScriptConfig::Script* script, EEvent event) :
m_nzbId(nzbId), m_script(script), m_event(event) {}
int GetNzbId() { return m_nzbId; }
ScriptConfig::Script* GetScript() { return m_script; }
EEvent GetEvent() { return m_event; }
private:
int m_nzbId;
ScriptConfig::Script* m_script;
EEvent m_event;
};
typedef std::list<QueueItem*> Queue;
Queue m_Queue;
Mutex m_mutexQueue;
QueueItem* m_pCurItem;
bool m_bHasQueueScripts;
bool m_bStopped;
typedef std::deque<std::unique_ptr<QueueItem>> Queue;
void StartScript(NZBInfo* pNZBInfo, QueueItem* pQueueItem);
NZBInfo* FindNZBInfo(DownloadQueue* pDownloadQueue, int iNZBID);
Queue m_queue;
Mutex m_queueMutex;
std::unique_ptr<QueueItem> m_curItem;
bool m_hasQueueScripts = false;
bool m_stopped = false;
public:
QueueScriptCoordinator();
~QueueScriptCoordinator();
void Stop() { m_bStopped = true; }
void InitOptions();
void EnqueueScript(NZBInfo* pNZBInfo, EEvent eEvent);
void CheckQueue();
bool HasJob(int iNZBID, bool* pActive);
int GetQueueSize();
bool UsableScript(ScriptConfig::Script& script, NzbInfo* nzbInfo, EEvent event);
};
extern QueueScriptCoordinator* g_pQueueScriptCoordinator;
extern QueueScriptCoordinator* g_QueueScriptCoordinator;
#endif

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,194 +14,187 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include <sys/stat.h>
#include <stdio.h>
#include "nzbget.h"
#include "ScanScript.h"
#include "Scanner.h"
#include "Options.h"
#include "Log.h"
#include "Util.h"
#include "FileSystem.h"
void ScanScriptController::ExecuteScripts(const char* szNZBFilename,
const char* szUrl, const char* szDirectory, char** pNZBName, char** pCategory,
int* iPriority, NZBParameterList* pParameters, bool* bAddTop, bool* bAddPaused,
char** pDupeKey, int* iDupeScore, EDupeMode* eDupeMode)
class ScanScriptCheck : public NzbScriptController
{
ScanScriptController* pScriptController = new ScanScriptController();
protected:
virtual void ExecuteScript(ScriptConfig::Script* script) { has |= script->GetScanScript(); }
bool has = false;
friend class ScanScriptController;
};
pScriptController->m_szNZBFilename = szNZBFilename;
pScriptController->m_szUrl = szUrl;
pScriptController->m_szDirectory = szDirectory;
pScriptController->m_pNZBName = pNZBName;
pScriptController->m_pCategory = pCategory;
pScriptController->m_pParameters = pParameters;
pScriptController->m_iPriority = iPriority;
pScriptController->m_bAddTop = bAddTop;
pScriptController->m_bAddPaused = bAddPaused;
pScriptController->m_pDupeKey = pDupeKey;
pScriptController->m_iDupeScore = iDupeScore;
pScriptController->m_eDupeMode = eDupeMode;
pScriptController->m_iPrefixLen = 0;
pScriptController->ExecuteScriptList(g_pOptions->GetScanScript());
delete pScriptController;
bool ScanScriptController::HasScripts()
{
ScanScriptCheck check;
check.ExecuteScriptList(g_Options->GetExtensions());
return check.has;
}
void ScanScriptController::ExecuteScript(ScriptConfig::Script* pScript)
void ScanScriptController::ExecuteScripts(const char* nzbFilename,
const char* url, const char* directory, CString* nzbName, CString* category,
int* priority, NzbParameterList* parameters, bool* addTop, bool* addPaused,
CString* dupeKey, int* dupeScore, EDupeMode* dupeMode)
{
if (!pScript->GetScanScript() || !Util::FileExists(m_szNZBFilename))
ScanScriptController scriptController;
scriptController.m_nzbFilename = nzbFilename;
scriptController.m_url = url;
scriptController.m_directory = directory;
scriptController.m_nzbName = nzbName;
scriptController.m_category = category;
scriptController.m_parameters = parameters;
scriptController.m_priority = priority;
scriptController.m_addTop = addTop;
scriptController.m_addPaused = addPaused;
scriptController.m_dupeKey = dupeKey;
scriptController.m_dupeScore = dupeScore;
scriptController.m_dupeMode = dupeMode;
scriptController.m_prefixLen = 0;
const char* extensions = g_Options->GetExtensions();
if (!Util::EmptyStr(*category))
{
Options::Category* categoryObj = g_Options->FindCategory(*category, false);
if (categoryObj && !Util::EmptyStr(categoryObj->GetExtensions()))
{
extensions = categoryObj->GetExtensions();
}
}
scriptController.ExecuteScriptList(extensions);
}
void ScanScriptController::ExecuteScript(ScriptConfig::Script* script)
{
if (!script->GetScanScript() || !FileSystem::FileExists(m_nzbFilename))
{
return;
}
PrintMessage(Message::mkInfo, "Executing scan-script %s for %s", pScript->GetName(), Util::BaseFileName(m_szNZBFilename));
PrintMessage(Message::mkInfo, "Executing scan-script %s for %s", script->GetName(), FileSystem::BaseFileName(m_nzbFilename));
SetScript(pScript->GetLocation());
SetArgs(NULL, false);
SetArgs({script->GetLocation()});
char szInfoName[1024];
snprintf(szInfoName, 1024, "scan-script %s for %s", pScript->GetName(), Util::BaseFileName(m_szNZBFilename));
szInfoName[1024-1] = '\0';
SetInfoName(szInfoName);
BString<1024> infoName("scan-script %s for %s", script->GetName(), FileSystem::BaseFileName(m_nzbFilename));
SetInfoName(infoName);
SetLogPrefix(pScript->GetDisplayName());
m_iPrefixLen = strlen(pScript->GetDisplayName()) + 2; // 2 = strlen(": ");
PrepareParams(pScript->GetName());
SetLogPrefix(script->GetDisplayName());
m_prefixLen = strlen(script->GetDisplayName()) + 2; // 2 = strlen(": ");
PrepareParams(script->GetName());
Execute();
SetLogPrefix(NULL);
SetLogPrefix(nullptr);
}
void ScanScriptController::PrepareParams(const char* szScriptName)
void ScanScriptController::PrepareParams(const char* scriptName)
{
ResetEnv();
SetEnvVar("NZBNP_FILENAME", m_szNZBFilename);
SetEnvVar("NZBNP_URL", m_szUrl);
SetEnvVar("NZBNP_NZBNAME", strlen(*m_pNZBName) > 0 ? *m_pNZBName : Util::BaseFileName(m_szNZBFilename));
SetEnvVar("NZBNP_CATEGORY", *m_pCategory);
SetIntEnvVar("NZBNP_PRIORITY", *m_iPriority);
SetIntEnvVar("NZBNP_TOP", *m_bAddTop ? 1 : 0);
SetIntEnvVar("NZBNP_PAUSED", *m_bAddPaused ? 1 : 0);
SetEnvVar("NZBNP_DUPEKEY", *m_pDupeKey);
SetIntEnvVar("NZBNP_DUPESCORE", *m_iDupeScore);
SetEnvVar("NZBNP_FILENAME", m_nzbFilename);
SetEnvVar("NZBNP_URL", m_url);
SetEnvVar("NZBNP_NZBNAME", strlen(*m_nzbName) > 0 ? **m_nzbName : FileSystem::BaseFileName(m_nzbFilename));
SetEnvVar("NZBNP_CATEGORY", *m_category);
SetIntEnvVar("NZBNP_PRIORITY", *m_priority);
SetIntEnvVar("NZBNP_TOP", *m_addTop ? 1 : 0);
SetIntEnvVar("NZBNP_PAUSED", *m_addPaused ? 1 : 0);
SetEnvVar("NZBNP_DUPEKEY", *m_dupeKey);
SetIntEnvVar("NZBNP_DUPESCORE", *m_dupeScore);
const char* szDupeModeName[] = { "SCORE", "ALL", "FORCE" };
SetEnvVar("NZBNP_DUPEMODE", szDupeModeName[*m_eDupeMode]);
const char* dupeModeName[] = { "SCORE", "ALL", "FORCE" };
SetEnvVar("NZBNP_DUPEMODE", dupeModeName[*m_dupeMode]);
// remove trailing slash
char szDir[1024];
strncpy(szDir, m_szDirectory, 1024);
szDir[1024-1] = '\0';
int iLen = strlen(szDir);
if (szDir[iLen-1] == PATH_SEPARATOR)
BString<1024> dir = m_directory;
int len = strlen(dir);
if (dir[len-1] == PATH_SEPARATOR)
{
szDir[iLen-1] = '\0';
dir[len-1] = '\0';
}
SetEnvVar("NZBNP_DIRECTORY", szDir);
SetEnvVar("NZBNP_DIRECTORY", dir);
PrepareEnvScript(m_pParameters, szScriptName);
PrepareEnvScript(m_parameters, scriptName);
}
void ScanScriptController::AddMessage(Message::EKind eKind, const char* szText)
void ScanScriptController::AddMessage(Message::EKind kind, const char* text)
{
const char* szMsgText = szText + m_iPrefixLen;
const char* msgText = text + m_prefixLen;
if (!strncmp(szMsgText, "[NZB] ", 6))
if (!strncmp(msgText, "[NZB] ", 6))
{
debug("Command %s detected", szMsgText + 6);
if (!strncmp(szMsgText + 6, "NZBNAME=", 8))
debug("Command %s detected", msgText + 6);
if (!strncmp(msgText + 6, "NZBNAME=", 8))
{
free(*m_pNZBName);
*m_pNZBName = strdup(szMsgText + 6 + 8);
*m_nzbName = msgText + 6 + 8;
}
else if (!strncmp(szMsgText + 6, "CATEGORY=", 9))
else if (!strncmp(msgText + 6, "CATEGORY=", 9))
{
free(*m_pCategory);
*m_pCategory = strdup(szMsgText + 6 + 9);
g_pScanner->InitPPParameters(*m_pCategory, m_pParameters, true);
*m_category = msgText + 6 + 9;
g_Scanner->InitPPParameters(*m_category, m_parameters, true);
}
else if (!strncmp(szMsgText + 6, "NZBPR_", 6))
else if (!strncmp(msgText + 6, "NZBPR_", 6))
{
char* szParam = strdup(szMsgText + 6 + 6);
char* szValue = strchr(szParam, '=');
if (szValue)
CString param = msgText + 6 + 6;
char* value = strchr(param, '=');
if (value)
{
*szValue = '\0';
m_pParameters->SetParameter(szParam, szValue + 1);
*value = '\0';
m_parameters->SetParameter(param, value + 1);
}
else
{
error("Invalid command \"%s\" received from %s", szMsgText, GetInfoName());
error("Invalid command \"%s\" received from %s", msgText, GetInfoName());
}
free(szParam);
}
else if (!strncmp(szMsgText + 6, "PRIORITY=", 9))
else if (!strncmp(msgText + 6, "PRIORITY=", 9))
{
*m_iPriority = atoi(szMsgText + 6 + 9);
*m_priority = atoi(msgText + 6 + 9);
}
else if (!strncmp(szMsgText + 6, "TOP=", 4))
else if (!strncmp(msgText + 6, "TOP=", 4))
{
*m_bAddTop = atoi(szMsgText + 6 + 4) != 0;
*m_addTop = atoi(msgText + 6 + 4) != 0;
}
else if (!strncmp(szMsgText + 6, "PAUSED=", 7))
else if (!strncmp(msgText + 6, "PAUSED=", 7))
{
*m_bAddPaused = atoi(szMsgText + 6 + 7) != 0;
*m_addPaused = atoi(msgText + 6 + 7) != 0;
}
else if (!strncmp(szMsgText + 6, "DUPEKEY=", 8))
else if (!strncmp(msgText + 6, "DUPEKEY=", 8))
{
free(*m_pDupeKey);
*m_pDupeKey = strdup(szMsgText + 6 + 8);
*m_dupeKey = msgText + 6 + 8;
}
else if (!strncmp(szMsgText + 6, "DUPESCORE=", 10))
else if (!strncmp(msgText + 6, "DUPESCORE=", 10))
{
*m_iDupeScore = atoi(szMsgText + 6 + 10);
*m_dupeScore = atoi(msgText + 6 + 10);
}
else if (!strncmp(szMsgText + 6, "DUPEMODE=", 9))
else if (!strncmp(msgText + 6, "DUPEMODE=", 9))
{
const char* szDupeMode = szMsgText + 6 + 9;
if (strcasecmp(szDupeMode, "score") && strcasecmp(szDupeMode, "all") && strcasecmp(szDupeMode, "force"))
const char* dupeMode = msgText + 6 + 9;
if (strcasecmp(dupeMode, "score") && strcasecmp(dupeMode, "all") && strcasecmp(dupeMode, "force"))
{
error("Invalid value \"%s\" for command \"DUPEMODE\" received from %s", szDupeMode, GetInfoName());
error("Invalid value \"%s\" for command \"DUPEMODE\" received from %s", dupeMode, GetInfoName());
return;
}
*m_eDupeMode = !strcasecmp(szDupeMode, "all") ? dmAll :
!strcasecmp(szDupeMode, "force") ? dmForce : dmScore;
*m_dupeMode = !strcasecmp(dupeMode, "all") ? dmAll :
!strcasecmp(dupeMode, "force") ? dmForce : dmScore;
}
else
{
error("Invalid command \"%s\" received from %s", szMsgText, GetInfoName());
error("Invalid command \"%s\" received from %s", msgText, GetInfoName());
}
}
else
{
ScriptController::AddMessage(eKind, szText);
ScriptController::AddMessage(kind, text);
}
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,12 +14,7 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
@@ -28,34 +23,35 @@
#include "NzbScript.h"
class ScanScriptController : public NZBScriptController
class ScanScriptController : public NzbScriptController
{
private:
const char* m_szNZBFilename;
const char* m_szUrl;
const char* m_szDirectory;
char** m_pNZBName;
char** m_pCategory;
int* m_iPriority;
NZBParameterList* m_pParameters;
bool* m_bAddTop;
bool* m_bAddPaused;
char** m_pDupeKey;
int* m_iDupeScore;
EDupeMode* m_eDupeMode;
int m_iPrefixLen;
void PrepareParams(const char* szScriptName);
public:
static void ExecuteScripts(const char* nzbFilename, const char* url,
const char* directory, CString* nzbName, CString* category, int* priority,
NzbParameterList* parameters, bool* addTop, bool* addPaused,
CString* dupeKey, int* dupeScore, EDupeMode* dupeMode);
static bool HasScripts();
protected:
virtual void ExecuteScript(ScriptConfig::Script* pScript);
virtual void AddMessage(Message::EKind eKind, const char* szText);
virtual void ExecuteScript(ScriptConfig::Script* script);
virtual void AddMessage(Message::EKind kind, const char* text);
public:
static void ExecuteScripts(const char* szNZBFilename, const char* szUrl,
const char* szDirectory, char** pNZBName, char** pCategory, int* iPriority,
NZBParameterList* pParameters, bool* bAddTop, bool* bAddPaused,
char** pDupeKey, int* iDupeScore, EDupeMode* eDupeMode);
private:
const char* m_nzbFilename;
const char* m_url;
const char* m_directory;
CString* m_nzbName;
CString* m_category;
int* m_priority;
NzbParameterList* m_parameters;
bool* m_addTop;
bool* m_addPaused;
CString* m_dupeKey;
int* m_dupeScore;
EDupeMode* m_dupeMode;
int m_prefixLen;
void PrepareParams(const char* scriptName);
};
#endif

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,130 +14,101 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include <sys/stat.h>
#include <stdio.h>
#include "nzbget.h"
#include "SchedulerScript.h"
#include "Options.h"
#include "Log.h"
#include "Util.h"
#include "FileSystem.h"
SchedulerScriptController::~SchedulerScriptController()
void SchedulerScriptController::StartScript(const char* param, bool externalProcess, int taskId)
{
free(m_szScript);
}
void SchedulerScriptController::StartScript(const char* szParam, bool bExternalProcess, int iTaskID)
{
char** argv = NULL;
if (bExternalProcess && !Util::SplitCommandLine(szParam, &argv))
std::vector<CString> argv;
if (externalProcess && (argv = Util::SplitCommandLine(param)).empty())
{
error("Could not execute scheduled process-script, failed to parse command line: %s", szParam);
error("Could not execute scheduled process-script, failed to parse command line: %s", param);
return;
}
SchedulerScriptController* pScriptController = new SchedulerScriptController();
SchedulerScriptController* scriptController = new SchedulerScriptController();
pScriptController->m_bExternalProcess = bExternalProcess;
pScriptController->m_szScript = strdup(szParam);
pScriptController->m_iTaskID = iTaskID;
scriptController->m_externalProcess = externalProcess;
scriptController->m_script = param;
scriptController->m_taskId = taskId;
if (bExternalProcess)
if (externalProcess)
{
pScriptController->SetScript(argv[0]);
pScriptController->SetArgs((const char**)argv, true);
scriptController->SetArgs(std::move(argv));
}
pScriptController->SetAutoDestroy(true);
scriptController->SetAutoDestroy(true);
pScriptController->Start();
scriptController->Start();
}
void SchedulerScriptController::Run()
{
if (m_bExternalProcess)
if (m_externalProcess)
{
ExecuteExternalProcess();
}
else
{
ExecuteScriptList(m_szScript);
ExecuteScriptList(m_script);
}
}
void SchedulerScriptController::ExecuteScript(ScriptConfig::Script* pScript)
void SchedulerScriptController::ExecuteScript(ScriptConfig::Script* script)
{
if (!pScript->GetSchedulerScript())
if (!script->GetSchedulerScript())
{
return;
}
PrintMessage(Message::mkInfo, "Executing scheduler-script %s for Task%i", pScript->GetName(), m_iTaskID);
BString<1024> taskName(" for Task%i", m_taskId);
if (m_taskId == 0)
{
taskName = "";
}
SetScript(pScript->GetLocation());
SetArgs(NULL, false);
PrintMessage(Message::mkInfo, "Executing scheduler-script %s%s", script->GetName(), *taskName);
char szInfoName[1024];
snprintf(szInfoName, 1024, "scheduler-script %s for Task%i", pScript->GetName(), m_iTaskID);
szInfoName[1024-1] = '\0';
SetInfoName(szInfoName);
SetArgs({script->GetLocation()});
SetLogPrefix(pScript->GetDisplayName());
PrepareParams(pScript->GetName());
BString<1024> infoName("scheduler-script %s%s", script->GetName(), *taskName);
SetInfoName(infoName);
SetLogPrefix(script->GetDisplayName());
PrepareParams(script->GetName());
Execute();
SetLogPrefix(NULL);
SetLogPrefix(nullptr);
}
void SchedulerScriptController::PrepareParams(const char* szScriptName)
void SchedulerScriptController::PrepareParams(const char* scriptName)
{
ResetEnv();
SetIntEnvVar("NZBSP_TASKID", m_iTaskID);
SetIntEnvVar("NZBSP_TASKID", m_taskId);
PrepareEnvScript(NULL, szScriptName);
PrepareEnvScript(nullptr, scriptName);
}
void SchedulerScriptController::ExecuteExternalProcess()
{
info("Executing scheduled process-script %s for Task%i", Util::BaseFileName(GetScript()), m_iTaskID);
info("Executing scheduled process-script %s for Task%i", FileSystem::BaseFileName(GetScript()), m_taskId);
char szInfoName[1024];
snprintf(szInfoName, 1024, "scheduled process-script %s for Task%i", Util::BaseFileName(GetScript()), m_iTaskID);
szInfoName[1024-1] = '\0';
SetInfoName(szInfoName);
BString<1024> infoName("scheduled process-script %s for Task%i", FileSystem::BaseFileName(GetScript()), m_taskId);
SetInfoName(infoName);
char szLogPrefix[1024];
strncpy(szLogPrefix, Util::BaseFileName(GetScript()), 1024);
szLogPrefix[1024-1] = '\0';
if (char* ext = strrchr(szLogPrefix, '.')) *ext = '\0'; // strip file extension
SetLogPrefix(szLogPrefix);
BString<1024> logPrefix = FileSystem::BaseFileName(GetScript());
if (char* ext = strrchr(logPrefix, '.')) *ext = '\0'; // strip file extension
SetLogPrefix(logPrefix);
Execute();
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,37 +14,32 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SCHEDULERSCRIPT_H
#define SCHEDULERSCRIPT_H
#include "NString.h"
#include "NzbScript.h"
class SchedulerScriptController : public Thread, public NZBScriptController
class SchedulerScriptController : public Thread, public NzbScriptController
{
private:
char* m_szScript;
bool m_bExternalProcess;
int m_iTaskID;
void PrepareParams(const char* szScriptName);
void ExecuteExternalProcess();
public:
virtual void Run();
static void StartScript(const char* param, bool externalProcess, int taskId);
protected:
virtual void ExecuteScript(ScriptConfig::Script* pScript);
virtual void ExecuteScript(ScriptConfig::Script* script);
public:
virtual ~SchedulerScriptController();
virtual void Run();
static void StartScript(const char* szParam, bool bExternalProcess, int iTaskID);
private:
CString m_script;
bool m_externalProcess;
int m_taskId;
void PrepareParams(const char* scriptName);
void ExecuteExternalProcess();
};
#endif

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2013-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2013-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,32 +14,13 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <sys/stat.h>
#include <set>
#include "nzbget.h"
#include "Util.h"
#include "FileSystem.h"
#include "Options.h"
#include "Log.h"
#include "ScriptConfig.h"
@@ -52,118 +33,31 @@ static const char* SCHEDULER_SCRIPT_SIGNATURE = "SCHEDULER";
static const char* FEED_SCRIPT_SIGNATURE = "FEED";
static const char* END_SCRIPT_SIGNATURE = " SCRIPT";
static const char* QUEUE_EVENTS_SIGNATURE = "### QUEUE EVENTS:";
static const char* TASK_TIME_SIGNATURE = "### TASK TIME:";
static const char* DEFINITION_SIGNATURE = "###";
ScriptConfig* g_pScriptConfig = NULL;
ScriptConfig::ConfigTemplate::ConfigTemplate(Script* pScript, const char* szTemplate)
{
m_pScript = pScript;
m_szTemplate = strdup(szTemplate ? szTemplate : "");
}
ScriptConfig::ConfigTemplate::~ConfigTemplate()
{
delete m_pScript;
free(m_szTemplate);
}
ScriptConfig::ConfigTemplates::~ConfigTemplates()
{
for (iterator it = begin(); it != end(); it++)
{
delete *it;
}
}
ScriptConfig::Script::Script(const char* szName, const char* szLocation)
{
m_szName = strdup(szName);
m_szLocation = strdup(szLocation);
m_szDisplayName = strdup(szName);
m_bPostScript = false;
m_bScanScript = false;
m_bQueueScript = false;
m_bSchedulerScript = false;
m_bFeedScript = false;
m_szQueueEvents = NULL;
}
ScriptConfig::Script::~Script()
{
free(m_szName);
free(m_szLocation);
free(m_szDisplayName);
free(m_szQueueEvents);
}
void ScriptConfig::Script::SetDisplayName(const char* szDisplayName)
{
free(m_szDisplayName);
m_szDisplayName = strdup(szDisplayName);
}
void ScriptConfig::Script::SetQueueEvents(const char* szQueueEvents)
{
free(m_szQueueEvents);
m_szQueueEvents = szQueueEvents ? strdup(szQueueEvents) : NULL;
}
ScriptConfig::Scripts::~Scripts()
{
Clear();
}
void ScriptConfig::Scripts::Clear()
{
for (iterator it = begin(); it != end(); it++)
{
delete *it;
}
clear();
}
ScriptConfig::Script* ScriptConfig::Scripts::Find(const char* szName)
{
for (iterator it = begin(); it != end(); it++)
{
Script* pScript = *it;
if (!strcmp(pScript->GetName(), szName))
{
return pScript;
}
}
return NULL;
}
ScriptConfig::ScriptConfig()
void ScriptConfig::InitOptions()
{
InitScripts();
InitConfigTemplates();
CreateTasks();
}
ScriptConfig::~ScriptConfig()
{
}
bool ScriptConfig::LoadConfig(Options::OptEntries* pOptEntries)
bool ScriptConfig::LoadConfig(Options::OptEntries* optEntries)
{
// read config file
FILE* infile = fopen(g_pOptions->GetConfigFilename(), FOPEN_RB);
DiskFile infile;
if (!infile)
if (!infile.Open(g_Options->GetConfigFilename(), DiskFile::omRead))
{
return false;
}
int iBufLen = (int)Util::FileSize(g_pOptions->GetConfigFilename()) + 1;
char* buf = (char*)malloc(iBufLen);
int fileLen = (int)FileSystem::FileSize(g_Options->GetConfigFilename());
CString buf;
buf.Reserve(fileLen);
while (fgets(buf, iBufLen - 1, infile))
while (infile.ReadLine(buf, fileLen + 1))
{
// remove trailing '\n' and '\r' and spaces
Util::TrimRight(buf);
@@ -174,122 +68,101 @@ bool ScriptConfig::LoadConfig(Options::OptEntries* pOptEntries)
continue;
}
char* optname;
char* optvalue;
if (g_pOptions->SplitOptionString(buf, &optname, &optvalue))
CString optname;
CString optvalue;
if (Options::SplitOptionString(buf, optname, optvalue))
{
Options::OptEntry* pOptEntry = new Options::OptEntry();
pOptEntry->SetName(optname);
pOptEntry->SetValue(optvalue);
pOptEntries->push_back(pOptEntry);
free(optname);
free(optvalue);
optEntries->emplace_back(optname, optvalue);
}
}
fclose(infile);
free(buf);
infile.Close();
Options::ConvertOldOptions(optEntries);
return true;
}
bool ScriptConfig::SaveConfig(Options::OptEntries* pOptEntries)
bool ScriptConfig::SaveConfig(Options::OptEntries* optEntries)
{
// save to config file
FILE* infile = fopen(g_pOptions->GetConfigFilename(), FOPEN_RBP);
DiskFile infile;
if (!infile)
if (!infile.Open(g_Options->GetConfigFilename(), DiskFile::omReadWrite))
{
return false;
}
std::vector<char*> config;
std::vector<CString> config;
std::set<Options::OptEntry*> writtenOptions;
// read config file into memory array
int iBufLen = (int)Util::FileSize(g_pOptions->GetConfigFilename()) + 1;
char* buf = (char*)malloc(iBufLen);
while (fgets(buf, iBufLen - 1, infile))
int fileLen = (int)FileSystem::FileSize(g_Options->GetConfigFilename()) + 1;
CString content;
content.Reserve(fileLen);
while (infile.ReadLine(content, fileLen + 1))
{
config.push_back(strdup(buf));
config.push_back(*content);
}
free(buf);
content.Clear();
// write config file back to disk, replace old values of existing options with new values
rewind(infile);
for (std::vector<char*>::iterator it = config.begin(); it != config.end(); it++)
{
char* buf = *it;
infile.Seek(0);
for (CString& buf : config)
{
const char* eq = strchr(buf, '=');
if (eq && buf[0] != '#')
{
// remove trailing '\n' and '\r' and spaces
Util::TrimRight(buf);
buf.TrimRight();
char* optname;
char* optvalue;
if (g_pOptions->SplitOptionString(buf, &optname, &optvalue))
CString optname;
CString optvalue;
if (g_Options->SplitOptionString(buf, optname, optvalue))
{
Options::OptEntry *pOptEntry = pOptEntries->FindOption(optname);
if (pOptEntry)
Options::OptEntry* optEntry = optEntries->FindOption(optname);
if (optEntry)
{
fputs(pOptEntry->GetName(), infile);
fputs("=", infile);
fputs(pOptEntry->GetValue(), infile);
fputs("\n", infile);
writtenOptions.insert(pOptEntry);
infile.Print("%s=%s\n", optEntry->GetName(), optEntry->GetValue());
writtenOptions.insert(optEntry);
}
free(optname);
free(optvalue);
}
}
else
{
fputs(buf, infile);
infile.Print("%s", *buf);
}
free(buf);
}
// write new options
for (Options::OptEntries::iterator it = pOptEntries->begin(); it != pOptEntries->end(); it++)
for (Options::OptEntry& optEntry : *optEntries)
{
Options::OptEntry* pOptEntry = *it;
std::set<Options::OptEntry*>::iterator fit = writtenOptions.find(pOptEntry);
std::set<Options::OptEntry*>::iterator fit = writtenOptions.find(&optEntry);
if (fit == writtenOptions.end())
{
fputs(pOptEntry->GetName(), infile);
fputs("=", infile);
fputs(pOptEntry->GetValue(), infile);
fputs("\n", infile);
infile.Print("%s=%s\n", optEntry.GetName(), optEntry.GetValue());
}
}
// close and truncate the file
int pos = (int)ftell(infile);
fclose(infile);
int pos = (int)infile.Position();
infile.Close();
Util::TruncateFile(g_pOptions->GetConfigFilename(), pos);
FileSystem::TruncateFile(g_Options->GetConfigFilename(), pos);
return true;
}
bool ScriptConfig::LoadConfigTemplates(ConfigTemplates* pConfigTemplates)
bool ScriptConfig::LoadConfigTemplates(ConfigTemplates* configTemplates)
{
char* szBuffer;
int iLength;
if (!Util::LoadFileIntoBuffer(g_pOptions->GetConfigTemplate(), &szBuffer, &iLength))
CharBuffer buffer;
if (!FileSystem::LoadFileIntoBuffer(g_Options->GetConfigTemplate(), buffer, true))
{
return false;
}
ConfigTemplate* pConfigTemplate = new ConfigTemplate(NULL, szBuffer);
pConfigTemplates->push_back(pConfigTemplate);
free(szBuffer);
configTemplates->emplace_back(Script("", ""), buffer);
if (!g_pOptions->GetScriptDir())
if (!g_Options->GetScriptDir())
{
return true;
}
@@ -297,28 +170,26 @@ bool ScriptConfig::LoadConfigTemplates(ConfigTemplates* pConfigTemplates)
Scripts scriptList;
LoadScripts(&scriptList);
const int iBeginSignatureLen = strlen(BEGIN_SCRIPT_SIGNATURE);
const int iQueueEventsSignatureLen = strlen(QUEUE_EVENTS_SIGNATURE);
const int beginSignatureLen = strlen(BEGIN_SCRIPT_SIGNATURE);
const int definitionSignatureLen = strlen(DEFINITION_SIGNATURE);
for (Scripts::iterator it = scriptList.begin(); it != scriptList.end(); it++)
for (Script& script : scriptList)
{
Script* pScript = *it;
FILE* infile = fopen(pScript->GetLocation(), FOPEN_RB);
if (!infile)
DiskFile infile;
if (!infile.Open(script.GetLocation(), DiskFile::omRead))
{
ConfigTemplate* pConfigTemplate = new ConfigTemplate(pScript, "");
pConfigTemplates->push_back(pConfigTemplate);
configTemplates->emplace_back(std::move(script), "");
continue;
}
StringBuilder stringBuilder;
StringBuilder templ;
char buf[1024];
bool bInConfig = false;
bool inConfig = false;
bool inHeader = false;
while (fgets(buf, sizeof(buf) - 1, infile))
while (infile.ReadLine(buf, sizeof(buf) - 1))
{
if (!strncmp(buf, BEGIN_SCRIPT_SIGNATURE, iBeginSignatureLen) &&
if (!strncmp(buf, BEGIN_SCRIPT_SIGNATURE, beginSignatureLen) &&
strstr(buf, END_SCRIPT_SIGNATURE) &&
(strstr(buf, POST_SCRIPT_SIGNATURE) ||
strstr(buf, SCAN_SCRIPT_SIGNATURE) ||
@@ -326,37 +197,34 @@ bool ScriptConfig::LoadConfigTemplates(ConfigTemplates* pConfigTemplates)
strstr(buf, SCHEDULER_SCRIPT_SIGNATURE) ||
strstr(buf, FEED_SCRIPT_SIGNATURE)))
{
if (bInConfig)
if (inConfig)
{
break;
}
bInConfig = true;
inConfig = true;
inHeader = true;
continue;
}
bool bSkip = !strncmp(buf, QUEUE_EVENTS_SIGNATURE, iQueueEventsSignatureLen);
inHeader &= !strncmp(buf, DEFINITION_SIGNATURE, definitionSignatureLen);
if (bInConfig && !bSkip)
if (inConfig && !inHeader)
{
stringBuilder.Append(buf);
templ.Append(buf);
}
}
fclose(infile);
infile.Close();
ConfigTemplate* pConfigTemplate = new ConfigTemplate(pScript, stringBuilder.GetBuffer());
pConfigTemplates->push_back(pConfigTemplate);
configTemplates->emplace_back(std::move(script), templ);
}
// clearing the list without deleting of objects, which are in pConfigTemplates now
scriptList.clear();
return true;
}
void ScriptConfig::InitConfigTemplates()
{
if (!LoadConfigTemplates(&m_ConfigTemplates))
if (!LoadConfigTemplates(&m_configTemplates))
{
error("Could not read configuration templates");
}
@@ -364,196 +232,269 @@ void ScriptConfig::InitConfigTemplates()
void ScriptConfig::InitScripts()
{
LoadScripts(&m_Scripts);
LoadScripts(&m_scripts);
}
void ScriptConfig::LoadScripts(Scripts* pScripts)
void ScriptConfig::LoadScripts(Scripts* scripts)
{
if (strlen(g_pOptions->GetScriptDir()) == 0)
if (Util::EmptyStr(g_Options->GetScriptDir()))
{
return;
}
Scripts tmpScripts;
LoadScriptDir(&tmpScripts, g_pOptions->GetScriptDir(), false);
tmpScripts.sort(CompareScripts);
// first add all scripts from m_szScriptOrder
Tokenizer tok(g_pOptions->GetScriptOrder(), ",;");
while (const char* szScriptName = tok.Next())
Tokenizer tokDir(g_Options->GetScriptDir(), ",;");
while (const char* scriptDir = tokDir.Next())
{
Script* pScript = tmpScripts.Find(szScriptName);
if (pScript)
LoadScriptDir(&tmpScripts, scriptDir, false);
}
tmpScripts.sort(
[](Script& script1, Script& script2)
{
tmpScripts.remove(pScript);
pScripts->push_back(pScript);
return strcmp(script1.GetName(), script2.GetName()) < 0;
});
// first add all scripts from ScriptOrder
Tokenizer tokOrder(g_Options->GetScriptOrder(), ",;");
while (const char* scriptName = tokOrder.Next())
{
Scripts::iterator pos = std::find_if(tmpScripts.begin(), tmpScripts.end(),
[scriptName](Script& script)
{
return !strcmp(script.GetName(), scriptName);
});
if (pos != tmpScripts.end())
{
scripts->splice(scripts->end(), tmpScripts, pos);
}
}
// second add all other scripts from scripts directory
for (Scripts::iterator it = tmpScripts.begin(); it != tmpScripts.end(); it++)
{
Script* pScript = *it;
if (!pScripts->Find(pScript->GetName()))
{
pScripts->push_back(pScript);
}
}
// then add all other scripts from scripts directory
scripts->splice(scripts->end(), std::move(tmpScripts));
tmpScripts.clear();
BuildScriptDisplayNames(pScripts);
BuildScriptDisplayNames(scripts);
}
void ScriptConfig::LoadScriptDir(Scripts* pScripts, const char* szDirectory, bool bIsSubDir)
void ScriptConfig::LoadScriptDir(Scripts* scripts, const char* directory, bool isSubDir)
{
int iBufSize = 1024*10;
char* szBuffer = (char*)malloc(iBufSize+1);
const int iBeginSignatureLen = strlen(BEGIN_SCRIPT_SIGNATURE);
const int iQueueEventsSignatureLen = strlen(QUEUE_EVENTS_SIGNATURE);
DirBrowser dir(szDirectory);
while (const char* szFilename = dir.Next())
DirBrowser dir(directory);
while (const char* filename = dir.Next())
{
if (szFilename[0] != '.' && szFilename[0] != '_')
if (filename[0] != '.' && filename[0] != '_')
{
char szFullFilename[1024];
snprintf(szFullFilename, 1024, "%s%s", szDirectory, szFilename);
szFullFilename[1024-1] = '\0';
BString<1024> fullFilename("%s%c%s", directory, PATH_SEPARATOR, filename);
if (!Util::DirectoryExists(szFullFilename))
if (!FileSystem::DirectoryExists(fullFilename))
{
// check if the file contains pp-script-signature
FILE* infile = fopen(szFullFilename, FOPEN_RB);
if (infile)
BString<1024> scriptName = BuildScriptName(directory, filename, isSubDir);
if (ScriptExists(scripts, scriptName))
{
// read first 10KB of the file and look for signature
int iReadBytes = fread(szBuffer, 1, iBufSize, infile);
fclose(infile);
szBuffer[iReadBytes] = 0;
continue;
}
// split buffer into lines
Tokenizer tok(szBuffer, "\n\r", true);
while (char* szLine = tok.Next())
{
if (!strncmp(szLine, BEGIN_SCRIPT_SIGNATURE, iBeginSignatureLen) &&
strstr(szLine, END_SCRIPT_SIGNATURE))
{
bool bPostScript = strstr(szLine, POST_SCRIPT_SIGNATURE);
bool bScanScript = strstr(szLine, SCAN_SCRIPT_SIGNATURE);
bool bQueueScript = strstr(szLine, QUEUE_SCRIPT_SIGNATURE);
bool bSchedulerScript = strstr(szLine, SCHEDULER_SCRIPT_SIGNATURE);
bool bFeedScript = strstr(szLine, FEED_SCRIPT_SIGNATURE);
if (bPostScript || bScanScript || bQueueScript || bSchedulerScript || bFeedScript)
{
char szScriptName[1024];
if (bIsSubDir)
{
char szDirectory2[1024];
snprintf(szDirectory2, 1024, "%s", szDirectory);
szDirectory2[1024-1] = '\0';
int iLen = strlen(szDirectory2);
if (szDirectory2[iLen-1] == PATH_SEPARATOR || szDirectory2[iLen-1] == ALT_PATH_SEPARATOR)
{
// trim last path-separator
szDirectory2[iLen-1] = '\0';
}
snprintf(szScriptName, 1024, "%s%c%s", Util::BaseFileName(szDirectory2), PATH_SEPARATOR, szFilename);
}
else
{
snprintf(szScriptName, 1024, "%s", szFilename);
}
szScriptName[1024-1] = '\0';
char* szQueueEvents = NULL;
if (bQueueScript)
{
while (char* szLine = tok.Next())
{
if (!strncmp(szLine, QUEUE_EVENTS_SIGNATURE, iQueueEventsSignatureLen))
{
szQueueEvents = szLine + iQueueEventsSignatureLen;
break;
}
}
}
Script* pScript = new Script(szScriptName, szFullFilename);
pScript->SetPostScript(bPostScript);
pScript->SetScanScript(bScanScript);
pScript->SetQueueScript(bQueueScript);
pScript->SetSchedulerScript(bSchedulerScript);
pScript->SetFeedScript(bFeedScript);
pScript->SetQueueEvents(szQueueEvents);
pScripts->push_back(pScript);
break;
}
}
}
Script script(scriptName, fullFilename);
if (LoadScriptFile(&script))
{
scripts->push_back(std::move(script));
}
}
else if (!bIsSubDir)
else if (!isSubDir)
{
snprintf(szFullFilename, 1024, "%s%s%c", szDirectory, szFilename, PATH_SEPARATOR);
szFullFilename[1024-1] = '\0';
LoadScriptDir(scripts, fullFilename, true);
}
}
}
}
LoadScriptDir(pScripts, szFullFilename, true);
bool ScriptConfig::LoadScriptFile(Script* script)
{
DiskFile infile;
if (!infile.Open(script->GetLocation(), DiskFile::omRead))
{
return false;
}
CharBuffer buffer(1024 * 10 + 1);
const int beginSignatureLen = strlen(BEGIN_SCRIPT_SIGNATURE);
const int queueEventsSignatureLen = strlen(QUEUE_EVENTS_SIGNATURE);
const int taskTimeSignatureLen = strlen(TASK_TIME_SIGNATURE);
const int definitionSignatureLen = strlen(DEFINITION_SIGNATURE);
// check if the file contains pp-script-signature
// read first 10KB of the file and look for signature
int readBytes = (int)infile.Read(buffer, buffer.Size() - 1);
infile.Close();
buffer[readBytes] = '\0';
bool postScript = false;
bool scanScript = false;
bool queueScript = false;
bool schedulerScript = false;
bool feedScript = false;
char* queueEvents = nullptr;
char* taskTime = nullptr;
bool inConfig = false;
bool afterConfig = false;
// Declarations "QUEUE EVENT:" and "TASK TIME:" can be placed:
// - in script definition body (between opening and closing script signatures);
// - immediately before script definition (before opening script signature);
// - immediately after script definition (after closing script signature).
// The last two pissibilities are provided to increase compatibility of scripts with older
// nzbget versions which do not expect the extra declarations in the script defintion body.
Tokenizer tok(buffer, "\n\r", true);
while (char* line = tok.Next())
{
if (!strncmp(line, QUEUE_EVENTS_SIGNATURE, queueEventsSignatureLen))
{
queueEvents = line + queueEventsSignatureLen;
}
else if (!strncmp(line, TASK_TIME_SIGNATURE, taskTimeSignatureLen))
{
taskTime = line + taskTimeSignatureLen;
}
bool header = !strncmp(line, DEFINITION_SIGNATURE, definitionSignatureLen);
if (!header && !inConfig)
{
queueEvents = nullptr;
taskTime = nullptr;
}
if (!header && afterConfig)
{
break;
}
if (!strncmp(line, BEGIN_SCRIPT_SIGNATURE, beginSignatureLen) && strstr(line, END_SCRIPT_SIGNATURE))
{
if (!inConfig)
{
inConfig = true;
postScript = strstr(line, POST_SCRIPT_SIGNATURE);
scanScript = strstr(line, SCAN_SCRIPT_SIGNATURE);
queueScript = strstr(line, QUEUE_SCRIPT_SIGNATURE);
schedulerScript = strstr(line, SCHEDULER_SCRIPT_SIGNATURE);
feedScript = strstr(line, FEED_SCRIPT_SIGNATURE);
}
else
{
afterConfig = true;
}
}
}
free(szBuffer);
if (!(postScript || scanScript || queueScript || schedulerScript || feedScript))
{
return false;
}
// trim decorations
char* p;
while (queueEvents && *queueEvents && *(p = queueEvents + strlen(queueEvents) - 1) == '#') *p = '\0';
if (queueEvents) queueEvents = Util::Trim(queueEvents);
while (taskTime && *taskTime && *(p = taskTime + strlen(taskTime) - 1) == '#') *p = '\0';
if (taskTime) taskTime = Util::Trim(taskTime);
script->SetPostScript(postScript);
script->SetScanScript(scanScript);
script->SetQueueScript(queueScript);
script->SetSchedulerScript(schedulerScript);
script->SetFeedScript(feedScript);
script->SetQueueEvents(queueEvents);
script->SetTaskTime(taskTime);
return true;
}
bool ScriptConfig::CompareScripts(Script* pScript1, Script* pScript2)
BString<1024> ScriptConfig::BuildScriptName(const char* directory, const char* filename, bool isSubDir)
{
return strcmp(pScript1->GetName(), pScript2->GetName()) < 0;
if (isSubDir)
{
BString<1024> directory2 = directory;
int len = strlen(directory2);
if (directory2[len-1] == PATH_SEPARATOR || directory2[len-1] == ALT_PATH_SEPARATOR)
{
// trim last path-separator
directory2[len-1] = '\0';
}
return BString<1024>("%s%c%s", FileSystem::BaseFileName(directory2), PATH_SEPARATOR, filename);
}
else
{
return filename;
}
}
void ScriptConfig::BuildScriptDisplayNames(Scripts* pScripts)
bool ScriptConfig::ScriptExists(Scripts* scripts, const char* scriptName)
{
return std::find_if(scripts->begin(), scripts->end(),
[scriptName](Script& script)
{
return !strcmp(script.GetName(), scriptName);
}) != scripts->end();
}
void ScriptConfig::BuildScriptDisplayNames(Scripts* scripts)
{
// trying to use short name without path and extension.
// if there are other scripts with the same short name - using a longer name instead (with ot without extension)
for (Scripts::iterator it = pScripts->begin(); it != pScripts->end(); it++)
for (Script& script : scripts)
{
Script* pScript = *it;
BString<1024> shortName = script.GetName();
if (char* ext = strrchr(shortName, '.')) *ext = '\0'; // strip file extension
char szShortName[256];
strncpy(szShortName, pScript->GetName(), 256);
szShortName[256-1] = '\0';
if (char* ext = strrchr(szShortName, '.')) *ext = '\0'; // strip file extension
const char* displayName = FileSystem::BaseFileName(shortName);
const char* szDisplayName = Util::BaseFileName(szShortName);
for (Scripts::iterator it2 = pScripts->begin(); it2 != pScripts->end(); it2++)
for (Script& script2 : scripts)
{
Script* pScript2 = *it2;
BString<1024> shortName2 = script2.GetName();
if (char* ext = strrchr(shortName2, '.')) *ext = '\0'; // strip file extension
char szShortName2[256];
strncpy(szShortName2, pScript2->GetName(), 256);
szShortName2[256-1] = '\0';
if (char* ext = strrchr(szShortName2, '.')) *ext = '\0'; // strip file extension
const char* displayName2 = FileSystem::BaseFileName(shortName2);
const char* szDisplayName2 = Util::BaseFileName(szShortName2);
if (!strcmp(szDisplayName, szDisplayName2) && pScript->GetName() != pScript2->GetName())
if (!strcmp(displayName, displayName2) && script.GetName() != script2.GetName())
{
if (!strcmp(szShortName, szShortName2))
if (!strcmp(shortName, shortName2))
{
szDisplayName = pScript->GetName();
displayName = script.GetName();
}
else
{
szDisplayName = szShortName;
displayName = shortName;
}
break;
}
}
pScript->SetDisplayName(szDisplayName);
script.SetDisplayName(displayName);
}
}
void ScriptConfig::CreateTasks()
{
for (Script& script : m_scripts)
{
if (script.GetSchedulerScript() && !Util::EmptyStr(script.GetTaskTime()))
{
Tokenizer tok(g_Options->GetExtensions(), ",;");
while (const char* scriptName = tok.Next())
{
if (FileSystem::SameFilename(scriptName, script.GetName()))
{
g_Options->CreateSchedulerTask(0, script.GetTaskTime(),
nullptr, Options::scScript, script.GetName());
break;
}
}
}
}
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2013-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2013-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,22 +14,15 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SCRIPTCONFIG_H
#define SCRIPTCONFIG_H
#include <vector>
#include <list>
#include <time.h>
#include "NString.h"
#include "Container.h"
#include "Options.h"
class ScriptConfig
@@ -37,92 +30,81 @@ class ScriptConfig
public:
class Script
{
public:
Script(const char* name, const char* location) :
m_name(name), m_location(location), m_displayName(name) {};
Script(Script&&) = default;
const char* GetName() { return m_name; }
const char* GetLocation() { return m_location; }
void SetDisplayName(const char* displayName) { m_displayName = displayName; }
const char* GetDisplayName() { return m_displayName; }
bool GetPostScript() { return m_postScript; }
void SetPostScript(bool postScript) { m_postScript = postScript; }
bool GetScanScript() { return m_scanScript; }
void SetScanScript(bool scanScript) { m_scanScript = scanScript; }
bool GetQueueScript() { return m_queueScript; }
void SetQueueScript(bool queueScript) { m_queueScript = queueScript; }
bool GetSchedulerScript() { return m_schedulerScript; }
void SetSchedulerScript(bool schedulerScript) { m_schedulerScript = schedulerScript; }
bool GetFeedScript() { return m_feedScript; }
void SetFeedScript(bool feedScript) { m_feedScript = feedScript; }
void SetQueueEvents(const char* queueEvents) { m_queueEvents = queueEvents; }
const char* GetQueueEvents() { return m_queueEvents; }
void SetTaskTime(const char* taskTime) { m_taskTime = taskTime; }
const char* GetTaskTime() { return m_taskTime; }
private:
char* m_szName;
char* m_szLocation;
char* m_szDisplayName;
bool m_bPostScript;
bool m_bScanScript;
bool m_bQueueScript;
bool m_bSchedulerScript;
bool m_bFeedScript;
char* m_szQueueEvents;
public:
Script(const char* szName, const char* szLocation);
~Script();
const char* GetName() { return m_szName; }
const char* GetLocation() { return m_szLocation; }
void SetDisplayName(const char* szDisplayName);
const char* GetDisplayName() { return m_szDisplayName; }
bool GetPostScript() { return m_bPostScript; }
void SetPostScript(bool bPostScript) { m_bPostScript = bPostScript; }
bool GetScanScript() { return m_bScanScript; }
void SetScanScript(bool bScanScript) { m_bScanScript = bScanScript; }
bool GetQueueScript() { return m_bQueueScript; }
void SetQueueScript(bool bQueueScript) { m_bQueueScript = bQueueScript; }
bool GetSchedulerScript() { return m_bSchedulerScript; }
void SetSchedulerScript(bool bSchedulerScript) { m_bSchedulerScript = bSchedulerScript; }
bool GetFeedScript() { return m_bFeedScript; }
void SetFeedScript(bool bFeedScript) { m_bFeedScript = bFeedScript; }
void SetQueueEvents(const char* szQueueEvents);
const char* GetQueueEvents() { return m_szQueueEvents; }
CString m_name;
CString m_location;
CString m_displayName;
bool m_postScript = false;
bool m_scanScript = false;
bool m_queueScript = false;
bool m_schedulerScript = false;
bool m_feedScript = false;
CString m_queueEvents;
CString m_taskTime;
};
typedef std::list<Script*> ScriptsBase;
class Scripts: public ScriptsBase
{
public:
~Scripts();
void Clear();
Script* Find(const char* szName);
};
typedef std::list<Script> Scripts;
class ConfigTemplate
{
public:
ConfigTemplate(Script&& script, const char* templ) :
m_script(std::move(script)), m_template(templ) {}
Script* GetScript() { return &m_script; }
const char* GetTemplate() { return m_template; }
private:
Script* m_pScript;
char* m_szTemplate;
friend class Options;
public:
ConfigTemplate(Script* pScript, const char* szTemplate);
~ConfigTemplate();
Script* GetScript() { return m_pScript; }
const char* GetTemplate() { return m_szTemplate; }
Script m_script;
CString m_template;
};
typedef std::vector<ConfigTemplate*> ConfigTemplatesBase;
class ConfigTemplates: public ConfigTemplatesBase
{
public:
~ConfigTemplates();
};
typedef std::deque<ConfigTemplate> ConfigTemplates;
void InitOptions();
Scripts* GetScripts() { return &m_scripts; }
bool LoadConfig(Options::OptEntries* optEntries);
bool SaveConfig(Options::OptEntries* optEntries);
bool LoadConfigTemplates(ConfigTemplates* configTemplates);
ConfigTemplates* GetConfigTemplates() { return &m_configTemplates; }
private:
Scripts m_Scripts;
ConfigTemplates m_ConfigTemplates;
Scripts m_scripts;
ConfigTemplates m_configTemplates;
void InitScripts();
void InitConfigTemplates();
static bool CompareScripts(Script* pScript1, Script* pScript2);
void LoadScriptDir(Scripts* pScripts, const char* szDirectory, bool bIsSubDir);
void BuildScriptDisplayNames(Scripts* pScripts);
void LoadScripts(Scripts* pScripts);
public:
ScriptConfig();
~ScriptConfig();
Scripts* GetScripts() { return &m_Scripts; }
bool LoadConfig(Options::OptEntries* pOptEntries);
bool SaveConfig(Options::OptEntries* pOptEntries);
bool LoadConfigTemplates(ConfigTemplates* pConfigTemplates);
ConfigTemplates* GetConfigTemplates() { return &m_ConfigTemplates; }
void InitScripts();
void InitConfigTemplates();
void CreateTasks();
void LoadScriptDir(Scripts* scripts, const char* directory, bool isSubDir);
void BuildScriptDisplayNames(Scripts* scripts);
void LoadScripts(Scripts* scripts);
bool LoadScriptFile(Script* script);
BString<1024>BuildScriptName(const char* directory, const char* filename, bool isSubDir);
bool ScriptExists(Scripts* scripts, const char* scriptName);
};
extern ScriptConfig* g_pScriptConfig;
extern ScriptConfig* g_ScriptConfig;
#endif

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2013-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2013-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,26 +14,19 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef FEEDCOORDINATOR_H
#define FEEDCOORDINATOR_H
#include <deque>
#include <list>
#include <time.h>
#include "NString.h"
#include "Log.h"
#include "Thread.h"
#include "WebDownloader.h"
#include "DownloadInfo.h"
#include "FeedFile.h"
#include "FeedInfo.h"
#include "Observer.h"
#include "Util.h"
@@ -43,100 +36,115 @@ class FeedDownloader;
class FeedCoordinator : public Thread, public Observer, public Subject, public Debuggable
{
public:
FeedCoordinator();
virtual ~FeedCoordinator();
virtual void Run();
virtual void Stop();
void Update(Subject* caller, void* aspect);
void AddFeed(std::unique_ptr<FeedInfo> feedInfo) { m_feeds.push_back(std::move(feedInfo)); }
/* may return empty pointer on error */
std::shared_ptr<FeedItemList> PreviewFeed(int id, const char* name, const char* url,
const char* filter, bool backlog, bool pauseNzb, const char* category, int priority,
int interval, const char* feedScript, int cacheTimeSec, const char* cacheId);
/* may return empty pointer on error */
std::shared_ptr<FeedItemList> ViewFeed(int id);
void FetchFeed(int id);
bool HasActiveDownloads();
Feeds* GetFeeds() { return &m_feeds; }
protected:
virtual void LogDebugInfo();
private:
class DownloadQueueObserver: public Observer
{
public:
FeedCoordinator* m_pOwner;
virtual void Update(Subject* pCaller, void* pAspect) { m_pOwner->DownloadQueueUpdate(pCaller, pAspect); }
FeedCoordinator* m_owner;
virtual void Update(Subject* caller, void* aspect) { m_owner->DownloadQueueUpdate(caller, aspect); }
};
class WorkStateObserver: public Observer
{
public:
FeedCoordinator* m_owner;
virtual void Update(Subject* caller, void* aspect) { m_owner->WorkStateUpdate(caller, aspect); }
};
class FeedCacheItem
{
private:
char* m_szUrl;
int m_iCacheTimeSec;
char* m_szCacheId;
time_t m_tLastUsage;
FeedItemInfos* m_pFeedItemInfos;
public:
FeedCacheItem(const char* szUrl, int iCacheTimeSec,const char* szCacheId,
time_t tLastUsage, FeedItemInfos* pFeedItemInfos);
~FeedCacheItem();
const char* GetUrl() { return m_szUrl; }
int GetCacheTimeSec() { return m_iCacheTimeSec; }
const char* GetCacheId() { return m_szCacheId; }
time_t GetLastUsage() { return m_tLastUsage; }
void SetLastUsage(time_t tLastUsage) { m_tLastUsage = tLastUsage; }
FeedItemInfos* GetFeedItemInfos() { return m_pFeedItemInfos; }
FeedCacheItem(const char* url, int cacheTimeSec,const char* cacheId,
time_t lastUsage, std::shared_ptr<FeedItemList> feedItems) :
m_url(url), m_cacheTimeSec(cacheTimeSec), m_cacheId(cacheId),
m_lastUsage(lastUsage), m_feedItems(feedItems) {}
const char* GetUrl() { return m_url; }
int GetCacheTimeSec() { return m_cacheTimeSec; }
const char* GetCacheId() { return m_cacheId; }
time_t GetLastUsage() { return m_lastUsage; }
void SetLastUsage(time_t lastUsage) { m_lastUsage = lastUsage; }
std::shared_ptr<FeedItemList> GetFeedItems() { return m_feedItems; }
private:
CString m_url;
int m_cacheTimeSec;
CString m_cacheId;
time_t m_lastUsage;
std::shared_ptr<FeedItemList> m_feedItems;
};
class FilterHelper : public FeedFilterHelper
{
private:
RegEx* m_pSeasonEpisodeRegEx;
public:
FilterHelper();
~FilterHelper();
virtual RegEx** GetSeasonEpisodeRegEx() { return &m_pSeasonEpisodeRegEx; };
virtual void CalcDupeStatus(const char* szTitle, const char* szDupeKey, char* szStatusBuf, int iBufLen);
virtual std::unique_ptr<RegEx>& GetRegEx(int id);
virtual void CalcDupeStatus(const char* title, const char* dupeKey, char* statusBuf, int bufLen);
private:
std::vector<std::unique_ptr<RegEx>> m_regExes;
};
typedef std::deque<FeedCacheItem*> FeedCache;
typedef std::list<FeedDownloader*> ActiveDownloads;
typedef std::list<FeedCacheItem> FeedCache;
typedef std::deque<FeedDownloader*> ActiveDownloads;
private:
Feeds m_Feeds;
ActiveDownloads m_ActiveDownloads;
FeedHistory m_FeedHistory;
Mutex m_mutexDownloads;
DownloadQueueObserver m_DownloadQueueObserver;
bool m_bForce;
bool m_bSave;
FeedCache m_FeedCache;
FilterHelper m_FilterHelper;
Feeds m_feeds;
ActiveDownloads m_activeDownloads;
FeedHistory m_feedHistory;
Mutex m_downloadsMutex;
DownloadQueueObserver m_downloadQueueObserver;
WorkStateObserver m_workStateObserver;
bool m_force = false;
bool m_save = false;
FeedCache m_feedCache;
ConditionVar m_waitCond;
bool m_wokenUp = false;
void StartFeedDownload(FeedInfo* pFeedInfo, bool bForce);
void FeedCompleted(FeedDownloader* pFeedDownloader);
void FilterFeed(FeedInfo* pFeedInfo, FeedItemInfos* pFeedItemInfos);
void ProcessFeed(FeedInfo* pFeedInfo, FeedItemInfos* pFeedItemInfos, NZBList* pAddedNZBs);
NZBInfo* CreateNZBInfo(FeedInfo* pFeedInfo, FeedItemInfo* pFeedItemInfo);
void ResetHangingDownloads();
void DownloadQueueUpdate(Subject* pCaller, void* pAspect);
void CleanupHistory();
void CleanupCache();
void CheckSaveFeeds();
protected:
virtual void LogDebugInfo();
public:
FeedCoordinator();
virtual ~FeedCoordinator();
virtual void Run();
virtual void Stop();
void Update(Subject* pCaller, void* pAspect);
void AddFeed(FeedInfo* pFeedInfo);
bool PreviewFeed(int iID, const char* szName, const char* szUrl, const char* szFilter, bool bBacklog,
bool bPauseNzb, const char* szCategory, int iPriority, int iInterval, const char* szFeedScript,
int iCacheTimeSec, const char* szCacheId, FeedItemInfos** ppFeedItemInfos);
bool ViewFeed(int iID, FeedItemInfos** ppFeedItemInfos);
void FetchFeed(int iID);
bool HasActiveDownloads();
Feeds* GetFeeds() { return &m_Feeds; }
void StartFeedDownload(FeedInfo* feedInfo, bool force);
void FeedCompleted(FeedDownloader* feedDownloader);
void FilterFeed(FeedInfo* feedInfo, FeedItemList* feedItems);
std::vector<std::unique_ptr<NzbInfo>> ProcessFeed(FeedInfo* feedInfo, FeedItemList* feedItems);
std::unique_ptr<NzbInfo> CreateNzbInfo(FeedInfo* feedInfo, FeedItemInfo& feedItemInfo);
void ResetHangingDownloads();
void DownloadQueueUpdate(Subject* caller, void* aspect);
void CleanupHistory();
void CleanupCache();
void CheckSaveFeeds();
std::unique_ptr<FeedFile> parseFeed(FeedInfo* feedInfo);
void SchedulerNextUpdate(FeedInfo* feedInfo, bool success);
void WorkStateUpdate(Subject* caller, void* aspect);
};
extern FeedCoordinator* g_pFeedCoordinator;
extern FeedCoordinator* g_FeedCoordinator;
class FeedDownloader : public WebDownloader
{
private:
FeedInfo* m_pFeedInfo;
public:
void SetFeedInfo(FeedInfo* pFeedInfo) { m_pFeedInfo = pFeedInfo; }
FeedInfo* GetFeedInfo() { return m_pFeedInfo; }
void SetFeedInfo(FeedInfo* feedInfo) { m_feedInfo = feedInfo; }
FeedInfo* GetFeedInfo() { return m_feedInfo; }
private:
FeedInfo* m_feedInfo;
};
#endif

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2013-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2013-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,36 +14,10 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <string.h>
#include <list>
#ifdef WIN32
#include <comutil.h>
#import <msxml.tlb> named_guids
using namespace MSXML;
#else
#include <libxml/parser.h>
#include <libxml/xmlreader.h>
#include <libxml/xmlerror.h>
#include <libxml/entities.h>
#endif
#include "nzbget.h"
#include "FeedFile.h"
#include "Log.h"
@@ -51,49 +25,28 @@ using namespace MSXML;
#include "Options.h"
#include "Util.h"
FeedFile::FeedFile(const char* szFileName)
FeedFile::FeedFile(const char* fileName, const char* infoName) :
m_fileName(fileName), m_infoName(infoName)
{
debug("Creating FeedFile");
debug("Creating FeedFile");
m_szFileName = strdup(szFileName);
m_pFeedItemInfos = new FeedItemInfos();
m_pFeedItemInfos->Retain();
m_feedItems = std::make_unique<FeedItemList>();
#ifndef WIN32
m_pFeedItemInfo = NULL;
m_szTagContent = NULL;
m_iTagContentLen = 0;
#endif
}
FeedFile::~FeedFile()
{
debug("Destroying FeedFile");
// Cleanup
free(m_szFileName);
m_pFeedItemInfos->Release();
#ifndef WIN32
delete m_pFeedItemInfo;
free(m_szTagContent);
m_feedItemInfo = nullptr;
m_tagContent.Clear();
#endif
}
void FeedFile::LogDebugInfo()
{
info(" FeedFile %s", m_szFileName);
info(" FeedFile %s", *m_fileName);
}
void FeedFile::AddItem(FeedItemInfo* pFeedItemInfo)
void FeedFile::ParseSubject(FeedItemInfo& feedItemInfo)
{
m_pFeedItemInfos->Add(pFeedItemInfo);
}
void FeedFile::ParseSubject(FeedItemInfo* pFeedItemInfo)
{
// if title has quatation marks we use only part within quatation marks
char* p = (char*)pFeedItemInfo->GetTitle();
// if title has quatation marks we use only part within quatation marks
char* p = (char*)feedItemInfo.GetTitle();
char* start = strchr(p, '\"');
if (start)
{
@@ -105,9 +58,7 @@ void FeedFile::ParseSubject(FeedItemInfo* pFeedItemInfo)
char* point = strchr(start + 1, '.');
if (point && point < end)
{
char* filename = (char*)malloc(len + 1);
strncpy(filename, start, len);
filename[len] = '\0';
CString filename(start, len);
char* ext = strrchr(filename, '.');
if (ext && !strcasecmp(ext, ".par2"))
@@ -115,81 +66,87 @@ void FeedFile::ParseSubject(FeedItemInfo* pFeedItemInfo)
*ext = '\0';
}
pFeedItemInfo->SetFilename(filename);
free(filename);
feedItemInfo.SetFilename(filename);
return;
}
}
}
pFeedItemInfo->SetFilename(pFeedItemInfo->GetTitle());
feedItemInfo.SetFilename(feedItemInfo.GetTitle());
}
#ifdef WIN32
FeedFile* FeedFile::Create(const char* szFileName)
bool FeedFile::Parse()
{
CoInitialize(NULL);
CoInitialize(nullptr);
HRESULT hr;
MSXML::IXMLDOMDocumentPtr doc;
hr = doc.CreateInstance(MSXML::CLSID_DOMDocument);
if (FAILED(hr))
{
return NULL;
}
if (FAILED(hr))
{
return false;
}
// Load the XML document file...
// Load the XML document file...
doc->put_resolveExternals(VARIANT_FALSE);
doc->put_validateOnParse(VARIANT_FALSE);
doc->put_async(VARIANT_FALSE);
// filename needs to be properly encoded
char* szURL = (char*)malloc(strlen(szFileName)*3 + 1);
EncodeURL(szFileName, szURL);
debug("url=\"%s\"", szURL);
_variant_t v(szURL);
free(szURL);
_variant_t vFilename(*WString(m_fileName));
// 1. first trying to load via filename without URL-encoding (certain charaters doesn't work when encoded)
VARIANT_BOOL success = doc->load(vFilename);
if (success == VARIANT_FALSE)
{
// 2. now trying filename encoded as URL
char url[2048];
EncodeUrl(m_fileName, url, 2048);
debug("url=\"%s\"", url);
_variant_t vUrl(url);
success = doc->load(vUrl);
}
VARIANT_BOOL success = doc->load(v);
if (success == VARIANT_FALSE)
{
_bstr_t r(doc->GetparseError()->reason);
const char* szErrMsg = r;
error("Error parsing rss feed: %s", szErrMsg);
return NULL;
const char* errMsg = r;
error("Error parsing rss feed %s: %s", *m_infoName, errMsg);
return false;
}
FeedFile* pFile = new FeedFile(szFileName);
if (!pFile->ParseFeed(doc))
{
delete pFile;
pFile = NULL;
}
bool ok = ParseFeed(doc);
return pFile;
return ok;
}
void FeedFile::EncodeURL(const char* szFilename, char* szURL)
void FeedFile::EncodeUrl(const char* filename, char* url, int bufLen)
{
while (char ch = *szFilename++)
WString widefilename(filename);
char* end = url + bufLen;
for (wchar_t* p = widefilename; *p && url < end - 3; p++)
{
wchar_t ch = *p;
if (('0' <= ch && ch <= '9') ||
('a' <= ch && ch <= 'z') ||
('A' <= ch && ch <= 'Z') )
('A' <= ch && ch <= 'Z') ||
ch == '-' || ch == '.' || ch == '_' || ch == '~')
{
*szURL++ = ch;
*url++ = (char)ch;
}
else
{
*szURL++ = '%';
int a = ch >> 4;
*szURL++ = a > 9 ? a - 10 + 'a' : a + '0';
*url++ = '%';
uint32 a = (uint32)ch >> 4;
*url++ = a > 9 ? a - 10 + 'A' : a + '0';
a = ch & 0xF;
*szURL++ = a > 9 ? a - 10 + 'a' : a + '0';
*url++ = a > 9 ? a - 10 + 'A' : a + '0';
}
}
*szURL = NULL;
*url = '\0';
}
bool FeedFile::ParseFeed(IUnknown* nzb)
@@ -202,13 +159,13 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
{
MSXML::IXMLDOMNodePtr node = itemList->Getitem(i);
FeedItemInfo* pFeedItemInfo = new FeedItemInfo();
AddItem(pFeedItemInfo);
m_feedItems->emplace_back();
FeedItemInfo& feedItemInfo = m_feedItems->back();
MSXML::IXMLDOMNodePtr tag;
MSXML::IXMLDOMNodePtr attr;
// <title>Debian 6</title>
// <title>Debian 6</title>
tag = node->selectSingleNode("title");
if (!tag)
{
@@ -216,8 +173,8 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
return false;
}
_bstr_t title(tag->Gettext());
pFeedItemInfo->SetTitle(title);
ParseSubject(pFeedItemInfo);
feedItemInfo.SetTitle(title);
ParseSubject(feedItemInfo);
// <pubDate>Wed, 26 Jun 2013 00:02:54 -0600</pubDate>
tag = node->selectSingleNode("pubDate");
@@ -227,7 +184,7 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
time_t unixtime = WebUtil::ParseRfc822DateTime(time);
if (unixtime > 0)
{
pFeedItemInfo->SetTime(unixtime);
feedItemInfo.SetTime(unixtime);
}
}
@@ -236,21 +193,20 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
if (tag)
{
_bstr_t category(tag->Gettext());
pFeedItemInfo->SetCategory(category);
feedItemInfo.SetCategory(category);
}
// <description>long text</description>
tag = node->selectSingleNode("description");
if (tag)
{
_bstr_t description(tag->Gettext());
_bstr_t bdescription(tag->Gettext());
// cleanup CDATA
char* szDescription = strdup((const char*)description);
WebUtil::XmlStripTags(szDescription);
WebUtil::XmlDecode(szDescription);
WebUtil::XmlRemoveEntities(szDescription);
pFeedItemInfo->SetDescription(szDescription);
free(szDescription);
CString description = (const char*)bdescription;
WebUtil::XmlStripTags(description);
WebUtil::XmlDecode(description);
WebUtil::XmlRemoveEntities(description);
feedItemInfo.SetDescription(description);
}
//<enclosure url="http://myindexer.com/fetch/9eeb264aecce961a6e0d" length="150263340" type="application/x-nzb" />
@@ -261,19 +217,19 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
if (attr)
{
_bstr_t url(attr->Gettext());
pFeedItemInfo->SetUrl(url);
feedItemInfo.SetUrl(url);
}
attr = tag->Getattributes()->getNamedItem("length");
if (attr)
{
_bstr_t size(attr->Gettext());
long long lSize = atoll(size);
pFeedItemInfo->SetSize(lSize);
_bstr_t bsize(attr->Gettext());
int64 size = atoll(bsize);
feedItemInfo.SetSize(size);
}
}
if (!pFeedItemInfo->GetUrl())
if (!feedItemInfo.GetUrl())
{
// <link>https://nzb.org/fetch/334534ce/4364564564</link>
tag = node->selectSingleNode("link");
@@ -283,81 +239,107 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
return false;
}
_bstr_t link(tag->Gettext());
pFeedItemInfo->SetUrl(link);
feedItemInfo.SetUrl(link);
}
// newznab special
//<newznab:attr name="size" value="5423523453534" />
if (pFeedItemInfo->GetSize() == 0)
if (feedItemInfo.GetSize() == 0)
{
tag = node->selectSingleNode("newznab:attr[@name='size']");
tag = node->selectSingleNode("newznab:attr[@name='size'] | nZEDb:attr[@name='size']");
if (tag)
{
attr = tag->Getattributes()->getNamedItem("value");
if (attr)
{
_bstr_t size(attr->Gettext());
long long lSize = atoll(size);
pFeedItemInfo->SetSize(lSize);
_bstr_t bsize(attr->Gettext());
int64 size = atoll(bsize);
feedItemInfo.SetSize(size);
}
}
}
//<newznab:attr name="imdb" value="1588173"/>
tag = node->selectSingleNode("newznab:attr[@name='imdb']");
tag = node->selectSingleNode("newznab:attr[@name='imdb'] | nZEDb:attr[@name='imdb']");
if (tag)
{
attr = tag->Getattributes()->getNamedItem("value");
if (attr)
{
_bstr_t val(attr->Gettext());
int iVal = atoi(val);
pFeedItemInfo->SetImdbId(iVal);
_bstr_t bval(attr->Gettext());
int val = atoi(bval);
feedItemInfo.SetImdbId(val);
}
}
//<newznab:attr name="rageid" value="33877"/>
tag = node->selectSingleNode("newznab:attr[@name='rageid']");
tag = node->selectSingleNode("newznab:attr[@name='rageid'] | nZEDb:attr[@name='rageid']");
if (tag)
{
attr = tag->Getattributes()->getNamedItem("value");
if (attr)
{
_bstr_t val(attr->Gettext());
int iVal = atoi(val);
pFeedItemInfo->SetRageId(iVal);
_bstr_t bval(attr->Gettext());
int val = atoi(bval);
feedItemInfo.SetRageId(val);
}
}
//<newznab:attr name="tdvdbid" value="33877"/>
tag = node->selectSingleNode("newznab:attr[@name='tvdbid'] | nZEDb:attr[@name='tvdbid']");
if (tag)
{
attr = tag->Getattributes()->getNamedItem("value");
if (attr)
{
_bstr_t bval(attr->Gettext());
int val = atoi(bval);
feedItemInfo.SetTvdbId(val);
}
}
//<newznab:attr name="tvmazeid" value="33877"/>
tag = node->selectSingleNode("newznab:attr[@name='tvmazeid'] | nZEDb:attr[@name='tvmazeid']");
if (tag)
{
attr = tag->Getattributes()->getNamedItem("value");
if (attr)
{
_bstr_t bval(attr->Gettext());
int val = atoi(bval);
feedItemInfo.SetTvmazeId(val);
}
}
//<newznab:attr name="episode" value="E09"/>
//<newznab:attr name="episode" value="9"/>
tag = node->selectSingleNode("newznab:attr[@name='episode']");
tag = node->selectSingleNode("newznab:attr[@name='episode'] | nZEDb:attr[@name='episode']");
if (tag)
{
attr = tag->Getattributes()->getNamedItem("value");
if (attr)
{
_bstr_t val(attr->Gettext());
pFeedItemInfo->SetEpisode(val);
feedItemInfo.SetEpisode(val);
}
}
//<newznab:attr name="season" value="S03"/>
//<newznab:attr name="season" value="3"/>
tag = node->selectSingleNode("newznab:attr[@name='season']");
tag = node->selectSingleNode("newznab:attr[@name='season'] | nZEDb:attr[@name='season']");
if (tag)
{
attr = tag->Getattributes()->getNamedItem("value");
if (attr)
{
_bstr_t val(attr->Gettext());
pFeedItemInfo->SetSeason(val);
feedItemInfo.SetSeason(val);
}
}
MSXML::IXMLDOMNodeListPtr itemList = node->selectNodes("newznab:attr");
MSXML::IXMLDOMNodeListPtr itemList = node->selectNodes("newznab:attr | nZEDb:attr");
for (int i = 0; i < itemList->Getlength(); i++)
{
MSXML::IXMLDOMNodePtr node = itemList->Getitem(i);
@@ -365,9 +347,9 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
MSXML::IXMLDOMNodePtr value = node->Getattributes()->getNamedItem("value");
if (name && value)
{
_bstr_t name(name->Gettext());
_bstr_t val(value->Gettext());
pFeedItemInfo->GetAttributes()->Add(name, val);
_bstr_t bname(name->Gettext());
_bstr_t bval(value->Gettext());
feedItemInfo.GetAttributes()->emplace_back(bname, bval);
}
}
}
@@ -376,10 +358,12 @@ bool FeedFile::ParseFeed(IUnknown* nzb)
#else
FeedFile* FeedFile::Create(const char* szFileName)
bool FeedFile::Parse()
{
FeedFile* pFile = new FeedFile(szFileName);
#ifdef DISABLE_LIBXML2
error("Could not parse rss feed, program was compiled without libxml2 support");
return false;
#else
xmlSAXHandler SAX_handler = {0};
SAX_handler.startElement = reinterpret_cast<startElementSAXFunc>(SAX_StartElement);
SAX_handler.endElement = reinterpret_cast<endElementSAXFunc>(SAX_EndElement);
@@ -387,132 +371,137 @@ FeedFile* FeedFile::Create(const char* szFileName)
SAX_handler.error = reinterpret_cast<errorSAXFunc>(SAX_error);
SAX_handler.getEntity = reinterpret_cast<getEntitySAXFunc>(SAX_getEntity);
pFile->m_bIgnoreNextError = false;
m_ignoreNextError = false;
int ret = xmlSAXUserParseFile(&SAX_handler, pFile, szFileName);
if (ret != 0)
int ret = xmlSAXUserParseFile(&SAX_handler, this, m_fileName);
if (ret != 0)
{
error("Failed to parse rss feed");
delete pFile;
pFile = NULL;
error("Failed to parse rss feed %s", *m_infoName);
return false;
}
return pFile;
return true;
#endif
}
void FeedFile::Parse_StartElement(const char *name, const char **atts)
{
ResetTagContent();
if (!strcmp("item", name))
{
delete m_pFeedItemInfo;
m_pFeedItemInfo = new FeedItemInfo();
m_feedItems->emplace_back();
m_feedItemInfo = &m_feedItems->back();
}
else if (!strcmp("enclosure", name) && m_pFeedItemInfo)
else if (!strcmp("enclosure", name) && m_feedItemInfo)
{
//<enclosure url="http://myindexer.com/fetch/9eeb264aecce961a6e0d" length="150263340" type="application/x-nzb" />
for (; *atts; atts+=2)
{
if (!strcmp("url", atts[0]))
{
char* szUrl = strdup(atts[1]);
WebUtil::XmlDecode(szUrl);
m_pFeedItemInfo->SetUrl(szUrl);
free(szUrl);
CString url = atts[1];
WebUtil::XmlDecode(url);
m_feedItemInfo->SetUrl(url);
}
else if (!strcmp("length", atts[0]))
{
long long lSize = atoll(atts[1]);
m_pFeedItemInfo->SetSize(lSize);
int64 size = atoll(atts[1]);
m_feedItemInfo->SetSize(size);
}
}
}
else if (m_pFeedItemInfo && !strcmp("newznab:attr", name) &&
else if (m_feedItemInfo &&
(!strcmp("newznab:attr", name) || !strcmp("nZEDb:attr", name)) &&
atts[0] && atts[1] && atts[2] && atts[3] &&
!strcmp("name", atts[0]) && !strcmp("value", atts[2]))
{
m_pFeedItemInfo->GetAttributes()->Add(atts[1], atts[3]);
m_feedItemInfo->GetAttributes()->emplace_back(atts[1], atts[3]);
//<newznab:attr name="size" value="5423523453534" />
if (m_pFeedItemInfo->GetSize() == 0 &&
if (m_feedItemInfo->GetSize() == 0 &&
!strcmp("size", atts[1]))
{
long long lSize = atoll(atts[3]);
m_pFeedItemInfo->SetSize(lSize);
int64 size = atoll(atts[3]);
m_feedItemInfo->SetSize(size);
}
//<newznab:attr name="imdb" value="1588173"/>
else if (!strcmp("imdb", atts[1]))
{
m_pFeedItemInfo->SetImdbId(atoi(atts[3]));
m_feedItemInfo->SetImdbId(atoi(atts[3]));
}
//<newznab:attr name="rageid" value="33877"/>
else if (!strcmp("rageid", atts[1]))
{
m_pFeedItemInfo->SetRageId(atoi(atts[3]));
m_feedItemInfo->SetRageId(atoi(atts[3]));
}
//<newznab:attr name="tvdbid" value="33877"/>
else if (!strcmp("tvdbid", atts[1]))
{
m_feedItemInfo->SetTvdbId(atoi(atts[3]));
}
//<newznab:attr name="tvmazeid" value="33877"/>
else if (!strcmp("tvmazeid", atts[1]))
{
m_feedItemInfo->SetTvmazeId(atoi(atts[3]));
}
//<newznab:attr name="episode" value="E09"/>
//<newznab:attr name="episode" value="9"/>
else if (!strcmp("episode", atts[1]))
{
m_pFeedItemInfo->SetEpisode(atts[3]);
m_feedItemInfo->SetEpisode(atts[3]);
}
//<newznab:attr name="season" value="S03"/>
//<newznab:attr name="season" value="3"/>
else if (!strcmp("season", atts[1]))
{
m_pFeedItemInfo->SetSeason(atts[3]);
m_feedItemInfo->SetSeason(atts[3]);
}
}
}
void FeedFile::Parse_EndElement(const char *name)
{
if (!strcmp("item", name))
if (!strcmp("title", name) && m_feedItemInfo)
{
// Close the file element, add the new file to file-list
AddItem(m_pFeedItemInfo);
m_pFeedItemInfo = NULL;
}
else if (!strcmp("title", name) && m_pFeedItemInfo)
{
m_pFeedItemInfo->SetTitle(m_szTagContent);
m_feedItemInfo->SetTitle(m_tagContent);
ResetTagContent();
ParseSubject(m_pFeedItemInfo);
ParseSubject(*m_feedItemInfo);
}
else if (!strcmp("link", name) && m_pFeedItemInfo &&
(!m_pFeedItemInfo->GetUrl() || strlen(m_pFeedItemInfo->GetUrl()) == 0))
else if (!strcmp("link", name) && m_feedItemInfo &&
(!m_feedItemInfo->GetUrl() || strlen(m_feedItemInfo->GetUrl()) == 0))
{
m_pFeedItemInfo->SetUrl(m_szTagContent);
m_feedItemInfo->SetUrl(m_tagContent);
ResetTagContent();
}
else if (!strcmp("category", name) && m_pFeedItemInfo)
else if (!strcmp("category", name) && m_feedItemInfo)
{
m_pFeedItemInfo->SetCategory(m_szTagContent);
m_feedItemInfo->SetCategory(m_tagContent);
ResetTagContent();
}
else if (!strcmp("description", name) && m_pFeedItemInfo)
else if (!strcmp("description", name) && m_feedItemInfo)
{
// cleanup CDATA
char* szDescription = strdup(m_szTagContent);
WebUtil::XmlStripTags(szDescription);
WebUtil::XmlDecode(szDescription);
WebUtil::XmlRemoveEntities(szDescription);
m_pFeedItemInfo->SetDescription(szDescription);
free(szDescription);
CString description = *m_tagContent;
WebUtil::XmlStripTags(description);
WebUtil::XmlDecode(description);
WebUtil::XmlRemoveEntities(description);
m_feedItemInfo->SetDescription(description);
ResetTagContent();
}
else if (!strcmp("pubDate", name) && m_pFeedItemInfo)
else if (!strcmp("pubDate", name) && m_feedItemInfo)
{
time_t unixtime = WebUtil::ParseRfc822DateTime(m_szTagContent);
time_t unixtime = WebUtil::ParseRfc822DateTime(m_tagContent);
if (unixtime > 0)
{
m_pFeedItemInfo->SetTime(unixtime);
m_feedItemInfo->SetTime(unixtime);
}
ResetTagContent();
}
@@ -520,33 +509,28 @@ void FeedFile::Parse_EndElement(const char *name)
void FeedFile::Parse_Content(const char *buf, int len)
{
m_szTagContent = (char*)realloc(m_szTagContent, m_iTagContentLen + len + 1);
strncpy(m_szTagContent + m_iTagContentLen, buf, len);
m_iTagContentLen += len;
m_szTagContent[m_iTagContentLen] = '\0';
m_tagContent.Append(buf, len);
}
void FeedFile::ResetTagContent()
{
free(m_szTagContent);
m_szTagContent = NULL;
m_iTagContentLen = 0;
m_tagContent.Clear();
}
void FeedFile::SAX_StartElement(FeedFile* pFile, const char *name, const char **atts)
void FeedFile::SAX_StartElement(FeedFile* file, const char *name, const char **atts)
{
pFile->Parse_StartElement(name, atts);
file->Parse_StartElement(name, atts);
}
void FeedFile::SAX_EndElement(FeedFile* pFile, const char *name)
void FeedFile::SAX_EndElement(FeedFile* file, const char *name)
{
pFile->Parse_EndElement(name);
file->Parse_EndElement(name);
}
void FeedFile::SAX_characters(FeedFile* pFile, const char * xmlstr, int len)
void FeedFile::SAX_characters(FeedFile* file, const char * xmlstr, int len)
{
char* str = (char*)xmlstr;
// trim starting blanks
int off = 0;
for (int i = 0; i < len; i++)
@@ -561,9 +545,9 @@ void FeedFile::SAX_characters(FeedFile* pFile, const char * xmlstr, int len)
break;
}
}
int newlen = len - off;
// trim ending blanks
for (int i = len - 1; i >= off; i--)
{
@@ -577,43 +561,47 @@ void FeedFile::SAX_characters(FeedFile* pFile, const char * xmlstr, int len)
break;
}
}
if (newlen > 0)
{
// interpret tag content
pFile->Parse_Content(str + off, newlen);
file->Parse_Content(str + off, newlen);
}
}
void* FeedFile::SAX_getEntity(FeedFile* pFile, const char * name)
void* FeedFile::SAX_getEntity(FeedFile* file, const char * name)
{
#ifdef DISABLE_LIBXML2
void* e = nullptr;
#else
xmlEntityPtr e = xmlGetPredefinedEntity((xmlChar* )name);
#endif
if (!e)
{
warn("entity not found");
pFile->m_bIgnoreNextError = true;
file->m_ignoreNextError = true;
}
return e;
}
void FeedFile::SAX_error(FeedFile* pFile, const char *msg, ...)
void FeedFile::SAX_error(FeedFile* file, const char *msg, ...)
{
if (pFile->m_bIgnoreNextError)
if (file->m_ignoreNextError)
{
pFile->m_bIgnoreNextError = false;
file->m_ignoreNextError = false;
return;
}
va_list argp;
va_start(argp, msg);
char szErrMsg[1024];
vsnprintf(szErrMsg, sizeof(szErrMsg), msg, argp);
szErrMsg[1024-1] = '\0';
va_end(argp);
va_list argp;
va_start(argp, msg);
char errMsg[1024];
vsnprintf(errMsg, sizeof(errMsg), msg, argp);
errMsg[1024-1] = '\0';
va_end(argp);
// remove trailing CRLF
for (char* pend = szErrMsg + strlen(szErrMsg) - 1; pend >= szErrMsg && (*pend == '\n' || *pend == '\r' || *pend == ' '); pend--) *pend = '\0';
error("Error parsing rss feed: %s", szErrMsg);
for (char* pend = errMsg + strlen(errMsg) - 1; pend >= errMsg && (*pend == '\n' || *pend == '\r' || *pend == ' '); pend--) *pend = '\0';
error("Error parsing rss feed %s: %s", *file->m_infoName, errMsg);
}
#endif

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2013-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,57 +14,49 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef FEEDFILE_H
#define FEEDFILE_H
#include <vector>
#include "NString.h"
#include "FeedInfo.h"
class FeedFile
{
private:
FeedItemInfos* m_pFeedItemInfos;
char* m_szFileName;
FeedFile(const char* szFileName);
void AddItem(FeedItemInfo* pFeedItemInfo);
void ParseSubject(FeedItemInfo* pFeedItemInfo);
#ifdef WIN32
bool ParseFeed(IUnknown* nzb);
static void EncodeURL(const char* szFilename, char* szURL);
#else
FeedItemInfo* m_pFeedItemInfo;
char* m_szTagContent;
int m_iTagContentLen;
bool m_bIgnoreNextError;
static void SAX_StartElement(FeedFile* pFile, const char *name, const char **atts);
static void SAX_EndElement(FeedFile* pFile, const char *name);
static void SAX_characters(FeedFile* pFile, const char * xmlstr, int len);
static void* SAX_getEntity(FeedFile* pFile, const char * name);
static void SAX_error(FeedFile* pFile, const char *msg, ...);
void Parse_StartElement(const char *name, const char **atts);
void Parse_EndElement(const char *name);
void Parse_Content(const char *buf, int len);
void ResetTagContent();
#endif
public:
virtual ~FeedFile();
static FeedFile* Create(const char* szFileName);
FeedItemInfos* GetFeedItemInfos() { return m_pFeedItemInfos; }
FeedFile(const char* fileName, const char* infoName);
bool Parse();
std::unique_ptr<FeedItemList> DetachFeedItems() { return std::move(m_feedItems); }
void LogDebugInfo();
void LogDebugInfo();
private:
std::unique_ptr<FeedItemList> m_feedItems;
CString m_fileName;
CString m_infoName;
void ParseSubject(FeedItemInfo& feedItemInfo);
#ifdef WIN32
bool ParseFeed(IUnknown* nzb);
static void EncodeUrl(const char* filename, char* url, int bufLen);
#else
FeedItemInfo* m_feedItemInfo;
StringBuilder m_tagContent;
bool m_ignoreNextError;
static void SAX_StartElement(FeedFile* file, const char *name, const char **atts);
static void SAX_EndElement(FeedFile* file, const char *name);
static void SAX_characters(FeedFile* file, const char * xmlstr, int len);
static void* SAX_getEntity(FeedFile* file, const char * name);
static void SAX_error(FeedFile* file, const char *msg, ...);
void Parse_StartElement(const char *name, const char **atts);
void Parse_EndElement(const char *name);
void Parse_Content(const char *buf, int len);
void ResetTagContent();
#endif
};
#endif

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2013-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,26 +14,26 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef FEEDFILTER_H
#define FEEDFILTER_H
#include "NString.h"
#include "DownloadInfo.h"
#include "FeedInfo.h"
#include "Util.h"
class FeedFilter
{
public:
FeedFilter(const char* filter);
void Match(FeedItemInfo& feedItemInfo);
private:
typedef std::deque<char*> RefValues;
typedef std::vector<CString> RefValues;
enum ETermCommand
{
@@ -48,42 +48,42 @@ private:
fcClosingBrace,
fcOrOperator
};
class Term
{
private:
bool m_bPositive;
char* m_szField;
ETermCommand m_eCommand;
char* m_szParam;
long long m_iIntParam;
double m_fFloatParam;
bool m_bFloat;
RegEx* m_pRegEx;
RefValues* m_pRefValues;
bool GetFieldData(const char* szField, FeedItemInfo* pFeedItemInfo,
const char** StrValue, long long* IntValue);
bool ParseParam(const char* szField, const char* szParam);
bool ParseSizeParam(const char* szParam);
bool ParseAgeParam(const char* szParam);
bool ParseNumericParam(const char* szParam);
bool MatchValue(const char* szStrValue, long long iIntValue);
bool MatchText(const char* szStrValue);
bool MatchRegex(const char* szStrValue);
void FillWildMaskRefValues(const char* szStrValue, WildMask* pMask, int iRefOffset);
void FillRegExRefValues(const char* szStrValue, RegEx* pRegEx);
public:
Term();
~Term();
void SetRefValues(RefValues* pRefValues) { m_pRefValues = pRefValues; }
bool Compile(char* szToken);
bool Match(FeedItemInfo* pFeedItemInfo);
ETermCommand GetCommand() { return m_eCommand; }
Term() {}
Term(Term&&) = delete; // catch performance issues
void SetRefValues(RefValues* refValues) { m_refValues = refValues; }
bool Compile(char* token);
bool Match(FeedItemInfo& feedItemInfo);
ETermCommand GetCommand() { return m_command; }
private:
bool m_positive;
CString m_field;
ETermCommand m_command;
CString m_param;
int64 m_intParam = 0;
double m_floatParam = 0.0;
bool m_float = false;
std::unique_ptr<RegEx> m_regEx;
RefValues* m_refValues = nullptr;
bool GetFieldData(const char* field, FeedItemInfo* feedItemInfo,
const char** StrValue, int64* IntValue);
bool ParseParam(const char* field, const char* param);
bool ParseSizeParam(const char* param);
bool ParseAgeParam(const char* param);
bool ParseNumericParam(const char* param);
bool MatchValue(const char* strValue, int64 intValue);
bool MatchText(const char* strValue);
bool MatchRegex(const char* strValue);
void FillWildMaskRefValues(const char* strValue, WildMask* mask, int refOffset);
void FillRegExRefValues(const char* strValue, RegEx* regEx);
};
typedef std::deque<Term*> TermList;
typedef std::deque<Term> TermList;
enum ERuleCommand
{
@@ -96,91 +96,93 @@ private:
class Rule
{
private:
bool m_bIsValid;
ERuleCommand m_eCommand;
char* m_szCategory;
int m_iPriority;
int m_iAddPriority;
bool m_bPause;
int m_iDupeScore;
int m_iAddDupeScore;
char* m_szDupeKey;
char* m_szAddDupeKey;
EDupeMode m_eDupeMode;
char* m_szSeries;
char* m_szRageId;
bool m_bHasCategory;
bool m_bHasPriority;
bool m_bHasAddPriority;
bool m_bHasPause;
bool m_bHasDupeScore;
bool m_bHasAddDupeScore;
bool m_bHasDupeKey;
bool m_bHasAddDupeKey;
bool m_bHasDupeMode;
bool m_bPatCategory;
bool m_bPatDupeKey;
bool m_bPatAddDupeKey;
bool m_bHasSeries;
bool m_bHasRageId;
char* m_szPatCategory;
char* m_szPatDupeKey;
char* m_szPatAddDupeKey;
TermList m_Terms;
RefValues m_RefValues;
char* CompileCommand(char* szRule);
char* CompileOptions(char* szRule);
bool CompileTerm(char* szTerm);
bool MatchExpression(FeedItemInfo* pFeedItemInfo);
public:
Rule();
~Rule();
void Compile(char* szRule);
bool IsValid() { return m_bIsValid; }
ERuleCommand GetCommand() { return m_eCommand; }
const char* GetCategory() { return m_szCategory; }
int GetPriority() { return m_iPriority; }
int GetAddPriority() { return m_iAddPriority; }
bool GetPause() { return m_bPause; }
const char* GetDupeKey() { return m_szDupeKey; }
const char* GetAddDupeKey() { return m_szAddDupeKey; }
int GetDupeScore() { return m_iDupeScore; }
int GetAddDupeScore() { return m_iAddDupeScore; }
EDupeMode GetDupeMode() { return m_eDupeMode; }
const char* GetRageId() { return m_szRageId; }
const char* GetSeries() { return m_szSeries; }
bool HasCategory() { return m_bHasCategory; }
bool HasPriority() { return m_bHasPriority; }
bool HasAddPriority() { return m_bHasAddPriority; }
bool HasPause() { return m_bHasPause; }
bool HasDupeScore() { return m_bHasDupeScore; }
bool HasAddDupeScore() { return m_bHasAddDupeScore; }
bool HasDupeKey() { return m_bHasDupeKey; }
bool HasAddDupeKey() { return m_bHasAddDupeKey; }
bool HasDupeMode() { return m_bHasDupeMode; }
bool HasRageId() { return m_bHasRageId; }
bool HasSeries() { return m_bHasSeries; }
bool Match(FeedItemInfo* pFeedItemInfo);
void ExpandRefValues(FeedItemInfo* pFeedItemInfo, char** pDestStr, char* pPatStr);
const char* GetRefValue(FeedItemInfo* pFeedItemInfo, const char* szVarName);
Rule() {}
Rule(Rule&&) = delete; // catch performance issues
void Compile(char* rule);
bool IsValid() { return m_isValid; }
ERuleCommand GetCommand() { return m_command; }
const char* GetCategory() { return m_category; }
int GetPriority() { return m_priority; }
int GetAddPriority() { return m_addPriority; }
bool GetPause() { return m_pause; }
const char* GetDupeKey() { return m_dupeKey; }
const char* GetAddDupeKey() { return m_addDupeKey; }
int GetDupeScore() { return m_dupeScore; }
int GetAddDupeScore() { return m_addDupeScore; }
EDupeMode GetDupeMode() { return m_dupeMode; }
const char* GetRageId() { return m_rageId; }
const char* GetTvdbId() { return m_tvdbId; }
const char* GetTvmazeId() { return m_tvmazeId; }
const char* GetSeries() { return m_series; }
bool HasCategory() { return m_hasCategory; }
bool HasPriority() { return m_hasPriority; }
bool HasAddPriority() { return m_hasAddPriority; }
bool HasPause() { return m_hasPause; }
bool HasDupeScore() { return m_hasDupeScore; }
bool HasAddDupeScore() { return m_hasAddDupeScore; }
bool HasDupeKey() { return m_hasDupeKey; }
bool HasAddDupeKey() { return m_hasAddDupeKey; }
bool HasDupeMode() { return m_hasDupeMode; }
bool HasRageId() { return m_hasRageId; }
bool HasTvdbId() { return m_hasTvdbId; }
bool HasTvmazeId() { return m_hasTvmazeId; }
bool HasSeries() { return m_hasSeries; }
bool Match(FeedItemInfo& feedItemInfo);
void ExpandRefValues(FeedItemInfo& feedItemInfo, CString* destStr, const char* patStr);
const char* GetRefValue(FeedItemInfo& feedItemInfo, const char* varName);
private:
bool m_isValid = false;
ERuleCommand m_command = frAccept;
CString m_category;
int m_priority = 0;
int m_addPriority = 0;
bool m_pause = false;
int m_dupeScore;
int m_addDupeScore = 0;
CString m_dupeKey;
CString m_addDupeKey;
EDupeMode m_dupeMode = dmScore;
CString m_series;
CString m_rageId;
CString m_tvdbId;
CString m_tvmazeId;
bool m_hasCategory = false;
bool m_hasPriority = false;
bool m_hasAddPriority = false;
bool m_hasPause = false;
bool m_hasDupeScore = false;
bool m_hasAddDupeScore = false;
bool m_hasDupeKey = false;
bool m_hasAddDupeKey = false;
bool m_hasDupeMode = false;
bool m_hasPatCategory = false;
bool m_hasPatDupeKey = false;
bool m_hasPatAddDupeKey = false;
bool m_hasSeries = false;
bool m_hasRageId = false;
bool m_hasTvdbId = false;
bool m_hasTvmazeId = false;
CString m_patCategory;
CString m_patDupeKey;
CString m_patAddDupeKey;
TermList m_terms;
RefValues m_refValues;
char* CompileCommand(char* rule);
char* CompileOptions(char* rule);
bool CompileTerm(char* term);
bool MatchExpression(FeedItemInfo& feedItemInfo);
};
typedef std::deque<Rule*> RuleList;
typedef std::deque<Rule> RuleList;
private:
RuleList m_Rules;
RuleList m_rules;
void Compile(const char* szFilter);
void CompileRule(char* szRule);
void ApplyOptions(Rule* pRule, FeedItemInfo* pFeedItemInfo);
public:
FeedFilter(const char* szFilter);
~FeedFilter();
void Match(FeedItemInfo* pFeedItemInfo);
void Compile(const char* filter);
void CompileRule(char* rule);
void ApplyOptions(Rule& rule, FeedItemInfo& feedItemInfo);
};
#endif

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2013-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2013-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,426 +14,189 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision: 0 $
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <sys/stat.h>
#include "nzbget.h"
#include "FeedInfo.h"
#include "Util.h"
FeedInfo::FeedInfo(int iID, const char* szName, const char* szUrl, bool bBacklog, int iInterval,
const char* szFilter, bool bPauseNzb, const char* szCategory, int iPriority, const char* szFeedScript)
FeedInfo::FeedInfo(int id, const char* name, const char* url, bool backlog, int interval,
const char* filter, bool pauseNzb, const char* category, int priority, const char* extensions) :
m_backlog(backlog), m_interval(interval), m_pauseNzb(pauseNzb), m_priority(priority)
{
m_iID = iID;
m_szName = strdup(szName ? szName : "");
m_szUrl = strdup(szUrl ? szUrl : "");
m_szFilter = strdup(szFilter ? szFilter : "");
m_bBacklog = bBacklog;
m_iFilterHash = Util::HashBJ96(m_szFilter, strlen(m_szFilter), 0);
m_szCategory = strdup(szCategory ? szCategory : "");
m_iInterval = iInterval;
m_szFeedScript = strdup(szFeedScript ? szFeedScript : "");
m_bPauseNzb = bPauseNzb;
m_iPriority = iPriority;
m_tLastUpdate = 0;
m_bPreview = false;
m_eStatus = fsUndefined;
m_szOutputFilename = NULL;
m_bFetch = false;
m_bForce = false;
}
FeedInfo::~FeedInfo()
{
free(m_szName);
free(m_szUrl);
free(m_szFilter);
free(m_szCategory);
free(m_szOutputFilename);
free(m_szFeedScript);
}
void FeedInfo::SetOutputFilename(const char* szOutputFilename)
{
free(m_szOutputFilename);
m_szOutputFilename = strdup(szOutputFilename);
}
FeedItemInfo::Attr::Attr(const char* szName, const char* szValue)
{
m_szName = strdup(szName ? szName : "");
m_szValue = strdup(szValue ? szValue : "");
}
FeedItemInfo::Attr::~Attr()
{
free(m_szName);
free(m_szValue);
}
FeedItemInfo::Attributes::~Attributes()
{
for (iterator it = begin(); it != end(); it++)
m_id = id;
m_name = name ? name : "";
if (m_name.Length() == 0)
{
delete *it;
m_name.Format("Feed%i", m_id);
}
m_url = url ? url : "";
m_filter = filter ? filter : "";
m_filterHash = Util::HashBJ96(m_filter, strlen(m_filter), 0);
m_category = category ? category : "";
m_extensions = extensions ? extensions : "";
}
void FeedItemInfo::Attributes::Add(const char* szName, const char* szValue)
{
push_back(new Attr(szName, szValue));
}
FeedItemInfo::Attr* FeedItemInfo::Attributes::Find(const char* szName)
FeedItemInfo::Attr* FeedItemInfo::Attributes::Find(const char* name)
{
for (iterator it = begin(); it != end(); it++)
for (Attr& attr : this)
{
Attr* pAttr = *it;
if (!strcasecmp(pAttr->GetName(), szName))
if (!strcasecmp(attr.GetName(), name))
{
return pAttr;
return &attr;
}
}
return NULL;
return nullptr;
}
FeedItemInfo::FeedItemInfo()
void FeedItemInfo::SetSeason(const char* season)
{
m_pFeedFilterHelper = NULL;
m_szTitle = NULL;
m_szFilename = NULL;
m_szUrl = NULL;
m_szCategory = strdup("");
m_lSize = 0;
m_tTime = 0;
m_iImdbId = 0;
m_iRageId = 0;
m_szDescription = strdup("");
m_szSeason = NULL;
m_szEpisode = NULL;
m_iSeasonNum = 0;
m_iEpisodeNum = 0;
m_bSeasonEpisodeParsed = false;
m_szAddCategory = strdup("");
m_bPauseNzb = false;
m_iPriority = 0;
m_eStatus = isUnknown;
m_eMatchStatus = msIgnored;
m_iMatchRule = 0;
m_szDupeKey = NULL;
m_iDupeScore = 0;
m_eDupeMode = dmScore;
m_szDupeStatus = NULL;
m_season = season;
m_seasonNum = season ? ParsePrefixedInt(season) : 0;
}
FeedItemInfo::~FeedItemInfo()
void FeedItemInfo::SetEpisode(const char* episode)
{
free(m_szTitle);
free(m_szFilename);
free(m_szUrl);
free(m_szCategory);
free(m_szDescription);
free(m_szSeason);
free(m_szEpisode);
free(m_szAddCategory);
free(m_szDupeKey);
free(m_szDupeStatus);
m_episode = episode;
m_episodeNum = episode ? ParsePrefixedInt(episode) : 0;
}
void FeedItemInfo::SetTitle(const char* szTitle)
int FeedItemInfo::ParsePrefixedInt(const char *value)
{
free(m_szTitle);
m_szTitle = szTitle ? strdup(szTitle) : NULL;
}
void FeedItemInfo::SetFilename(const char* szFilename)
{
free(m_szFilename);
m_szFilename = szFilename ? strdup(szFilename) : NULL;
}
void FeedItemInfo::SetUrl(const char* szUrl)
{
free(m_szUrl);
m_szUrl = szUrl ? strdup(szUrl) : NULL;
}
void FeedItemInfo::SetCategory(const char* szCategory)
{
free(m_szCategory);
m_szCategory = strdup(szCategory ? szCategory: "");
}
void FeedItemInfo::SetDescription(const char* szDescription)
{
free(m_szDescription);
m_szDescription = strdup(szDescription ? szDescription: "");
}
void FeedItemInfo::SetSeason(const char* szSeason)
{
free(m_szSeason);
m_szSeason = szSeason ? strdup(szSeason) : NULL;
m_iSeasonNum = szSeason ? ParsePrefixedInt(szSeason) : 0;
}
void FeedItemInfo::SetEpisode(const char* szEpisode)
{
free(m_szEpisode);
m_szEpisode = szEpisode ? strdup(szEpisode) : NULL;
m_iEpisodeNum = szEpisode ? ParsePrefixedInt(szEpisode) : 0;
}
int FeedItemInfo::ParsePrefixedInt(const char *szValue)
{
const char* szVal = szValue;
if (!strchr("0123456789", *szVal))
const char* val = value;
if (!strchr("0123456789", *val))
{
szVal++;
val++;
}
return atoi(szVal);
return atoi(val);
}
void FeedItemInfo::SetAddCategory(const char* szAddCategory)
void FeedItemInfo::AppendDupeKey(const char* extraDupeKey)
{
free(m_szAddCategory);
m_szAddCategory = strdup(szAddCategory ? szAddCategory : "");
}
void FeedItemInfo::SetDupeKey(const char* szDupeKey)
{
free(m_szDupeKey);
m_szDupeKey = strdup(szDupeKey ? szDupeKey : "");
}
void FeedItemInfo::AppendDupeKey(const char* szExtraDupeKey)
{
if (!m_szDupeKey || *m_szDupeKey == '\0' || !szExtraDupeKey || *szExtraDupeKey == '\0')
if (!m_dupeKey.Empty() && !Util::EmptyStr(extraDupeKey))
{
return;
m_dupeKey.AppendFmt("-%s", extraDupeKey);
}
int iLen = (m_szDupeKey ? strlen(m_szDupeKey) : 0) + 1 + strlen(szExtraDupeKey) + 1;
char* szNewKey = (char*)malloc(iLen);
snprintf(szNewKey, iLen, "%s-%s", m_szDupeKey, szExtraDupeKey);
szNewKey[iLen - 1] = '\0';
free(m_szDupeKey);
m_szDupeKey = szNewKey;
}
void FeedItemInfo::BuildDupeKey(const char* szRageId, const char* szSeries)
void FeedItemInfo::BuildDupeKey(const char* rageId, const char* tvdbId, const char* tvmazeId, const char* series)
{
int iRageId = szRageId && *szRageId ? atoi(szRageId) : m_iRageId;
int rageIdVal = !Util::EmptyStr(rageId) ? atoi(rageId) : m_rageId;
int tvdbIdVal = !Util::EmptyStr(tvdbId) ? atoi(tvdbId) : m_tvdbId;
int tvmazeIdVal = !Util::EmptyStr(tvmazeId) ? atoi(tvmazeId) : m_tvmazeId;
free(m_szDupeKey);
if (m_iImdbId != 0)
if (m_imdbId != 0)
{
m_szDupeKey = (char*)malloc(20);
snprintf(m_szDupeKey, 20, "imdb=%i", m_iImdbId);
m_dupeKey.Format("imdb=%i", m_imdbId);
}
else if (szSeries && *szSeries && GetSeasonNum() != 0 && GetEpisodeNum() != 0)
else if (!Util::EmptyStr(series) && GetSeasonNum() != 0 && GetEpisodeNum() != 0)
{
int iLen = strlen(szSeries) + 50;
m_szDupeKey = (char*)malloc(iLen);
snprintf(m_szDupeKey, iLen, "series=%s-%s-%s", szSeries, m_szSeason, m_szEpisode);
m_szDupeKey[iLen-1] = '\0';
m_dupeKey.Format("series=%s-%s-%s", series, *m_season, *m_episode);
}
else if (iRageId != 0 && GetSeasonNum() != 0 && GetEpisodeNum() != 0)
else if (rageIdVal != 0 && GetSeasonNum() != 0 && GetEpisodeNum() != 0)
{
m_szDupeKey = (char*)malloc(100);
snprintf(m_szDupeKey, 100, "rageid=%i-%s-%s", iRageId, m_szSeason, m_szEpisode);
m_szDupeKey[100-1] = '\0';
m_dupeKey.Format("rageid=%i-%s-%s", rageIdVal, *m_season, *m_episode);
}
else if (tvdbIdVal != 0 && GetSeasonNum() != 0 && GetEpisodeNum() != 0)
{
m_dupeKey.Format("tvdbid=%i-%s-%s", tvdbIdVal, *m_season, *m_episode);
}
else if (tvmazeIdVal != 0 && GetSeasonNum() != 0 && GetEpisodeNum() != 0)
{
m_dupeKey.Format("tvmazeid=%i-%s-%s", tvmazeIdVal, *m_season, *m_episode);
}
else
{
m_szDupeKey = strdup("");
m_dupeKey = "";
}
}
int FeedItemInfo::GetSeasonNum()
{
if (!m_szSeason && !m_bSeasonEpisodeParsed)
if (!m_season && !m_seasonEpisodeParsed)
{
ParseSeasonEpisode();
}
return m_iSeasonNum;
return m_seasonNum;
}
int FeedItemInfo::GetEpisodeNum()
{
if (!m_szEpisode && !m_bSeasonEpisodeParsed)
if (!m_episode && !m_seasonEpisodeParsed)
{
ParseSeasonEpisode();
}
return m_iEpisodeNum;
return m_episodeNum;
}
void FeedItemInfo::ParseSeasonEpisode()
{
m_bSeasonEpisodeParsed = true;
m_seasonEpisodeParsed = true;
RegEx** ppRegEx = m_pFeedFilterHelper->GetSeasonEpisodeRegEx();
if (!*ppRegEx)
const char* pattern = "[^[:alnum:]]s?([0-9]+)[ex]([0-9]+(-?e[0-9]+)?)[^[:alnum:]]";
std::unique_ptr<RegEx>& regEx = m_feedFilterHelper->GetRegEx(1);
if (!regEx)
{
*ppRegEx = new RegEx("[^[:alnum:]]s?([0-9]+)[ex]([0-9]+(-?e[0-9]+)?)[^[:alnum:]]", 10);
regEx = std::make_unique<RegEx>(pattern, 10);
}
if ((*ppRegEx)->Match(m_szTitle))
if (regEx->Match(m_title))
{
char szRegValue[100];
char szValue[100];
SetSeason(BString<100>("S%02d", atoi(m_title + regEx->GetMatchStart(1))));
snprintf(szValue, 100, "S%02d", atoi(m_szTitle + (*ppRegEx)->GetMatchStart(1)));
szValue[100-1] = '\0';
SetSeason(szValue);
BString<100> regValue;
regValue.Set(m_title + regEx->GetMatchStart(2), regEx->GetMatchLen(2));
int iLen = (*ppRegEx)->GetMatchLen(2);
iLen = iLen < 99 ? iLen : 99;
strncpy(szRegValue, m_szTitle + (*ppRegEx)->GetMatchStart(2), (*ppRegEx)->GetMatchLen(2));
szRegValue[iLen] = '\0';
snprintf(szValue, 100, "E%s", szRegValue);
szValue[100-1] = '\0';
Util::ReduceStr(szValue, "-", "");
for (char* p = szValue; *p; p++) *p = toupper(*p); // convert string to uppercase e02 -> E02
SetEpisode(szValue);
BString<100> episode("E%s", *regValue);
Util::ReduceStr(episode, "-", "");
for (char* p = episode; *p; p++) *p = toupper(*p); // convert string to uppercase e02 -> E02
SetEpisode(episode);
}
}
const char* FeedItemInfo::GetDupeStatus()
{
if (!m_szDupeStatus)
if (!m_dupeStatus)
{
char szStatuses[200];
szStatuses[0] = '\0';
m_pFeedFilterHelper->CalcDupeStatus(m_szTitle, m_szDupeKey, szStatuses, sizeof(szStatuses));
m_szDupeStatus = strdup(szStatuses);
BString<1024> statuses;
m_feedFilterHelper->CalcDupeStatus(m_title, m_dupeKey, statuses, statuses.Capacity());
m_dupeStatus = statuses;
}
return m_szDupeStatus;
return m_dupeStatus;
}
FeedHistoryInfo::FeedHistoryInfo(const char* szUrl, FeedHistoryInfo::EStatus eStatus, time_t tLastSeen)
{
m_szUrl = szUrl ? strdup(szUrl) : NULL;
m_eStatus = eStatus;
m_tLastSeen = tLastSeen;
}
FeedHistoryInfo::~FeedHistoryInfo()
{
free(m_szUrl);
}
FeedHistory::~FeedHistory()
{
Clear();
}
void FeedHistory::Clear()
void FeedHistory::Remove(const char* url)
{
for (iterator it = begin(); it != end(); it++)
{
delete *it;
}
clear();
}
void FeedHistory::Add(const char* szUrl, FeedHistoryInfo::EStatus eStatus, time_t tLastSeen)
{
push_back(new FeedHistoryInfo(szUrl, eStatus, tLastSeen));
}
void FeedHistory::Remove(const char* szUrl)
{
for (iterator it = begin(); it != end(); it++)
{
FeedHistoryInfo* pFeedHistoryInfo = *it;
if (!strcmp(pFeedHistoryInfo->GetUrl(), szUrl))
FeedHistoryInfo& feedHistoryInfo = *it;
if (!strcmp(feedHistoryInfo.GetUrl(), url))
{
delete pFeedHistoryInfo;
erase(it);
break;
}
}
}
FeedHistoryInfo* FeedHistory::Find(const char* szUrl)
FeedHistoryInfo* FeedHistory::Find(const char* url)
{
for (iterator it = begin(); it != end(); it++)
for (FeedHistoryInfo& feedHistoryInfo : this)
{
FeedHistoryInfo* pFeedHistoryInfo = *it;
if (!strcmp(pFeedHistoryInfo->GetUrl(), szUrl))
if (!strcmp(feedHistoryInfo.GetUrl(), url))
{
return pFeedHistoryInfo;
return &feedHistoryInfo;
}
}
return NULL;
}
FeedItemInfos::FeedItemInfos()
{
debug("Creating FeedItemInfos");
m_iRefCount = 0;
}
FeedItemInfos::~FeedItemInfos()
{
debug("Destroing FeedItemInfos");
for (iterator it = begin(); it != end(); it++)
{
delete *it;
}
}
void FeedItemInfos::Retain()
{
m_iRefCount++;
}
void FeedItemInfos::Release()
{
m_iRefCount--;
if (m_iRefCount <= 0)
{
delete this;
}
}
void FeedItemInfos::Add(FeedItemInfo* pFeedItemInfo)
{
push_back(pFeedItemInfo);
return nullptr;
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2013-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2013-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,25 +14,17 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision: 0 $
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef FEEDINFO_H
#define FEEDINFO_H
#include <deque>
#include <time.h>
#include "NString.h"
#include "Util.h"
#include "DownloadInfo.h"
class FeedInfo
{
public:
@@ -44,63 +36,67 @@ public:
fsFailed
};
private:
int m_iID;
char* m_szName;
char* m_szUrl;
int m_iInterval;
char* m_szFilter;
unsigned int m_iFilterHash;
bool m_bPauseNzb;
char* m_szCategory;
char* m_szFeedScript;
int m_iPriority;
time_t m_tLastUpdate;
bool m_bPreview;
EStatus m_eStatus;
char* m_szOutputFilename;
bool m_bFetch;
bool m_bForce;
bool m_bBacklog;
FeedInfo(int id, const char* name, const char* url, bool backlog, int interval,
const char* filter, bool pauseNzb, const char* category, int priority,
const char* extensions);
int GetId() { return m_id; }
const char* GetName() { return m_name; }
const char* GetUrl() { return m_url; }
int GetInterval() { return m_interval; }
const char* GetFilter() { return m_filter; }
uint32 GetFilterHash() { return m_filterHash; }
bool GetPauseNzb() { return m_pauseNzb; }
const char* GetCategory() { return m_category; }
int GetPriority() { return m_priority; }
const char* GetExtensions() { return m_extensions; }
time_t GetLastUpdate() { return m_lastUpdate; }
void SetLastUpdate(time_t lastUpdate) { m_lastUpdate = lastUpdate; }
time_t GetNextUpdate() { return m_nextUpdate; }
void SetNextUpdate(time_t nextUpdate) { m_nextUpdate = nextUpdate; }
int GetLastInterval() { return m_lastInterval; }
void SetLastInterval(int lastInterval) { m_lastInterval = lastInterval; }
bool GetPreview() { return m_preview; }
void SetPreview(bool preview) { m_preview = preview; }
EStatus GetStatus() { return m_status; }
void SetStatus(EStatus Status) { m_status = Status; }
const char* GetOutputFilename() { return m_outputFilename; }
void SetOutputFilename(const char* outputFilename) { m_outputFilename = outputFilename; }
bool GetFetch() { return m_fetch; }
void SetFetch(bool fetch) { m_fetch = fetch; }
bool GetForce() { return m_force; }
void SetForce(bool force) { m_force = force; }
bool GetBacklog() { return m_backlog; }
void SetBacklog(bool backlog) { m_backlog = backlog; }
public:
FeedInfo(int iID, const char* szName, const char* szUrl, bool bBacklog, int iInterval,
const char* szFilter, bool bPauseNzb, const char* szCategory, int iPriority,
const char* szFeedScript);
~FeedInfo();
int GetID() { return m_iID; }
const char* GetName() { return m_szName; }
const char* GetUrl() { return m_szUrl; }
int GetInterval() { return m_iInterval; }
const char* GetFilter() { return m_szFilter; }
unsigned int GetFilterHash() { return m_iFilterHash; }
bool GetPauseNzb() { return m_bPauseNzb; }
const char* GetCategory() { return m_szCategory; }
int GetPriority() { return m_iPriority; }
const char* GetFeedScript() { return m_szFeedScript; }
time_t GetLastUpdate() { return m_tLastUpdate; }
void SetLastUpdate(time_t tLastUpdate) { m_tLastUpdate = tLastUpdate; }
bool GetPreview() { return m_bPreview; }
void SetPreview(bool bPreview) { m_bPreview = bPreview; }
EStatus GetStatus() { return m_eStatus; }
void SetStatus(EStatus Status) { m_eStatus = Status; }
const char* GetOutputFilename() { return m_szOutputFilename; }
void SetOutputFilename(const char* szOutputFilename);
bool GetFetch() { return m_bFetch; }
void SetFetch(bool bFetch) { m_bFetch = bFetch; }
bool GetForce() { return m_bForce; }
void SetForce(bool bForce) { m_bForce = bForce; }
bool GetBacklog() { return m_bBacklog; }
void SetBacklog(bool bBacklog) { m_bBacklog = bBacklog; }
private:
int m_id;
CString m_name;
CString m_url;
bool m_backlog;
int m_interval;
CString m_filter;
uint32 m_filterHash;
bool m_pauseNzb;
CString m_category;
CString m_extensions;
int m_priority;
time_t m_lastUpdate = 0;
time_t m_nextUpdate = 0;
int m_lastInterval = 0;
bool m_preview = false;
EStatus m_status = fsUndefined;
CString m_outputFilename;
bool m_fetch = false;
bool m_force = false;
};
typedef std::deque<FeedInfo*> Feeds;
typedef std::deque<std::unique_ptr<FeedInfo>> Feeds;
class FeedFilterHelper
{
public:
virtual RegEx** GetSeasonEpisodeRegEx() = 0;
virtual void CalcDupeStatus(const char* szTitle, const char* szDupeKey, char* szStatusBuf, int iBufLen) = 0;
virtual std::unique_ptr<RegEx>& GetRegEx(int id) = 0;
virtual void CalcDupeStatus(const char* title, const char* dupeKey, char* statusBuf, int bufLen) = 0;
};
class FeedItemInfo
@@ -123,123 +119,113 @@ public:
class Attr
{
private:
char* m_szName;
char* m_szValue;
public:
Attr(const char* szName, const char* szValue);
~Attr();
const char* GetName() { return m_szName; }
const char* GetValue() { return m_szValue; }
Attr(const char* name, const char* value) :
m_name(name ? name : ""), m_value(value ? value : "") {}
const char* GetName() { return m_name; }
const char* GetValue() { return m_value; }
private:
CString m_name;
CString m_value;
};
typedef std::deque<Attr*> AttributesBase;
typedef std::deque<Attr> AttributesBase;
class Attributes: public AttributesBase
{
public:
~Attributes();
void Add(const char* szName, const char* szValue);
Attr* Find(const char* szName);
Attr* Find(const char* name);
};
FeedItemInfo() {}
FeedItemInfo(FeedItemInfo&&) = delete; // catch performance issues
void SetFeedFilterHelper(FeedFilterHelper* feedFilterHelper) { m_feedFilterHelper = feedFilterHelper; }
const char* GetTitle() { return m_title; }
void SetTitle(const char* title) { m_title = title; }
const char* GetFilename() { return m_filename; }
void SetFilename(const char* filename) { m_filename = filename; }
const char* GetUrl() { return m_url; }
void SetUrl(const char* url) { m_url = url; }
int64 GetSize() { return m_size; }
void SetSize(int64 size) { m_size = size; }
const char* GetCategory() { return m_category; }
void SetCategory(const char* category) { m_category = category; }
int GetImdbId() { return m_imdbId; }
void SetImdbId(int imdbId) { m_imdbId = imdbId; }
int GetRageId() { return m_rageId; }
void SetRageId(int rageId) { m_rageId = rageId; }
int GetTvdbId() { return m_tvdbId; }
void SetTvdbId(int tvdbId) { m_tvdbId = tvdbId; }
int GetTvmazeId() { return m_tvmazeId; }
void SetTvmazeId(int tvmazeId) { m_tvmazeId = tvmazeId; }
const char* GetDescription() { return m_description; }
void SetDescription(const char* description) { m_description = description ? description: ""; }
const char* GetSeason() { return m_season; }
void SetSeason(const char* season);
const char* GetEpisode() { return m_episode; }
void SetEpisode(const char* episode);
int GetSeasonNum();
int GetEpisodeNum();
const char* GetAddCategory() { return m_addCategory; }
void SetAddCategory(const char* addCategory) { m_addCategory = addCategory ? addCategory : ""; }
bool GetPauseNzb() { return m_pauseNzb; }
void SetPauseNzb(bool pauseNzb) { m_pauseNzb = pauseNzb; }
int GetPriority() { return m_priority; }
void SetPriority(int priority) { m_priority = priority; }
time_t GetTime() { return m_time; }
void SetTime(time_t time) { m_time = time; }
EStatus GetStatus() { return m_status; }
void SetStatus(EStatus status) { m_status = status; }
EMatchStatus GetMatchStatus() { return m_matchStatus; }
void SetMatchStatus(EMatchStatus matchStatus) { m_matchStatus = matchStatus; }
int GetMatchRule() { return m_matchRule; }
void SetMatchRule(int matchRule) { m_matchRule = matchRule; }
const char* GetDupeKey() { return m_dupeKey; }
void SetDupeKey(const char* dupeKey) { m_dupeKey = dupeKey ? dupeKey : ""; }
void AppendDupeKey(const char* extraDupeKey);
void BuildDupeKey(const char* rageId, const char* tvdbId, const char* tvmazeId, const char* series);
int GetDupeScore() { return m_dupeScore; }
void SetDupeScore(int dupeScore) { m_dupeScore = dupeScore; }
EDupeMode GetDupeMode() { return m_dupeMode; }
void SetDupeMode(EDupeMode dupeMode) { m_dupeMode = dupeMode; }
const char* GetDupeStatus();
Attributes* GetAttributes() { return &m_attributes; }
private:
char* m_szTitle;
char* m_szFilename;
char* m_szUrl;
time_t m_tTime;
long long m_lSize;
char* m_szCategory;
int m_iImdbId;
int m_iRageId;
char* m_szDescription;
char* m_szSeason;
char* m_szEpisode;
int m_iSeasonNum;
int m_iEpisodeNum;
bool m_bSeasonEpisodeParsed;
char* m_szAddCategory;
bool m_bPauseNzb;
int m_iPriority;
EStatus m_eStatus;
EMatchStatus m_eMatchStatus;
int m_iMatchRule;
char* m_szDupeKey;
int m_iDupeScore;
EDupeMode m_eDupeMode;
char* m_szDupeStatus;
FeedFilterHelper* m_pFeedFilterHelper;
Attributes m_Attributes;
CString m_title;
CString m_filename;
CString m_url;
time_t m_time = 0;
int64 m_size = 0;
CString m_category = "";
int m_imdbId = 0;
int m_rageId = 0;
int m_tvdbId = 0;
int m_tvmazeId = 0;
CString m_description = "";
CString m_season;
CString m_episode;
int m_seasonNum = 0;
int m_episodeNum = 0;
bool m_seasonEpisodeParsed = false;
CString m_addCategory = "";
bool m_pauseNzb = false;
int m_priority = 0;
EStatus m_status = isUnknown;
EMatchStatus m_matchStatus = msIgnored;
int m_matchRule = 0;
CString m_dupeKey;
int m_dupeScore = 0;
EDupeMode m_dupeMode = dmScore;
CString m_dupeStatus;
FeedFilterHelper* m_feedFilterHelper = nullptr;
Attributes m_attributes;
int ParsePrefixedInt(const char *szValue);
void ParseSeasonEpisode();
public:
FeedItemInfo();
~FeedItemInfo();
void SetFeedFilterHelper(FeedFilterHelper* pFeedFilterHelper) { m_pFeedFilterHelper = pFeedFilterHelper; }
const char* GetTitle() { return m_szTitle; }
void SetTitle(const char* szTitle);
const char* GetFilename() { return m_szFilename; }
void SetFilename(const char* szFilename);
const char* GetUrl() { return m_szUrl; }
void SetUrl(const char* szUrl);
long long GetSize() { return m_lSize; }
void SetSize(long long lSize) { m_lSize = lSize; }
const char* GetCategory() { return m_szCategory; }
void SetCategory(const char* szCategory);
int GetImdbId() { return m_iImdbId; }
void SetImdbId(int iImdbId) { m_iImdbId = iImdbId; }
int GetRageId() { return m_iRageId; }
void SetRageId(int iRageId) { m_iRageId = iRageId; }
const char* GetDescription() { return m_szDescription; }
void SetDescription(const char* szDescription);
const char* GetSeason() { return m_szSeason; }
void SetSeason(const char* szSeason);
const char* GetEpisode() { return m_szEpisode; }
void SetEpisode(const char* szEpisode);
int GetSeasonNum();
int GetEpisodeNum();
const char* GetAddCategory() { return m_szAddCategory; }
void SetAddCategory(const char* szAddCategory);
bool GetPauseNzb() { return m_bPauseNzb; }
void SetPauseNzb(bool bPauseNzb) { m_bPauseNzb = bPauseNzb; }
int GetPriority() { return m_iPriority; }
void SetPriority(int iPriority) { m_iPriority = iPriority; }
time_t GetTime() { return m_tTime; }
void SetTime(time_t tTime) { m_tTime = tTime; }
EStatus GetStatus() { return m_eStatus; }
void SetStatus(EStatus eStatus) { m_eStatus = eStatus; }
EMatchStatus GetMatchStatus() { return m_eMatchStatus; }
void SetMatchStatus(EMatchStatus eMatchStatus) { m_eMatchStatus = eMatchStatus; }
int GetMatchRule() { return m_iMatchRule; }
void SetMatchRule(int iMatchRule) { m_iMatchRule = iMatchRule; }
const char* GetDupeKey() { return m_szDupeKey; }
void SetDupeKey(const char* szDupeKey);
void AppendDupeKey(const char* szExtraDupeKey);
void BuildDupeKey(const char* szRageId, const char* szSeries);
int GetDupeScore() { return m_iDupeScore; }
void SetDupeScore(int iDupeScore) { m_iDupeScore = iDupeScore; }
EDupeMode GetDupeMode() { return m_eDupeMode; }
void SetDupeMode(EDupeMode eDupeMode) { m_eDupeMode = eDupeMode; }
const char* GetDupeStatus();
Attributes* GetAttributes() { return &m_Attributes; }
int ParsePrefixedInt(const char *value);
void ParseSeasonEpisode();
};
typedef std::deque<FeedItemInfo*> FeedItemInfosBase;
class FeedItemInfos : public FeedItemInfosBase
{
private:
int m_iRefCount;
public:
FeedItemInfos();
~FeedItemInfos();
void Retain();
void Release();
void Add(FeedItemInfo* pFeedItemInfo);
};
typedef std::deque<FeedItemInfo> FeedItemList;
class FeedHistoryInfo
{
@@ -251,31 +237,27 @@ public:
hsFetched
};
private:
char* m_szUrl;
EStatus m_eStatus;
time_t m_tLastSeen;
FeedHistoryInfo(const char* url, EStatus status, time_t lastSeen) :
m_url(url), m_status(status), m_lastSeen(lastSeen) {}
const char* GetUrl() { return m_url; }
EStatus GetStatus() { return m_status; }
void SetStatus(EStatus Status) { m_status = Status; }
time_t GetLastSeen() { return m_lastSeen; }
void SetLastSeen(time_t lastSeen) { m_lastSeen = lastSeen; }
public:
FeedHistoryInfo(const char* szUrl, EStatus eStatus, time_t tLastSeen);
~FeedHistoryInfo();
const char* GetUrl() { return m_szUrl; }
EStatus GetStatus() { return m_eStatus; }
void SetStatus(EStatus Status) { m_eStatus = Status; }
time_t GetLastSeen() { return m_tLastSeen; }
void SetLastSeen(time_t tLastSeen) { m_tLastSeen = tLastSeen; }
private:
CString m_url;
EStatus m_status;
time_t m_lastSeen;
};
typedef std::deque<FeedHistoryInfo*> FeedHistoryBase;
typedef std::deque<FeedHistoryInfo> FeedHistoryBase;
class FeedHistory : public FeedHistoryBase
{
public:
~FeedHistory();
void Clear();
void Add(const char* szUrl, FeedHistoryInfo::EStatus eStatus, time_t tLastSeen);
void Remove(const char* szUrl);
FeedHistoryInfo* Find(const char* szUrl);
void Remove(const char* url);
FeedHistoryInfo* Find(const char* url);
};
#endif

View File

@@ -1,8 +1,8 @@
/*
* This file if part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2010 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,149 +15,114 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include "nzbget.h"
#include "ColoredFrontend.h"
#include "Util.h"
ColoredFrontend::ColoredFrontend()
{
m_bSummary = true;
m_bNeedGoBack = false;
m_summary = true;
#ifdef WIN32
m_hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
m_console = GetStdHandle(STD_OUTPUT_HANDLE);
#endif
}
void ColoredFrontend::BeforePrint()
{
if (m_bNeedGoBack)
if (m_needGoBack)
{
// go back one line
#ifdef WIN32
CONSOLE_SCREEN_BUFFER_INFO BufInfo;
GetConsoleScreenBufferInfo(m_hConsole, &BufInfo);
GetConsoleScreenBufferInfo(m_console, &BufInfo);
BufInfo.dwCursorPosition.Y--;
SetConsoleCursorPosition(m_hConsole, BufInfo.dwCursorPosition);
SetConsoleCursorPosition(m_console, BufInfo.dwCursorPosition);
#else
printf("\r\033[1A");
#endif
m_bNeedGoBack = false;
m_needGoBack = false;
}
}
void ColoredFrontend::PrintStatus()
{
char tmp[1024];
char timeString[100];
timeString[0] = '\0';
int iCurrentDownloadSpeed = m_bStandBy ? 0 : m_iCurrentDownloadSpeed;
BString<100> timeString;
int currentDownloadSpeed = m_standBy ? 0 : m_currentDownloadSpeed;
if (iCurrentDownloadSpeed > 0 && !m_bPauseDownload)
if (currentDownloadSpeed > 0 && !m_pauseDownload)
{
long long remain_sec = (long long)(m_lRemainingSize / iCurrentDownloadSpeed);
int64 remain_sec = (int64)(m_remainingSize / currentDownloadSpeed);
int h = (int)(remain_sec / 3600);
int m = (int)((remain_sec % 3600) / 60);
int s = (int)(remain_sec % 60);
sprintf(timeString, " (~ %.2d:%.2d:%.2d)", h, m, s);
timeString.Format(" (~ %.2d:%.2d:%.2d)", h, m, s);
}
char szDownloadLimit[128];
if (m_iDownloadLimit > 0)
BString<100> downloadLimit;
if (m_downloadLimit > 0)
{
sprintf(szDownloadLimit, ", Limit %i KB/s", m_iDownloadLimit / 1024);
}
else
{
szDownloadLimit[0] = 0;
downloadLimit.Format(", Limit %i KB/s", m_downloadLimit / 1024);
}
char szPostStatus[128];
if (m_iPostJobCount > 0)
{
sprintf(szPostStatus, ", %i post-job%s", m_iPostJobCount, m_iPostJobCount > 1 ? "s" : "");
}
else
{
szPostStatus[0] = 0;
}
BString<100> postStatus;
if (m_postJobCount > 0)
{
postStatus.Format(", %i post-job%s", m_postJobCount, m_postJobCount > 1 ? "s" : "");
}
#ifdef WIN32
char* szControlSeq = "";
const char* controlSeq = "";
#else
printf("\033[s");
const char* szControlSeq = "\033[K";
const char* controlSeq = "\033[K";
#endif
char szFileSize[20];
char szCurrendSpeed[20];
snprintf(tmp, 1024, " %d threads, %s, %s remaining%s%s%s%s%s\n",
m_iThreadCount, Util::FormatSpeed(szCurrendSpeed, sizeof(szCurrendSpeed), iCurrentDownloadSpeed),
Util::FormatSize(szFileSize, sizeof(szFileSize), m_lRemainingSize),
timeString, szPostStatus, m_bPauseDownload ? (m_bStandBy ? ", Paused" : ", Pausing") : "",
szDownloadLimit, szControlSeq);
tmp[1024-1] = '\0';
printf("%s", tmp);
m_bNeedGoBack = true;
}
BString<1024> status(" %d threads, %s, %s remaining%s%s%s%s%s\n",
m_threadCount, *Util::FormatSpeed(currentDownloadSpeed),
*Util::FormatSize(m_remainingSize), *timeString, *postStatus,
m_pauseDownload ? (m_standBy ? ", Paused" : ", Pausing") : "",
*downloadLimit, controlSeq);
printf("%s", *status);
m_needGoBack = true;
}
void ColoredFrontend::PrintMessage(Message * pMessage)
void ColoredFrontend::PrintMessage(Message& message)
{
#ifdef WIN32
switch (pMessage->GetKind())
switch (message.GetKind())
{
case Message::mkDebug:
SetConsoleTextAttribute(m_hConsole, 8);
SetConsoleTextAttribute(m_console, 8);
printf("[DEBUG]");
break;
case Message::mkError:
SetConsoleTextAttribute(m_hConsole, 4);
SetConsoleTextAttribute(m_console, 4);
printf("[ERROR]");
break;
break;
case Message::mkWarning:
SetConsoleTextAttribute(m_hConsole, 5);
SetConsoleTextAttribute(m_console, 5);
printf("[WARNING]");
break;
case Message::mkInfo:
SetConsoleTextAttribute(m_hConsole, 2);
SetConsoleTextAttribute(m_console, 2);
printf("[INFO]");
break;
case Message::mkDetail:
SetConsoleTextAttribute(m_hConsole, 2);
SetConsoleTextAttribute(m_console, 2);
printf("[DETAIL]");
break;
}
SetConsoleTextAttribute(m_hConsole, 7);
char* msg = strdup(pMessage->GetText());
SetConsoleTextAttribute(m_console, 7);
CString msg = message.GetText();
CharToOem(msg, msg);
printf(" %s\n", msg);
free(msg);
printf(" %s\n", *msg);
#else
const char* msg = pMessage->GetText();
switch (pMessage->GetKind())
const char* msg = message.GetText();
switch (message.GetKind())
{
case Message::mkDebug:
printf("[DEBUG] %s\033[K\n", msg);

View File

@@ -1,8 +1,8 @@
/*
* This file if part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,12 +15,7 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
@@ -32,22 +27,22 @@
class ColoredFrontend : public LoggableFrontend
{
private:
bool m_bNeedGoBack;
#ifdef WIN32
HANDLE m_hConsole;
#endif
protected:
virtual void BeforePrint();
virtual void PrintMessage(Message* pMessage);
virtual void PrintStatus();
virtual void PrintSkip();
virtual void BeforeExit();
public:
ColoredFrontend();
protected:
virtual void BeforePrint();
virtual void PrintMessage(Message& message);
virtual void PrintStatus();
virtual void PrintSkip();
virtual void BeforeExit();
private:
bool m_needGoBack = false;
#ifdef WIN32
HANDLE m_console;
#endif
};
#endif

View File

@@ -1,8 +1,8 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,34 +15,13 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#ifndef WIN32
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#endif
#include "nzbget.h"
#include "Options.h"
#include "WorkState.h"
#include "Frontend.h"
#include "Log.h"
#include "Connection.h"
@@ -55,21 +34,22 @@ Frontend::Frontend()
{
debug("Creating Frontend");
m_iNeededLogFirstID = 0;
m_iNeededLogEntries = 0;
m_bSummary = false;
m_bFileList = false;
m_iCurrentDownloadSpeed = 0;
m_lRemainingSize = 0;
m_bPauseDownload = false;
m_iDownloadLimit = 0;
m_iThreadCount = 0;
m_iPostJobCount = 0;
m_iUpTimeSec = 0;
m_iDnTimeSec = 0;
m_iAllBytes = 0;
m_bStandBy = 0;
m_iUpdateInterval = g_pOptions->GetUpdateInterval();
m_workStateObserver.m_owner = this;
g_WorkState->Attach(&m_workStateObserver);
m_updateInterval = g_Options->GetUpdateInterval();
}
void Frontend::Stop()
{
Thread::Stop();
m_waitCond.NotifyAll();
}
void Frontend::WorkStateUpdate(Subject* caller, void* aspect)
{
m_waitCond.NotifyAll();
}
bool Frontend::PrepareData()
@@ -80,34 +60,31 @@ bool Frontend::PrepareData()
{
return false;
}
if (!RequestMessages() || ((m_bSummary || m_bFileList) && !RequestFileList()))
if (!RequestMessages() || ((m_summary || m_fileList) && !RequestFileList()))
{
const char* szControlIP = !strcmp(g_pOptions->GetControlIP(), "0.0.0.0") ? "127.0.0.1" : g_pOptions->GetControlIP();
printf("\nUnable to send request to nzbget-server at %s (port %i) \n", szControlIP, g_pOptions->GetControlPort());
const char* controlIp = !strcmp(g_Options->GetControlIp(), "0.0.0.0") ? "127.0.0.1" : g_Options->GetControlIp();
printf("\nUnable to send request to nzbget-server at %s (port %i) \n", controlIp, g_Options->GetControlPort());
Stop();
return false;
}
}
else
{
if (m_bSummary)
if (m_summary)
{
m_iCurrentDownloadSpeed = g_pStatMeter->CalcCurrentDownloadSpeed();
m_bPauseDownload = g_pOptions->GetPauseDownload();
m_iDownloadLimit = g_pOptions->GetDownloadRate();
m_iThreadCount = Thread::GetThreadCount();
g_pStatMeter->CalcTotalStat(&m_iUpTimeSec, &m_iDnTimeSec, &m_iAllBytes, &m_bStandBy);
m_currentDownloadSpeed = g_StatMeter->CalcCurrentDownloadSpeed();
m_pauseDownload = g_WorkState->GetPauseDownload();
m_downloadLimit = g_WorkState->GetSpeedLimit();
m_threadCount = Thread::GetThreadCount();
g_StatMeter->CalcTotalStat(&m_upTimeSec, &m_dnTimeSec, &m_allBytes, &m_standBy);
DownloadQueue *pDownloadQueue = DownloadQueue::Lock();
m_iPostJobCount = 0;
for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++)
GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
m_postJobCount = 0;
for (NzbInfo* nzbInfo : downloadQueue->GetQueue())
{
NZBInfo* pNZBInfo = *it;
m_iPostJobCount += pNZBInfo->GetPostInfo() ? 1 : 0;
m_postJobCount += nzbInfo->GetPostInfo() ? 1 : 0;
}
pDownloadQueue->CalcRemainingSize(&m_lRemainingSize, NULL);
DownloadQueue::Unlock();
downloadQueue->CalcRemainingSize(&m_remainingSize, nullptr);
}
}
return true;
@@ -117,107 +94,82 @@ void Frontend::FreeData()
{
if (IsRemoteMode())
{
m_RemoteMessages.Clear();
DownloadQueue* pDownloadQueue = DownloadQueue::Lock();
pDownloadQueue->GetQueue()->Clear();
DownloadQueue::Unlock();
m_remoteMessages.clear();
DownloadQueue::Guard()->GetQueue()->clear();
}
}
MessageList* Frontend::LockMessages()
GuardedMessageList Frontend::GuardMessages()
{
if (IsRemoteMode())
{
return &m_RemoteMessages;
return GuardedMessageList(&m_remoteMessages, nullptr);
}
else
{
return g_pLog->LockMessages();
return g_Log->GuardMessages();
}
}
void Frontend::UnlockMessages()
{
if (!IsRemoteMode())
{
g_pLog->UnlockMessages();
}
}
DownloadQueue* Frontend::LockQueue()
{
return DownloadQueue::Lock();
}
void Frontend::UnlockQueue()
{
DownloadQueue::Unlock();
}
bool Frontend::IsRemoteMode()
{
return g_pOptions->GetRemoteClientMode();
return g_Options->GetRemoteClientMode();
}
void Frontend::ServerPauseUnpause(bool bPause)
void Frontend::ServerPauseUnpause(bool pause)
{
if (IsRemoteMode())
{
RequestPauseUnpause(bPause);
RequestPauseUnpause(pause);
}
else
{
g_pOptions->SetResumeTime(0);
g_pOptions->SetPauseDownload(bPause);
g_WorkState->SetResumeTime(0);
g_WorkState->SetPauseDownload(pause);
}
}
void Frontend::ServerSetDownloadRate(int iRate)
void Frontend::ServerSetDownloadRate(int rate)
{
if (IsRemoteMode())
{
RequestSetDownloadRate(iRate);
RequestSetDownloadRate(rate);
}
else
{
g_pOptions->SetDownloadRate(iRate);
g_WorkState->SetSpeedLimit(rate);
}
}
bool Frontend::ServerEditQueue(DownloadQueue::EEditAction eAction, int iOffset, int iID)
bool Frontend::ServerEditQueue(DownloadQueue::EEditAction action, int offset, int id)
{
if (IsRemoteMode())
{
return RequestEditQueue(eAction, iOffset, iID);
return RequestEditQueue(action, offset, id);
}
else
{
DownloadQueue* pDownloadQueue = LockQueue();
bool bOK = pDownloadQueue->EditEntry(iID, eAction, iOffset, NULL);
UnlockQueue();
return bOK;
return DownloadQueue::Guard()->EditEntry(id, action, CString::FormatStr("%i", offset));
}
return false;
}
void Frontend::InitMessageBase(SNZBRequestBase* pMessageBase, int iRequest, int iSize)
void Frontend::InitMessageBase(SNzbRequestBase* messageBase, int request, int size)
{
pMessageBase->m_iSignature = htonl(NZBMESSAGE_SIGNATURE);
pMessageBase->m_iType = htonl(iRequest);
pMessageBase->m_iStructSize = htonl(iSize);
messageBase->m_signature = htonl(NZBMESSAGE_SIGNATURE);
messageBase->m_type = htonl(request);
messageBase->m_structSize = htonl(size);
strncpy(pMessageBase->m_szUsername, g_pOptions->GetControlUsername(), NZBREQUESTPASSWORDSIZE - 1);
pMessageBase->m_szUsername[NZBREQUESTPASSWORDSIZE - 1] = '\0';
strncpy(messageBase->m_username, g_Options->GetControlUsername(), NZBREQUESTPASSWORDSIZE - 1);
messageBase->m_username[NZBREQUESTPASSWORDSIZE - 1] = '\0';
strncpy(pMessageBase->m_szPassword, g_pOptions->GetControlPassword(), NZBREQUESTPASSWORDSIZE);
pMessageBase->m_szPassword[NZBREQUESTPASSWORDSIZE - 1] = '\0';
strncpy(messageBase->m_password, g_Options->GetControlPassword(), NZBREQUESTPASSWORDSIZE);
messageBase->m_password[NZBREQUESTPASSWORDSIZE - 1] = '\0';
}
bool Frontend::RequestMessages()
{
const char* szControlIP = !strcmp(g_pOptions->GetControlIP(), "0.0.0.0") ? "127.0.0.1" : g_pOptions->GetControlIP();
Connection connection(szControlIP, g_pOptions->GetControlPort(), false);
const char* controlIp = !strcmp(g_Options->GetControlIp(), "0.0.0.0") ? "127.0.0.1" : g_Options->GetControlIp();
Connection connection(controlIp, g_Options->GetControlPort(), false);
bool OK = connection.Connect();
if (!OK)
@@ -225,16 +177,16 @@ bool Frontend::RequestMessages()
return false;
}
SNZBLogRequest LogRequest;
InitMessageBase(&LogRequest.m_MessageBase, eRemoteRequestLog, sizeof(LogRequest));
LogRequest.m_iLines = htonl(m_iNeededLogEntries);
if (m_iNeededLogEntries == 0)
SNzbLogRequest LogRequest;
InitMessageBase(&LogRequest.m_messageBase, rrLog, sizeof(LogRequest));
LogRequest.m_lines = htonl(m_neededLogEntries);
if (m_neededLogEntries == 0)
{
LogRequest.m_iIDFrom = htonl(m_iNeededLogFirstID > 0 ? m_iNeededLogFirstID : 1);
LogRequest.m_idFrom = htonl(m_neededLogFirstId > 0 ? m_neededLogFirstId : 1);
}
else
{
LogRequest.m_iIDFrom = 0;
LogRequest.m_idFrom = 0;
}
if (!connection.Send((char*)(&LogRequest), sizeof(LogRequest)))
@@ -243,44 +195,40 @@ bool Frontend::RequestMessages()
}
// Now listen for the returned log
SNZBLogResponse LogResponse;
bool bRead = connection.Recv((char*) &LogResponse, sizeof(LogResponse));
if (!bRead ||
(int)ntohl(LogResponse.m_MessageBase.m_iSignature) != (int)NZBMESSAGE_SIGNATURE ||
ntohl(LogResponse.m_MessageBase.m_iStructSize) != sizeof(LogResponse))
SNzbLogResponse LogResponse;
bool read = connection.Recv((char*) &LogResponse, sizeof(LogResponse));
if (!read ||
(int)ntohl(LogResponse.m_messageBase.m_signature) != (int)NZBMESSAGE_SIGNATURE ||
ntohl(LogResponse.m_messageBase.m_structSize) != sizeof(LogResponse))
{
return false;
}
char* pBuf = NULL;
if (ntohl(LogResponse.m_iTrailingDataLength) > 0)
CharBuffer buf;
if (ntohl(LogResponse.m_trailingDataLength) > 0)
{
pBuf = (char*)malloc(ntohl(LogResponse.m_iTrailingDataLength));
if (!connection.Recv(pBuf, ntohl(LogResponse.m_iTrailingDataLength)))
buf.Reserve(ntohl(LogResponse.m_trailingDataLength));
if (!connection.Recv(buf, buf.Size()))
{
free(pBuf);
return false;
}
}
connection.Disconnect();
if (ntohl(LogResponse.m_iTrailingDataLength) > 0)
if (ntohl(LogResponse.m_trailingDataLength) > 0)
{
char* pBufPtr = (char*)pBuf;
for (unsigned int i = 0; i < ntohl(LogResponse.m_iNrTrailingEntries); i++)
char* bufPtr = (char*)buf;
for (uint32 i = 0; i < ntohl(LogResponse.m_nrTrailingEntries); i++)
{
SNZBLogResponseEntry* pLogAnswer = (SNZBLogResponseEntry*) pBufPtr;
SNzbLogResponseEntry* logAnswer = (SNzbLogResponseEntry*) bufPtr;
char* szText = pBufPtr + sizeof(SNZBLogResponseEntry);
char* text = bufPtr + sizeof(SNzbLogResponseEntry);
Message* pMessage = new Message(ntohl(pLogAnswer->m_iID), (Message::EKind)ntohl(pLogAnswer->m_iKind), ntohl(pLogAnswer->m_tTime), szText);
m_RemoteMessages.push_back(pMessage);
m_remoteMessages.emplace_back(ntohl(logAnswer->m_id), (Message::EKind)ntohl(logAnswer->m_kind), ntohl(logAnswer->m_time), text);
pBufPtr += sizeof(SNZBLogResponseEntry) + ntohl(pLogAnswer->m_iTextLen);
bufPtr += sizeof(SNzbLogResponseEntry) + ntohl(logAnswer->m_textLen);
}
free(pBuf);
}
return true;
@@ -288,8 +236,8 @@ bool Frontend::RequestMessages()
bool Frontend::RequestFileList()
{
const char* szControlIP = !strcmp(g_pOptions->GetControlIP(), "0.0.0.0") ? "127.0.0.1" : g_pOptions->GetControlIP();
Connection connection(szControlIP, g_pOptions->GetControlPort(), false);
const char* controlIp = !strcmp(g_Options->GetControlIp(), "0.0.0.0") ? "127.0.0.1" : g_Options->GetControlIp();
Connection connection(controlIp, g_Options->GetControlPort(), false);
bool OK = connection.Connect();
if (!OK)
@@ -297,10 +245,10 @@ bool Frontend::RequestFileList()
return false;
}
SNZBListRequest ListRequest;
InitMessageBase(&ListRequest.m_MessageBase, eRemoteRequestList, sizeof(ListRequest));
ListRequest.m_bFileList = htonl(m_bFileList);
ListRequest.m_bServerState = htonl(m_bSummary);
SNzbListRequest ListRequest;
InitMessageBase(&ListRequest.m_messageBase, rrList, sizeof(ListRequest));
ListRequest.m_fileList = htonl(m_fileList);
ListRequest.m_serverState = htonl(m_summary);
if (!connection.Send((char*)(&ListRequest), sizeof(ListRequest)))
{
@@ -308,77 +256,83 @@ bool Frontend::RequestFileList()
}
// Now listen for the returned list
SNZBListResponse ListResponse;
bool bRead = connection.Recv((char*) &ListResponse, sizeof(ListResponse));
if (!bRead ||
(int)ntohl(ListResponse.m_MessageBase.m_iSignature) != (int)NZBMESSAGE_SIGNATURE ||
ntohl(ListResponse.m_MessageBase.m_iStructSize) != sizeof(ListResponse))
SNzbListResponse ListResponse;
bool read = connection.Recv((char*) &ListResponse, sizeof(ListResponse));
if (!read ||
(int)ntohl(ListResponse.m_messageBase.m_signature) != (int)NZBMESSAGE_SIGNATURE ||
ntohl(ListResponse.m_messageBase.m_structSize) != sizeof(ListResponse))
{
return false;
}
char* pBuf = NULL;
if (ntohl(ListResponse.m_iTrailingDataLength) > 0)
CharBuffer buf;
if (ntohl(ListResponse.m_trailingDataLength) > 0)
{
pBuf = (char*)malloc(ntohl(ListResponse.m_iTrailingDataLength));
if (!connection.Recv(pBuf, ntohl(ListResponse.m_iTrailingDataLength)))
buf.Reserve(ntohl(ListResponse.m_trailingDataLength));
if (!connection.Recv(buf, buf.Size()))
{
free(pBuf);
return false;
}
}
connection.Disconnect();
if (m_bSummary)
if (m_summary)
{
m_bPauseDownload = ntohl(ListResponse.m_bDownloadPaused);
m_lRemainingSize = Util::JoinInt64(ntohl(ListResponse.m_iRemainingSizeHi), ntohl(ListResponse.m_iRemainingSizeLo));
m_iCurrentDownloadSpeed = ntohl(ListResponse.m_iDownloadRate);
m_iDownloadLimit = ntohl(ListResponse.m_iDownloadLimit);
m_iThreadCount = ntohl(ListResponse.m_iThreadCount);
m_iPostJobCount = ntohl(ListResponse.m_iPostJobCount);
m_iUpTimeSec = ntohl(ListResponse.m_iUpTimeSec);
m_iDnTimeSec = ntohl(ListResponse.m_iDownloadTimeSec);
m_bStandBy = ntohl(ListResponse.m_bDownloadStandBy);
m_iAllBytes = Util::JoinInt64(ntohl(ListResponse.m_iDownloadedBytesHi), ntohl(ListResponse.m_iDownloadedBytesLo));
m_pauseDownload = ntohl(ListResponse.m_downloadPaused);
m_remainingSize = Util::JoinInt64(ntohl(ListResponse.m_remainingSizeHi), ntohl(ListResponse.m_remainingSizeLo));
m_currentDownloadSpeed = ntohl(ListResponse.m_downloadRate);
m_downloadLimit = ntohl(ListResponse.m_downloadLimit);
m_threadCount = ntohl(ListResponse.m_threadCount);
m_postJobCount = ntohl(ListResponse.m_postJobCount);
m_upTimeSec = ntohl(ListResponse.m_upTimeSec);
m_dnTimeSec = ntohl(ListResponse.m_downloadTimeSec);
m_standBy = ntohl(ListResponse.m_downloadStandBy);
m_allBytes = Util::JoinInt64(ntohl(ListResponse.m_downloadedBytesHi), ntohl(ListResponse.m_downloadedBytesLo));
}
if (m_bFileList && ntohl(ListResponse.m_iTrailingDataLength) > 0)
if (m_fileList && ntohl(ListResponse.m_trailingDataLength) > 0)
{
RemoteClient client;
client.SetVerbose(false);
DownloadQueue* pDownloadQueue = LockQueue();
client.BuildFileList(&ListResponse, pBuf, pDownloadQueue);
UnlockQueue();
}
if (pBuf)
{
free(pBuf);
client.BuildFileList(&ListResponse, buf, DownloadQueue::Guard());
}
return true;
}
bool Frontend::RequestPauseUnpause(bool bPause)
bool Frontend::RequestPauseUnpause(bool pause)
{
RemoteClient client;
client.SetVerbose(false);
return client.RequestServerPauseUnpause(bPause, eRemotePauseUnpauseActionDownload);
return client.RequestServerPauseUnpause(pause, rpDownload);
}
bool Frontend::RequestSetDownloadRate(int iRate)
bool Frontend::RequestSetDownloadRate(int rate)
{
RemoteClient client;
client.SetVerbose(false);
return client.RequestServerSetDownloadRate(iRate);
return client.RequestServerSetDownloadRate(rate);
}
bool Frontend::RequestEditQueue(DownloadQueue::EEditAction eAction, int iOffset, int iID)
bool Frontend::RequestEditQueue(DownloadQueue::EEditAction action, int offset, int id)
{
RemoteClient client;
client.SetVerbose(false);
return client.RequestServerEditQueue(eAction, iOffset, NULL, &iID, 1, NULL, eRemoteMatchModeID);
IdList ids = { id };
return client.RequestServerEditQueue(action, offset, nullptr, &ids, nullptr, rmId);
}
void Frontend::Wait(int milliseconds)
{
if (g_WorkState->GetPauseFrontend())
{
Guard guard(m_waitMutex);
m_waitCond.WaitFor(m_waitMutex, 2000);
}
else
{
Util::Sleep(milliseconds);
}
}

View File

@@ -1,8 +1,8 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,12 +15,7 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
@@ -32,51 +27,62 @@
#include "DownloadInfo.h"
#include "MessageBase.h"
#include "QueueEditor.h"
#include "Observer.h"
class Frontend : public Thread
{
private:
MessageList m_RemoteMessages;
bool RequestMessages();
bool RequestFileList();
public:
Frontend();
protected:
bool m_bSummary;
bool m_bFileList;
unsigned int m_iNeededLogEntries;
unsigned int m_iNeededLogFirstID;
int m_iUpdateInterval;
bool m_summary = false;
bool m_fileList = false;
uint32 m_neededLogEntries = 0;
uint32 m_neededLogFirstId = 0;
int m_updateInterval;
// summary
int m_iCurrentDownloadSpeed;
long long m_lRemainingSize;
bool m_bPauseDownload;
int m_iDownloadLimit;
int m_iThreadCount;
int m_iPostJobCount;
int m_iUpTimeSec;
int m_iDnTimeSec;
long long m_iAllBytes;
bool m_bStandBy;
int m_currentDownloadSpeed = 0;
int64 m_remainingSize = 0;
bool m_pauseDownload = false;
int m_downloadLimit = 0;
int m_threadCount = 0;
int m_postJobCount = 0;
int m_upTimeSec = 0;
int m_dnTimeSec = 0;
int64 m_allBytes = 0;
bool m_standBy = false;
Mutex m_waitMutex;
ConditionVar m_waitCond;
bool PrepareData();
void FreeData();
MessageList* LockMessages();
void UnlockMessages();
DownloadQueue* LockQueue();
void UnlockQueue();
bool IsRemoteMode();
void InitMessageBase(SNZBRequestBase* pMessageBase, int iRequest, int iSize);
void ServerPauseUnpause(bool bPause);
bool RequestPauseUnpause(bool bPause);
void ServerSetDownloadRate(int iRate);
bool RequestSetDownloadRate(int iRate);
bool ServerEditQueue(DownloadQueue::EEditAction eAction, int iOffset, int iEntry);
bool RequestEditQueue(DownloadQueue::EEditAction eAction, int iOffset, int iID);
virtual void Stop();
bool PrepareData();
void FreeData();
GuardedMessageList GuardMessages();
bool IsRemoteMode();
void InitMessageBase(SNzbRequestBase* messageBase, int request, int size);
void ServerPauseUnpause(bool pause);
bool RequestPauseUnpause(bool pause);
void ServerSetDownloadRate(int rate);
bool RequestSetDownloadRate(int rate);
bool ServerEditQueue(DownloadQueue::EEditAction action, int offset, int entry);
bool RequestEditQueue(DownloadQueue::EEditAction action, int offset, int id);
void Wait(int milliseconds);
public:
Frontend();
private:
class WorkStateObserver : public Observer
{
public:
Frontend* m_owner;
virtual void Update(Subject* caller, void* aspect) { m_owner->WorkStateUpdate(caller, aspect); }
};
MessageList m_remoteMessages;
WorkStateObserver m_workStateObserver;
bool RequestMessages();
bool RequestFileList();
void WorkStateUpdate(Subject* caller, void* aspect);
};
#endif

View File

@@ -1,8 +1,8 @@
/*
* This file if part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,43 +15,15 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include "nzbget.h"
#include "Util.h"
#include "LoggableFrontend.h"
#include "Log.h"
LoggableFrontend::LoggableFrontend()
{
debug("Creating LoggableFrontend");
m_iNeededLogEntries = 0;
m_bSummary = false;
m_bFileList = false;
}
void LoggableFrontend::Run()
{
debug("Entering LoggableFrontend-loop");
@@ -59,7 +31,7 @@ void LoggableFrontend::Run()
while (!IsStopped())
{
Update();
usleep(m_iUpdateInterval * 1000);
Wait(m_updateInterval);
}
// Printing the last messages
Update();
@@ -79,23 +51,24 @@ void LoggableFrontend::Update()
BeforePrint();
MessageList* pMessages = LockMessages();
if (!pMessages->empty())
{
Message* pFirstMessage = pMessages->front();
int iStart = m_iNeededLogFirstID - pFirstMessage->GetID() + 1;
if (iStart < 0)
GuardedMessageList messages = GuardMessages();
if (!messages->empty())
{
PrintSkip();
iStart = 0;
}
for (unsigned int i = (unsigned int)iStart; i < pMessages->size(); i++)
{
PrintMessage((*pMessages)[i]);
m_iNeededLogFirstID = (*pMessages)[i]->GetID();
Message& firstMessage = messages->front();
int start = m_neededLogFirstId - firstMessage.GetId() + 1;
if (start < 0)
{
PrintSkip();
start = 0;
}
for (uint32 i = (uint32)start; i < messages->size(); i++)
{
PrintMessage(messages->at(i));
m_neededLogFirstId = messages->at(i).GetId();
}
}
}
UnlockMessages();
PrintStatus();
@@ -104,15 +77,16 @@ void LoggableFrontend::Update()
fflush(stdout);
}
void LoggableFrontend::PrintMessage(Message * pMessage)
void LoggableFrontend::PrintMessage(Message& message)
{
#ifdef WIN32
char* msg = strdup(pMessage->GetText());
CharToOem(msg, msg);
CString cmsg = message.GetText();
CharToOem(cmsg, cmsg);
const char* msg = cmsg;
#else
const char* msg = pMessage->GetText();
const char* msg = message.GetText();
#endif
switch (pMessage->GetKind())
switch (message.GetKind())
{
case Message::mkDebug:
printf("[DEBUG] %s\n", msg);
@@ -130,9 +104,6 @@ void LoggableFrontend::PrintMessage(Message * pMessage)
printf("[DETAIL] %s\n", msg);
break;
}
#ifdef WIN32
free(msg);
#endif
}
void LoggableFrontend::PrintSkip()

View File

@@ -1,8 +1,8 @@
/*
* This file if part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,12 +15,7 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
@@ -33,16 +28,13 @@
class LoggableFrontend : public Frontend
{
protected:
virtual void Run();
virtual void Update();
virtual void BeforePrint() {};
virtual void BeforeExit() {};
virtual void PrintMessage(Message* pMessage);
virtual void PrintStatus() {};
virtual void PrintSkip();
public:
LoggableFrontend();
virtual void Run();
virtual void Update();
virtual void BeforePrint() {};
virtual void BeforeExit() {};
virtual void PrintMessage(Message& message);
virtual void PrintStatus() {};
virtual void PrintSkip();
};
#endif

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,12 +15,7 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
@@ -29,97 +24,93 @@
#ifndef DISABLE_CURSES
#include <vector>
#include <time.h>
#include "NString.h"
#include "Frontend.h"
#include "Log.h"
#include "DownloadInfo.h"
class NCursesFrontend : public Frontend
{
private:
enum EInputMode
{
eNormal,
eEditQueue,
eDownloadRate
};
bool m_bUseColor;
int m_iDataUpdatePos;
bool m_bUpdateNextTime;
int m_iScreenHeight;
int m_iScreenWidth;
int m_iQueueWinTop;
int m_iQueueWinHeight;
int m_iQueueWinClientHeight;
int m_iMessagesWinTop;
int m_iMessagesWinHeight;
int m_iMessagesWinClientHeight;
int m_iSelectedQueueEntry;
int m_iLastEditEntry;
bool m_bLastPausePars;
int m_iQueueScrollOffset;
char* m_szHint;
time_t m_tStartHint;
int m_iColWidthFiles;
int m_iColWidthTotal;
int m_iColWidthLeft;
// Inputting numbers
int m_iInputNumberIndex;
int m_iInputValue;
#ifdef WIN32
CHAR_INFO* m_pScreenBuffer;
CHAR_INFO* m_pOldScreenBuffer;
int m_iScreenBufferSize;
std::vector<WORD> m_ColorAttr;
#else
void* m_pWindow; // WINDOW*
#endif
EInputMode m_eInputMode;
bool m_bShowNZBname;
bool m_bShowTimestamp;
bool m_bGroupFiles;
int m_QueueWindowPercentage;
#ifdef WIN32
void init_pair(int iColorNumber, WORD wForeColor, WORD wBackColor);
#endif
void PlotLine(const char * szString, int iRow, int iPos, int iColorPair);
void PlotText(const char * szString, int iRow, int iPos, int iColorPair, bool bBlink);
void PrintMessages();
void PrintQueue();
void PrintFileQueue();
void PrintFilename(FileInfo* pFileInfo, int iRow, bool bSelected);
void PrintGroupQueue();
void ResetColWidths();
void PrintGroupname(NZBInfo* pNZBInfo, int iRow, bool bSelected, bool bCalcColWidth);
void PrintTopHeader(char* szHeader, int iLineNr, bool bUpTime);
int PrintMessage(Message* Msg, int iRow, int iMaxLines);
void PrintKeyInputBar();
void PrintStatus();
void UpdateInput(int initialKey);
void Update(int iKey);
void SetCurrentQueueEntry(int iEntry);
void CalcWindowSizes();
void RefreshScreen();
int ReadConsoleKey();
int CalcQueueSize();
void NeedUpdateData();
bool EditQueue(DownloadQueue::EEditAction eAction, int iOffset);
void SetHint(const char* szHint);
public:
NCursesFrontend();
virtual ~NCursesFrontend();
protected:
virtual void Run();
virtual void Run();
public:
NCursesFrontend();
virtual ~NCursesFrontend();
private:
enum EInputMode
{
normal,
editQueue,
downloadRate
};
bool m_useColor = true;
int m_dataUpdatePos = 0;
bool m_updateNextTime = false;
int m_screenHeight = 0;
int m_screenWidth = 0;
int m_queueWinTop = 0;
int m_queueWinHeight = 0;
int m_queueWinClientHeight = 0;
int m_messagesWinTop = 0;
int m_messagesWinHeight = 0;
int m_messagesWinClientHeight = 0;
int m_selectedQueueEntry = 0;
int m_lastEditEntry = -1;
bool m_lastPausePars = false;
int m_queueScrollOffset = 0;
CString m_hint;
time_t m_startHint;
int m_colWidthFiles;
int m_colWidthTotal;
int m_colWidthLeft;
// Inputting numbers
int m_inputNumberIndex = 0;
int m_inputValue;
#ifdef WIN32
std::vector<CHAR_INFO> m_screenBuffer;
std::vector<CHAR_INFO> m_oldScreenBuffer;
std::vector<WORD> m_colorAttr;
#else
void* m_window; // WINDOW*
#endif
EInputMode m_inputMode = normal;
bool m_showNzbname;
bool m_showTimestamp;
bool m_groupFiles;
int m_queueWindowPercentage = 50;
#ifdef WIN32
void init_pair(int colorNumber, WORD wForeColor, WORD wBackColor);
#endif
void PlotLine(const char * string, int row, int pos, int colorPair);
void PlotText(const char * string, int row, int pos, int colorPair, bool blink);
void PrintMessages();
void PrintQueue();
void PrintFileQueue();
void PrintFilename(FileInfo* fileInfo, int row, bool selected);
void PrintGroupQueue();
void ResetColWidths();
void PrintGroupname(NzbInfo* nzbInfo, int row, bool selected, bool calcColWidth);
void PrintTopHeader(char* header, int lineNr, bool upTime);
int PrintMessage(Message& msg, int row, int maxLines);
void PrintKeyInputBar();
void PrintStatus();
void UpdateInput(int initialKey);
void Update(int key);
void SetCurrentQueueEntry(int entry);
void CalcWindowSizes();
void RefreshScreen();
int ReadConsoleKey();
int CalcQueueSize();
void NeedUpdateData();
bool EditQueue(DownloadQueue::EEditAction action, int offset);
void SetHint(const char* hint);
};
#endif

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,21 +14,14 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef COMMANDLINEPARSER_H
#define COMMANDLINEPARSER_H
#include <vector>
#include <list>
#include <time.h>
#include "NString.h"
class CommandLineParser
{
@@ -62,108 +55,100 @@ public:
};
enum EMatchMode
{
mmID = 1,
mmId = 1,
mmName,
mmRegEx
};
typedef std::vector<char*> NameList;
typedef std::vector<int> IdList;
typedef std::vector<CString> NameList;
CommandLineParser(int argc, const char* argv[]);
void PrintUsage(const char* com);
bool GetErrors() { return m_errors; }
bool GetNoConfig() { return m_noConfig; }
const char* GetConfigFilename() { return m_configFilename; }
bool GetServerMode() { return m_serverMode; }
bool GetDaemonMode() { return m_daemonMode; }
bool GetRemoteClientMode() { return m_remoteClientMode; }
EClientOperation GetClientOperation() { return m_clientOperation; }
NameList* GetOptionList() { return &m_optionList; }
int GetEditQueueAction() { return m_editQueueAction; }
int GetEditQueueOffset() { return m_editQueueOffset; }
IdList* GetEditQueueIdList() { return &m_editQueueIdList; }
NameList* GetEditQueueNameList() { return &m_editQueueNameList; }
EMatchMode GetMatchMode() { return m_matchMode; }
const char* GetEditQueueText() { return m_editQueueText; }
const char* GetArgFilename() { return m_argFilename; }
const char* GetAddCategory() { return m_addCategory; }
bool GetAddPaused() { return m_addPaused; }
const char* GetLastArg() { return m_lastArg; }
int GetAddPriority() { return m_addPriority; }
const char* GetAddNzbFilename() { return m_addNzbFilename; }
bool GetAddTop() { return m_addTop; }
const char* GetAddDupeKey() { return m_addDupeKey; }
int GetAddDupeScore() { return m_addDupeScore; }
int GetAddDupeMode() { return m_addDupeMode; }
int GetSetRate() { return m_setRate; }
int GetLogLines() { return m_logLines; }
int GetWriteLogKind() { return m_writeLogKind; }
bool GetTestBacktrace() { return m_testBacktrace; }
bool GetWebGet() { return m_webGet; }
const char* GetWebGetFilename() { return m_webGetFilename; }
bool GetSigVerify() { return m_sigVerify; }
const char* GetPubKeyFilename() { return m_pubKeyFilename; }
const char* GetSigFilename() { return m_sigFilename; }
bool GetPrintOptions() { return m_printOptions; }
bool GetPrintVersion() { return m_printVersion; }
bool GetPrintUsage() { return m_printUsage; }
bool GetPauseDownload() const { return m_pauseDownload; }
private:
bool m_bNoConfig;
char* m_szConfigFilename;
bool m_noConfig = false;
CString m_configFilename;
// Parsed command-line parameters
bool m_bErrors;
bool m_bPrintVersion;
bool m_bPrintUsage;
bool m_bServerMode;
bool m_bDaemonMode;
bool m_bRemoteClientMode;
EClientOperation m_eClientOperation;
NameList m_OptionList;
int m_iEditQueueAction;
int m_iEditQueueOffset;
int* m_pEditQueueIDList;
int m_iEditQueueIDCount;
NameList m_EditQueueNameList;
EMatchMode m_EMatchMode;
char* m_szEditQueueText;
char* m_szArgFilename;
char* m_szAddCategory;
int m_iAddPriority;
bool m_bAddPaused;
char* m_szAddNZBFilename;
char* m_szLastArg;
bool m_bPrintOptions;
bool m_bAddTop;
char* m_szAddDupeKey;
int m_iAddDupeScore;
int m_iAddDupeMode;
int m_iSetRate;
int m_iLogLines;
int m_iWriteLogKind;
bool m_bTestBacktrace;
bool m_bWebGet;
char* m_szWebGetFilename;
bool m_bSigVerify;
char* m_szPubKeyFilename;
char* m_szSigFilename;
bool m_bPauseDownload;
bool m_errors = false;
bool m_printVersion = false;
bool m_printUsage = false;
bool m_serverMode = false;
bool m_daemonMode = false;
bool m_remoteClientMode = false;
EClientOperation m_clientOperation;
NameList m_optionList;
int m_editQueueAction = 0;
int m_editQueueOffset = 0;
IdList m_editQueueIdList;
NameList m_editQueueNameList;
EMatchMode m_matchMode = mmId;
CString m_editQueueText;
CString m_argFilename;
CString m_addCategory;
int m_addPriority = 0;
bool m_addPaused = false;
CString m_addNzbFilename;
CString m_lastArg;
bool m_printOptions = false;
bool m_addTop = false;
CString m_addDupeKey;
int m_addDupeScore = 0;
int m_addDupeMode = 0;
int m_setRate = 0;
int m_logLines = 0;
int m_writeLogKind = 0;
bool m_testBacktrace = false;
bool m_webGet = false;
CString m_webGetFilename;
bool m_sigVerify = false;
CString m_pubKeyFilename;
CString m_sigFilename;
bool m_pauseDownload = false;
void InitCommandLine(int argc, const char* argv[]);
void InitFileArg(int argc, const char* argv[]);
void ParseFileIDList(int argc, const char* argv[], int optind);
void ParseFileNameList(int argc, const char* argv[], int optind);
bool ParseTime(const char* szTime, int* pHours, int* pMinutes);
void ReportError(const char* szErrMessage);
public:
CommandLineParser(int argc, const char* argv[]);
~CommandLineParser();
void PrintUsage(const char* com);
bool GetErrors() { return m_bErrors; }
bool GetNoConfig() { return m_bNoConfig; }
const char* GetConfigFilename() { return m_szConfigFilename; }
bool GetServerMode() { return m_bServerMode; }
bool GetDaemonMode() { return m_bDaemonMode; }
bool GetRemoteClientMode() { return m_bRemoteClientMode; }
EClientOperation GetClientOperation() { return m_eClientOperation; }
NameList* GetOptionList() { return &m_OptionList; }
int GetEditQueueAction() { return m_iEditQueueAction; }
int GetEditQueueOffset() { return m_iEditQueueOffset; }
int* GetEditQueueIDList() { return m_pEditQueueIDList; }
int GetEditQueueIDCount() { return m_iEditQueueIDCount; }
NameList* GetEditQueueNameList() { return &m_EditQueueNameList; }
EMatchMode GetMatchMode() { return m_EMatchMode; }
const char* GetEditQueueText() { return m_szEditQueueText; }
const char* GetArgFilename() { return m_szArgFilename; }
const char* GetAddCategory() { return m_szAddCategory; }
bool GetAddPaused() { return m_bAddPaused; }
const char* GetLastArg() { return m_szLastArg; }
int GetAddPriority() { return m_iAddPriority; }
char* GetAddNZBFilename() { return m_szAddNZBFilename; }
bool GetAddTop() { return m_bAddTop; }
const char* GetAddDupeKey() { return m_szAddDupeKey; }
int GetAddDupeScore() { return m_iAddDupeScore; }
int GetAddDupeMode() { return m_iAddDupeMode; }
int GetSetRate() { return m_iSetRate; }
int GetLogLines() { return m_iLogLines; }
int GetWriteLogKind() { return m_iWriteLogKind; }
bool GetTestBacktrace() { return m_bTestBacktrace; }
bool GetWebGet() { return m_bWebGet; }
const char* GetWebGetFilename() { return m_szWebGetFilename; }
bool GetSigVerify() { return m_bSigVerify; }
const char* GetPubKeyFilename() { return m_szPubKeyFilename; }
const char* GetSigFilename() { return m_szSigFilename; }
bool GetPrintOptions() { return m_bPrintOptions; }
bool GetPrintVersion() { return m_bPrintVersion; }
bool GetPrintUsage() { return m_bPrintUsage; }
bool GetPauseDownload() const { return m_bPauseDownload; }
void InitCommandLine(int argc, const char* argv[]);
void InitFileArg(int argc, const char* argv[]);
void ParseFileIdList(int argc, const char* argv[], int optind);
void ParseFileNameList(int argc, const char* argv[], int optind);
void ReportError(const char* errMessage);
};
extern CommandLineParser* g_pCommandLineParser;
#endif

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2015-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,61 +14,49 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#ifdef WIN32
#include <direct.h>
#else
#include <unistd.h>
#endif
#include "nzbget.h"
#include "DiskService.h"
#include "Options.h"
#include "WorkState.h"
#include "StatMeter.h"
#include "Log.h"
#include "Util.h"
#include "FileSystem.h"
DiskService::DiskService()
{
m_iInterval = 0;
m_bWaitingReported = false;
m_bWaitingRequiredDir = true;
g_WorkState->Attach(this);
}
void DiskService::Update(Subject* caller, void* aspect)
{
WakeUp();
}
int DiskService::ServiceInterval()
{
return m_waitingRequiredDir ? 1 :
g_Options->GetDiskSpace() <= 0 ? Service::Sleep :
// notifications from 'WorkState' are not 100% reliable due to race conditions
!g_WorkState->GetDownloading() ? 10 :
1;
}
void DiskService::ServiceWork()
{
m_iInterval++;
if (m_iInterval == 5)
debug("Disk service work");
if (g_Options->GetDiskSpace() > 0 && g_WorkState->GetDownloading())
{
if (!g_pOptions->GetPauseDownload() &&
g_pOptions->GetDiskSpace() > 0 && !g_pStatMeter->GetStandBy())
{
// check free disk space every 1 second
CheckDiskSpace();
}
m_iInterval = 0;
// check free disk space every 1 second
CheckDiskSpace();
}
if (m_bWaitingRequiredDir)
if (m_waitingRequiredDir)
{
CheckRequiredDir();
}
@@ -76,56 +64,60 @@ void DiskService::ServiceWork()
void DiskService::CheckDiskSpace()
{
long long lFreeSpace = Util::FreeDiskSize(g_pOptions->GetDestDir());
if (lFreeSpace > -1 && lFreeSpace / 1024 / 1024 < g_pOptions->GetDiskSpace())
debug("Disk service work: check disk space");
int64 freeSpace = FileSystem::FreeDiskSize(g_Options->GetDestDir());
if (freeSpace > -1 && freeSpace / 1024 / 1024 < g_Options->GetDiskSpace())
{
warn("Low disk space on %s. Pausing download", g_pOptions->GetDestDir());
g_pOptions->SetPauseDownload(true);
warn("Low disk space on %s. Pausing download", g_Options->GetDestDir());
g_WorkState->SetPauseDownload(true);
}
if (!Util::EmptyStr(g_pOptions->GetInterDir()))
if (!Util::EmptyStr(g_Options->GetInterDir()))
{
lFreeSpace = Util::FreeDiskSize(g_pOptions->GetInterDir());
if (lFreeSpace > -1 && lFreeSpace / 1024 / 1024 < g_pOptions->GetDiskSpace())
freeSpace = FileSystem::FreeDiskSize(g_Options->GetInterDir());
if (freeSpace > -1 && freeSpace / 1024 / 1024 < g_Options->GetDiskSpace())
{
warn("Low disk space on %s. Pausing download", g_pOptions->GetInterDir());
g_pOptions->SetPauseDownload(true);
warn("Low disk space on %s. Pausing download", g_Options->GetInterDir());
g_WorkState->SetPauseDownload(true);
}
}
}
void DiskService::CheckRequiredDir()
{
if (!Util::EmptyStr(g_pOptions->GetRequiredDir()))
debug("Disk service work: check required dir");
if (!Util::EmptyStr(g_Options->GetRequiredDir()))
{
bool bAllExist = true;
bool bWasWaitingReported = m_bWaitingReported;
bool allExist = true;
bool wasWaitingReported = m_waitingReported;
// split RequiredDir into tokens
Tokenizer tok(g_pOptions->GetRequiredDir(), ",;");
while (const char* szDir = tok.Next())
Tokenizer tok(g_Options->GetRequiredDir(), ",;");
while (const char* dir = tok.Next())
{
if (!Util::FileExists(szDir) && !Util::DirectoryExists(szDir))
if (!FileSystem::FileExists(dir) && !FileSystem::DirectoryExists(dir))
{
if (!bWasWaitingReported)
if (!wasWaitingReported)
{
info("Waiting for required directory %s", szDir);
m_bWaitingReported = true;
info("Waiting for required directory %s", dir);
m_waitingReported = true;
}
bAllExist = false;
allExist = false;
}
}
if (!bAllExist)
if (!allExist)
{
return;
}
}
if (m_bWaitingReported)
if (m_waitingReported)
{
info("All required directories available");
}
g_pOptions->SetTempPauseDownload(false);
g_pOptions->SetTempPausePostprocess(false);
m_bWaitingRequiredDir = false;
g_WorkState->SetTempPauseDownload(false);
g_WorkState->SetTempPausePostprocess(false);
m_waitingRequiredDir = false;
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2015-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,12 +14,7 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
@@ -27,23 +22,24 @@
#define DISKSERVICE_H
#include "Service.h"
#include "Observer.h"
class DiskService : public Service
class DiskService : public Service, public Observer
{
private:
int m_iInterval;
bool m_bWaitingRequiredDir;
bool m_bWaitingReported;
void CheckDiskSpace();
void CheckRequiredDir();
public:
DiskService();
protected:
virtual int ServiceInterval() { return 200; }
virtual void ServiceWork();
virtual int ServiceInterval();
virtual void ServiceWork();
virtual void Update(Subject* caller, void* aspect);
public:
DiskService();
private:
bool m_waitingRequiredDir = true;
bool m_waitingReported = false;
void CheckDiskSpace();
void CheckRequiredDir();
};
#endif

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2013-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2013-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,224 +14,160 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include <errno.h>
#ifdef HAVE_OPENSSL
#include <openssl/rsa.h>
#include <openssl/sha.h>
#include <openssl/pem.h>
#endif /* HAVE_OPENSSL */
#include "nzbget.h"
#include "Log.h"
#include "Util.h"
#include "FileSystem.h"
#include "Maintenance.h"
#include "Options.h"
#include "CommandLineParser.h"
extern void ExitProc();
extern int g_iArgumentCount;
extern char* (*g_szArguments)[];
extern int g_ArgumentCount;
extern char* (*g_Arguments)[];
#ifdef HAVE_OPENSSL
class Signature
{
private:
const char* m_szInFilename;
const char* m_szSigFilename;
const char* m_szPubKeyFilename;
unsigned char m_InHash[SHA256_DIGEST_LENGTH];
unsigned char m_Signature[256];
RSA* m_pPubKey;
bool ReadSignature();
bool ComputeInHash();
bool ReadPubKey();
public:
Signature(const char* szInFilename, const char* szSigFilename, const char* szPubKeyFilename);
~Signature();
bool Verify();
Signature(const char* inFilename, const char* sigFilename, const char* pubKeyFilename);
~Signature();
bool Verify();
private:
const char* m_inFilename;
const char* m_sigFilename;
const char* m_pubKeyFilename;
uchar m_inHash[SHA256_DIGEST_LENGTH];
uchar m_signature[256];
RSA* m_pubKey;
bool ReadSignature();
bool ComputeInHash();
bool ReadPubKey();
};
#endif
Maintenance::Maintenance()
{
m_iIDMessageGen = 0;
m_UpdateScriptController = NULL;
m_szUpdateScript = NULL;
}
Maintenance::~Maintenance()
{
m_mutexController.Lock();
if (m_UpdateScriptController)
bool waitScript = false;
{
m_UpdateScriptController->Detach();
m_mutexController.Unlock();
while (m_UpdateScriptController)
Guard guard(m_controllerMutex);
if (m_updateScriptController)
{
usleep(20*1000);
m_updateScriptController->Detach();
waitScript = true;
}
}
m_Messages.Clear();
free(m_szUpdateScript);
if (waitScript)
{
while (m_updateScriptController)
{
Util::Sleep(20);
}
}
}
void Maintenance::ResetUpdateController()
{
m_mutexController.Lock();
m_UpdateScriptController = NULL;
m_mutexController.Unlock();
Guard guard(m_controllerMutex);
m_updateScriptController = nullptr;
}
MessageList* Maintenance::LockMessages()
void Maintenance::AddMessage(Message::EKind kind, time_t time, const char * text)
{
m_mutexLog.Lock();
return &m_Messages;
}
void Maintenance::UnlockMessages()
{
m_mutexLog.Unlock();
}
void Maintenance::AddMessage(Message::EKind eKind, time_t tTime, const char * szText)
{
if (tTime == 0)
if (time == 0)
{
tTime = time(NULL);
time = Util::CurrentTime();
}
m_mutexLog.Lock();
Message* pMessage = new Message(++m_iIDMessageGen, eKind, tTime, szText);
m_Messages.push_back(pMessage);
m_mutexLog.Unlock();
Guard guard(m_logMutex);
m_messages.emplace_back(++m_idMessageGen, kind, time, text);
}
bool Maintenance::StartUpdate(EBranch eBranch)
bool Maintenance::StartUpdate(EBranch branch)
{
m_mutexController.Lock();
bool bAlreadyUpdating = m_UpdateScriptController != NULL;
m_mutexController.Unlock();
bool alreadyUpdating;
{
Guard guard(m_controllerMutex);
alreadyUpdating = m_updateScriptController != nullptr;
}
if (bAlreadyUpdating)
if (alreadyUpdating)
{
error("Could not start update-script: update-script is already running");
return false;
}
if (m_szUpdateScript)
{
free(m_szUpdateScript);
m_szUpdateScript = NULL;
}
if (!ReadPackageInfoStr("install-script", &m_szUpdateScript))
if (!ReadPackageInfoStr("install-script", m_updateScript))
{
return false;
}
// make absolute path
if (m_szUpdateScript[0] != PATH_SEPARATOR
if (m_updateScript[0] != PATH_SEPARATOR
#ifdef WIN32
&& !(strlen(m_szUpdateScript) > 2 && m_szUpdateScript[1] == ':')
&& !(strlen(m_updateScript) > 2 && m_updateScript[1] == ':')
#endif
)
{
char szFilename[MAX_PATH + 100];
snprintf(szFilename, sizeof(szFilename), "%s%c%s", g_pOptions->GetAppDir(), PATH_SEPARATOR, m_szUpdateScript);
free(m_szUpdateScript);
m_szUpdateScript = strdup(szFilename);
m_updateScript = CString::FormatStr("%s%c%s", g_Options->GetAppDir(), PATH_SEPARATOR, *m_updateScript);
}
m_Messages.Clear();
m_messages.clear();
m_UpdateScriptController = new UpdateScriptController();
m_UpdateScriptController->SetScript(m_szUpdateScript);
m_UpdateScriptController->SetBranch(eBranch);
m_UpdateScriptController->SetAutoDestroy(true);
m_updateScriptController = new UpdateScriptController();
m_updateScriptController->SetArgs({*m_updateScript});
m_updateScriptController->SetBranch(branch);
m_updateScriptController->SetAutoDestroy(true);
m_UpdateScriptController->Start();
m_updateScriptController->Start();
return true;
}
bool Maintenance::CheckUpdates(char** pUpdateInfo)
bool Maintenance::CheckUpdates(CString& updateInfo)
{
char* szUpdateInfoScript;
if (!ReadPackageInfoStr("update-info-script", &szUpdateInfoScript))
CString updateInfoScript;
if (!ReadPackageInfoStr("update-info-script", updateInfoScript))
{
return false;
}
*pUpdateInfo = NULL;
UpdateInfoScriptController::ExecuteScript(szUpdateInfoScript, pUpdateInfo);
UpdateInfoScriptController::ExecuteScript(updateInfoScript, updateInfo);
free(szUpdateInfoScript);
return *pUpdateInfo;
return updateInfo.Length() > 0;
}
bool Maintenance::ReadPackageInfoStr(const char* szKey, char** pValue)
bool Maintenance::ReadPackageInfoStr(const char* key, CString& value)
{
char szFileName[1024];
snprintf(szFileName, 1024, "%s%cpackage-info.json", g_pOptions->GetWebDir(), PATH_SEPARATOR);
szFileName[1024-1] = '\0';
BString<1024> fileName("%s%cpackage-info.json", g_Options->GetWebDir(), PATH_SEPARATOR);
char* szPackageInfo;
int iPackageInfoLen;
if (!Util::LoadFileIntoBuffer(szFileName, &szPackageInfo, &iPackageInfoLen))
CharBuffer packageInfo;
if (!FileSystem::LoadFileIntoBuffer(fileName, packageInfo, true))
{
error("Could not load file %s", szFileName);
error("Could not load file %s", *fileName);
return false;
}
char szKeyStr[100];
snprintf(szKeyStr, 100, "\"%s\"", szKey);
szKeyStr[100-1] = '\0';
BString<100> keyStr("\"%s\"", key);
char* p = strstr(szPackageInfo, szKeyStr);
char* p = strstr(packageInfo, keyStr);
if (!p)
{
error("Could not parse file %s", szFileName);
free(szPackageInfo);
error("Could not parse file %s", *fileName);
return false;
}
p = strchr(p + strlen(szKeyStr), '"');
p = strchr(p + strlen(keyStr), '"');
if (!p)
{
error("Could not parse file %s", szFileName);
free(szPackageInfo);
error("Could not parse file %s", *fileName);
return false;
}
@@ -239,34 +175,30 @@ bool Maintenance::ReadPackageInfoStr(const char* szKey, char** pValue)
char* pend = strchr(p, '"');
if (!pend)
{
error("Could not parse file %s", szFileName);
free(szPackageInfo);
error("Could not parse file %s", *fileName);
return false;
}
int iLen = pend - p;
if (iLen >= sizeof(szFileName))
size_t len = pend - p;
if (len >= sizeof(fileName))
{
error("Could not parse file %s", szFileName);
free(szPackageInfo);
error("Could not parse file %s", *fileName);
return false;
}
*pValue = (char*)malloc(iLen+1);
strncpy(*pValue, p, iLen);
(*pValue)[iLen] = '\0';
value.Reserve(len);
strncpy(value, p, len);
value[len] = '\0';
WebUtil::JsonDecode(*pValue);
free(szPackageInfo);
WebUtil::JsonDecode(value);
return true;
}
bool Maintenance::VerifySignature(const char* szInFilename, const char* szSigFilename, const char* szPubKeyFilename)
bool Maintenance::VerifySignature(const char* inFilename, const char* sigFilename, const char* pubKeyFilename)
{
#ifdef HAVE_OPENSSL
Signature signature(szInFilename, szSigFilename, szPubKeyFilename);
Signature signature(inFilename, sigFilename, pubKeyFilename);
return signature.Verify();
#else
return false;
@@ -278,230 +210,207 @@ void UpdateScriptController::Run()
// the update-script should not be automatically terminated when the program quits
UnregisterRunningScript();
m_iPrefixLen = 0;
m_prefixLen = 0;
PrintMessage(Message::mkInfo, "Executing update-script %s", GetScript());
char szInfoName[1024];
snprintf(szInfoName, 1024, "update-script %s", Util::BaseFileName(GetScript()));
szInfoName[1024-1] = '\0';
SetInfoName(szInfoName);
BString<1024> infoName("update-script %s", FileSystem::BaseFileName(GetScript()));
SetInfoName(infoName);
const char* szBranchName[] = { "STABLE", "TESTING", "DEVEL" };
SetEnvVar("NZBUP_BRANCH", szBranchName[m_eBranch]);
const char* branchName[] = { "STABLE", "TESTING", "DEVEL" };
SetEnvVar("NZBUP_BRANCH", branchName[m_branch]);
SetEnvVar("NZBUP_RUNMODE", g_pCommandLineParser->GetDaemonMode() ? "DAEMON" : "SERVER");
SetEnvVar("NZBUP_RUNMODE", g_Options->GetDaemonMode() ? "DAEMON" : "SERVER");
for (int i = 0; i < g_iArgumentCount; i++)
for (int i = 0; i < g_ArgumentCount; i++)
{
char szEnvName[40];
snprintf(szEnvName, 40, "NZBUP_CMDLINE%i", i);
szInfoName[40-1] = '\0';
SetEnvVar(szEnvName, (*g_szArguments)[i]);
BString<100> envName("NZBUP_CMDLINE%i", i);
SetEnvVar(envName, (*g_Arguments)[i]);
}
char szProcessID[20];
#ifdef WIN32
int pid = (int)GetCurrentProcessId();
#else
int pid = (int)getpid();
#endif
snprintf(szProcessID, 20, "%i", pid);
szProcessID[20-1] = '\0';
SetEnvVar("NZBUP_PROCESSID", szProcessID);
char szLogPrefix[100];
strncpy(szLogPrefix, Util::BaseFileName(GetScript()), 100);
szLogPrefix[100-1] = '\0';
if (char* ext = strrchr(szLogPrefix, '.')) *ext = '\0'; // strip file extension
SetLogPrefix(szLogPrefix);
m_iPrefixLen = strlen(szLogPrefix) + 2; // 2 = strlen(": ");
SetEnvVar("NZBUP_PROCESSID", BString<100>("%i", pid));
BString<100> logPrefix = FileSystem::BaseFileName(GetScript());
if (char* ext = strrchr(logPrefix, '.')) *ext = '\0'; // strip file extension
SetLogPrefix(logPrefix);
m_prefixLen = strlen(logPrefix) + 2; // 2 = strlen(": ");
Execute();
g_pMaintenance->ResetUpdateController();
g_Maintenance->ResetUpdateController();
}
void UpdateScriptController::AddMessage(Message::EKind eKind, const char* szText)
void UpdateScriptController::AddMessage(Message::EKind kind, const char* text)
{
szText = szText + m_iPrefixLen;
text = text + m_prefixLen;
if (!strncmp(szText, "[NZB] ", 6))
if (!strncmp(text, "[NZB] ", 6))
{
debug("Command %s detected", szText + 6);
if (!strcmp(szText + 6, "QUIT"))
debug("Command %s detected", text + 6);
if (!strcmp(text + 6, "QUIT"))
{
Detach();
ExitProc();
}
else
{
error("Invalid command \"%s\" received", szText);
error("Invalid command \"%s\" received", text);
}
}
else
{
g_pMaintenance->AddMessage(eKind, time(NULL), szText);
ScriptController::AddMessage(eKind, szText);
g_Maintenance->AddMessage(kind, Util::CurrentTime(), text);
ScriptController::AddMessage(kind, text);
}
}
void UpdateInfoScriptController::ExecuteScript(const char* szScript, char** pUpdateInfo)
void UpdateInfoScriptController::ExecuteScript(const char* script, CString& updateInfo)
{
detail("Executing update-info-script %s", Util::BaseFileName(szScript));
detail("Executing update-info-script %s", FileSystem::BaseFileName(script));
UpdateInfoScriptController* pScriptController = new UpdateInfoScriptController();
pScriptController->SetScript(szScript);
UpdateInfoScriptController scriptController;
scriptController.SetArgs({script});
char szInfoName[1024];
snprintf(szInfoName, 1024, "update-info-script %s", Util::BaseFileName(szScript));
szInfoName[1024-1] = '\0';
pScriptController->SetInfoName(szInfoName);
BString<1024> infoName("update-info-script %s", FileSystem::BaseFileName(script));
scriptController.SetInfoName(infoName);
char szLogPrefix[1024];
strncpy(szLogPrefix, Util::BaseFileName(szScript), 1024);
szLogPrefix[1024-1] = '\0';
if (char* ext = strrchr(szLogPrefix, '.')) *ext = '\0'; // strip file extension
pScriptController->SetLogPrefix(szLogPrefix);
pScriptController->m_iPrefixLen = strlen(szLogPrefix) + 2; // 2 = strlen(": ");
BString<1024> logPrefix = FileSystem::BaseFileName(script);
if (char* ext = strrchr(logPrefix, '.')) *ext = '\0'; // strip file extension
scriptController.SetLogPrefix(logPrefix);
scriptController.m_prefixLen = strlen(logPrefix) + 2; // 2 = strlen(": ");
pScriptController->Execute();
scriptController.Execute();
if (pScriptController->m_UpdateInfo.GetBuffer())
if (!scriptController.m_updateInfo.Empty())
{
int iLen = strlen(pScriptController->m_UpdateInfo.GetBuffer());
*pUpdateInfo = (char*)malloc(iLen + 1);
strncpy(*pUpdateInfo, pScriptController->m_UpdateInfo.GetBuffer(), iLen);
(*pUpdateInfo)[iLen] = '\0';
updateInfo = scriptController.m_updateInfo;
}
delete pScriptController;
}
void UpdateInfoScriptController::AddMessage(Message::EKind eKind, const char* szText)
void UpdateInfoScriptController::AddMessage(Message::EKind kind, const char* text)
{
szText = szText + m_iPrefixLen;
text = text + m_prefixLen;
if (!strncmp(szText, "[NZB] ", 6))
if (!strncmp(text, "[NZB] ", 6))
{
debug("Command %s detected", szText + 6);
if (!strncmp(szText + 6, "[UPDATEINFO]", 12))
debug("Command %s detected", text + 6);
if (!strncmp(text + 6, "[UPDATEINFO]", 12))
{
m_UpdateInfo.Append(szText + 6 + 12);
m_updateInfo.Append(text + 6 + 12);
}
else
{
error("Invalid command \"%s\" received from %s", szText, GetInfoName());
error("Invalid command \"%s\" received from %s", text, GetInfoName());
}
}
else
{
ScriptController::AddMessage(eKind, szText);
ScriptController::AddMessage(kind, text);
}
}
#ifdef HAVE_OPENSSL
Signature::Signature(const char *szInFilename, const char *szSigFilename, const char *szPubKeyFilename)
Signature::Signature(const char *inFilename, const char *sigFilename, const char *pubKeyFilename)
{
m_szInFilename = szInFilename;
m_szSigFilename = szSigFilename;
m_szPubKeyFilename = szPubKeyFilename;
m_pPubKey = NULL;
m_inFilename = inFilename;
m_sigFilename = sigFilename;
m_pubKeyFilename = pubKeyFilename;
m_pubKey = nullptr;
}
Signature::~Signature()
{
RSA_free(m_pPubKey);
RSA_free(m_pubKey);
}
// Calculate SHA-256 for input file (m_szInFilename)
// Calculate SHA-256 for input file (m_inFilename)
bool Signature::ComputeInHash()
{
FILE* infile = fopen(m_szInFilename, FOPEN_RB);
if (!infile)
DiskFile infile;
if (!infile.Open(m_inFilename, DiskFile::omRead))
{
return false;
}
SHA256_CTX sha256;
SHA256_CTX sha256;
SHA256_Init(&sha256);
const int bufSize = 32*1024;
char* buffer = (char*)malloc(bufSize);
while(int bytesRead = fread(buffer, 1, bufSize, infile))
{
SHA256_Update(&sha256, buffer, bytesRead);
}
SHA256_Final(m_InHash, &sha256);
free(buffer);
fclose(infile);
CharBuffer buffer(32*1024);
while(int bytesRead = (int)infile.Read(buffer, buffer.Size()))
{
SHA256_Update(&sha256, buffer, bytesRead);
}
SHA256_Final(m_inHash, &sha256);
infile.Close();
return true;
}
// Read signature from file (m_szSigFilename) into memory
// Read signature from file (m_sigFilename) into memory
bool Signature::ReadSignature()
{
char szSigTitle[256];
snprintf(szSigTitle, sizeof(szSigTitle), "\"RSA-SHA256(%s)\" : \"", Util::BaseFileName(m_szInFilename));
szSigTitle[256-1] = '\0';
BString<1024> sigTitle("\"RSA-SHA256(%s)\" : \"", FileSystem::BaseFileName(m_inFilename));
FILE* infile = fopen(m_szSigFilename, FOPEN_RB);
if (!infile)
DiskFile infile;
if (!infile.Open(m_sigFilename, DiskFile::omRead))
{
return false;
}
bool bOK = false;
int iTitLen = strlen(szSigTitle);
bool ok = false;
int titLen = strlen(sigTitle);
char buf[1024];
unsigned char* output = m_Signature;
while (fgets(buf, sizeof(buf) - 1, infile))
uchar* output = m_signature;
while (infile.ReadLine(buf, sizeof(buf) - 1))
{
if (!strncmp(buf, szSigTitle, iTitLen))
if (!strncmp(buf, sigTitle, titLen))
{
char* szHexSig = buf + iTitLen;
int iSigLen = strlen(szHexSig);
if (iSigLen > 2)
char* hexSig = buf + titLen;
int sigLen = strlen(hexSig);
if (sigLen > 2)
{
szHexSig[iSigLen - 2] = '\0'; // trim trailing ",
hexSig[sigLen - 2] = '\0'; // trim trailing ",
}
for (; *szHexSig && *(szHexSig+1);)
while (*hexSig && *(hexSig+1) && output != m_signature + sizeof(m_signature))
{
unsigned char c1 = *szHexSig++;
unsigned char c2 = *szHexSig++;
uchar c1 = *hexSig++;
uchar c2 = *hexSig++;
c1 = '0' <= c1 && c1 <= '9' ? c1 - '0' : 'A' <= c1 && c1 <= 'F' ? c1 - 'A' + 10 :
'a' <= c1 && c1 <= 'f' ? c1 - 'a' + 10 : 0;
c2 = '0' <= c2 && c2 <= '9' ? c2 - '0' : 'A' <= c2 && c2 <= 'F' ? c2 - 'A' + 10 :
'a' <= c2 && c2 <= 'f' ? c2 - 'a' + 10 : 0;
unsigned char ch = (c1 << 4) + c2;
uchar ch = (c1 << 4) + c2;
*output++ = (char)ch;
}
bOK = output == m_Signature + sizeof(m_Signature);
ok = output == m_signature + sizeof(m_signature);
break;
}
}
fclose(infile);
return bOK;
infile.Close();
return ok;
}
// Read public key from file (m_szPubKeyFilename) into memory
bool Signature::ReadPubKey()
{
char* keybuf;
int keybuflen;
if (!Util::LoadFileIntoBuffer(m_szPubKeyFilename, &keybuf, &keybuflen))
CharBuffer keybuf;
if (!FileSystem::LoadFileIntoBuffer(m_pubKeyFilename, keybuf, false))
{
return false;
}
BIO* mem = BIO_new_mem_buf(keybuf, keybuflen);
m_pPubKey = PEM_read_bio_RSA_PUBKEY(mem, NULL, NULL, NULL);
BIO* mem = BIO_new_mem_buf(keybuf, keybuf.Size());
m_pubKey = PEM_read_bio_RSA_PUBKEY(mem, nullptr, nullptr, nullptr);
BIO_free(mem);
free(keybuf);
return m_pPubKey != NULL;
return m_pubKey != nullptr;
}
bool Signature::Verify()
{
return ComputeInHash() && ReadSignature() && ReadPubKey() &&
RSA_verify(NID_sha256, m_InHash, sizeof(m_InHash), m_Signature, sizeof(m_Signature), m_pPubKey) == 1;
RSA_verify(NID_sha256, m_inHash, sizeof(m_inHash), m_signature, sizeof(m_signature), m_pubKey) == 1;
}
#endif /* HAVE_OPENSSL */

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2013-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2013-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,17 +14,13 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MAINTENANCE_H
#define MAINTENANCE_H
#include "NString.h"
#include "Thread.h"
#include "Script.h"
#include "Log.h"
@@ -34,16 +30,6 @@ class UpdateScriptController;
class Maintenance
{
private:
MessageList m_Messages;
Mutex m_mutexLog;
Mutex m_mutexController;
int m_iIDMessageGen;
UpdateScriptController* m_UpdateScriptController;
char* m_szUpdateScript;
bool ReadPackageInfoStr(const char* szKey, char** pValue);
public:
enum EBranch
{
@@ -52,44 +38,52 @@ public:
brDevel
};
Maintenance();
~Maintenance();
void AddMessage(Message::EKind eKind, time_t tTime, const char* szText);
MessageList* LockMessages();
void UnlockMessages();
bool StartUpdate(EBranch eBranch);
void ResetUpdateController();
bool CheckUpdates(char** pUpdateInfo);
static bool VerifySignature(const char* szInFilename, const char* szSigFilename, const char* szPubKeyFilename);
~Maintenance();
void AddMessage(Message::EKind kind, time_t time, const char* text);
GuardedMessageList GuardMessages() { return GuardedMessageList(&m_messages, &m_logMutex); }
bool StartUpdate(EBranch branch);
void ResetUpdateController();
bool CheckUpdates(CString& updateInfo);
static bool VerifySignature(const char* inFilename, const char* sigFilename, const char* pubKeyFilename);
private:
MessageList m_messages;
Mutex m_logMutex;
Mutex m_controllerMutex;
int m_idMessageGen = 0;
UpdateScriptController* m_updateScriptController = nullptr;
CString m_updateScript;
bool ReadPackageInfoStr(const char* key, CString& value);
};
extern Maintenance* g_pMaintenance;
extern Maintenance* g_Maintenance;
class UpdateScriptController : public Thread, public ScriptController
{
private:
Maintenance::EBranch m_eBranch;
int m_iPrefixLen;
public:
virtual void Run();
void SetBranch(Maintenance::EBranch branch) { m_branch = branch; }
protected:
virtual void AddMessage(Message::EKind eKind, const char* szText);
virtual void AddMessage(Message::EKind kind, const char* text);
public:
virtual void Run();
void SetBranch(Maintenance::EBranch eBranch) { m_eBranch = eBranch; }
private:
Maintenance::EBranch m_branch;
int m_prefixLen;
};
class UpdateInfoScriptController : public ScriptController
{
public:
static void ExecuteScript(const char* script, CString& updateInfo);
private:
int m_iPrefixLen;
StringBuilder m_UpdateInfo;
int m_prefixLen;
StringBuilder m_updateInfo;
protected:
virtual void AddMessage(Message::EKind eKind, const char* szText);
public:
static void ExecuteScript(const char* szScript, char** pUpdateInfo);
virtual void AddMessage(Message::EKind kind, const char* text);
};
#endif

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,22 +15,14 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef OPTIONS_H
#define OPTIONS_H
#include <vector>
#include <list>
#include <time.h>
#include "NString.h"
#include "Thread.h"
#include "Util.h"
@@ -75,6 +67,7 @@ public:
{
hcPause,
hcDelete,
hcPark,
hcNone
};
enum ESchedulerCommand
@@ -92,379 +85,392 @@ public:
scDeactivateServer,
scFetchFeed
};
enum EPostStrategy
{
ppSequential,
ppBalanced,
ppAggressive,
ppRocket
};
enum EFileNaming
{
nfAuto,
nfArticle,
nfNzb
};
class OptEntry
{
private:
char* m_szName;
char* m_szValue;
char* m_szDefValue;
int m_iLineNo;
public:
OptEntry(const char* name, const char* value) :
m_name(name), m_value(value) {}
void SetName(const char* name) { m_name = name; }
const char* GetName() { return m_name; }
void SetValue(const char* value);
const char* GetValue() { return m_value; }
const char* GetDefValue() { return m_defValue; }
int GetLineNo() { return m_lineNo; }
bool Restricted();
void SetLineNo(int iLineNo) { m_iLineNo = iLineNo; }
private:
CString m_name;
CString m_value;
CString m_defValue;
int m_lineNo = 0;
void SetLineNo(int lineNo) { m_lineNo = lineNo; }
friend class Options;
public:
OptEntry();
OptEntry(const char* szName, const char* szValue);
~OptEntry();
void SetName(const char* szName);
const char* GetName() { return m_szName; }
void SetValue(const char* szValue);
const char* GetValue() { return m_szValue; }
const char* GetDefValue() { return m_szDefValue; }
int GetLineNo() { return m_iLineNo; }
bool Restricted();
};
typedef std::vector<OptEntry*> OptEntriesBase;
typedef std::deque<OptEntry> OptEntriesBase;
class OptEntries: public OptEntriesBase
{
public:
~OptEntries();
OptEntry* FindOption(const char* szName);
OptEntry* FindOption(const char* name);
};
typedef std::vector<char*> NameList;
typedef std::vector<const char*> CmdOptList;
typedef GuardedPtr<OptEntries> GuardedOptEntries;
typedef std::vector<CString> NameList;
typedef std::vector<const char*> CmdOptList;
class Category
{
private:
char* m_szName;
char* m_szDestDir;
bool m_bUnpack;
char* m_szPostScript;
NameList m_Aliases;
public:
Category(const char* szName, const char* szDestDir, bool bUnpack, const char* szPostScript);
~Category();
const char* GetName() { return m_szName; }
const char* GetDestDir() { return m_szDestDir; }
bool GetUnpack() { return m_bUnpack; }
const char* GetPostScript() { return m_szPostScript; }
NameList* GetAliases() { return &m_Aliases; }
Category(const char* name, const char* destDir, bool unpack, const char* extensions) :
m_name(name), m_destDir(destDir), m_unpack(unpack), m_extensions(extensions) {}
const char* GetName() { return m_name; }
const char* GetDestDir() { return m_destDir; }
bool GetUnpack() { return m_unpack; }
const char* GetExtensions() { return m_extensions; }
NameList* GetAliases() { return &m_aliases; }
private:
CString m_name;
CString m_destDir;
bool m_unpack;
CString m_extensions;
NameList m_aliases;
};
typedef std::vector<Category*> CategoriesBase;
typedef std::deque<Category> CategoriesBase;
class Categories: public CategoriesBase
{
public:
~Categories();
Category* FindCategory(const char* szName, bool bSearchAliases);
Category* FindCategory(const char* name, bool searchAliases);
};
class Extender
{
public:
virtual void AddNewsServer(int iID, bool bActive, const char* szName, const char* szHost,
int iPort, const char* szUser, const char* szPass, bool bJoinGroup,
bool bTLS, const char* szCipher, int iMaxConnections, int iRetention,
int iLevel, int iGroup) = 0;
virtual void AddFeed(int iID, const char* szName, const char* szUrl, int iInterval,
const char* szFilter, bool bBacklog, bool bPauseNzb, const char* szCategory,
int iPriority, const char* szFeedScript) {}
virtual void AddTask(int iID, int iHours, int iMinutes, int iWeekDaysBits, ESchedulerCommand eCommand,
const char* szParam) {}
virtual void SetupFirstStart() {}
virtual void AddNewsServer(int id, bool active, const char* name, const char* host,
int port, int ipVersion, const char* user, const char* pass, bool joinGroup,
bool tls, const char* cipher, int maxConnections, int retention,
int level, int group, bool optional) = 0;
virtual void AddFeed(int id, const char* name, const char* url, int interval,
const char* filter, bool backlog, bool pauseNzb, const char* category,
int priority, const char* extensions) {}
virtual void AddTask(int id, int hours, int minutes, int weekDaysBits, ESchedulerCommand command,
const char* param) {}
virtual void SetupFirstStart() {}
};
Options(const char* exeName, const char* configFilename, bool noConfig,
CmdOptList* commandLineOptions, Extender* extender);
Options(CmdOptList* commandLineOptions, Extender* extender);
~Options();
static bool SplitOptionString(const char* option, CString& optName, CString& optValue);
static void ConvertOldOptions(OptEntries* optEntries);
bool GetFatalError() { return m_fatalError; }
GuardedOptEntries GuardOptEntries() { return GuardedOptEntries(&m_optEntries, &m_optEntriesMutex); }
void CreateSchedulerTask(int id, const char* time, const char* weekDays,
ESchedulerCommand command, const char* param);
// Options
const char* GetConfigFilename() { return m_configFilename; }
bool GetConfigErrors() { return m_configErrors; }
const char* GetAppDir() { return m_appDir; }
const char* GetDestDir() { return m_destDir; }
const char* GetInterDir() { return m_interDir; }
const char* GetTempDir() { return m_tempDir; }
const char* GetQueueDir() { return m_queueDir; }
const char* GetNzbDir() { return m_nzbDir; }
const char* GetWebDir() { return m_webDir; }
const char* GetConfigTemplate() { return m_configTemplate; }
const char* GetScriptDir() { return m_scriptDir; }
const char* GetRequiredDir() { return m_requiredDir; }
bool GetNzbLog() const { return m_nzbLog; }
EMessageTarget GetInfoTarget() const { return m_infoTarget; }
EMessageTarget GetWarningTarget() const { return m_warningTarget; }
EMessageTarget GetErrorTarget() const { return m_errorTarget; }
EMessageTarget GetDebugTarget() const { return m_debugTarget; }
EMessageTarget GetDetailTarget() const { return m_detailTarget; }
int GetArticleTimeout() { return m_articleTimeout; }
int GetUrlTimeout() { return m_urlTimeout; }
int GetRemoteTimeout() { return m_remoteTimeout; }
bool GetRawArticle() { return m_rawArticle; };
bool GetSkipWrite() { return m_skipWrite; };
bool GetAppendCategoryDir() { return m_appendCategoryDir; }
bool GetContinuePartial() { return m_continuePartial; }
int GetArticleRetries() { return m_articleRetries; }
int GetArticleInterval() { return m_articleInterval; }
int GetUrlRetries() { return m_urlRetries; }
int GetUrlInterval() { return m_urlInterval; }
bool GetFlushQueue() { return m_flushQueue; }
bool GetDupeCheck() { return m_dupeCheck; }
const char* GetControlIp() { return m_controlIp; }
const char* GetControlUsername() { return m_controlUsername; }
const char* GetControlPassword() { return m_controlPassword; }
const char* GetRestrictedUsername() { return m_restrictedUsername; }
const char* GetRestrictedPassword() { return m_restrictedPassword; }
const char* GetAddUsername() { return m_addUsername; }
const char* GetAddPassword() { return m_addPassword; }
int GetControlPort() { return m_controlPort; }
bool GetFormAuth() { return m_formAuth; }
bool GetSecureControl() { return m_secureControl; }
int GetSecurePort() { return m_securePort; }
const char* GetSecureCert() { return m_secureCert; }
const char* GetSecureKey() { return m_secureKey; }
const char* GetCertStore() { return m_certStore; }
bool GetCertCheck() { return m_certCheck; }
const char* GetAuthorizedIp() { return m_authorizedIp; }
const char* GetLockFile() { return m_lockFile; }
const char* GetDaemonUsername() { return m_daemonUsername; }
EOutputMode GetOutputMode() { return m_outputMode; }
int GetUrlConnections() { return m_urlConnections; }
int GetLogBuffer() { return m_logBuffer; }
EWriteLog GetWriteLog() { return m_writeLog; }
const char* GetLogFile() { return m_logFile; }
int GetRotateLog() { return m_rotateLog; }
EParCheck GetParCheck() { return m_parCheck; }
bool GetParRepair() { return m_parRepair; }
EParScan GetParScan() { return m_parScan; }
bool GetParQuick() { return m_parQuick; }
EPostStrategy GetPostStrategy() { return m_postStrategy; }
bool GetParRename() { return m_parRename; }
int GetParBuffer() { return m_parBuffer; }
int GetParThreads() { return m_parThreads; }
bool GetRarRename() { return m_rarRename; }
EHealthCheck GetHealthCheck() { return m_healthCheck; }
const char* GetScriptOrder() { return m_scriptOrder; }
const char* GetExtensions() { return m_extensions; }
int GetUMask() { return m_umask; }
int GetUpdateInterval() {return m_updateInterval; }
bool GetCursesNzbName() { return m_cursesNzbName; }
bool GetCursesTime() { return m_cursesTime; }
bool GetCursesGroup() { return m_cursesGroup; }
bool GetCrcCheck() { return m_crcCheck; }
bool GetDirectWrite() { return m_directWrite; }
int GetWriteBuffer() { return m_writeBuffer; }
int GetNzbDirInterval() { return m_nzbDirInterval; }
int GetNzbDirFileAge() { return m_nzbDirFileAge; }
int GetDiskSpace() { return m_diskSpace; }
bool GetTls() { return m_tls; }
bool GetCrashTrace() { return m_crashTrace; }
bool GetCrashDump() { return m_crashDump; }
bool GetParPauseQueue() { return m_parPauseQueue; }
bool GetScriptPauseQueue() { return m_scriptPauseQueue; }
bool GetNzbCleanupDisk() { return m_nzbCleanupDisk; }
int GetParTimeLimit() { return m_parTimeLimit; }
int GetKeepHistory() { return m_keepHistory; }
bool GetUnpack() { return m_unpack; }
bool GetDirectUnpack() { return m_directUnpack; }
bool GetUnpackCleanupDisk() { return m_unpackCleanupDisk; }
const char* GetUnrarCmd() { return m_unrarCmd; }
const char* GetSevenZipCmd() { return m_sevenZipCmd; }
const char* GetUnpackPassFile() { return m_unpackPassFile; }
bool GetUnpackPauseQueue() { return m_unpackPauseQueue; }
const char* GetExtCleanupDisk() { return m_extCleanupDisk; }
const char* GetParIgnoreExt() { return m_parIgnoreExt; }
const char* GetUnpackIgnoreExt() { return m_unpackIgnoreExt; }
int GetFeedHistory() { return m_feedHistory; }
bool GetUrlForce() { return m_urlForce; }
int GetTimeCorrection() { return m_timeCorrection; }
int GetPropagationDelay() { return m_propagationDelay; }
int GetArticleCache() { return m_articleCache; }
int GetEventInterval() { return m_eventInterval; }
const char* GetShellOverride() { return m_shellOverride; }
int GetMonthlyQuota() { return m_monthlyQuota; }
int GetQuotaStartDay() { return m_quotaStartDay; }
int GetDailyQuota() { return m_dailyQuota; }
bool GetDirectRename() { return m_directRename; }
bool GetReorderFiles() { return m_reorderFiles; }
EFileNaming GetFileNaming() { return m_fileNaming; }
int GetDownloadRate() const { return m_downloadRate; }
Categories* GetCategories() { return &m_categories; }
Category* FindCategory(const char* name, bool searchAliases) { return m_categories.FindCategory(name, searchAliases); }
// Current state
void SetServerMode(bool serverMode) { m_serverMode = serverMode; }
bool GetServerMode() { return m_serverMode; }
void SetDaemonMode(bool daemonMode) { m_daemonMode = daemonMode; }
bool GetDaemonMode() { return m_daemonMode; }
void SetRemoteClientMode(bool remoteClientMode) { m_remoteClientMode = remoteClientMode; }
bool GetRemoteClientMode() { return m_remoteClientMode; }
private:
OptEntries m_OptEntries;
Mutex m_mutexOptEntries;
Categories m_Categories;
bool m_bNoDiskAccess;
bool m_bFatalError;
Extender* m_pExtender;
OptEntries m_optEntries;
Mutex m_optEntriesMutex;
Categories m_categories;
bool m_noDiskAccess = false;
bool m_noConfig = false;
bool m_fatalError = false;
Extender* m_extender;
// Options
bool m_bConfigErrors;
int m_iConfigLine;
char* m_szAppDir;
char* m_szConfigFilename;
char* m_szDestDir;
char* m_szInterDir;
char* m_szTempDir;
char* m_szQueueDir;
char* m_szNzbDir;
char* m_szWebDir;
char* m_szConfigTemplate;
char* m_szScriptDir;
char* m_szRequiredDir;
EMessageTarget m_eInfoTarget;
EMessageTarget m_eWarningTarget;
EMessageTarget m_eErrorTarget;
EMessageTarget m_eDebugTarget;
EMessageTarget m_eDetailTarget;
bool m_bDecode;
bool m_bBrokenLog;
bool m_bNzbLog;
int m_iArticleTimeout;
int m_iUrlTimeout;
int m_iTerminateTimeout;
bool m_bAppendCategoryDir;
bool m_bContinuePartial;
int m_iRetries;
int m_iRetryInterval;
bool m_bSaveQueue;
bool m_bFlushQueue;
bool m_bDupeCheck;
char* m_szControlIP;
char* m_szControlUsername;
char* m_szControlPassword;
char* m_szRestrictedUsername;
char* m_szRestrictedPassword;
char* m_szAddUsername;
char* m_szAddPassword;
int m_iControlPort;
bool m_bSecureControl;
int m_iSecurePort;
char* m_szSecureCert;
char* m_szSecureKey;
char* m_szAuthorizedIP;
char* m_szLockFile;
char* m_szDaemonUsername;
EOutputMode m_eOutputMode;
bool m_bReloadQueue;
int m_iUrlConnections;
int m_iLogBufferSize;
EWriteLog m_eWriteLog;
int m_iRotateLog;
char* m_szLogFile;
EParCheck m_eParCheck;
bool m_bParRepair;
EParScan m_eParScan;
bool m_bParQuick;
bool m_bParRename;
int m_iParBuffer;
int m_iParThreads;
EHealthCheck m_eHealthCheck;
char* m_szPostScript;
char* m_szScriptOrder;
char* m_szScanScript;
char* m_szQueueScript;
char* m_szFeedScript;
bool m_bNoConfig;
int m_iUMask;
int m_iUpdateInterval;
bool m_bCursesNZBName;
bool m_bCursesTime;
bool m_bCursesGroup;
bool m_bCrcCheck;
bool m_bDirectWrite;
int m_iWriteBuffer;
int m_iNzbDirInterval;
int m_iNzbDirFileAge;
bool m_bParCleanupQueue;
int m_iDiskSpace;
bool m_bTLS;
bool m_bDumpCore;
bool m_bParPauseQueue;
bool m_bScriptPauseQueue;
bool m_bNzbCleanupDisk;
bool m_bDeleteCleanupDisk;
int m_iParTimeLimit;
int m_iKeepHistory;
bool m_bAccurateRate;
bool m_bUnpack;
bool m_bUnpackCleanupDisk;
char* m_szUnrarCmd;
char* m_szSevenZipCmd;
char* m_szUnpackPassFile;
bool m_bUnpackPauseQueue;
char* m_szExtCleanupDisk;
char* m_szParIgnoreExt;
int m_iFeedHistory;
bool m_bUrlForce;
int m_iTimeCorrection;
int m_iPropagationDelay;
int m_iArticleCache;
int m_iEventInterval;
bool m_configErrors = false;
int m_configLine = 0;
CString m_appDir;
CString m_configFilename;
CString m_destDir;
CString m_interDir;
CString m_tempDir;
CString m_queueDir;
CString m_nzbDir;
CString m_webDir;
CString m_configTemplate;
CString m_scriptDir;
CString m_requiredDir;
EMessageTarget m_infoTarget = mtScreen;
EMessageTarget m_warningTarget = mtScreen;
EMessageTarget m_errorTarget = mtScreen;
EMessageTarget m_debugTarget = mtNone;
EMessageTarget m_detailTarget = mtScreen;
bool m_skipWrite = false;
bool m_rawArticle = false;
bool m_nzbLog = false;
int m_articleTimeout = 0;
int m_urlTimeout = 0;
int m_remoteTimeout = 0;
bool m_appendCategoryDir = false;
bool m_continuePartial = false;
int m_articleRetries = 0;
int m_articleInterval = 0;
int m_urlRetries = 0;
int m_urlInterval = 0;
bool m_flushQueue = false;
bool m_dupeCheck = false;
CString m_controlIp;
CString m_controlUsername;
CString m_controlPassword;
CString m_restrictedUsername;
CString m_restrictedPassword;
CString m_addUsername;
CString m_addPassword;
bool m_formAuth = false;
int m_controlPort = 0;
bool m_secureControl = false;
int m_securePort = 0;
CString m_secureCert;
CString m_secureKey;
CString m_certStore;
bool m_certCheck = false;
CString m_authorizedIp;
CString m_lockFile;
CString m_daemonUsername;
EOutputMode m_outputMode = omLoggable;
int m_urlConnections = 0;
int m_logBuffer = 0;
EWriteLog m_writeLog = wlAppend;
int m_rotateLog = 0;
CString m_logFile;
EParCheck m_parCheck = pcManual;
bool m_parRepair = false;
EParScan m_parScan = psLimited;
bool m_parQuick = true;
EPostStrategy m_postStrategy = ppSequential;
bool m_parRename = false;
int m_parBuffer = 0;
int m_parThreads = 0;
bool m_rarRename = false;
bool m_directRename = false;
EHealthCheck m_healthCheck = hcNone;
CString m_extensions;
CString m_scriptOrder;
int m_umask = 0;
int m_updateInterval = 0;
bool m_cursesNzbName = false;
bool m_cursesTime = false;
bool m_cursesGroup = false;
bool m_crcCheck = false;
bool m_directWrite = false;
int m_writeBuffer = 0;
int m_nzbDirInterval = 0;
int m_nzbDirFileAge = 0;
int m_diskSpace = 0;
bool m_tls = false;
bool m_crashTrace = false;
bool m_crashDump = false;
bool m_parPauseQueue = false;
bool m_scriptPauseQueue = false;
bool m_nzbCleanupDisk = false;
int m_parTimeLimit = 0;
int m_keepHistory = 0;
bool m_unpack = false;
bool m_directUnpack = false;
bool m_unpackCleanupDisk = false;
CString m_unrarCmd;
CString m_sevenZipCmd;
CString m_unpackPassFile;
bool m_unpackPauseQueue;
CString m_extCleanupDisk;
CString m_parIgnoreExt;
CString m_unpackIgnoreExt;
int m_feedHistory = 0;
bool m_urlForce = false;
int m_timeCorrection = 0;
int m_propagationDelay = 0;
int m_articleCache = 0;
int m_eventInterval = 0;
CString m_shellOverride;
int m_monthlyQuota = 0;
int m_quotaStartDay = 0;
int m_dailyQuota = 0;
bool m_reorderFiles = false;
EFileNaming m_fileNaming = nfArticle;
int m_downloadRate = 0;
// Current state
bool m_bServerMode;
bool m_bRemoteClientMode;
bool m_bPauseDownload;
bool m_bPausePostProcess;
bool m_bPauseScan;
bool m_bTempPauseDownload;
int m_iDownloadRate;
time_t m_tResumeTime;
int m_iLocalTimeOffset;
bool m_bTempPausePostprocess;
// Application mode
bool m_serverMode = false;
bool m_daemonMode = false;
bool m_remoteClientMode = false;
void Init(const char* szExeName, const char* szConfigFilename, bool bNoConfig,
CmdOptList* pCommandLineOptions, bool bNoDiskAccess, Extender* pExtender);
void InitDefaults();
void InitOptions();
void InitOptFile();
void InitServers();
void InitCategories();
void InitScheduler();
void InitFeeds();
void InitCommandLineOptions(CmdOptList* pCommandLineOptions);
void CheckOptions();
void Dump();
int ParseEnumValue(const char* OptName, int argc, const char* argn[], const int argv[]);
int ParseIntValue(const char* OptName, int iBase);
OptEntry* FindOption(const char* optname);
const char* GetOption(const char* optname);
void SetOption(const char* optname, const char* value);
bool SetOptionString(const char* option);
bool ValidateOptionName(const char* optname, const char* optvalue);
void LoadConfigFile();
void CheckDir(char** dir, const char* szOptionName, const char* szParentDir,
bool bAllowEmpty, bool bCreate);
bool ParseTime(const char* szTime, int* pHours, int* pMinutes);
bool ParseWeekDays(const char* szWeekDays, int* pWeekDaysBits);
void ConfigError(const char* msg, ...);
void ConfigWarn(const char* msg, ...);
void LocateOptionSrcPos(const char *szOptionName);
void ConvertOldOption(char *szOption, int iOptionBufLen, char *szValue, int iValueBufLen);
public:
Options(const char* szExeName, const char* szConfigFilename, bool bNoConfig,
CmdOptList* pCommandLineOptions, Extender* pExtender);
Options(CmdOptList* pCommandLineOptions, Extender* pExtender);
~Options();
bool SplitOptionString(const char* option, char** pOptName, char** pOptValue);
bool GetFatalError() { return m_bFatalError; }
OptEntries* LockOptEntries();
void UnlockOptEntries();
// Options
const char* GetConfigFilename() { return m_szConfigFilename; }
bool GetConfigErrors() { return m_bConfigErrors; }
const char* GetAppDir() { return m_szAppDir; }
const char* GetDestDir() { return m_szDestDir; }
const char* GetInterDir() { return m_szInterDir; }
const char* GetTempDir() { return m_szTempDir; }
const char* GetQueueDir() { return m_szQueueDir; }
const char* GetNzbDir() { return m_szNzbDir; }
const char* GetWebDir() { return m_szWebDir; }
const char* GetConfigTemplate() { return m_szConfigTemplate; }
const char* GetScriptDir() { return m_szScriptDir; }
const char* GetRequiredDir() { return m_szRequiredDir; }
bool GetBrokenLog() const { return m_bBrokenLog; }
bool GetNzbLog() const { return m_bNzbLog; }
EMessageTarget GetInfoTarget() const { return m_eInfoTarget; }
EMessageTarget GetWarningTarget() const { return m_eWarningTarget; }
EMessageTarget GetErrorTarget() const { return m_eErrorTarget; }
EMessageTarget GetDebugTarget() const { return m_eDebugTarget; }
EMessageTarget GetDetailTarget() const { return m_eDetailTarget; }
int GetArticleTimeout() { return m_iArticleTimeout; }
int GetUrlTimeout() { return m_iUrlTimeout; }
int GetTerminateTimeout() { return m_iTerminateTimeout; }
bool GetDecode() { return m_bDecode; };
bool GetAppendCategoryDir() { return m_bAppendCategoryDir; }
bool GetContinuePartial() { return m_bContinuePartial; }
int GetRetries() { return m_iRetries; }
int GetRetryInterval() { return m_iRetryInterval; }
bool GetSaveQueue() { return m_bSaveQueue; }
bool GetFlushQueue() { return m_bFlushQueue; }
bool GetDupeCheck() { return m_bDupeCheck; }
const char* GetControlIP() { return m_szControlIP; }
const char* GetControlUsername() { return m_szControlUsername; }
const char* GetControlPassword() { return m_szControlPassword; }
const char* GetRestrictedUsername() { return m_szRestrictedUsername; }
const char* GetRestrictedPassword() { return m_szRestrictedPassword; }
const char* GetAddUsername() { return m_szAddUsername; }
const char* GetAddPassword() { return m_szAddPassword; }
int GetControlPort() { return m_iControlPort; }
bool GetSecureControl() { return m_bSecureControl; }
int GetSecurePort() { return m_iSecurePort; }
const char* GetSecureCert() { return m_szSecureCert; }
const char* GetSecureKey() { return m_szSecureKey; }
const char* GetAuthorizedIP() { return m_szAuthorizedIP; }
const char* GetLockFile() { return m_szLockFile; }
const char* GetDaemonUsername() { return m_szDaemonUsername; }
EOutputMode GetOutputMode() { return m_eOutputMode; }
bool GetReloadQueue() { return m_bReloadQueue; }
int GetUrlConnections() { return m_iUrlConnections; }
int GetLogBufferSize() { return m_iLogBufferSize; }
EWriteLog GetWriteLog() { return m_eWriteLog; }
const char* GetLogFile() { return m_szLogFile; }
int GetRotateLog() { return m_iRotateLog; }
EParCheck GetParCheck() { return m_eParCheck; }
bool GetParRepair() { return m_bParRepair; }
EParScan GetParScan() { return m_eParScan; }
bool GetParQuick() { return m_bParQuick; }
bool GetParRename() { return m_bParRename; }
int GetParBuffer() { return m_iParBuffer; }
int GetParThreads() { return m_iParThreads; }
EHealthCheck GetHealthCheck() { return m_eHealthCheck; }
const char* GetScriptOrder() { return m_szScriptOrder; }
const char* GetPostScript() { return m_szPostScript; }
const char* GetScanScript() { return m_szScanScript; }
const char* GetQueueScript() { return m_szQueueScript; }
const char* GetFeedScript() { return m_szFeedScript; }
int GetUMask() { return m_iUMask; }
int GetUpdateInterval() {return m_iUpdateInterval; }
bool GetCursesNZBName() { return m_bCursesNZBName; }
bool GetCursesTime() { return m_bCursesTime; }
bool GetCursesGroup() { return m_bCursesGroup; }
bool GetCrcCheck() { return m_bCrcCheck; }
bool GetDirectWrite() { return m_bDirectWrite; }
int GetWriteBuffer() { return m_iWriteBuffer; }
int GetNzbDirInterval() { return m_iNzbDirInterval; }
int GetNzbDirFileAge() { return m_iNzbDirFileAge; }
bool GetParCleanupQueue() { return m_bParCleanupQueue; }
int GetDiskSpace() { return m_iDiskSpace; }
bool GetTLS() { return m_bTLS; }
bool GetDumpCore() { return m_bDumpCore; }
bool GetParPauseQueue() { return m_bParPauseQueue; }
bool GetScriptPauseQueue() { return m_bScriptPauseQueue; }
bool GetNzbCleanupDisk() { return m_bNzbCleanupDisk; }
bool GetDeleteCleanupDisk() { return m_bDeleteCleanupDisk; }
int GetParTimeLimit() { return m_iParTimeLimit; }
int GetKeepHistory() { return m_iKeepHistory; }
bool GetAccurateRate() { return m_bAccurateRate; }
bool GetUnpack() { return m_bUnpack; }
bool GetUnpackCleanupDisk() { return m_bUnpackCleanupDisk; }
const char* GetUnrarCmd() { return m_szUnrarCmd; }
const char* GetSevenZipCmd() { return m_szSevenZipCmd; }
const char* GetUnpackPassFile() { return m_szUnpackPassFile; }
bool GetUnpackPauseQueue() { return m_bUnpackPauseQueue; }
const char* GetExtCleanupDisk() { return m_szExtCleanupDisk; }
const char* GetParIgnoreExt() { return m_szParIgnoreExt; }
int GetFeedHistory() { return m_iFeedHistory; }
bool GetUrlForce() { return m_bUrlForce; }
int GetTimeCorrection() { return m_iTimeCorrection; }
int GetPropagationDelay() { return m_iPropagationDelay; }
int GetArticleCache() { return m_iArticleCache; }
int GetEventInterval() { return m_iEventInterval; }
Categories* GetCategories() { return &m_Categories; }
Category* FindCategory(const char* szName, bool bSearchAliases) { return m_Categories.FindCategory(szName, bSearchAliases); }
// Current state
void SetServerMode(bool bServerMode) { m_bServerMode = bServerMode; }
bool GetServerMode() { return m_bServerMode; }
void SetRemoteClientMode(bool bRemoteClientMode) { m_bRemoteClientMode = bRemoteClientMode; }
bool GetRemoteClientMode() { return m_bRemoteClientMode; }
void SetPauseDownload(bool bPauseDownload) { m_bPauseDownload = bPauseDownload; }
bool GetPauseDownload() const { return m_bPauseDownload; }
void SetPausePostProcess(bool bPausePostProcess) { m_bPausePostProcess = bPausePostProcess; }
bool GetPausePostProcess() const { return m_bPausePostProcess; }
void SetPauseScan(bool bPauseScan) { m_bPauseScan = bPauseScan; }
bool GetPauseScan() const { return m_bPauseScan; }
void SetTempPauseDownload(bool bTempPauseDownload) { m_bTempPauseDownload = bTempPauseDownload; }
bool GetTempPauseDownload() const { return m_bTempPauseDownload; }
bool GetTempPausePostprocess() const { return m_bTempPausePostprocess; }
void SetTempPausePostprocess(bool bTempPausePostprocess) { m_bTempPausePostprocess = bTempPausePostprocess; }
void SetDownloadRate(int iRate) { m_iDownloadRate = iRate; }
int GetDownloadRate() const { return m_iDownloadRate; }
void SetResumeTime(time_t tResumeTime) { m_tResumeTime = tResumeTime; }
time_t GetResumeTime() const { return m_tResumeTime; }
void SetLocalTimeOffset(int iLocalTimeOffset) { m_iLocalTimeOffset = iLocalTimeOffset; }
int GetLocalTimeOffset() { return m_iLocalTimeOffset; }
void Init(const char* exeName, const char* configFilename, bool noConfig,
CmdOptList* commandLineOptions, bool noDiskAccess, Extender* extender);
void InitDefaults();
void InitOptions();
void InitOptFile();
void InitServers();
void InitCategories();
void InitScheduler();
void InitFeeds();
void InitCommandLineOptions(CmdOptList* commandLineOptions);
void CheckOptions();
int ParseEnumValue(const char* OptName, int argc, const char* argn[], const int argv[]);
int ParseIntValue(const char* OptName, int base);
OptEntry* FindOption(const char* optname);
const char* GetOption(const char* optname);
void SetOption(const char* optname, const char* value);
bool SetOptionString(const char* option);
bool ValidateOptionName(const char* optname, const char* optvalue);
void LoadConfigFile();
void CheckDir(CString& dir, const char* optionName, const char* parentDir,
bool allowEmpty, bool create);
bool ParseTime(const char* time, int* hours, int* minutes);
bool ParseWeekDays(const char* weekDays, int* weekDaysBits);
void ConfigError(const char* msg, ...);
void ConfigWarn(const char* msg, ...);
void LocateOptionSrcPos(const char *optionName);
static void ConvertOldOption(CString& option, CString& value);
static void MergeOldScriptOption(OptEntries* optEntries, const char* optname, bool mergeCategories);
static bool HasScript(const char* scriptList, const char* scriptName);
};
extern Options* g_pOptions;
extern Options* g_Options;
#endif

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2008-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2008-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,32 +14,14 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#else
#include <unistd.h>
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "nzbget.h"
#include "Scheduler.h"
#include "Options.h"
#include "WorkState.h"
#include "Log.h"
#include "NewsServer.h"
#include "ServerPool.h"
@@ -47,223 +29,212 @@
#include "FeedCoordinator.h"
#include "SchedulerScript.h"
Scheduler::Task::Task(int iID, int iHours, int iMinutes, int iWeekDaysBits, ECommand eCommand, const char* szParam)
void Scheduler::AddTask(std::unique_ptr<Task> task)
{
m_iID = iID;
m_iHours = iHours;
m_iMinutes = iMinutes;
m_iWeekDaysBits = iWeekDaysBits;
m_eCommand = eCommand;
m_szParam = szParam ? strdup(szParam) : NULL;
m_tLastExecuted = 0;
}
Scheduler::Task::~Task()
{
free(m_szParam);
}
Scheduler::Scheduler()
{
debug("Creating Scheduler");
m_bFirstChecked = false;
m_tLastCheck = 0;
m_TaskList.clear();
}
Scheduler::~Scheduler()
{
debug("Destroying Scheduler");
for (TaskList::iterator it = m_TaskList.begin(); it != m_TaskList.end(); it++)
{
delete *it;
}
}
void Scheduler::AddTask(Task* pTask)
{
m_mutexTaskList.Lock();
m_TaskList.push_back(pTask);
m_mutexTaskList.Unlock();
}
bool Scheduler::CompareTasks(Scheduler::Task* pTask1, Scheduler::Task* pTask2)
{
return (pTask1->m_iHours < pTask2->m_iHours) ||
((pTask1->m_iHours == pTask2->m_iHours) && (pTask1->m_iMinutes < pTask2->m_iMinutes));
Guard guard(m_taskListMutex);
m_taskList.push_back(std::move(task));
}
void Scheduler::FirstCheck()
{
m_mutexTaskList.Lock();
m_TaskList.sort(CompareTasks);
m_mutexTaskList.Unlock();
{
Guard guard(m_taskListMutex);
std::sort(m_taskList.begin(), m_taskList.end(),
[](const std::unique_ptr<Task>& task1, const std::unique_ptr<Task>& task2)
{
return (task1->m_hours < task2->m_hours) ||
((task1->m_hours == task2->m_hours) && (task1->m_minutes < task2->m_minutes));
});
}
// check all tasks for the last week
CheckTasks();
}
void Scheduler::ScheduleNextWork()
{
// Ideally we should calculate wait time until next scheduler task or until resume time.
// The first isn't trivial and the second requires watching/reaction on changed scheduled resume time.
// We do it simpler instead: check once per minute, when seconds are changing from 59 to 00.
time_t curTime = Util::CurrentTime();
tm sched;
gmtime_r(&curTime, &sched);
sched.tm_min++;
sched.tm_sec = 0;
time_t nextMinute = Util::Timegm(&sched);
m_serviceInterval = nextMinute - curTime;
}
void Scheduler::ServiceWork()
{
debug("Scheduler service work");
if (!DownloadQueue::IsLoaded())
{
return;
}
if (!m_bFirstChecked)
debug("Scheduler service work: doing work");
if (!m_firstChecked)
{
FirstCheck();
m_bFirstChecked = true;
m_firstChecked = true;
return;
}
m_bExecuteProcess = true;
m_executeProcess = true;
CheckTasks();
CheckScheduledResume();
ScheduleNextWork();
}
void Scheduler::CheckTasks()
{
PrepareLog();
m_mutexTaskList.Lock();
time_t tCurrent = time(NULL);
if (!m_TaskList.empty())
{
// Detect large step changes of system time
time_t tDiff = tCurrent - m_tLastCheck;
if (tDiff > 60*90 || tDiff < 0)
Guard guard(m_taskListMutex);
time_t current = Util::CurrentTime();
if (!m_taskList.empty())
{
debug("Reset scheduled tasks (detected clock change greater than 90 minutes or negative)");
// check all tasks for the last week
m_tLastCheck = tCurrent - 60*60*24*7;
m_bExecuteProcess = false;
for (TaskList::iterator it = m_TaskList.begin(); it != m_TaskList.end(); it++)
// Detect large step changes of system time
time_t diff = current - m_lastCheck;
if (diff > 60 * 90 || diff < 0)
{
Task* pTask = *it;
pTask->m_tLastExecuted = 0;
}
}
debug("Reset scheduled tasks (detected clock change greater than 90 minutes or negative)");
time_t tLocalCurrent = tCurrent + g_pOptions->GetLocalTimeOffset();
time_t tLocalLastCheck = m_tLastCheck + g_pOptions->GetLocalTimeOffset();
// check all tasks for the last week
m_lastCheck = current - 60 * 60 * 24 * 7;
m_executeProcess = false;
tm tmCurrent;
gmtime_r(&tLocalCurrent, &tmCurrent);
tm tmLastCheck;
gmtime_r(&tLocalLastCheck, &tmLastCheck);
tm tmLoop;
memcpy(&tmLoop, &tmLastCheck, sizeof(tmLastCheck));
tmLoop.tm_hour = tmCurrent.tm_hour;
tmLoop.tm_min = tmCurrent.tm_min;
tmLoop.tm_sec = tmCurrent.tm_sec;
time_t tLoop = Util::Timegm(&tmLoop);
while (tLoop <= tLocalCurrent)
{
for (TaskList::iterator it = m_TaskList.begin(); it != m_TaskList.end(); it++)
{
Task* pTask = *it;
if (pTask->m_tLastExecuted != tLoop)
for (Task* task : &m_taskList)
{
tm tmAppoint;
memcpy(&tmAppoint, &tmLoop, sizeof(tmLoop));
tmAppoint.tm_hour = pTask->m_iHours;
tmAppoint.tm_min = pTask->m_iMinutes;
tmAppoint.tm_sec = 0;
time_t tAppoint = Util::Timegm(&tmAppoint);
int iWeekDay = tmAppoint.tm_wday;
if (iWeekDay == 0)
if (task->m_hours != Task::STARTUP_TASK)
{
iWeekDay = 7;
}
bool bWeekDayOK = pTask->m_iWeekDaysBits == 0 || (pTask->m_iWeekDaysBits & (1 << (iWeekDay - 1)));
bool bDoTask = bWeekDayOK && tLocalLastCheck < tAppoint && tAppoint <= tLocalCurrent;
//debug("TEMP: 1) m_tLastCheck=%i, tLocalCurrent=%i, tLoop=%i, tAppoint=%i, bWeekDayOK=%i, bDoTask=%i", m_tLastCheck, tLocalCurrent, tLoop, tAppoint, (int)bWeekDayOK, (int)bDoTask);
if (bDoTask)
{
ExecuteTask(pTask);
pTask->m_tLastExecuted = tLoop;
task->m_lastExecuted = 0;
}
}
}
tLoop += 60*60*24; // inc day
gmtime_r(&tLoop, &tmLoop);
time_t localCurrent = current + g_WorkState->GetLocalTimeOffset();
time_t localLastCheck = m_lastCheck + g_WorkState->GetLocalTimeOffset();
tm tmCurrent;
gmtime_r(&localCurrent, &tmCurrent);
tm tmLastCheck;
gmtime_r(&localLastCheck, &tmLastCheck);
tm tmLoop;
memcpy(&tmLoop, &tmLastCheck, sizeof(tmLastCheck));
tmLoop.tm_hour = tmCurrent.tm_hour;
tmLoop.tm_min = tmCurrent.tm_min;
tmLoop.tm_sec = tmCurrent.tm_sec;
time_t loop = Util::Timegm(&tmLoop);
while (loop <= localCurrent)
{
for (Task* task : &m_taskList)
{
if (task->m_lastExecuted != loop)
{
tm tmAppoint;
memcpy(&tmAppoint, &tmLoop, sizeof(tmLoop));
tmAppoint.tm_hour = task->m_hours;
tmAppoint.tm_min = task->m_minutes;
tmAppoint.tm_sec = 0;
time_t appoint = Util::Timegm(&tmAppoint);
int weekDay = tmAppoint.tm_wday;
if (weekDay == 0)
{
weekDay = 7;
}
bool weekDayOK = task->m_weekDaysBits == 0 || (task->m_weekDaysBits & (1 << (weekDay - 1)));
bool doTask = (task->m_hours >= 0 && weekDayOK && localLastCheck < appoint && appoint <= localCurrent) ||
(task->m_hours == Task::STARTUP_TASK && task->m_lastExecuted == 0);
if (doTask)
{
ExecuteTask(task);
task->m_lastExecuted = loop;
}
}
}
loop += 60 * 60 * 24; // inc day
gmtime_r(&loop, &tmLoop);
}
}
m_lastCheck = current;
}
m_tLastCheck = tCurrent;
m_mutexTaskList.Unlock();
PrintLog();
}
void Scheduler::ExecuteTask(Task* pTask)
void Scheduler::ExecuteTask(Task* task)
{
const char* szCommandName[] = { "Pause", "Unpause", "Pause Post-processing", "Unpause Post-processing",
#ifdef DEBUG
const char* commandName[] = { "Pause", "Unpause", "Pause Post-processing", "Unpause Post-processing",
"Set download rate", "Execute process", "Execute script",
"Pause Scan", "Unpause Scan", "Enable Server", "Disable Server", "Fetch Feed" };
debug("Executing scheduled command: %s", szCommandName[pTask->m_eCommand]);
debug("Executing scheduled command: %s", commandName[task->m_command]);
#endif
switch (pTask->m_eCommand)
bool executeProcess = m_executeProcess || task->m_hours == Task::STARTUP_TASK;
switch (task->m_command)
{
case scDownloadRate:
if (!Util::EmptyStr(pTask->m_szParam))
if (!task->m_param.Empty())
{
g_pOptions->SetDownloadRate(atoi(pTask->m_szParam) * 1024);
m_bDownloadRateChanged = true;
g_WorkState->SetSpeedLimit(atoi(task->m_param) * 1024);
m_downloadRateChanged = true;
}
break;
case scPauseDownload:
case scUnpauseDownload:
g_pOptions->SetPauseDownload(pTask->m_eCommand == scPauseDownload);
m_bPauseDownloadChanged = true;
g_WorkState->SetPauseDownload(task->m_command == scPauseDownload);
m_pauseDownloadChanged = true;
break;
case scPausePostProcess:
case scUnpausePostProcess:
g_pOptions->SetPausePostProcess(pTask->m_eCommand == scPausePostProcess);
m_bPausePostProcessChanged = true;
g_WorkState->SetPausePostProcess(task->m_command == scPausePostProcess);
m_pausePostProcessChanged = true;
break;
case scPauseScan:
case scUnpauseScan:
g_pOptions->SetPauseScan(pTask->m_eCommand == scPauseScan);
m_bPauseScanChanged = true;
g_WorkState->SetPauseScan(task->m_command == scPauseScan);
m_pauseScanChanged = true;
break;
case scScript:
case scExtensions:
case scProcess:
if (m_bExecuteProcess)
if (executeProcess)
{
SchedulerScriptController::StartScript(pTask->m_szParam, pTask->m_eCommand == scProcess, pTask->m_iID);
SchedulerScriptController::StartScript(task->m_param, task->m_command == scProcess, task->m_id);
}
break;
case scActivateServer:
case scDeactivateServer:
EditServer(pTask->m_eCommand == scActivateServer, pTask->m_szParam);
EditServer(task->m_command == scActivateServer, task->m_param);
break;
case scFetchFeed:
if (m_bExecuteProcess)
if (executeProcess)
{
FetchFeed(pTask->m_szParam);
FetchFeed(task->m_param);
break;
}
}
@@ -271,91 +242,88 @@ void Scheduler::ExecuteTask(Task* pTask)
void Scheduler::PrepareLog()
{
m_bDownloadRateChanged = false;
m_bPauseDownloadChanged = false;
m_bPausePostProcessChanged = false;
m_bPauseScanChanged = false;
m_bServerChanged = false;
m_downloadRateChanged = false;
m_pauseDownloadChanged = false;
m_pausePostProcessChanged = false;
m_pauseScanChanged = false;
m_serverChanged = false;
}
void Scheduler::PrintLog()
{
if (m_bDownloadRateChanged)
if (m_downloadRateChanged)
{
info("Scheduler: setting download rate to %i KB/s", g_pOptions->GetDownloadRate() / 1024);
info("Scheduler: setting download rate to %i KB/s", g_WorkState->GetSpeedLimit() / 1024);
}
if (m_bPauseDownloadChanged)
if (m_pauseDownloadChanged)
{
info("Scheduler: %s download", g_pOptions->GetPauseDownload() ? "pausing" : "unpausing");
info("Scheduler: %s download", g_WorkState->GetPauseDownload() ? "pausing" : "unpausing");
}
if (m_bPausePostProcessChanged)
if (m_pausePostProcessChanged)
{
info("Scheduler: %s post-processing", g_pOptions->GetPausePostProcess() ? "pausing" : "unpausing");
info("Scheduler: %s post-processing", g_WorkState->GetPausePostProcess() ? "pausing" : "unpausing");
}
if (m_bPauseScanChanged)
if (m_pauseScanChanged)
{
info("Scheduler: %s scan", g_pOptions->GetPauseScan() ? "pausing" : "unpausing");
info("Scheduler: %s scan", g_WorkState->GetPauseScan() ? "pausing" : "unpausing");
}
if (m_bServerChanged)
if (m_serverChanged)
{
int index = 0;
for (Servers::iterator it = g_pServerPool->GetServers()->begin(); it != g_pServerPool->GetServers()->end(); it++, index++)
for (NewsServer* server : g_ServerPool->GetServers())
{
NewsServer* pServer = *it;
if (pServer->GetActive() != m_ServerStatusList[index])
if (server->GetActive() != m_serverStatusList[index])
{
info("Scheduler: %s %s", pServer->GetActive() ? "activating" : "deactivating", pServer->GetName());
info("Scheduler: %s %s", server->GetActive() ? "activating" : "deactivating", server->GetName());
}
index++;
}
g_pServerPool->Changed();
g_ServerPool->Changed();
}
}
void Scheduler::EditServer(bool bActive, const char* szServerList)
void Scheduler::EditServer(bool active, const char* serverList)
{
Tokenizer tok(szServerList, ",;");
while (const char* szServer = tok.Next())
Tokenizer tok(serverList, ",;");
while (const char* serverRef = tok.Next())
{
int iID = atoi(szServer);
for (Servers::iterator it = g_pServerPool->GetServers()->begin(); it != g_pServerPool->GetServers()->end(); it++)
int id = atoi(serverRef);
for (NewsServer* server : g_ServerPool->GetServers())
{
NewsServer* pServer = *it;
if ((iID > 0 && pServer->GetID() == iID) ||
!strcasecmp(pServer->GetName(), szServer))
if ((id > 0 && server->GetId() == id) ||
!strcasecmp(server->GetName(), serverRef))
{
if (!m_bServerChanged)
if (!m_serverChanged)
{
// store old server status for logging
m_ServerStatusList.clear();
m_ServerStatusList.reserve(g_pServerPool->GetServers()->size());
for (Servers::iterator it2 = g_pServerPool->GetServers()->begin(); it2 != g_pServerPool->GetServers()->end(); it2++)
m_serverStatusList.clear();
m_serverStatusList.reserve(g_ServerPool->GetServers()->size());
for (NewsServer* server2 : g_ServerPool->GetServers())
{
NewsServer* pServer2 = *it2;
m_ServerStatusList.push_back(pServer2->GetActive());
m_serverStatusList.push_back(server2->GetActive());
}
}
m_bServerChanged = true;
pServer->SetActive(bActive);
m_serverChanged = true;
server->SetActive(active);
break;
}
}
}
}
void Scheduler::FetchFeed(const char* szFeedList)
void Scheduler::FetchFeed(const char* feedList)
{
Tokenizer tok(szFeedList, ",;");
while (const char* szFeed = tok.Next())
Tokenizer tok(feedList, ",;");
while (const char* feedRef = tok.Next())
{
int iID = atoi(szFeed);
for (Feeds::iterator it = g_pFeedCoordinator->GetFeeds()->begin(); it != g_pFeedCoordinator->GetFeeds()->end(); it++)
int id = atoi(feedRef);
for (FeedInfo* feed : g_FeedCoordinator->GetFeeds())
{
FeedInfo* pFeed = *it;
if (pFeed->GetID() == iID ||
!strcasecmp(pFeed->GetName(), szFeed) ||
!strcasecmp("0", szFeed))
if (feed->GetId() == id ||
!strcasecmp(feed->GetName(), feedRef) ||
!strcasecmp("0", feedRef))
{
g_pFeedCoordinator->FetchFeed(!strcasecmp("0", szFeed) ? 0 : pFeed->GetID());
g_FeedCoordinator->FetchFeed(!strcasecmp("0", feedRef) ? 0 : feed->GetId());
break;
}
}
@@ -364,14 +332,14 @@ void Scheduler::FetchFeed(const char* szFeedList)
void Scheduler::CheckScheduledResume()
{
time_t tResumeTime = g_pOptions->GetResumeTime();
time_t tCurrentTime = time(NULL);
if (tResumeTime > 0 && tCurrentTime >= tResumeTime)
time_t resumeTime = g_WorkState->GetResumeTime();
time_t currentTime = Util::CurrentTime();
if (resumeTime > 0 && currentTime >= resumeTime)
{
info("Autoresume");
g_pOptions->SetResumeTime(0);
g_pOptions->SetPauseDownload(false);
g_pOptions->SetPausePostProcess(false);
g_pOptions->SetPauseScan(false);
g_WorkState->SetResumeTime(0);
g_WorkState->SetPauseDownload(false);
g_WorkState->SetPausePostProcess(false);
g_WorkState->SetPauseScan(false);
}
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2008-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2008-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,22 +14,14 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SCHEDULER_H
#define SCHEDULER_H
#include <list>
#include <vector>
#include <time.h>
#include "NString.h"
#include "Thread.h"
#include "Service.h"
@@ -43,7 +35,7 @@ public:
scPausePostProcess,
scUnpausePostProcess,
scDownloadRate,
scScript,
scExtensions,
scProcess,
scPauseScan,
scUnpauseScan,
@@ -54,59 +46,55 @@ public:
class Task
{
private:
int m_iID;
int m_iHours;
int m_iMinutes;
int m_iWeekDaysBits;
ECommand m_eCommand;
char* m_szParam;
time_t m_tLastExecuted;
public:
Task(int iID, int iHours, int iMinutes, int iWeekDaysBits, ECommand eCommand,
const char* szParam);
~Task();
friend class Scheduler;
Task(int id, int hours, int minutes, int weekDaysBits, ECommand command,
const char* param) :
m_id(id), m_hours(hours), m_minutes(minutes),
m_weekDaysBits(weekDaysBits), m_command(command), m_param(param) {}
friend class Scheduler;
static const int STARTUP_TASK = -1;
private:
int m_id;
int m_hours;
int m_minutes;
int m_weekDaysBits;
ECommand m_command;
CString m_param;
time_t m_lastExecuted = 0;
};
private:
typedef std::list<Task*> TaskList;
typedef std::vector<bool> ServerStatusList;
TaskList m_TaskList;
Mutex m_mutexTaskList;
time_t m_tLastCheck;
bool m_bDownloadRateChanged;
bool m_bExecuteProcess;
bool m_bPauseDownloadChanged;
bool m_bPausePostProcessChanged;
bool m_bPauseScanChanged;
bool m_bServerChanged;
ServerStatusList m_ServerStatusList;
bool m_bFirstChecked;
void ExecuteTask(Task* pTask);
void CheckTasks();
static bool CompareTasks(Scheduler::Task* pTask1, Scheduler::Task* pTask2);
void PrepareLog();
void PrintLog();
void EditServer(bool bActive, const char* szServerList);
void FetchFeed(const char* szFeedList);
void CheckScheduledResume();
void FirstCheck();
void AddTask(std::unique_ptr<Task> task);
protected:
virtual int ServiceInterval() { return 1000; }
virtual void ServiceWork();
virtual int ServiceInterval() { return m_serviceInterval; }
virtual void ServiceWork();
public:
Scheduler();
~Scheduler();
void AddTask(Task* pTask);
private:
typedef std::vector<std::unique_ptr<Task>> TaskList;
typedef std::vector<bool> ServerStatusList;
TaskList m_taskList;
Mutex m_taskListMutex;
time_t m_lastCheck = 0;
bool m_downloadRateChanged;
bool m_executeProcess;
bool m_pauseDownloadChanged;
bool m_pausePostProcessChanged;
bool m_pauseScanChanged;
bool m_serverChanged;
ServerStatusList m_serverStatusList;
bool m_firstChecked = false;
int m_serviceInterval = 1;
void ExecuteTask(Task* task);
void CheckTasks();
void PrepareLog();
void PrintLog();
void EditServer(bool active, const char* serverList);
void FetchFeed(const char* feedList);
void CheckScheduledResume();
void FirstCheck();
void ScheduleNextWork();
};
extern Scheduler* g_pScheduler;
#endif

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,41 +14,10 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#ifdef WIN32
#include <dbghelp.h>
#else
#include <unistd.h>
#include <sys/resource.h>
#include <signal.h>
#endif
#ifdef HAVE_SYS_PRCTL_H
#include <sys/prctl.h>
#endif
#ifdef HAVE_BACKTRACE
#include <execinfo.h>
#endif
#include "nzbget.h"
#include "Log.h"
#include "Options.h"
@@ -60,29 +29,29 @@ extern void ExitProc();
#ifdef DEBUG
void PrintBacktrace(PCONTEXT pContext)
void PrintBacktrace(PCONTEXT context)
{
HANDLE hProcess = GetCurrentProcess();
HANDLE hThread = GetCurrentThread();
char szAppDir[MAX_PATH + 1];
GetModuleFileName(NULL, szAppDir, sizeof(szAppDir));
char* end = strrchr(szAppDir, PATH_SEPARATOR);
char appDir[MAX_PATH + 1];
GetModuleFileName(nullptr, appDir, sizeof(appDir));
char* end = strrchr(appDir, PATH_SEPARATOR);
if (end) *end = '\0';
SymSetOptions(SymGetOptions() | SYMOPT_LOAD_LINES | SYMOPT_FAIL_CRITICAL_ERRORS);
if (!SymInitialize(hProcess, szAppDir, TRUE))
if (!SymInitialize(hProcess, appDir, TRUE))
{
warn("Could not obtain detailed exception information: SymInitialize failed");
return;
}
const int MAX_NAMELEN = 1024;
IMAGEHLP_SYMBOL64* pSym = (IMAGEHLP_SYMBOL64 *) malloc(sizeof(IMAGEHLP_SYMBOL64) + MAX_NAMELEN);
memset(pSym, 0, sizeof(IMAGEHLP_SYMBOL64) + MAX_NAMELEN);
pSym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
pSym->MaxNameLength = MAX_NAMELEN;
IMAGEHLP_SYMBOL64* sym = (IMAGEHLP_SYMBOL64 *) malloc(sizeof(IMAGEHLP_SYMBOL64) + MAX_NAMELEN);
memset(sym, 0, sizeof(IMAGEHLP_SYMBOL64) + MAX_NAMELEN);
sym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
sym->MaxNameLength = MAX_NAMELEN;
IMAGEHLP_LINE64 ilLine;
memset(&ilLine, 0, sizeof(ilLine));
@@ -93,19 +62,19 @@ void PrintBacktrace(PCONTEXT pContext)
DWORD imageType;
#ifdef _M_IX86
imageType = IMAGE_FILE_MACHINE_I386;
sfStackFrame.AddrPC.Offset = pContext->Eip;
sfStackFrame.AddrPC.Offset = context->Eip;
sfStackFrame.AddrPC.Mode = AddrModeFlat;
sfStackFrame.AddrFrame.Offset = pContext->Ebp;
sfStackFrame.AddrFrame.Offset = context->Ebp;
sfStackFrame.AddrFrame.Mode = AddrModeFlat;
sfStackFrame.AddrStack.Offset = pContext->Esp;
sfStackFrame.AddrStack.Offset = context->Esp;
sfStackFrame.AddrStack.Mode = AddrModeFlat;
#elif _M_X64
imageType = IMAGE_FILE_MACHINE_AMD64;
sfStackFrame.AddrPC.Offset = pContext->Rip;
sfStackFrame.AddrPC.Offset = context->Rip;
sfStackFrame.AddrPC.Mode = AddrModeFlat;
sfStackFrame.AddrFrame.Offset = pContext->Rsp;
sfStackFrame.AddrFrame.Offset = context->Rsp;
sfStackFrame.AddrFrame.Mode = AddrModeFlat;
sfStackFrame.AddrStack.Offset = pContext->Rsp;
sfStackFrame.AddrStack.Offset = context->Rsp;
sfStackFrame.AddrStack.Mode = AddrModeFlat;
#else
warn("Could not obtain detailed exception information: platform not supported");
@@ -120,47 +89,46 @@ void PrintBacktrace(PCONTEXT pContext)
return;
}
if (!StackWalk64(imageType, hProcess, hThread, &sfStackFrame, pContext, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL))
if (!StackWalk64(imageType, hProcess, hThread, &sfStackFrame, context, nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr))
{
warn("Could not obtain detailed exception information: StackWalk64 failed");
return;
}
DWORD64 dwAddr = sfStackFrame.AddrPC.Offset;
char szSymName[1024];
char szSrcFileName[1024];
int iLineNumber = 0;
BString<1024> symName;
BString<1024> srcFileName;
int lineNumber = 0;
DWORD64 dwSymbolDisplacement;
if (SymGetSymFromAddr64(hProcess, dwAddr, &dwSymbolDisplacement, pSym))
if (SymGetSymFromAddr64(hProcess, dwAddr, &dwSymbolDisplacement, sym))
{
UnDecorateSymbolName(pSym->Name, szSymName, sizeof(szSymName), UNDNAME_COMPLETE);
szSymName[sizeof(szSymName) - 1] = '\0';
UnDecorateSymbolName(sym->Name, symName, symName.Capacity(), UNDNAME_COMPLETE);
symName[sizeof(symName) - 1] = '\0';
}
else
{
strncpy(szSymName, "<symbol not available>", sizeof(szSymName));
symName = "<symbol not available>";
}
DWORD dwLineDisplacement;
if (SymGetLineFromAddr64(hProcess, dwAddr, &dwLineDisplacement, &ilLine))
{
iLineNumber = ilLine.LineNumber;
char* szUseFileName = ilLine.FileName;
char* szRoot = strstr(szUseFileName, "\\daemon\\");
if (szRoot)
lineNumber = ilLine.LineNumber;
char* useFileName = ilLine.FileName;
char* root = strstr(useFileName, "\\daemon\\");
if (root)
{
szUseFileName = szRoot;
useFileName = root;
}
strncpy(szSrcFileName, szUseFileName, sizeof(szSrcFileName));
szSrcFileName[sizeof(szSrcFileName) - 1] = '\0';
srcFileName = useFileName;
}
else
{
strncpy(szSrcFileName, "<filename not available>", sizeof(szSymName));
srcFileName = "<filename not available>";
}
info("%s (%i) : %s", szSrcFileName, iLineNumber, szSymName);
info("%s (%i) : %s", *srcFileName, lineNumber, *symName);
if (sfStackFrame.AddrReturn.Offset == 0)
{
@@ -170,26 +138,29 @@ void PrintBacktrace(PCONTEXT pContext)
}
#endif
LONG __stdcall ExceptionFilter(EXCEPTION_POINTERS* pExPtrs)
LONG __stdcall ExceptionFilter(EXCEPTION_POINTERS* exPtrs)
{
error("Unhandled Exception: code: 0x%8.8X, flags: %d, address: 0x%8.8X",
pExPtrs->ExceptionRecord->ExceptionCode,
pExPtrs->ExceptionRecord->ExceptionFlags,
pExPtrs->ExceptionRecord->ExceptionAddress);
exPtrs->ExceptionRecord->ExceptionCode,
exPtrs->ExceptionRecord->ExceptionFlags,
exPtrs->ExceptionRecord->ExceptionAddress);
#ifdef DEBUG
PrintBacktrace(pExPtrs->ContextRecord);
#else
info("Detailed exception information can be printed by debug version of NZBGet (available from download page)");
#endif
ExitProcess(-1);
PrintBacktrace(exPtrs->ContextRecord);
return EXCEPTION_CONTINUE_SEARCH;
#else
info("Detailed crash information can be printed by debug version of NZBGet."
" For more info visit http://nzbget.net/crash-dump");
return EXCEPTION_EXECUTE_HANDLER;
#endif
}
void InstallErrorHandler()
{
SetUnhandledExceptionFilter(ExceptionFilter);
if (g_Options->GetCrashTrace())
{
SetUnhandledExceptionFilter(ExceptionFilter);
}
}
#else
@@ -203,7 +174,7 @@ std::vector<sighandler> SignalProcList;
/**
* activates the creation of core-files
*/
void EnableDumpCore()
void EnableCoreDump()
{
rlimit rlim;
rlim.rlim_cur= RLIM_INFINITY;
@@ -217,7 +188,7 @@ void PrintBacktrace()
{
#ifdef HAVE_BACKTRACE
printf("Segmentation fault, tracing...\n");
void *array[100];
size_t size;
char **strings;
@@ -250,9 +221,9 @@ void PrintBacktrace()
/*
* Signal handler
*/
void SignalProc(int iSignal)
void SignalProc(int signum)
{
switch (iSignal)
switch (signum)
{
case SIGINT:
signal(SIGINT, SIG_DFL); // Reset the signal handler
@@ -280,9 +251,9 @@ void SignalProc(int iSignal)
void InstallErrorHandler()
{
#ifdef HAVE_SYS_PRCTL_H
if (g_pOptions->GetDumpCore())
if (g_Options->GetCrashDump())
{
EnableDumpCore();
EnableCoreDump();
}
#endif
@@ -290,11 +261,14 @@ void InstallErrorHandler()
signal(SIGTERM, SignalProc);
signal(SIGPIPE, SIG_IGN);
#ifdef DEBUG
signal(SIGSEGV, SignalProc);
if (g_Options->GetCrashTrace())
{
signal(SIGSEGV, SignalProc);
}
#endif
#ifdef SIGCHLD_HANDLER
// it could be necessary on some systems to activate a handler for SIGCHLD
// however it make troubles on other systems and is deactivated by default
// it could be necessary on some systems to activate a handler for SIGCHLD
// however it make troubles on other systems and is deactivated by default
signal(SIGCHLD, SignalProc);
#endif
}
@@ -307,8 +281,8 @@ class SegFault
public:
void DoSegFault()
{
char* N = NULL;
strcpy(N, "");
char* N = nullptr;
*N = '\0';
}
};

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,12 +14,7 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

27
daemon/main/WorkState.cpp Normal file
View File

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

76
daemon/main/WorkState.h Normal file
View File

@@ -0,0 +1,76 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef WORKSTATE_H
#define WORKSTATE_H
#include "Observer.h"
// WorkState is observable but notifications are not 100% reliable.
// The changes via Set-methods and readings via Get-methods are not synchronized throughout the program.
// As result race conditions may occur and some changes may go unnoticed.
// When waiting for changes don't wait too long to avoid lock ups.
class WorkState : public Subject
{
public:
void SetPauseDownload(bool pauseDownload) { m_pauseDownload = pauseDownload; Changed(); }
bool GetPauseDownload() const { return m_pauseDownload; }
void SetPausePostProcess(bool pausePostProcess) { m_pausePostProcess = pausePostProcess; Changed(); }
bool GetPausePostProcess() const { return m_pausePostProcess; }
void SetPauseScan(bool pauseScan) { m_pauseScan = pauseScan; Changed(); }
bool GetPauseScan() const { return m_pauseScan; }
void SetTempPauseDownload(bool tempPauseDownload) { m_tempPauseDownload = tempPauseDownload; Changed(); }
bool GetTempPauseDownload() const { return m_tempPauseDownload; }
void SetTempPausePostprocess(bool tempPausePostprocess) { m_tempPausePostprocess = tempPausePostprocess; Changed(); }
bool GetTempPausePostprocess() const { return m_tempPausePostprocess; }
void SetPauseFrontend(bool pauseFrontend) { m_pauseFrontend = pauseFrontend; Changed(); }
bool GetPauseFrontend() const { return m_pauseFrontend; }
void SetSpeedLimit(int speedLimit) { m_speedLimit = speedLimit; Changed(); }
int GetSpeedLimit() const { return m_speedLimit; }
void SetResumeTime(time_t resumeTime) { m_resumeTime = resumeTime; Changed(); }
time_t GetResumeTime() const { return m_resumeTime; }
void SetLocalTimeOffset(int localTimeOffset) { m_localTimeOffset = localTimeOffset; Changed(); }
int GetLocalTimeOffset() { return m_localTimeOffset; }
void SetQuotaReached(bool quotaReached) { m_quotaReached = quotaReached; Changed(); }
bool GetQuotaReached() { return m_quotaReached; }
void SetDownloading(bool downloading) { m_downloading = downloading; Changed(); }
bool GetDownloading() { return m_downloading; }
private:
bool m_pauseDownload = false;
bool m_pausePostProcess = false;
bool m_pauseScan = false;
bool m_tempPauseDownload = true;
bool m_tempPausePostprocess = true;
bool m_pauseFrontend = false;
int m_downloadRate = 0;
time_t m_resumeTime = 0;
int m_localTimeOffset = 0;
bool m_quotaReached = false;
int m_speedLimit = 0;
bool m_downloading = false;
void Changed();
};
extern WorkState* g_WorkState;
#endif

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,21 +14,278 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef NZBGET_H
#define NZBGET_H
/***************** DEFINES FOR WINDOWS *****************/
#ifdef WIN32
// WIN32
/* Define to 1 to not use curses */
//#define DISABLE_CURSES
/* Define to 1 to disable smart par-verification and restoration */
//#define DISABLE_PARCHECK
/* Define to 1 to disable TLS/SSL-support. */
//#define DISABLE_TLS
#ifndef DISABLE_TLS
/* Define to 1 to use OpenSSL library for TLS/SSL-support */
#define HAVE_OPENSSL
/* Define to 1 to use GnuTLS library for TLS/SSL-support */
//#define HAVE_LIBGNUTLS
#endif
/* Define to 1 if OpenSSL supports function "X509_check_host". */
#define HAVE_X509_CHECK_HOST 1
/* Define to the name of macro which returns the name of function being
compiled */
#define FUNCTION_MACRO_NAME __FUNCTION__
/* Define to 1 if ctime_r takes 2 arguments */
#undef HAVE_CTIME_R_2
/* Define to 1 if ctime_r takes 3 arguments */
#define HAVE_CTIME_R_3
/* Define to 1 if getopt_long is supported */
#undef HAVE_GETOPT_LONG
/* Define to 1 if variadic macros are supported */
#define HAVE_VARIADIC_MACROS
/* Define to 1 if function GetAddrInfo is supported */
#define HAVE_GETADDRINFO
/* Determine what socket length (socklen_t) data type is */
#define SOCKLEN_T socklen_t
/* Define to 1 if you have the <regex.h> header file. */
#ifndef DISABLE_REGEX
#define HAVE_REGEX_H 1
// Static linking to regex library
#define REGEX_STATIC
#endif
#ifndef DISABLE_GZIP
// Static linking to zlib library
#define ZLIB_WINAPI
#endif
/* Suppress warnings */
#define _CRT_SECURE_NO_DEPRECATE
/* Suppress warnings */
#define _CRT_NONSTDC_NO_WARNINGS
#ifndef _WIN64
#define _USE_32BIT_TIME_T
#endif
#if _WIN32_WINNT < 0x0501
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#endif
#ifdef _WIN64
#define __amd64__
#else
#define __i686__
#endif
#ifdef _DEBUG
// detection of memory leaks
#define _CRTDBG_MAP_ALLOC
#endif
#pragma warning(disable:4800) // 'type' : forcing value to bool 'true' or 'false' (performance warning)
#pragma warning(disable:4267) // 'var' : conversion from 'size_t' to 'type', possible loss of data
#endif
/***************** GLOBAL INCLUDES *****************/
#ifdef WIN32
// WINDOWS INCLUDES
// Using "WIN32_LEAN_AND_MEAN" to disable including of many unneeded headers
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <winsvc.h>
#include <direct.h>
#include <shlobj.h>
#include <dbghelp.h>
#include <mmsystem.h>
#include <io.h>
#include <process.h>
#include <WinIoCtl.h>
#include <wincon.h>
#include <shellapi.h>
#include <winreg.h>
#include <comutil.h>
#import <msxml.tlb> named_guids
using namespace MSXML;
#if _MSC_VER >= 1600
#include <stdint.h>
#define HAVE_STDINT_H
#endif
#ifdef _DEBUG
#include <crtdbg.h>
#endif
#else
// POSIX INCLUDES
#include "config.h"
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <signal.h>
#include <netdb.h>
#include <unistd.h>
#include <pthread.h>
#include <unistd.h>
#include <getopt.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/statvfs.h>
#include <sys/wait.h>
#include <sys/un.h>
#include <sys/file.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdint.h>
#include <pwd.h>
#include <dirent.h>
#ifndef DISABLE_LIBXML2
#include <libxml/parser.h>
#include <libxml/xmlreader.h>
#include <libxml/xmlerror.h>
#include <libxml/entities.h>
#endif
#ifdef HAVE_SYS_PRCTL_H
#include <sys/prctl.h>
#endif
#ifdef HAVE_ENDIAN_H
#include <endian.h>
#endif
#ifdef HAVE_BACKTRACE
#include <execinfo.h>
#endif
#endif /* POSIX INCLUDES */
// COMMON INCLUDES
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>
#include <ctype.h>
#include <inttypes.h>
#include <string>
#include <vector>
#include <deque>
#include <list>
#include <set>
#include <map>
#include <unordered_map>
#include <iterator>
#include <algorithm>
#include <fstream>
#include <memory>
#include <functional>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
// NOTE: do not include <iostream> in "nzbget.h". <iostream> contains objects requiring
// intialization, causing every unit in nzbget to have initialization routine. This in particular
// is causing fatal problems in SIMD units which must not have static initialization because
// they contain code with runtime CPU dispatching.
//#include <iostream>
#ifdef HAVE_LIBGNUTLS
#ifdef WIN32
#include <BaseTsd.h>
typedef SSIZE_T ssize_t;
typedef int pid_t;
#endif
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#if GNUTLS_VERSION_NUMBER <= 0x020b00
#define NEED_GCRYPT_LOCKING
#endif
#ifdef NEED_GCRYPT_LOCKING
#include <gcrypt.h>
#endif /* NEED_GCRYPT_LOCKING */
#include <nettle/sha.h>
#include <nettle/pbkdf2.h>
#include <nettle/aes.h>
#endif /* HAVE_LIBGNUTLS */
#ifdef HAVE_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rsa.h>
#include <openssl/sha.h>
#include <openssl/pem.h>
#include <openssl/x509v3.h>
#include <openssl/comp.h>
#endif /* HAVE_OPENSSL */
#ifdef HAVE_REGEX_H
#include <regex.h>
#endif
#ifndef DISABLE_GZIP
#include <zlib.h>
#endif
#ifndef DISABLE_PARCHECK
#include <assert.h>
#include <iomanip>
#include <cassert>
#ifdef HAVE_MEMORY_H
# include <memory.h>
#endif
#endif /* NOT DISABLE_PARCHECK */
/***************** GLOBAL FUNCTION AND CONST OVERRIDES *****************/
#ifdef WIN32
// WINDOWS
#define snprintf _snprintf
#ifndef strdup
@@ -39,30 +296,25 @@
#define gmtime_r(time, tm) gmtime_s(tm, time)
#define strtok_r(str, delim, saveptr) strtok_s(str, delim, saveptr)
#define strerror_r(errnum, buffer, size) strerror_s(buffer, size, errnum)
#if (_MSC_VER < 1600)
#define int32_t __int32
#endif
#define mkdir(dir, flags) _mkdir(dir)
#define rmdir _rmdir
#define strcasecmp(a, b) _stricmp(a, b)
#define strncasecmp(a, b, c) _strnicmp(a, b, c)
#define ssize_t SSIZE_T
#define __S_ISTYPE(mode, mask) (((mode) & _S_IFMT) == (mask))
#define S_ISDIR(mode) __S_ISTYPE((mode), _S_IFDIR)
#define S_ISREG(mode) __S_ISTYPE((mode), _S_IFREG)
#define S_DIRMODE NULL
#define usleep(usec) Sleep((usec) / 1000)
#define __S_ISTYPE(mode, mask) (((mode) & _S_IFMT) == (mask))
#define S_ISDIR(mode) __S_ISTYPE((mode), _S_IFDIR)
#define S_ISREG(mode) __S_ISTYPE((mode), _S_IFREG)
#define S_DIRMODE nullptr
#define socklen_t int
#define SHUT_WR 0x01
#define SHUT_RDWR 0x02
#define PATH_SEPARATOR '\\'
#define ALT_PATH_SEPARATOR '/'
#define LINE_ENDING "\r\n"
#define pid_t int
#define atoll _atoi64
#define fseek _fseeki64
#define ftell _ftelli64
#if _MSC_VER < 1800 // va_copy is available in vc2013 and onwards
// va_copy is available in vc2013 and onwards
#if _MSC_VER < 1800
#define va_copy(d,s) ((d) = (s))
#endif
#ifndef FSCTL_SET_SPARSE
@@ -71,17 +323,23 @@
#define FOPEN_RB "rbN"
#define FOPEN_RBP "rb+N"
#define FOPEN_WB "wbN"
#define FOPEN_WBP "wb+N"
#define FOPEN_AB "abN"
#define FOPEN_ABP "ab+N"
#define __SSE2__
#define __SSSE3__
#define __PCLMUL__
#ifdef DEBUG
// redefine "exit" to avoid printing memory leaks report when terminated because of wrong command line switches
#define exit(code) ExitProcess(code)
#endif
#pragma warning(disable:4800) // 'type' : forcing value to bool 'true' or 'false' (performance warning)
#pragma warning(disable:4267) // 'var' : conversion from 'size_t' to 'type', possible loss of data
#ifdef HAVE_OPENSSL
FILE _iob[] = {*stdin, *stdout, *stderr};
extern "C" FILE * __cdecl __iob_func(void) { return _iob; }
// For static linking of OpenSSL libraries:
#pragma comment (lib, "legacy_stdio_definitions.lib")
#endif /* HAVE_OPENSSL */
#else
@@ -98,14 +356,74 @@
#define FOPEN_RB "rb"
#define FOPEN_RBP "rb+"
#define FOPEN_WB "wb"
#define FOPEN_WBP "wb+"
#define FOPEN_AB "ab"
#define FOPEN_ABP "ab+"
#define CHILD_WATCHDOG 1
#endif
#endif /* POSIX */
// COMMON DEFINES FOR ALL PLATFORMS
#ifndef SHUT_RDWR
#define SHUT_RDWR 2
#endif
#ifdef HAVE_STDINT_H
typedef uint8_t uint8;
typedef int16_t int16;
typedef uint16_t uint16;
typedef uint32_t int32;
typedef uint32_t uint32;
typedef int64_t int64;
typedef uint64_t uint64;
#else
typedef unsigned char uint8;
typedef signed short int16;
typedef unsigned short uint16;
typedef signed int int32;
typedef unsigned int uint32;
typedef signed long long int64;
typedef unsigned long long uint64;
#endif
#ifndef PRId64
#define PRId64 "lld"
#endif
#ifndef PRIi64
#define PRIi64 "lli"
#endif
#ifndef PRIu64
#define PRIu64 "llu"
#endif
typedef unsigned char uchar;
// Assume little endian if byte order is not defined
#ifndef __BYTE_ORDER
#define __LITTLE_ENDIAN 1234
#define __BIG_ENDIAN 4321
#define __BYTE_ORDER __LITTLE_ENDIAN
#endif
#ifdef __GNUC__
#define PRINTF_SYNTAX(strindex) __attribute__ ((format (printf, strindex, strindex+1)))
#define SCANF_SYNTAX(strindex) __attribute__ ((format (scanf, strindex, strindex+1)))
#else
#define PRINTF_SYNTAX(strindex)
#define SCANF_SYNTAX(strindex)
#endif
// providing "std::make_unique" for GCC 4.8.x (only 4.8.x)
#if __GNUC__ && __cplusplus < 201402L && __cpp_generic_lambdas < 201304
namespace std {
template<class T> struct _Unique_if { typedef unique_ptr<T> _Single_object; };
template<class T> struct _Unique_if<T[]> { typedef unique_ptr<T[]> _Unknown_bound; };
template<class T, class... Args> typename _Unique_if<T>::_Single_object make_unique(Args&&... args) {
return unique_ptr<T>(new T(std::forward<Args>(args)...));
}
template<class T> typename _Unique_if<T>::_Unknown_bound make_unique(size_t n) {
typedef typename remove_extent<T>::type U;
return unique_ptr<T>(new U[n]());
}
}
#endif
#endif /* NZBGET_H */

View File

@@ -1,8 +1,8 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,41 +15,17 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#ifdef WIN32
#include <direct.h>
#else
#include <unistd.h>
#include <sys/time.h>
#endif
#include <sys/stat.h>
#include <errno.h>
#include "nzbget.h"
#include "ArticleDownloader.h"
#include "ArticleWriter.h"
#include "Decoder.h"
#include "Log.h"
#include "Options.h"
#include "WorkState.h"
#include "ServerPool.h"
#include "StatMeter.h"
#include "Util.h"
@@ -58,29 +34,18 @@ ArticleDownloader::ArticleDownloader()
{
debug("Creating ArticleDownloader");
m_szInfoName = NULL;
m_szConnectionName[0] = '\0';
m_pConnection = NULL;
m_eStatus = adUndefined;
m_eFormat = Decoder::efUnknown;
m_szArticleFilename = NULL;
m_iDownloadedSize = 0;
m_ArticleWriter.SetOwner(this);
SetLastUpdateTimeNow();
}
ArticleDownloader::~ArticleDownloader()
{
debug("Destroying ArticleDownloader");
free(m_szInfoName);
free(m_szArticleFilename);
}
void ArticleDownloader::SetInfoName(const char* szInfoName)
void ArticleDownloader::SetInfoName(const char* infoName)
{
m_szInfoName = strdup(szInfoName);
m_ArticleWriter.SetInfoName(m_szInfoName);
m_infoName = infoName;
m_articleWriter.SetInfoName(m_infoName);
}
/*
@@ -98,8 +63,8 @@ void ArticleDownloader::SetInfoName(const char* szInfoName)
- if download fails with error "Not-Found" (article or group not found) or with CRC error,
add the server to failed server list;
- if download fails with general failure error (article incomplete, other unknown error
codes), try the same server again as many times as defined by option <Retries>; if all attempts
fail, add the server to failed server list;
codes), try the same server again as many times as defined by option <ArticleRetries>;
if all attempts fail, add the server to failed server list;
- if all servers from current level were tried, increase level;
- if all servers from all levels were tried, break the loop with failure status.
<end-loop>
@@ -110,615 +75,518 @@ void ArticleDownloader::Run()
SetStatus(adRunning);
m_ArticleWriter.SetFileInfo(m_pFileInfo);
m_ArticleWriter.SetArticleInfo(m_pArticleInfo);
m_ArticleWriter.Prepare();
m_articleWriter.SetFileInfo(m_fileInfo);
m_articleWriter.SetArticleInfo(m_articleInfo);
m_articleWriter.Prepare();
EStatus Status = adFailed;
int iRetries = g_pOptions->GetRetries() > 0 ? g_pOptions->GetRetries() : 1;
int iRemainedRetries = iRetries;
Servers failedServers;
failedServers.reserve(g_pServerPool->GetServers()->size());
NewsServer* pWantServer = NULL;
NewsServer* pLastServer = NULL;
int iLevel = 0;
int iServerConfigGeneration = g_pServerPool->GetGeneration();
bool bForce = m_pFileInfo->GetNZBInfo()->GetForcePriority();
EStatus status = adFailed;
int retries = g_Options->GetArticleRetries() > 0 ? g_Options->GetArticleRetries() : 1;
int remainedRetries = retries;
ServerPool::RawServerList failedServers;
failedServers.reserve(g_ServerPool->GetServers()->size());
NewsServer* wantServer = nullptr;
NewsServer* lastServer = nullptr;
int level = 0;
int serverConfigGeneration = g_ServerPool->GetGeneration();
bool force = m_fileInfo->GetNzbInfo()->GetForcePriority();
while (!IsStopped())
{
Status = adFailed;
status = adFailed;
SetStatus(adWaiting);
while (!m_pConnection && !(IsStopped() || iServerConfigGeneration != g_pServerPool->GetGeneration()))
while (!m_connection && !(IsStopped() || serverConfigGeneration != g_ServerPool->GetGeneration()))
{
m_pConnection = g_pServerPool->GetConnection(iLevel, pWantServer, &failedServers);
usleep(5 * 1000);
m_connection = g_ServerPool->GetConnection(level, wantServer, &failedServers);
Util::Sleep(5);
}
SetLastUpdateTimeNow();
SetStatus(adRunning);
if (IsStopped() || (g_pOptions->GetPauseDownload() && !bForce) ||
(g_pOptions->GetTempPauseDownload() && !m_pFileInfo->GetExtraPriority()) ||
iServerConfigGeneration != g_pServerPool->GetGeneration())
if (IsStopped() || ((g_WorkState->GetPauseDownload() || g_WorkState->GetQuotaReached()) && !force) ||
(g_WorkState->GetTempPauseDownload() && !m_fileInfo->GetExtraPriority()) ||
serverConfigGeneration != g_ServerPool->GetGeneration())
{
Status = adRetry;
status = adRetry;
break;
}
pLastServer = m_pConnection->GetNewsServer();
lastServer = m_connection->GetNewsServer();
level = lastServer->GetNormLevel();
m_pConnection->SetSuppressErrors(false);
m_connection->SetSuppressErrors(false);
snprintf(m_szConnectionName, sizeof(m_szConnectionName), "%s (%s)",
m_pConnection->GetNewsServer()->GetName(), m_pConnection->GetHost());
m_szConnectionName[sizeof(m_szConnectionName) - 1] = '\0';
m_connectionName.Format("%s (%s)",
m_connection->GetNewsServer()->GetName(), m_connection->GetHost());
// check server retention
bool bRetentionFailure = m_pConnection->GetNewsServer()->GetRetention() > 0 &&
(time(NULL) - m_pFileInfo->GetTime()) / 86400 > m_pConnection->GetNewsServer()->GetRetention();
if (bRetentionFailure)
bool retentionFailure = m_connection->GetNewsServer()->GetRetention() > 0 &&
(Util::CurrentTime() - m_fileInfo->GetTime()) / 86400 > m_connection->GetNewsServer()->GetRetention();
if (retentionFailure)
{
detail("Article %s @ %s failed: out of server retention (file age: %i, configured retention: %i)",
m_szInfoName, m_szConnectionName,
(time(NULL) - m_pFileInfo->GetTime()) / 86400,
m_pConnection->GetNewsServer()->GetRetention());
Status = adFailed;
*m_infoName, *m_connectionName,
(int)(Util::CurrentTime() - m_fileInfo->GetTime()) / 86400,
m_connection->GetNewsServer()->GetRetention());
status = adFailed;
FreeConnection(true);
}
if (m_pConnection && !IsStopped())
if (m_connection && !IsStopped())
{
detail("Downloading %s @ %s", m_szInfoName, m_szConnectionName);
detail("Downloading %s @ %s", *m_infoName, *m_connectionName);
}
// test connection
bool bConnected = m_pConnection && m_pConnection->Connect();
if (bConnected && !IsStopped())
bool connected = m_connection && m_connection->Connect();
if (connected && !IsStopped())
{
NewsServer* pNewsServer = m_pConnection->GetNewsServer();
NewsServer* newsServer = m_connection->GetNewsServer();
// Download article
Status = Download();
status = Download();
if (Status == adFinished || Status == adFailed || Status == adNotFound || Status == adCrcError)
if (status == adFinished || status == adFailed || status == adNotFound || status == adCrcError)
{
m_ServerStats.StatOp(pNewsServer->GetID(), Status == adFinished ? 1 : 0, Status == adFinished ? 0 : 1, ServerStatList::soSet);
m_serverStats.StatOp(newsServer->GetId(), status == adFinished ? 1 : 0, status == adFinished ? 0 : 1, ServerStatList::soSet);
}
}
if (m_pConnection)
if (m_connection)
{
AddServerData();
}
if (!bConnected && m_pConnection)
if (!connected && m_connection)
{
detail("Article %s @ %s failed: could not establish connection", m_szInfoName, m_szConnectionName);
detail("Article %s @ %s failed: could not establish connection", *m_infoName, *m_connectionName);
}
if (Status == adConnectError)
if (status == adConnectError)
{
bConnected = false;
Status = adFailed;
connected = false;
status = adFailed;
}
if (bConnected && Status == adFailed)
if (connected && status == adFailed)
{
iRemainedRetries--;
remainedRetries--;
}
if (!bConnected && m_pConnection && !IsStopped())
bool optionalBlocked = false;
if (!connected && m_connection && !IsStopped())
{
g_pServerPool->BlockServer(pLastServer);
g_ServerPool->BlockServer(lastServer);
optionalBlocked = lastServer->GetOptional();
}
pWantServer = NULL;
if (bConnected && Status == adFailed && iRemainedRetries > 0 && !bRetentionFailure)
wantServer = nullptr;
if (connected && status == adFailed && remainedRetries > 0 && !retentionFailure)
{
pWantServer = pLastServer;
wantServer = lastServer;
}
else
{
FreeConnection(Status == adFinished || Status == adNotFound);
FreeConnection(status == adFinished || status == adNotFound);
}
if (Status == adFinished || Status == adFatalError)
if (status == adFinished || status == adFatalError)
{
break;
}
if (IsStopped() || (g_pOptions->GetPauseDownload() && !bForce) ||
(g_pOptions->GetTempPauseDownload() && !m_pFileInfo->GetExtraPriority()) ||
iServerConfigGeneration != g_pServerPool->GetGeneration())
if (IsStopped() || ((g_WorkState->GetPauseDownload() || g_WorkState->GetQuotaReached()) && !force) ||
(g_WorkState->GetTempPauseDownload() && !m_fileInfo->GetExtraPriority()) ||
serverConfigGeneration != g_ServerPool->GetGeneration())
{
Status = adRetry;
status = adRetry;
break;
}
if (!pWantServer && (bConnected || bRetentionFailure))
if (!wantServer && (connected || retentionFailure || optionalBlocked))
{
failedServers.push_back(pLastServer);
if (!optionalBlocked)
{
failedServers.push_back(lastServer);
}
// if all servers from current level were tried, increase level
// if all servers from all levels were tried, break the loop with failure status
bool bAllServersOnLevelFailed = true;
for (Servers::iterator it = g_pServerPool->GetServers()->begin(); it != g_pServerPool->GetServers()->end(); it++)
bool allServersOnLevelFailed = true;
for (NewsServer* candidateServer : g_ServerPool->GetServers())
{
NewsServer* pCandidateServer = *it;
if (pCandidateServer->GetNormLevel() == iLevel)
if (candidateServer->GetNormLevel() == level)
{
bool bServerFailed = !pCandidateServer->GetActive() || pCandidateServer->GetMaxConnections() == 0;
if (!bServerFailed)
bool serverFailed = !candidateServer->GetActive() || candidateServer->GetMaxConnections() == 0 ||
(candidateServer->GetOptional() && g_ServerPool->IsServerBlocked(candidateServer));
if (!serverFailed)
{
for (Servers::iterator it = failedServers.begin(); it != failedServers.end(); it++)
for (NewsServer* ignoreServer : failedServers)
{
NewsServer* pIgnoreServer = *it;
if (pIgnoreServer == pCandidateServer ||
(pIgnoreServer->GetGroup() > 0 && pIgnoreServer->GetGroup() == pCandidateServer->GetGroup() &&
pIgnoreServer->GetNormLevel() == pCandidateServer->GetNormLevel()))
if (ignoreServer == candidateServer ||
(ignoreServer->GetGroup() > 0 && ignoreServer->GetGroup() == candidateServer->GetGroup() &&
ignoreServer->GetNormLevel() == candidateServer->GetNormLevel()))
{
bServerFailed = true;
serverFailed = true;
break;
}
}
}
}
if (!bServerFailed)
if (!serverFailed)
{
bAllServersOnLevelFailed = false;
allServersOnLevelFailed = false;
break;
}
}
}
if (bAllServersOnLevelFailed)
if (allServersOnLevelFailed)
{
if (iLevel < g_pServerPool->GetMaxNormLevel())
if (level < g_ServerPool->GetMaxNormLevel())
{
detail("Article %s @ all level %i servers failed, increasing level", m_szInfoName, iLevel);
iLevel++;
detail("Article %s @ all level %i servers failed, increasing level", *m_infoName, level);
level++;
}
else
{
detail("Article %s @ all servers failed", m_szInfoName);
Status = adFailed;
detail("Article %s @ all servers failed", *m_infoName);
status = adFailed;
break;
}
}
iRemainedRetries = iRetries;
remainedRetries = retries;
}
}
FreeConnection(Status == adFinished);
FreeConnection(status == adFinished);
if (m_ArticleWriter.GetDuplicate())
if (m_articleWriter.GetDuplicate())
{
Status = adFinished;
status = adFinished;
}
if (Status != adFinished && Status != adRetry)
if (status != adFinished && status != adRetry)
{
Status = adFailed;
status = adFailed;
}
if (IsStopped())
{
detail("Download %s cancelled", m_szInfoName);
Status = adRetry;
detail("Download %s cancelled", *m_infoName);
status = adRetry;
}
if (Status == adFailed)
if (status == adFailed)
{
detail("Download %s failed", m_szInfoName);
detail("Download %s failed", *m_infoName);
}
SetStatus(Status);
Notify(NULL);
SetStatus(status);
Notify(nullptr);
debug("Exiting ArticleDownloader-loop");
}
ArticleDownloader::EStatus ArticleDownloader::Download()
{
const char* szResponse = NULL;
EStatus Status = adRunning;
m_bWritingStarted = false;
m_pArticleInfo->SetCrc(0);
const char* response = nullptr;
EStatus status = adRunning;
m_writingStarted = false;
m_articleInfo->SetCrc(0);
if (m_pConnection->GetNewsServer()->GetJoinGroup())
if (m_contentAnalyzer)
{
m_contentAnalyzer->Reset();
}
if (m_connection->GetNewsServer()->GetJoinGroup())
{
// change group
for (FileInfo::Groups::iterator it = m_pFileInfo->GetGroups()->begin(); it != m_pFileInfo->GetGroups()->end(); it++)
for (CString& group : m_fileInfo->GetGroups())
{
szResponse = m_pConnection->JoinGroup(*it);
if (szResponse && !strncmp(szResponse, "2", 1))
response = m_connection->JoinGroup(group);
if (response && !strncmp(response, "2", 1))
{
break;
break;
}
}
Status = CheckResponse(szResponse, "could not join group");
if (Status != adFinished)
status = CheckResponse(response, "could not join group");
if (status != adFinished)
{
return Status;
return status;
}
}
// retrieve article
char tmp[1024];
snprintf(tmp, 1024, "ARTICLE %s\r\n", m_pArticleInfo->GetMessageID());
tmp[1024-1] = '\0';
response = m_connection->Request(BString<1024>("%s %s\r\n",
g_Options->GetRawArticle() ? "ARTICLE" : "BODY", m_articleInfo->GetMessageId()));
for (int retry = 3; retry > 0; retry--)
status = CheckResponse(response, "could not fetch article");
if (status != adFinished)
{
szResponse = m_pConnection->Request(tmp);
if ((szResponse && !strncmp(szResponse, "2", 1)) || m_pConnection->GetAuthError())
{
break;
}
return status;
}
Status = CheckResponse(szResponse, "could not fetch article");
if (Status != adFinished)
m_decoder.Clear();
m_decoder.SetCrcCheck(g_Options->GetCrcCheck());
m_decoder.SetRawMode(g_Options->GetRawArticle());
status = adRunning;
CharBuffer lineBuf(1024*4);
while (!IsStopped() && !m_decoder.GetEof())
{
return Status;
}
if (g_pOptions->GetDecode())
{
m_YDecoder.Clear();
m_YDecoder.SetCrcCheck(g_pOptions->GetCrcCheck());
m_UDecoder.Clear();
}
bool bBody = false;
bool bEnd = false;
const int LineBufSize = 1024*10;
char* szLineBuf = (char*)malloc(LineBufSize);
Status = adRunning;
while (!IsStopped())
{
time_t tOldTime = m_tLastUpdateTime;
SetLastUpdateTimeNow();
if (tOldTime != m_tLastUpdateTime)
{
AddServerData();
}
// Throttle the bandwidth
while (!IsStopped() && (g_pOptions->GetDownloadRate() > 0.0f) &&
(g_pStatMeter->CalcCurrentDownloadSpeed() > g_pOptions->GetDownloadRate() ||
g_pStatMeter->CalcMomentaryDownloadSpeed() > g_pOptions->GetDownloadRate()))
// throttle the bandwidth
while (!IsStopped() && (g_WorkState->GetSpeedLimit() > 0.0f) &&
(g_StatMeter->CalcCurrentDownloadSpeed() > g_WorkState->GetSpeedLimit() ||
g_StatMeter->CalcMomentaryDownloadSpeed() > g_WorkState->GetSpeedLimit()))
{
SetLastUpdateTimeNow();
usleep(10 * 1000);
Util::Sleep(10);
}
int iLen = 0;
char* line = m_pConnection->ReadLine(szLineBuf, LineBufSize, &iLen);
g_pStatMeter->AddSpeedReading(iLen);
if (g_pOptions->GetAccurateRate())
char* buffer;
int len;
m_connection->ReadBuffer(&buffer, &len);
if (len == 0)
{
AddServerData();
len = m_connection->TryRecv(lineBuf, lineBuf.Size());
buffer = lineBuf;
}
// Have we encountered a timeout?
if (!line)
// have we encountered a timeout?
if (len <= 0)
{
if (!IsStopped())
{
detail("Article %s @ %s failed: Unexpected end of article", m_szInfoName, m_szConnectionName);
detail("Article %s @ %s failed: Unexpected end of article", *m_infoName, *m_connectionName);
}
Status = adFailed;
status = adFailed;
break;
}
//detect end of article
if (!strcmp(line, ".\r\n") || !strcmp(line, ".\n"))
g_StatMeter->AddSpeedReading(len);
time_t oldTime = m_lastUpdateTime;
SetLastUpdateTimeNow();
if (oldTime != m_lastUpdateTime)
{
bEnd = true;
break;
AddServerData();
}
//detect lines starting with "." (marked as "..")
if (!strncmp(line, "..", 2))
{
line++;
iLen--;
}
if (!bBody)
{
// detect body of article
if (*line == '\r' || *line == '\n')
{
bBody = true;
}
// check id of returned article
else if (!strncmp(line, "Message-ID: ", 12))
{
char* p = line + 12;
if (strncmp(p, m_pArticleInfo->GetMessageID(), strlen(m_pArticleInfo->GetMessageID())))
{
if (char* e = strrchr(p, '\r')) *e = '\0'; // remove trailing CR-character
detail("Article %s @ %s failed: Wrong message-id, expected %s, returned %s", m_szInfoName,
m_szConnectionName, m_pArticleInfo->GetMessageID(), p);
Status = adFailed;
break;
}
}
}
if (m_eFormat == Decoder::efUnknown && g_pOptions->GetDecode())
{
m_eFormat = Decoder::DetectFormat(line, iLen, bBody);
if (m_eFormat != Decoder::efUnknown)
{
// sometimes news servers misbehave and send article body without new line separator between headers and body
// if we found decoder signature we know the body is already arrived
bBody = true;
}
}
// decode article data
len = m_decoder.DecodeBuffer(buffer, len);
// write to output file
if (((bBody && m_eFormat != Decoder::efUnknown) || !g_pOptions->GetDecode()) && !Write(line, iLen))
if (len > 0 && !Write(buffer, len))
{
Status = adFatalError;
status = adFatalError;
break;
}
}
free(szLineBuf);
if (!bEnd && Status == adRunning && !IsStopped())
{
detail("Article %s @ %s failed: article incomplete", m_szInfoName, m_szConnectionName);
Status = adFailed;
}
if (IsStopped())
{
Status = adFailed;
status = adFailed;
}
if (Status == adRunning)
if (status == adRunning)
{
FreeConnection(true);
Status = DecodeCheck();
status = DecodeCheck();
}
if (m_bWritingStarted)
if (m_writingStarted)
{
m_ArticleWriter.Finish(Status == adFinished);
m_articleWriter.Finish(status == adFinished);
}
if (Status == adFinished)
if (status == adFinished)
{
detail("Successfully downloaded %s", m_szInfoName);
detail("Successfully downloaded %s", *m_infoName);
}
return Status;
return status;
}
ArticleDownloader::EStatus ArticleDownloader::CheckResponse(const char* szResponse, const char* szComment)
ArticleDownloader::EStatus ArticleDownloader::CheckResponse(const char* response, const char* comment)
{
if (!szResponse)
if (!response)
{
if (!IsStopped())
{
detail("Article %s @ %s failed, %s: Connection closed by remote host",
m_szInfoName, m_szConnectionName, szComment);
*m_infoName, *m_connectionName, comment);
}
return adConnectError;
}
else if (m_pConnection->GetAuthError() || !strncmp(szResponse, "400", 3) || !strncmp(szResponse, "499", 3))
else if (m_connection->GetAuthError() || !strncmp(response, "400", 3) || !strncmp(response, "499", 3))
{
detail("Article %s @ %s failed, %s: %s", m_szInfoName, m_szConnectionName, szComment, szResponse);
detail("Article %s @ %s failed, %s: %s", *m_infoName, *m_connectionName, comment, response);
return adConnectError;
}
else if (!strncmp(szResponse, "41", 2) || !strncmp(szResponse, "42", 2) || !strncmp(szResponse, "43", 2))
else if (!strncmp(response, "41", 2) || !strncmp(response, "42", 2) || !strncmp(response, "43", 2))
{
detail("Article %s @ %s failed, %s: %s", m_szInfoName, m_szConnectionName, szComment, szResponse);
detail("Article %s @ %s failed, %s: %s", *m_infoName, *m_connectionName, comment, response);
return adNotFound;
}
else if (!strncmp(szResponse, "2", 1))
else if (!strncmp(response, "2", 1))
{
// OK
return adFinished;
}
else
else
{
// unknown error, no special handling
detail("Article %s @ %s failed, %s: %s", m_szInfoName, m_szConnectionName, szComment, szResponse);
detail("Article %s @ %s failed, %s: %s", *m_infoName, *m_connectionName, comment, response);
return adFailed;
}
}
bool ArticleDownloader::Write(char* szLine, int iLen)
bool ArticleDownloader::Write(char* buffer, int len)
{
const char* szArticleFilename = NULL;
long long iArticleFileSize = 0;
long long iArticleOffset = 0;
int iArticleSize = 0;
const char* articleFilename = nullptr;
int64 articleFileSize = 0;
int64 articleOffset = 0;
int articleSize = 0;
if (g_pOptions->GetDecode())
if (!m_writingStarted)
{
if (m_eFormat == Decoder::efYenc)
if (!g_Options->GetRawArticle())
{
iLen = m_YDecoder.DecodeBuffer(szLine, iLen);
szArticleFilename = m_YDecoder.GetArticleFilename();
iArticleFileSize = m_YDecoder.GetSize();
}
else if (m_eFormat == Decoder::efUx)
{
iLen = m_UDecoder.DecodeBuffer(szLine, iLen);
szArticleFilename = m_UDecoder.GetArticleFilename();
}
else
{
detail("Decoding %s failed: unsupported encoding", m_szInfoName);
return false;
}
if (iLen > 0 && m_eFormat == Decoder::efYenc)
{
if (m_YDecoder.GetBegin() == 0 || m_YDecoder.GetEnd() == 0)
articleFilename = m_decoder.GetArticleFilename();
if (m_decoder.GetFormat() == Decoder::efYenc)
{
return false;
if (m_decoder.GetBeginPos() == 0 || m_decoder.GetEndPos() == 0)
{
return false;
}
articleFileSize = m_decoder.GetSize();
articleOffset = m_decoder.GetBeginPos() - 1;
articleSize = (int)(m_decoder.GetEndPos() - m_decoder.GetBeginPos() + 1);
if (articleSize <= 0 || articleSize > 1024*1024*1024)
{
warn("Malformed article %s: size %i out of range", *m_infoName, articleSize);
return false;
}
}
iArticleOffset = m_YDecoder.GetBegin() - 1;
iArticleSize = (int)(m_YDecoder.GetEnd() - m_YDecoder.GetBegin() + 1);
}
}
if (!m_bWritingStarted && iLen > 0)
{
if (!m_ArticleWriter.Start(m_eFormat, szArticleFilename, iArticleFileSize, iArticleOffset, iArticleSize))
if (!m_articleWriter.Start(m_decoder.GetFormat(), articleFilename, articleFileSize, articleOffset, articleSize))
{
return false;
}
m_bWritingStarted = true;
m_writingStarted = true;
}
bool bOK = iLen == 0 || m_ArticleWriter.Write(szLine, iLen);
bool ok = m_articleWriter.Write(buffer, len);
return bOK;
if (m_contentAnalyzer)
{
m_contentAnalyzer->Append(buffer, len);
}
return ok;
}
ArticleDownloader::EStatus ArticleDownloader::DecodeCheck()
{
if (g_pOptions->GetDecode())
if (!g_Options->GetRawArticle())
{
Decoder* pDecoder = NULL;
if (m_eFormat == Decoder::efYenc)
{
pDecoder = &m_YDecoder;
}
else if (m_eFormat == Decoder::efUx)
{
pDecoder = &m_UDecoder;
}
else
{
detail("Decoding %s failed: no binary data or unsupported encoding format", m_szInfoName);
return adFailed;
}
Decoder::EStatus status = m_decoder.Check();
Decoder::EStatus eStatus = pDecoder->Check();
if (eStatus == Decoder::eFinished)
if (status == Decoder::dsFinished)
{
if (pDecoder->GetArticleFilename())
if (m_decoder.GetArticleFilename())
{
free(m_szArticleFilename);
m_szArticleFilename = strdup(pDecoder->GetArticleFilename());
m_articleFilename = m_decoder.GetArticleFilename();
}
if (m_eFormat == Decoder::efYenc)
if (m_decoder.GetFormat() == Decoder::efYenc)
{
m_pArticleInfo->SetCrc(g_pOptions->GetCrcCheck() ?
m_YDecoder.GetCalculatedCrc() : m_YDecoder.GetExpectedCrc());
m_articleInfo->SetCrc(g_Options->GetCrcCheck() ?
m_decoder.GetCalculatedCrc() : m_decoder.GetExpectedCrc());
}
return adFinished;
}
else if (eStatus == Decoder::eCrcError)
else if (status == Decoder::dsCrcError)
{
detail("Decoding %s failed: CRC-Error", m_szInfoName);
detail("Decoding %s failed: CRC-Error", *m_infoName);
return adCrcError;
}
else if (eStatus == Decoder::eArticleIncomplete)
else if (status == Decoder::dsArticleIncomplete)
{
detail("Decoding %s failed: article incomplete", m_szInfoName);
detail("Decoding %s failed: article incomplete", *m_infoName);
return adFailed;
}
else if (eStatus == Decoder::eInvalidSize)
else if (status == Decoder::dsInvalidSize)
{
detail("Decoding %s failed: size mismatch", m_szInfoName);
detail("Decoding %s failed: size mismatch", *m_infoName);
return adFailed;
}
else if (eStatus == Decoder::eNoBinaryData)
else if (status == Decoder::dsNoBinaryData)
{
detail("Decoding %s failed: no binary data found", m_szInfoName);
detail("Decoding %s failed: no binary data found", *m_infoName);
return adFailed;
}
else
{
detail("Decoding %s failed", m_szInfoName);
detail("Decoding %s failed", *m_infoName);
return adFailed;
}
}
else
else
{
return adFinished;
}
}
void ArticleDownloader::SetLastUpdateTimeNow()
{
m_lastUpdateTime = Util::CurrentTime();
}
void ArticleDownloader::LogDebugInfo()
{
char szTime[50];
#ifdef HAVE_CTIME_R_3
ctime_r(&m_tLastUpdateTime, szTime, 50);
#else
ctime_r(&m_tLastUpdateTime, szTime);
#endif
info(" Download: Status=%i, LastUpdateTime=%s, InfoName=%s", m_eStatus, szTime, m_szInfoName);
info(" Download: status=%i, LastUpdateTime=%s, InfoName=%s", m_status,
*Util::FormatTime(m_lastUpdateTime), *m_infoName);
}
void ArticleDownloader::Stop()
{
debug("Trying to stop ArticleDownloader");
Thread::Stop();
m_mutexConnection.Lock();
if (m_pConnection)
Guard guard(m_connectionMutex);
if (m_connection)
{
m_pConnection->SetSuppressErrors(true);
m_pConnection->Cancel();
m_connection->SetSuppressErrors(true);
m_connection->Cancel();
}
m_mutexConnection.Unlock();
debug("ArticleDownloader stopped successfully");
}
bool ArticleDownloader::Terminate()
void ArticleDownloader::FreeConnection(bool keepConnected)
{
NNTPConnection* pConnection = m_pConnection;
bool terminated = Kill();
if (terminated && pConnection)
{
debug("Terminating connection");
pConnection->SetSuppressErrors(true);
pConnection->Cancel();
pConnection->Disconnect();
g_pStatMeter->AddServerData(pConnection->FetchTotalBytesRead(), pConnection->GetNewsServer()->GetID());
g_pServerPool->FreeConnection(pConnection, true);
}
return terminated;
}
void ArticleDownloader::FreeConnection(bool bKeepConnected)
{
if (m_pConnection)
if (m_connection)
{
debug("Releasing connection");
m_mutexConnection.Lock();
if (!bKeepConnected || m_pConnection->GetStatus() == Connection::csCancelled)
Guard guard(m_connectionMutex);
if (!keepConnected || m_connection->GetStatus() == Connection::csCancelled)
{
m_pConnection->Disconnect();
m_connection->Disconnect();
}
AddServerData();
g_pServerPool->FreeConnection(m_pConnection, true);
m_pConnection = NULL;
m_mutexConnection.Unlock();
g_ServerPool->FreeConnection(m_connection, true);
m_connection = nullptr;
}
}
void ArticleDownloader::AddServerData()
{
int iBytesRead = m_pConnection->FetchTotalBytesRead();
g_pStatMeter->AddServerData(iBytesRead, m_pConnection->GetNewsServer()->GetID());
m_iDownloadedSize += iBytesRead;
int bytesRead = m_connection->FetchTotalBytesRead();
g_StatMeter->AddServerData(bytesRead, m_connection->GetNewsServer()->GetId());
m_downloadedSize += bytesRead;
}

View File

@@ -1,8 +1,8 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,26 +15,29 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ARTICLEDOWNLOADER_H
#define ARTICLEDOWNLOADER_H
#include <time.h>
#include "NString.h"
#include "Observer.h"
#include "DownloadInfo.h"
#include "Thread.h"
#include "NNTPConnection.h"
#include "NntpConnection.h"
#include "Decoder.h"
#include "ArticleWriter.h"
#include "Util.h"
class ArticleContentAnalyzer
{
public:
virtual ~ArticleContentAnalyzer() {};
virtual void Reset() = 0;
virtual void Append(const void* buffer, int len) = 0;
};
class ArticleDownloader : public Thread, public Subject
{
@@ -53,65 +56,54 @@ public:
adFatalError
};
class ArticleWriterImpl : public ArticleWriter
{
private:
ArticleDownloader* m_pOwner;
protected:
virtual void SetLastUpdateTimeNow() { m_pOwner->SetLastUpdateTimeNow(); }
public:
void SetOwner(ArticleDownloader* pOwner) { m_pOwner = pOwner; }
};
ArticleDownloader();
virtual ~ArticleDownloader();
void SetFileInfo(FileInfo* fileInfo) { m_fileInfo = fileInfo; }
FileInfo* GetFileInfo() { return m_fileInfo; }
void SetArticleInfo(ArticleInfo* articleInfo) { m_articleInfo = articleInfo; }
ArticleInfo* GetArticleInfo() { return m_articleInfo; }
EStatus GetStatus() { return m_status; }
ServerStatList* GetServerStats() { return &m_serverStats; }
virtual void Run();
virtual void Stop();
time_t GetLastUpdateTime() { return m_lastUpdateTime; }
void SetLastUpdateTimeNow();
const char* GetArticleFilename() { return m_articleFilename; }
void SetInfoName(const char* infoName);
const char* GetInfoName() { return m_infoName; }
const char* GetConnectionName() { return m_connectionName; }
void SetConnection(NntpConnection* connection) { m_connection = connection; }
void CompleteFileParts() { m_articleWriter.CompleteFileParts(); }
int GetDownloadedSize() { return m_downloadedSize; }
void SetContentAnalyzer(std::unique_ptr<ArticleContentAnalyzer> contentAnalyzer) { m_contentAnalyzer = std::move(contentAnalyzer); }
ArticleContentAnalyzer* GetContentAnalyzer() { return m_contentAnalyzer.get(); }
void LogDebugInfo();
private:
FileInfo* m_pFileInfo;
ArticleInfo* m_pArticleInfo;
NNTPConnection* m_pConnection;
EStatus m_eStatus;
Mutex m_mutexConnection;
char* m_szInfoName;
char m_szConnectionName[250];
char* m_szArticleFilename;
time_t m_tLastUpdateTime;
Decoder::EFormat m_eFormat;
YDecoder m_YDecoder;
UDecoder m_UDecoder;
ArticleWriterImpl m_ArticleWriter;
ServerStatList m_ServerStats;
bool m_bWritingStarted;
int m_iDownloadedSize;
FileInfo* m_fileInfo;
ArticleInfo* m_articleInfo;
NntpConnection* m_connection = nullptr;
EStatus m_status = adUndefined;
Mutex m_connectionMutex;
CString m_infoName;
CString m_connectionName;
CString m_articleFilename;
time_t m_lastUpdateTime;
Decoder m_decoder;
ArticleWriter m_articleWriter;
ServerStatList m_serverStats;
bool m_writingStarted;
int m_downloadedSize = 0;
std::unique_ptr<ArticleContentAnalyzer> m_contentAnalyzer;
EStatus Download();
EStatus DecodeCheck();
void FreeConnection(bool bKeepConnected);
EStatus CheckResponse(const char* szResponse, const char* szComment);
void SetStatus(EStatus eStatus) { m_eStatus = eStatus; }
bool Write(char* szLine, int iLen);
void AddServerData();
public:
ArticleDownloader();
virtual ~ArticleDownloader();
void SetFileInfo(FileInfo* pFileInfo) { m_pFileInfo = pFileInfo; }
FileInfo* GetFileInfo() { return m_pFileInfo; }
void SetArticleInfo(ArticleInfo* pArticleInfo) { m_pArticleInfo = pArticleInfo; }
ArticleInfo* GetArticleInfo() { return m_pArticleInfo; }
EStatus GetStatus() { return m_eStatus; }
ServerStatList* GetServerStats() { return &m_ServerStats; }
virtual void Run();
virtual void Stop();
bool Terminate();
time_t GetLastUpdateTime() { return m_tLastUpdateTime; }
void SetLastUpdateTimeNow() { m_tLastUpdateTime = ::time(NULL); }
const char* GetArticleFilename() { return m_szArticleFilename; }
void SetInfoName(const char* szInfoName);
const char* GetInfoName() { return m_szInfoName; }
const char* GetConnectionName() { return m_szConnectionName; }
void SetConnection(NNTPConnection* pConnection) { m_pConnection = pConnection; }
void CompleteFileParts() { m_ArticleWriter.CompleteFileParts(); }
int GetDownloadedSize() { return m_iDownloadedSize; }
void LogDebugInfo();
EStatus Download();
EStatus DecodeCheck();
void FreeConnection(bool keepConnected);
EStatus CheckResponse(const char* response, const char* comment);
void SetStatus(EStatus status) { m_status = status; }
bool Write(char* buffer, int len);
void AddServerData();
};
#endif

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2014-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2014-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,91 +14,109 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ARTICLEWRITER_H
#define ARTICLEWRITER_H
#include "NString.h"
#include "DownloadInfo.h"
#include "Decoder.h"
#include "FileSystem.h"
class CachedSegmentData : public SegmentData
{
public:
CachedSegmentData() {}
CachedSegmentData(char* data, int size) : m_data(data), m_size(size) {}
CachedSegmentData(const CachedSegmentData&) = delete;
CachedSegmentData(CachedSegmentData&& other) :
m_data(other.m_data), m_size(other.m_size) { other.m_data = nullptr; other.m_size = 0; }
CachedSegmentData& operator=(CachedSegmentData&& other);
virtual ~CachedSegmentData();
virtual char* GetData() { return m_data; }
private:
char* m_data = nullptr;
int m_size = 0;
friend class ArticleCache;
};
class ArticleWriter
{
private:
FileInfo* m_pFileInfo;
ArticleInfo* m_pArticleInfo;
FILE* m_pOutFile;
char* m_szTempFilename;
char* m_szOutputFilename;
const char* m_szResultFilename;
Decoder::EFormat m_eFormat;
char* m_pArticleData;
long long m_iArticleOffset;
int m_iArticleSize;
int m_iArticlePtr;
bool m_bFlushing;
bool m_bDuplicate;
char* m_szInfoName;
bool PrepareFile(char* szLine);
bool CreateOutputFile(long long iSize);
void BuildOutputFilename();
bool IsFileCached();
void SetWriteBuffer(FILE* pOutFile, int iRecSize);
protected:
virtual void SetLastUpdateTimeNow() {}
public:
ArticleWriter();
~ArticleWriter();
void SetInfoName(const char* szInfoName);
void SetFileInfo(FileInfo* pFileInfo) { m_pFileInfo = pFileInfo; }
void SetArticleInfo(ArticleInfo* pArticleInfo) { m_pArticleInfo = pArticleInfo; }
void Prepare();
bool Start(Decoder::EFormat eFormat, const char* szFilename, long long iFileSize, long long iArticleOffset, int iArticleSize);
bool Write(char* szBufffer, int iLen);
void Finish(bool bSuccess);
bool GetDuplicate() { return m_bDuplicate; }
void CompleteFileParts();
static bool MoveCompletedFiles(NZBInfo* pNZBInfo, const char* szOldDestDir);
void FlushCache();
void SetInfoName(const char* infoName) { m_infoName = infoName; }
void SetFileInfo(FileInfo* fileInfo) { m_fileInfo = fileInfo; }
void SetArticleInfo(ArticleInfo* articleInfo) { m_articleInfo = articleInfo; }
void Prepare();
bool Start(Decoder::EFormat format, const char* filename, int64 fileSize, int64 articleOffset, int articleSize);
bool Write(char* buffer, int len);
void Finish(bool success);
bool GetDuplicate() { return m_duplicate; }
void CompleteFileParts();
static bool MoveCompletedFiles(NzbInfo* nzbInfo, const char* oldDestDir);
void FlushCache();
private:
FileInfo* m_fileInfo;
ArticleInfo* m_articleInfo;
DiskFile m_outFile;
CString m_tempFilename;
CString m_outputFilename;
const char* m_resultFilename = nullptr;
Decoder::EFormat m_format = Decoder::efUnknown;
CachedSegmentData m_articleData;
int64 m_articleOffset;
int m_articleSize;
int m_articlePtr;
bool m_duplicate = false;
CString m_infoName;
bool CreateOutputFile(int64 size);
void BuildOutputFilename();
void SetWriteBuffer(DiskFile& outFile, int recSize);
};
class ArticleCache : public Thread
{
private:
size_t m_iAllocated;
bool m_bFlushing;
Mutex m_mutexAlloc;
Mutex m_mutexFlush;
Mutex m_mutexContent;
FileInfo* m_pFileInfo;
bool CheckFlush(bool bFlushEverything);
public:
ArticleCache();
virtual void Run();
void* Alloc(int iSize);
void* Realloc(void* buf, int iOldSize, int iNewSize);
void Free(int iSize);
void LockFlush();
void UnlockFlush();
void LockContent() { m_mutexContent.Lock(); }
void UnlockContent() { m_mutexContent.Unlock(); }
bool GetFlushing() { return m_bFlushing; }
size_t GetAllocated() { return m_iAllocated; }
bool FileBusy(FileInfo* pFileInfo) { return pFileInfo == m_pFileInfo; }
class FlushGuard
{
public:
FlushGuard(FlushGuard&& other) = default;
~FlushGuard();
private:
Guard m_guard;
FlushGuard(Mutex& mutex);
friend class ArticleCache;
};
virtual void Run();
virtual void Stop();
CachedSegmentData Alloc(int size);
bool Realloc(CachedSegmentData* segment, int newSize);
void Free(CachedSegmentData* segment);
FlushGuard GuardFlush() { return FlushGuard(m_flushMutex); }
Guard GuardContent() { return Guard(m_contentMutex); }
bool GetFlushing() { return m_flushing; }
size_t GetAllocated() { return m_allocated; }
bool FileBusy(FileInfo* fileInfo) { return fileInfo == m_fileInfo; }
private:
size_t m_allocated = 0;
bool m_flushing = false;
Mutex m_allocMutex;
Mutex m_flushMutex;
Mutex m_contentMutex;
FileInfo* m_fileInfo = nullptr;
ConditionVar m_allocCond;
bool CheckFlush(bool flushEverything);
};
extern ArticleCache* g_pArticleCache;
extern ArticleCache* g_ArticleCache;
#endif

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,83 +14,155 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include "nzbget.h"
#include "Decoder.h"
#include "Log.h"
#include "Util.h"
const char* Decoder::FormatNames[] = { "Unknown", "yEnc", "UU" };
#include "YEncode.h"
Decoder::Decoder()
{
debug("Creating Decoder");
debug("%s", YEncode::decode_simd ? "SIMD yEnc decoder can be used" : "SIMD yEnc decoder isn't available for this CPU");
debug("%s", YEncode::crc_simd ? "SIMD Crc routine can be used" : "SIMD Crc routine isn't available for this CPU");
m_szArticleFilename = NULL;
}
Decoder::~ Decoder()
{
debug("Destroying Decoder");
free(m_szArticleFilename);
Clear();
}
void Decoder::Clear()
{
free(m_szArticleFilename);
m_szArticleFilename = NULL;
m_articleFilename.Clear();
m_body = false;
m_begin = false;
m_part = false;
m_end = false;
m_crc = false;
m_eof = false;
m_expectedCRC = 0;
m_crc32.Reset();
m_beginPos = 0;
m_endPos = 0;
m_size = 0;
m_endSize = 0;
m_outSize = 0;
m_state = 0;
m_crcCheck = false;
m_lineBuf.Reserve(1024*8);
m_lineBuf.SetLength(0);
}
Decoder::EFormat Decoder::DetectFormat(const char* buffer, int len, bool inBody)
/* At the beginning of article the processing goes line by line to find '=ybegin'-marker.
* Once the yEnc-data is started switches to blockwise processing.
* At the end of yEnc-data switches back to line by line mode to
* process '=yend'-marker and EOF-marker.
* UU-encoded articles are processed completely in line by line mode.
*/
int Decoder::DecodeBuffer(char* buffer, int len)
{
if (m_rawMode)
{
ProcessRaw(buffer, len);
return len;
}
int outlen = 0;
if (m_body && m_format == efYenc)
{
outlen = DecodeYenc(buffer, buffer, len);
if (m_body)
{
return outlen;
}
}
else
{
m_lineBuf.Append(buffer, len);
}
char* line = (char*)m_lineBuf;
while (char* end = strchr(line, '\n'))
{
int llen = (int)(end - line + 1);
if (line[0] == '.' && line[1] == '\r')
{
m_eof = true;
m_lineBuf.SetLength(0);
return outlen;
}
if (m_format == efUnknown)
{
m_format = DetectFormat(line, llen);
}
if (m_format == efYenc)
{
ProcessYenc(line, llen);
if (m_body)
{
outlen = DecodeYenc(end + 1, buffer, m_lineBuf.Length() - (int)(end + 1 - m_lineBuf));
if (m_body)
{
m_lineBuf.SetLength(0);
return outlen;
}
line = (char*)m_lineBuf;
continue;
}
}
else if (m_format == efUx)
{
outlen += DecodeUx(line, llen);
}
line = end + 1;
}
if (*line)
{
len = m_lineBuf.Length() - (int)(line - m_lineBuf);
memmove((char*)m_lineBuf, line, len);
m_lineBuf.SetLength(len);
}
else
{
m_lineBuf.SetLength(0);
}
return outlen;
}
Decoder::EFormat Decoder::DetectFormat(const char* buffer, int len)
{
if (!strncmp(buffer, "=ybegin ", 8))
{
return efYenc;
}
if (inBody && (len == 62 || len == 63) && (buffer[62] == '\n' || buffer[62] == '\r') && *buffer == 'M')
if ((len == 62 || len == 63) && (buffer[62] == '\n' || buffer[62] == '\r') && *buffer == 'M')
{
return efUx;
}
if (!strncmp(buffer, "begin ", 6))
{
bool bOK = true;
bool ok = true;
buffer += 6; //strlen("begin ")
while (*buffer && *buffer != ' ')
{
char ch = *buffer++;
if (ch < '0' || ch > '7')
{
bOK = false;
ok = false;
break;
}
}
if (bOK)
if (ok)
{
return efUx;
}
@@ -99,185 +171,148 @@ Decoder::EFormat Decoder::DetectFormat(const char* buffer, int len, bool inBody)
return efUnknown;
}
/**
* YDecoder: fast implementation of yEnc-Decoder
*/
YDecoder::YDecoder()
void Decoder::ProcessYenc(char* buffer, int len)
{
Clear();
}
void YDecoder::Clear()
{
Decoder::Clear();
m_bBody = false;
m_bBegin = false;
m_bPart = false;
m_bEnd = false;
m_bCrc = false;
m_lExpectedCRC = 0;
m_lCalculatedCRC = 0xFFFFFFFF;
m_iBegin = 0;
m_iEnd = 0;
m_iSize = 0;
m_iEndSize = 0;
m_bCrcCheck = false;
}
int YDecoder::DecodeBuffer(char* buffer, int len)
{
if (m_bBody && !m_bEnd)
if (!strncmp(buffer, "=ybegin ", 8))
{
if (!strncmp(buffer, "=yend ", 6))
m_begin = true;
char* pb = strstr(buffer, " name=");
if (pb)
{
m_bEnd = true;
char* pb = strstr(buffer, m_bPart ? " pcrc32=" : " crc32=");
if (pb)
{
m_bCrc = true;
pb += 7 + (int)m_bPart; //=strlen(" crc32=") or strlen(" pcrc32=")
m_lExpectedCRC = strtoul(pb, NULL, 16);
}
pb = strstr(buffer, " size=");
if (pb)
{
pb += 6; //=strlen(" size=")
m_iEndSize = (long long)atoll(pb);
}
return 0;
pb += 6; //=strlen(" name=")
char* pe;
for (pe = pb; *pe != '\0' && *pe != '\n' && *pe != '\r'; pe++);
m_articleFilename = WebUtil::Latin1ToUtf8(CString(pb, (int)(pe - pb)));
}
char* iptr = buffer;
char* optr = buffer;
while (true)
pb = strstr(buffer, " size=");
if (pb)
{
switch (*iptr)
{
case '=': //escape-sequence
iptr++;
*optr = *iptr - 64 - 42;
optr++;
break;
case '\n': // ignored char
case '\r': // ignored char
break;
case '\0':
goto BreakLoop;
default: // normal char
*optr = *iptr - 42;
optr++;
break;
}
iptr++;
pb += 6; //=strlen(" size=")
m_size = (int64)atoll(pb);
}
BreakLoop:
if (m_bCrcCheck)
m_part = strstr(buffer, " part=");
if (!m_part)
{
m_lCalculatedCRC = Util::Crc32m(m_lCalculatedCRC, (unsigned char *)buffer, (unsigned int)(optr - buffer));
}
return optr - buffer;
}
else
{
if (!m_bPart && !strncmp(buffer, "=ybegin ", 8))
{
m_bBegin = true;
char* pb = strstr(buffer, " name=");
if (pb)
{
pb += 6; //=strlen(" name=")
char* pe;
for (pe = pb; *pe != '\0' && *pe != '\n' && *pe != '\r'; pe++) ;
free(m_szArticleFilename);
m_szArticleFilename = (char*)malloc(pe - pb + 1);
strncpy(m_szArticleFilename, pb, pe - pb);
m_szArticleFilename[pe - pb] = '\0';
}
pb = strstr(buffer, " size=");
if (pb)
{
pb += 6; //=strlen(" size=")
m_iSize = (long long)atoll(pb);
}
m_bPart = strstr(buffer, " part=");
if (!m_bPart)
{
m_bBody = true;
m_iBegin = 1;
m_iEnd = m_iSize;
}
}
else if (m_bPart && !strncmp(buffer, "=ypart ", 7))
{
m_bPart = true;
m_bBody = true;
char* pb = strstr(buffer, " begin=");
if (pb)
{
pb += 7; //=strlen(" begin=")
m_iBegin = (long long)atoll(pb);
}
pb = strstr(buffer, " end=");
if (pb)
{
pb += 5; //=strlen(" end=")
m_iEnd = (long long)atoll(pb);
}
m_body = true;
m_beginPos = 1;
m_endPos = m_size;
}
}
else if (!strncmp(buffer, "=ypart ", 7))
{
m_part = true;
m_body = true;
char* pb = strstr(buffer, " begin=");
if (pb)
{
pb += 7; //=strlen(" begin=")
m_beginPos = (int64)atoll(pb);
}
pb = strstr(buffer, " end=");
if (pb)
{
pb += 5; //=strlen(" end=")
m_endPos = (int64)atoll(pb);
}
}
else if (!strncmp(buffer, "=yend ", 6))
{
m_end = true;
char* pb = strstr(buffer, m_part ? " pcrc32=" : " crc32=");
if (pb)
{
m_crc = true;
pb += 7 + (int)m_part; //=strlen(" crc32=") or strlen(" pcrc32=")
m_expectedCRC = strtoul(pb, nullptr, 16);
}
pb = strstr(buffer, " size=");
if (pb)
{
pb += 6; //=strlen(" size=")
m_endSize = (int64)atoll(pb);
}
}
return 0;
}
Decoder::EStatus YDecoder::Check()
int Decoder::DecodeYenc(char* buffer, char* outbuf, int len)
{
m_lCalculatedCRC ^= 0xFFFFFFFF;
const unsigned char* src = (unsigned char*)buffer;
unsigned char* dst = (unsigned char*)outbuf;
debug("Expected crc32=%x", m_lExpectedCRC);
debug("Calculated crc32=%x", m_lCalculatedCRC);
int endseq = YEncode::decode(&src, &dst, len, (YEncode::YencDecoderState*)&m_state);
int outlen = (int)((char*)dst - outbuf);
if (!m_bBegin)
// endseq:
// 0: no end sequence found
// 1: \r\n=y sequence found, src points to byte after 'y'
// 2: \r\n.\r\n sequence found, src points to byte after last '\n'
if (endseq != 0)
{
return eNoBinaryData;
}
else if (!m_bEnd)
{
return eArticleIncomplete;
}
else if (!m_bPart && m_iSize != m_iEndSize)
{
return eInvalidSize;
}
else if (m_bCrcCheck && m_bCrc && (m_lExpectedCRC != m_lCalculatedCRC))
{
return eCrcError;
// switch back to line mode to process '=yend'- or eof- marker
m_lineBuf.SetLength(0);
m_lineBuf.Append(endseq == 1 ? "=y" : ".\r\n");
int rem = len - (int)((const char*)src - buffer);
if (rem > 0)
{
m_lineBuf.Append((const char*)src, rem);
}
m_body = false;
}
return eFinished;
if (m_crcCheck)
{
m_crc32.Append((uchar*)outbuf, (uint32)outlen);
}
m_outSize += outlen;
return outlen;
}
/**
* UDecoder: supports UU encoding formats
*/
UDecoder::UDecoder()
Decoder::EStatus Decoder::Check()
{
switch (m_format)
{
case efYenc:
return CheckYenc();
case efUx:
return CheckUx();
default:
return dsUnknownError;
}
}
void UDecoder::Clear()
Decoder::EStatus Decoder::CheckYenc()
{
Decoder::Clear();
m_calculatedCRC = m_crc32.Finish();
m_bBody = false;
m_bEnd = false;
debug("Expected crc32=%x", m_expectedCRC);
debug("Calculated crc32=%x", m_calculatedCRC);
if (!m_begin)
{
return dsNoBinaryData;
}
else if (!m_end)
{
return dsArticleIncomplete;
}
else if ((!m_part && m_size != m_endSize) || (m_endSize != m_outSize))
{
return dsInvalidSize;
}
else if (m_crcCheck && m_crc && (m_expectedCRC != m_calculatedCRC))
{
return dsCrcError;
}
return dsFinished;
}
/* DecodeBuffer-function uses portions of code from tool UUDECODE by Clem Dye
/* DecodeUx-function uses portions of code from tool UUDECODE by Clem Dye
* UUDECODE.c (http://www.bastet.com/uue.zip)
* Copyright (C) 1998 Clem Dye
*
@@ -286,9 +321,9 @@ void UDecoder::Clear()
#define UU_DECODE_CHAR(c) (c == '`' ? 0 : (((c) - ' ') & 077))
int UDecoder::DecodeBuffer(char* buffer, int len)
int Decoder::DecodeUx(char* buffer, int len)
{
if (!m_bBody)
if (!m_body)
{
if (!strncmp(buffer, "begin ", 6))
{
@@ -296,35 +331,32 @@ int UDecoder::DecodeBuffer(char* buffer, int len)
pb += 6; //strlen("begin ")
// skip file-permissions
for (; *pb != ' ' && *pb != '\0' && *pb != '\n' && *pb != '\r'; pb++) ;
for (; *pb != ' ' && *pb != '\0' && *pb != '\n' && *pb != '\r'; pb++) ;
pb++;
// extracting filename
char* pe;
for (pe = pb; *pe != '\0' && *pe != '\n' && *pe != '\r'; pe++) ;
free(m_szArticleFilename);
m_szArticleFilename = (char*)malloc(pe - pb + 1);
strncpy(m_szArticleFilename, pb, pe - pb);
m_szArticleFilename[pe - pb] = '\0';
m_articleFilename = WebUtil::Latin1ToUtf8(CString(pb, (int)(pe - pb)));
m_bBody = true;
m_body = true;
return 0;
}
else if ((len == 62 || len == 63) && (buffer[62] == '\n' || buffer[62] == '\r') && *buffer == 'M')
{
m_bBody = true;
m_body = true;
}
}
if (m_bBody && (!strncmp(buffer, "end ", 4) || *buffer == '`'))
if (m_body && (!strncmp(buffer, "end ", 4) || *buffer == '`'))
{
m_bEnd = true;
m_end = true;
}
if (m_bBody && !m_bEnd)
if (m_body && !m_end)
{
int iEffLen = UU_DECODE_CHAR(buffer[0]);
if (iEffLen > len)
int effLen = UU_DECODE_CHAR(buffer[0]);
if (effLen > len)
{
// error;
return 0;
@@ -332,39 +364,83 @@ int UDecoder::DecodeBuffer(char* buffer, int len)
char* iptr = buffer;
char* optr = buffer;
for (++iptr; iEffLen > 0; iptr += 4, iEffLen -= 3)
for (++iptr; effLen > 0; iptr += 4, effLen -= 3)
{
if (iEffLen >= 3)
if (effLen >= 3)
{
*optr++ = UU_DECODE_CHAR (iptr[0]) << 2 | UU_DECODE_CHAR (iptr[1]) >> 4;
*optr++ = UU_DECODE_CHAR (iptr[1]) << 4 | UU_DECODE_CHAR (iptr[2]) >> 2;
*optr++ = UU_DECODE_CHAR (iptr[0]) << 2 | UU_DECODE_CHAR (iptr[1]) >> 4;
*optr++ = UU_DECODE_CHAR (iptr[1]) << 4 | UU_DECODE_CHAR (iptr[2]) >> 2;
*optr++ = UU_DECODE_CHAR (iptr[2]) << 6 | UU_DECODE_CHAR (iptr[3]);
}
else
{
if (iEffLen >= 1)
*optr++ = UU_DECODE_CHAR (iptr[0]) << 2 | UU_DECODE_CHAR (iptr[1]) >> 4;
if (effLen >= 2)
{
*optr++ = UU_DECODE_CHAR (iptr[0]) << 2 | UU_DECODE_CHAR (iptr[1]) >> 4;
}
if (iEffLen >= 2)
{
*optr++ = UU_DECODE_CHAR (iptr[1]) << 4 | UU_DECODE_CHAR (iptr[2]) >> 2;
*optr++ = UU_DECODE_CHAR (iptr[1]) << 4 | UU_DECODE_CHAR (iptr[2]) >> 2;
}
}
}
return optr - buffer;
return (int)(optr - buffer);
}
return 0;
}
Decoder::EStatus UDecoder::Check()
Decoder::EStatus Decoder::CheckUx()
{
if (!m_bBody)
if (!m_body)
{
return eNoBinaryData;
return dsNoBinaryData;
}
return eFinished;
return dsFinished;
}
void Decoder::ProcessRaw(char* buffer, int len)
{
switch (m_state)
{
case 1:
m_eof = len >= 4 && buffer[0] == '\n' &&
buffer[1] == '.' && buffer[2] == '\r' && buffer[3] == '\n';
break;
case 2:
m_eof = len >= 3 && buffer[0] == '.' && buffer[1] == '\r' && buffer[2] == '\n';
break;
case 3:
m_eof = len >= 2 && buffer[0] == '\r' && buffer[1] == '\n';
break;
case 4:
m_eof = len >= 1 && buffer[0] == '\n';
break;
}
m_eof |= len >= 5 && strstr(buffer, "\r\n.\r\n");
if (len >= 4 && buffer[len-4] == '\r' && buffer[len-3] == '\n' &&
buffer[len-2] == '.' && buffer[len-1] == '\r')
{
m_state = 4;
}
else if (len >= 3 && buffer[len-3] == '\r' && buffer[len-2] == '\n' && buffer[len-1] == '.')
{
m_state = 3;
}
else if (len >= 2 && buffer[len-2] == '\r' && buffer[len-1] == '\n')
{
m_state = 2;
}
else if (len >= 1 && buffer[len-1] == '\r')
{
m_state = 1;
}
else
{
m_state = 0;
}
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2007-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,29 +14,27 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef DECODER_H
#define DECODER_H
#include "NString.h"
#include "Util.h"
class Decoder
{
public:
enum EStatus
{
eUnknownError,
eFinished,
eArticleIncomplete,
eCrcError,
eInvalidSize,
eNoBinaryData
dsUnknownError,
dsFinished,
dsArticleIncomplete,
dsCrcError,
dsInvalidSize,
dsNoBinaryData
};
enum EFormat
@@ -46,61 +44,50 @@ public:
efUx,
};
static const char* FormatNames[];
Decoder();
EStatus Check();
void Clear();
int DecodeBuffer(char* buffer, int len);
void SetCrcCheck(bool crcCheck) { m_crcCheck = crcCheck; }
void SetRawMode(bool rawMode) { m_rawMode = rawMode; }
EFormat GetFormat() { return m_format; }
int64 GetBeginPos() { return m_beginPos; }
int64 GetEndPos() { return m_endPos; }
int64 GetSize() { return m_size; }
uint32 GetExpectedCrc() { return m_expectedCRC; }
uint32 GetCalculatedCrc() { return m_calculatedCRC; }
bool GetEof() { return m_eof; }
const char* GetArticleFilename() { return m_articleFilename; }
protected:
char* m_szArticleFilename;
private:
EFormat m_format = efUnknown;
bool m_begin;
bool m_part;
bool m_body;
bool m_end;
bool m_crc;
uint32 m_expectedCRC;
uint32 m_calculatedCRC;
int64 m_beginPos;
int64 m_endPos;
int64 m_size;
int64 m_endSize;
int64 m_outSize;
bool m_eof;
bool m_crcCheck;
char m_state;
bool m_rawMode = false;
CString m_articleFilename;
StringBuilder m_lineBuf;
Crc32 m_crc32;
public:
Decoder();
virtual ~Decoder();
virtual EStatus Check() = 0;
virtual void Clear();
virtual int DecodeBuffer(char* buffer, int len) = 0;
const char* GetArticleFilename() { return m_szArticleFilename; }
static EFormat DetectFormat(const char* buffer, int len, bool inBody);
};
class YDecoder: public Decoder
{
protected:
bool m_bBegin;
bool m_bPart;
bool m_bBody;
bool m_bEnd;
bool m_bCrc;
unsigned long m_lExpectedCRC;
unsigned long m_lCalculatedCRC;
long long m_iBegin;
long long m_iEnd;
long long m_iSize;
long long m_iEndSize;
bool m_bCrcCheck;
public:
YDecoder();
virtual EStatus Check();
virtual void Clear();
virtual int DecodeBuffer(char* buffer, int len);
void SetCrcCheck(bool bCrcCheck) { m_bCrcCheck = bCrcCheck; }
long long GetBegin() { return m_iBegin; }
long long GetEnd() { return m_iEnd; }
long long GetSize() { return m_iSize; }
unsigned long GetExpectedCrc() { return m_lExpectedCRC; }
unsigned long GetCalculatedCrc() { return m_lCalculatedCRC; }
};
class UDecoder: public Decoder
{
private:
bool m_bBody;
bool m_bEnd;
public:
UDecoder();
virtual EStatus Check();
virtual void Clear();
virtual int DecodeBuffer(char* buffer, int len);
EFormat DetectFormat(const char* buffer, int len);
void ProcessYenc(char* buffer, int len);
int DecodeYenc(char* buffer, char* outbuf, int len);
EStatus CheckYenc();
int DecodeUx(char* buffer, int len);
EStatus CheckUx();
void ProcessRaw(char* buffer, int len);
};
#endif

View File

@@ -1,284 +0,0 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include "nzbget.h"
#include "Log.h"
#include "NNTPConnection.h"
#include "Connection.h"
#include "NewsServer.h"
static const int CONNECTION_LINEBUFFER_SIZE = 1024*10;
NNTPConnection::NNTPConnection(NewsServer* pNewsServer) : Connection(pNewsServer->GetHost(), pNewsServer->GetPort(), pNewsServer->GetTLS())
{
m_pNewsServer = pNewsServer;
m_szActiveGroup = NULL;
m_szLineBuf = (char*)malloc(CONNECTION_LINEBUFFER_SIZE);
m_bAuthError = false;
SetCipher(pNewsServer->GetCipher());
}
NNTPConnection::~NNTPConnection()
{
free(m_szActiveGroup);
free(m_szLineBuf);
}
const char* NNTPConnection::Request(const char* req)
{
if (!req)
{
return NULL;
}
m_bAuthError = false;
WriteLine(req);
char* answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL);
if (!answer)
{
return NULL;
}
if (!strncmp(answer, "480", 3))
{
debug("%s requested authorization", GetHost());
if (!Authenticate())
{
return NULL;
}
//try again
WriteLine(req);
answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL);
}
return answer;
}
bool NNTPConnection::Authenticate()
{
if (strlen(m_pNewsServer->GetUser()) == 0 || strlen(m_pNewsServer->GetPassword()) == 0)
{
ReportError("Could not connect to %s: server requested authorization but username/password are not set in settings",
m_pNewsServer->GetHost(), false, 0);
m_bAuthError = true;
return false;
}
m_bAuthError = !AuthInfoUser(0);
return !m_bAuthError;
}
bool NNTPConnection::AuthInfoUser(int iRecur)
{
if (iRecur > 10)
{
return false;
}
char tmp[1024];
snprintf(tmp, 1024, "AUTHINFO USER %s\r\n", m_pNewsServer->GetUser());
tmp[1024-1] = '\0';
WriteLine(tmp);
char* answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL);
if (!answer)
{
ReportErrorAnswer("Authorization for %s (%s) failed: Connection closed by remote host", NULL);
return false;
}
if (!strncmp(answer, "281", 3))
{
debug("Authorization for %s successful", GetHost());
return true;
}
else if (!strncmp(answer, "381", 3))
{
return AuthInfoPass(++iRecur);
}
else if (!strncmp(answer, "480", 3))
{
return AuthInfoUser(++iRecur);
}
if (char* p = strrchr(answer, '\r')) *p = '\0'; // remove last CRLF from error message
if (GetStatus() != csCancelled)
{
ReportErrorAnswer("Authorization for %s (%s) failed: %s", answer);
}
return false;
}
bool NNTPConnection::AuthInfoPass(int iRecur)
{
if (iRecur > 10)
{
return false;
}
char tmp[1024];
snprintf(tmp, 1024, "AUTHINFO PASS %s\r\n", m_pNewsServer->GetPassword());
tmp[1024-1] = '\0';
WriteLine(tmp);
char* answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL);
if (!answer)
{
ReportErrorAnswer("Authorization failed for %s (%s): Connection closed by remote host", NULL);
return false;
}
else if (!strncmp(answer, "2", 1))
{
debug("Authorization for %s successful", GetHost());
return true;
}
else if (!strncmp(answer, "381", 3))
{
return AuthInfoPass(++iRecur);
}
if (char* p = strrchr(answer, '\r')) *p = '\0'; // remove last CRLF from error message
if (GetStatus() != csCancelled)
{
ReportErrorAnswer("Authorization for %s (%s) failed: %s", answer);
}
return false;
}
const char* NNTPConnection::JoinGroup(const char* grp)
{
if (m_szActiveGroup && !strcmp(m_szActiveGroup, grp))
{
// already in group
strcpy(m_szLineBuf, "211 ");
return m_szLineBuf;
}
char tmp[1024];
snprintf(tmp, 1024, "GROUP %s\r\n", grp);
tmp[1024-1] = '\0';
const char* answer = Request(tmp);
if (answer && !strncmp(answer, "2", 1))
{
debug("Changed group to %s on %s", grp, GetHost());
free(m_szActiveGroup);
m_szActiveGroup = strdup(grp);
}
else
{
debug("Error changing group on %s to %s: %s.", GetHost(), grp, answer);
}
return answer;
}
bool NNTPConnection::Connect()
{
debug("Opening connection to %s", GetHost());
if (m_eStatus == csConnected)
{
return true;
}
if (!Connection::Connect())
{
return false;
}
char* answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL);
if (!answer)
{
ReportErrorAnswer("Connection to %s (%s) failed: Connection closed by remote host", NULL);
Disconnect();
return false;
}
if (strncmp(answer, "2", 1))
{
ReportErrorAnswer("Connection to %s (%s) failed: %s", answer);
Disconnect();
return false;
}
if ((strlen(m_pNewsServer->GetUser()) > 0 && strlen(m_pNewsServer->GetPassword()) > 0) &&
!Authenticate())
{
return false;
}
debug("Connection to %s established", GetHost());
return true;
}
bool NNTPConnection::Disconnect()
{
if (m_eStatus == csConnected)
{
if (!m_bBroken)
{
Request("quit\r\n");
}
free(m_szActiveGroup);
m_szActiveGroup = NULL;
}
return Connection::Disconnect();
}
void NNTPConnection::ReportErrorAnswer(const char* szMsgPrefix, const char* szAnswer)
{
char szErrStr[1024];
snprintf(szErrStr, 1024, szMsgPrefix, m_pNewsServer->GetName(), m_pNewsServer->GetHost(), szAnswer);
szErrStr[1024-1] = '\0';
ReportError(szErrStr, NULL, false, 0);
}

View File

@@ -1,60 +0,0 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
*/
#ifndef NNTPCONNECTION_H
#define NNTPCONNECTION_H
#include "NewsServer.h"
#include "Connection.h"
class NNTPConnection : public Connection
{
private:
NewsServer* m_pNewsServer;
char* m_szActiveGroup;
char* m_szLineBuf;
bool m_bAuthError;
void Clear();
void ReportErrorAnswer(const char* szMsgPrefix, const char* szAnswer);
bool Authenticate();
bool AuthInfoUser(int iRecur);
bool AuthInfoPass(int iRecur);
public:
NNTPConnection(NewsServer* pNewsServer);
virtual ~NNTPConnection();
virtual bool Connect();
virtual bool Disconnect();
NewsServer* GetNewsServer() { return m_pNewsServer; }
const char* Request(const char* req);
const char* JoinGroup(const char* grp);
bool GetAuthError() { return m_bAuthError; }
};
#endif

View File

@@ -1,8 +1,8 @@
/*
* This file if part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2014 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,68 +15,23 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "nzbget.h"
#include "NewsServer.h"
NewsServer::NewsServer(int iID, bool bActive, const char* szName, const char* szHost, int iPort,
const char* szUser, const char* szPass, bool bJoinGroup, bool bTLS,
const char* szCipher, int iMaxConnections, int iRetention, int iLevel, int iGroup)
NewsServer::NewsServer(int id, bool active, const char* name, const char* host, int port, int ipVersion,
const char* user, const char* pass, bool joinGroup, bool tls, const char* cipher,
int maxConnections, int retention, int level, int group, bool optional) :
m_id(id), m_active(active), m_name(name), m_host(host ? host : ""), m_port(port), m_ipVersion(ipVersion),
m_user(user ? user : ""), m_password(pass ? pass : ""), m_joinGroup(joinGroup), m_tls(tls),
m_cipher(cipher ? cipher : ""), m_maxConnections(maxConnections), m_retention(retention),
m_level(level), m_normLevel(level), m_group(group), m_optional(optional)
{
m_iID = iID;
m_iStateID = 0;
m_bActive = bActive;
m_iPort = iPort;
m_iLevel = iLevel;
m_iNormLevel = iLevel;
m_iGroup = iGroup;
m_iMaxConnections = iMaxConnections;
m_bJoinGroup = bJoinGroup;
m_bTLS = bTLS;
m_szHost = strdup(szHost ? szHost : "");
m_szUser = strdup(szUser ? szUser : "");
m_szPassword = strdup(szPass ? szPass : "");
m_szCipher = strdup(szCipher ? szCipher : "");
m_iRetention = iRetention;
m_tBlockTime = 0;
if (szName && strlen(szName) > 0)
if (m_name.Empty())
{
m_szName = strdup(szName);
}
else
{
m_szName = (char*)malloc(20);
snprintf(m_szName, 20, "server%i", iID);
m_szName[20-1] = '\0';
m_name.Format("server%i", id);
}
}
NewsServer::~NewsServer()
{
free(m_szName);
free(m_szHost);
free(m_szUser);
free(m_szPassword);
free(m_szCipher);
}

View File

@@ -27,59 +27,61 @@
#ifndef NEWSSERVER_H
#define NEWSSERVER_H
#include <vector>
#include <time.h>
#include "NString.h"
class NewsServer
{
private:
int m_iID;
int m_iStateID;
bool m_bActive;
char* m_szName;
int m_iGroup;
char* m_szHost;
int m_iPort;
char* m_szUser;
char* m_szPassword;
int m_iMaxConnections;
int m_iLevel;
int m_iNormLevel;
bool m_bJoinGroup;
bool m_bTLS;
char* m_szCipher;
int m_iRetention;
time_t m_tBlockTime;
public:
NewsServer(int iID, bool bActive, const char* szName, const char* szHost, int iPort,
const char* szUser, const char* szPass, bool bJoinGroup,
bool bTLS, const char* szCipher, int iMaxConnections, int iRetention,
int iLevel, int iGroup);
~NewsServer();
int GetID() { return m_iID; }
int GetStateID() { return m_iStateID; }
void SetStateID(int iStateID) { m_iStateID = iStateID; }
bool GetActive() { return m_bActive; }
void SetActive(bool bActive) { m_bActive = bActive; }
const char* GetName() { return m_szName; }
int GetGroup() { return m_iGroup; }
const char* GetHost() { return m_szHost; }
int GetPort() { return m_iPort; }
const char* GetUser() { return m_szUser; }
const char* GetPassword() { return m_szPassword; }
int GetMaxConnections() { return m_iMaxConnections; }
int GetLevel() { return m_iLevel; }
int GetNormLevel() { return m_iNormLevel; }
void SetNormLevel(int iLevel) { m_iNormLevel = iLevel; }
int GetJoinGroup() { return m_bJoinGroup; }
bool GetTLS() { return m_bTLS; }
const char* GetCipher() { return m_szCipher; }
int GetRetention() { return m_iRetention; }
time_t GetBlockTime() { return m_tBlockTime; }
void SetBlockTime(time_t tBlockTime) { m_tBlockTime = tBlockTime; }
NewsServer(int id, bool active, const char* name, const char* host, int port, int ipVersion,
const char* user, const char* pass, bool joinGroup,
bool tls, const char* cipher, int maxConnections, int retention,
int level, int group, bool optional);
int GetId() { return m_id; }
int GetStateId() { return m_stateId; }
void SetStateId(int stateId) { m_stateId = stateId; }
bool GetActive() { return m_active; }
void SetActive(bool active) { m_active = active; }
const char* GetName() { return m_name; }
int GetGroup() { return m_group; }
const char* GetHost() { return m_host; }
int GetPort() { return m_port; }
int GetIpVersion() { return m_ipVersion; }
const char* GetUser() { return m_user; }
const char* GetPassword() { return m_password; }
int GetMaxConnections() { return m_maxConnections; }
int GetLevel() { return m_level; }
int GetNormLevel() { return m_normLevel; }
void SetNormLevel(int level) { m_normLevel = level; }
int GetJoinGroup() { return m_joinGroup; }
bool GetTls() { return m_tls; }
const char* GetCipher() { return m_cipher; }
int GetRetention() { return m_retention; }
bool GetOptional() { return m_optional; }
time_t GetBlockTime() { return m_blockTime; }
void SetBlockTime(time_t blockTime) { m_blockTime = blockTime; }
private:
int m_id;
int m_stateId = 0;
bool m_active;
CString m_name;
CString m_host;
int m_port;
int m_ipVersion;
CString m_user;
CString m_password;
bool m_joinGroup;
bool m_tls;
CString m_cipher;
int m_maxConnections;
int m_retention;
int m_level;
int m_normLevel;
int m_group;
bool m_optional = false;
time_t m_blockTime = 0;
};
typedef std::vector<NewsServer*> Servers;
typedef std::vector<std::unique_ptr<NewsServer>> Servers;
#endif

View File

@@ -0,0 +1,240 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "nzbget.h"
#include "Log.h"
#include "NntpConnection.h"
#include "Connection.h"
#include "NewsServer.h"
static const int CONNECTION_LINEBUFFER_SIZE = 1024*10;
NntpConnection::NntpConnection(NewsServer* newsServer) :
Connection(newsServer->GetHost(), newsServer->GetPort(), newsServer->GetTls()), m_newsServer(newsServer)
{
m_lineBuf.Reserve(CONNECTION_LINEBUFFER_SIZE);
SetCipher(newsServer->GetCipher());
SetIPVersion(newsServer->GetIpVersion() == 4 ? Connection::ipV4 :
newsServer->GetIpVersion() == 6 ? Connection::ipV6 : Connection::ipAuto);
}
const char* NntpConnection::Request(const char* req)
{
if (!req)
{
return nullptr;
}
m_authError = false;
WriteLine(req);
char* answer = ReadLine(m_lineBuf, m_lineBuf.Size(), nullptr);
if (!answer)
{
return nullptr;
}
if (!strncmp(answer, "480", 3))
{
debug("%s requested authorization", GetHost());
if (!Authenticate())
{
return nullptr;
}
//try again
WriteLine(req);
answer = ReadLine(m_lineBuf, m_lineBuf.Size(), nullptr);
}
return answer;
}
bool NntpConnection::Authenticate()
{
if (strlen(m_newsServer->GetUser()) == 0 || strlen(m_newsServer->GetPassword()) == 0)
{
ReportError("Could not connect to %s: server requested authorization but username/password are not set in settings",
m_newsServer->GetHost(), false, 0);
m_authError = true;
return false;
}
m_authError = !AuthInfoUser(0);
return !m_authError;
}
bool NntpConnection::AuthInfoUser(int recur)
{
if (recur > 10)
{
return false;
}
WriteLine(BString<1024>("AUTHINFO USER %s\r\n", m_newsServer->GetUser()));
char* answer = ReadLine(m_lineBuf, m_lineBuf.Size(), nullptr);
if (!answer)
{
ReportErrorAnswer("Authorization for %s (%s) failed: Connection closed by remote host", nullptr);
return false;
}
if (!strncmp(answer, "281", 3))
{
debug("Authorization for %s successful", GetHost());
return true;
}
else if (!strncmp(answer, "381", 3))
{
return AuthInfoPass(++recur);
}
else if (!strncmp(answer, "480", 3))
{
return AuthInfoUser(++recur);
}
if (char* p = strrchr(answer, '\r')) *p = '\0'; // remove last CRLF from error message
if (GetStatus() != csCancelled)
{
ReportErrorAnswer("Authorization for %s (%s) failed: %s", answer);
}
return false;
}
bool NntpConnection::AuthInfoPass(int recur)
{
if (recur > 10)
{
return false;
}
WriteLine(BString<1024>("AUTHINFO PASS %s\r\n", m_newsServer->GetPassword()));
char* answer = ReadLine(m_lineBuf, m_lineBuf.Size(), nullptr);
if (!answer)
{
ReportErrorAnswer("Authorization failed for %s (%s): Connection closed by remote host", nullptr);
return false;
}
else if (!strncmp(answer, "2", 1))
{
debug("Authorization for %s successful", GetHost());
return true;
}
else if (!strncmp(answer, "381", 3))
{
return AuthInfoPass(++recur);
}
if (char* p = strrchr(answer, '\r')) *p = '\0'; // remove last CRLF from error message
if (GetStatus() != csCancelled)
{
ReportErrorAnswer("Authorization for %s (%s) failed: %s", answer);
}
return false;
}
const char* NntpConnection::JoinGroup(const char* grp)
{
if (!m_activeGroup.Empty() && !strcmp(m_activeGroup, grp))
{
// already in group
strcpy(m_lineBuf, "211 ");
return m_lineBuf;
}
const char* answer = Request(BString<1024>("GROUP %s\r\n", grp));
if (answer && !strncmp(answer, "2", 1))
{
debug("Changed group to %s on %s", grp, GetHost());
m_activeGroup = grp;
}
else
{
debug("Error changing group on %s to %s: %s.", GetHost(), grp, answer);
}
return answer;
}
bool NntpConnection::Connect()
{
debug("Opening connection to %s", GetHost());
if (m_status == csConnected)
{
return true;
}
if (!Connection::Connect())
{
return false;
}
char* answer = ReadLine(m_lineBuf, m_lineBuf.Size(), nullptr);
if (!answer)
{
ReportErrorAnswer("Connection to %s (%s) failed: Connection closed by remote host", nullptr);
Disconnect();
return false;
}
if (strncmp(answer, "2", 1))
{
ReportErrorAnswer("Connection to %s (%s) failed: %s", answer);
Disconnect();
return false;
}
if ((strlen(m_newsServer->GetUser()) > 0 && strlen(m_newsServer->GetPassword()) > 0) &&
!Authenticate())
{
return false;
}
debug("Connection to %s established", GetHost());
return true;
}
bool NntpConnection::Disconnect()
{
if (m_status == csConnected)
{
Request("quit\r\n");
m_activeGroup = nullptr;
}
return Connection::Disconnect();
}
void NntpConnection::ReportErrorAnswer(const char* msgPrefix, const char* answer)
{
BString<1024> errStr(msgPrefix, m_newsServer->GetName(), m_newsServer->GetHost(), answer);
ReportError(errStr, nullptr, false, 0);
}

View File

@@ -0,0 +1,53 @@
/*
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef NNTPCONNECTION_H
#define NNTPCONNECTION_H
#include "NString.h"
#include "NewsServer.h"
#include "Connection.h"
class NntpConnection : public Connection
{
public:
NntpConnection(NewsServer* newsServer);
virtual bool Connect();
virtual bool Disconnect();
NewsServer* GetNewsServer() { return m_newsServer; }
const char* Request(const char* req);
const char* JoinGroup(const char* grp);
bool GetAuthError() { return m_authError; }
private:
NewsServer* m_newsServer;
CString m_activeGroup;
CharBuffer m_lineBuf;
bool m_authError = false;
void Clear();
void ReportErrorAnswer(const char* msgPrefix, const char* answer);
bool Authenticate();
bool AuthInfoUser(int recur);
bool AuthInfoPass(int recur);
};
#endif

View File

@@ -1,8 +1,8 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,82 +15,28 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#ifndef WIN32
#include <unistd.h>
#include <errno.h>
#endif
#include <algorithm>
#include "nzbget.h"
#include "ServerPool.h"
#include "Util.h"
static const int CONNECTION_HOLD_SECODNS = 5;
ServerPool::PooledConnection::PooledConnection(NewsServer* server) : NNTPConnection(server)
void ServerPool::PooledConnection::SetFreeTimeNow()
{
m_bInUse = false;
m_tFreeTime = 0;
m_freeTime = Util::CurrentTime();
}
ServerPool::ServerPool()
{
debug("Creating ServerPool");
m_iMaxNormLevel = 0;
m_iTimeout = 60;
m_iGeneration = 0;
m_iRetryInterval = 0;
g_pLog->RegisterDebuggable(this);
}
ServerPool::~ ServerPool()
{
debug("Destroying ServerPool");
g_pLog->UnregisterDebuggable(this);
m_Levels.clear();
for (Servers::iterator it = m_Servers.begin(); it != m_Servers.end(); it++)
{
delete *it;
}
m_Servers.clear();
m_SortedServers.clear();
for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++)
{
delete *it;
}
m_Connections.clear();
}
void ServerPool::AddServer(NewsServer* pNewsServer)
void ServerPool::AddServer(std::unique_ptr<NewsServer> newsServer)
{
debug("Adding server to ServerPool");
m_Servers.push_back(pNewsServer);
m_SortedServers.push_back(pNewsServer);
m_sortedServers.push_back(newsServer.get());
m_servers.push_back(std::move(newsServer));
}
/*
@@ -101,269 +47,284 @@ void ServerPool::AddServer(NewsServer* pNewsServer)
**/
void ServerPool::NormalizeLevels()
{
if (m_Servers.empty())
if (m_servers.empty())
{
return;
}
std::sort(m_SortedServers.begin(), m_SortedServers.end(), CompareServers);
std::sort(m_sortedServers.begin(), m_sortedServers.end(),
[](NewsServer* server1, NewsServer* server2)
{
return server1->GetLevel() < server2->GetLevel();
});
// find minimum level
int iMinLevel = m_SortedServers.front()->GetLevel();
for (Servers::iterator it = m_SortedServers.begin(); it != m_SortedServers.end(); it++)
int minLevel = m_sortedServers.front()->GetLevel();
for (NewsServer* newsServer : m_sortedServers)
{
NewsServer* pNewsServer = *it;
if (pNewsServer->GetLevel() < iMinLevel)
if (newsServer->GetLevel() < minLevel)
{
iMinLevel = pNewsServer->GetLevel();
minLevel = newsServer->GetLevel();
}
}
m_iMaxNormLevel = 0;
int iLastLevel = iMinLevel;
for (Servers::iterator it = m_SortedServers.begin(); it != m_SortedServers.end(); it++)
m_maxNormLevel = 0;
int lastLevel = minLevel;
for (NewsServer* newsServer : m_sortedServers)
{
NewsServer* pNewsServer = *it;
if ((pNewsServer->GetActive() && pNewsServer->GetMaxConnections() > 0) ||
(pNewsServer->GetLevel() == iMinLevel))
if ((newsServer->GetActive() && newsServer->GetMaxConnections() > 0) ||
(newsServer->GetLevel() == minLevel))
{
if (pNewsServer->GetLevel() != iLastLevel)
if (newsServer->GetLevel() != lastLevel)
{
m_iMaxNormLevel++;
m_maxNormLevel++;
}
pNewsServer->SetNormLevel(m_iMaxNormLevel);
iLastLevel = pNewsServer->GetLevel();
newsServer->SetNormLevel(m_maxNormLevel);
lastLevel = newsServer->GetLevel();
}
else
{
pNewsServer->SetNormLevel(-1);
newsServer->SetNormLevel(-1);
}
}
}
bool ServerPool::CompareServers(NewsServer* pServer1, NewsServer* pServer2)
{
return pServer1->GetLevel() < pServer2->GetLevel();
}
void ServerPool::InitConnections()
{
debug("Initializing connections in ServerPool");
m_mutexConnections.Lock();
Guard guard(m_connectionsMutex);
NormalizeLevels();
m_Levels.clear();
m_levels.clear();
for (Servers::iterator it = m_SortedServers.begin(); it != m_SortedServers.end(); it++)
for (NewsServer* newsServer : m_sortedServers)
{
NewsServer* pNewsServer = *it;
pNewsServer->SetBlockTime(0);
int iNormLevel = pNewsServer->GetNormLevel();
if (pNewsServer->GetNormLevel() > -1)
newsServer->SetBlockTime(0);
int normLevel = newsServer->GetNormLevel();
if (newsServer->GetNormLevel() > -1)
{
if ((int)m_Levels.size() <= iNormLevel)
if ((int)m_levels.size() <= normLevel)
{
m_Levels.push_back(0);
m_levels.push_back(0);
}
if (pNewsServer->GetActive())
if (newsServer->GetActive())
{
int iConnections = 0;
for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++)
int connections = 0;
for (PooledConnection* connection : &m_connections)
{
PooledConnection* pConnection = *it;
if (pConnection->GetNewsServer() == pNewsServer)
if (connection->GetNewsServer() == newsServer)
{
iConnections++;
connections++;
}
}
for (int i = iConnections; i < pNewsServer->GetMaxConnections(); i++)
for (int i = connections; i < newsServer->GetMaxConnections(); i++)
{
PooledConnection* pConnection = new PooledConnection(pNewsServer);
pConnection->SetTimeout(m_iTimeout);
m_Connections.push_back(pConnection);
iConnections++;
std::unique_ptr<PooledConnection> connection = std::make_unique<PooledConnection>(newsServer);
connection->SetTimeout(m_timeout);
m_connections.push_back(std::move(connection));
connections++;
}
m_Levels[iNormLevel] += iConnections;
m_levels[normLevel] += connections;
}
}
}
m_iGeneration++;
m_mutexConnections.Unlock();
m_generation++;
}
NNTPConnection* ServerPool::GetConnection(int iLevel, NewsServer* pWantServer, Servers* pIgnoreServers)
/* Returns connection from any server on a given level or nullptr if there is no free connection at the moment.
* If all servers are blocked and all are optional a connection from the next level is returned instead.
*/
NntpConnection* ServerPool::GetConnection(int level, NewsServer* wantServer, RawServerList* ignoreServers)
{
PooledConnection* pConnection = NULL;
m_mutexConnections.Lock();
Guard guard(m_connectionsMutex);
time_t tCurTime = time(NULL);
if (iLevel < (int)m_Levels.size() && m_Levels[iLevel] > 0)
for (; level < (int)m_levels.size() && m_levels[level] > 0; level++)
{
Connections candidates;
candidates.reserve(m_Connections.size());
for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++)
NntpConnection* connection = LockedGetConnection(level, wantServer, ignoreServers);
if (connection)
{
PooledConnection* pCandidateConnection = *it;
NewsServer* pCandidateServer = pCandidateConnection->GetNewsServer();
if (!pCandidateConnection->GetInUse() && pCandidateServer->GetActive() &&
pCandidateServer->GetNormLevel() == iLevel &&
(!pWantServer || pCandidateServer == pWantServer ||
(pWantServer->GetGroup() > 0 && pWantServer->GetGroup() == pCandidateServer->GetGroup())) &&
(pCandidateConnection->GetStatus() == Connection::csConnected ||
!pCandidateServer->GetBlockTime() ||
pCandidateServer->GetBlockTime() + m_iRetryInterval <= tCurTime ||
pCandidateServer->GetBlockTime() > tCurTime))
return connection;
}
for (NewsServer* newsServer : m_sortedServers)
{
if (newsServer->GetNormLevel() == level && newsServer->GetActive() &&
!(newsServer->GetOptional() && IsServerBlocked(newsServer)))
{
// free connection found, check if it's not from the server which should be ignored
bool bUseConnection = true;
if (pIgnoreServers && !pWantServer)
{
for (Servers::iterator it = pIgnoreServers->begin(); it != pIgnoreServers->end(); it++)
{
NewsServer* pIgnoreServer = *it;
if (pIgnoreServer == pCandidateServer ||
(pIgnoreServer->GetGroup() > 0 && pIgnoreServer->GetGroup() == pCandidateServer->GetGroup() &&
pIgnoreServer->GetNormLevel() == pCandidateServer->GetNormLevel()))
{
bUseConnection = false;
break;
}
}
}
pCandidateServer->SetBlockTime(0);
if (bUseConnection)
{
candidates.push_back(pCandidateConnection);
}
return nullptr;
}
}
if (!candidates.empty())
{
// Peeking a random free connection. This is better than taking the first
// available connection because provides better distribution across news servers,
// especially when one of servers becomes unavailable or doesn't have requested articles.
int iRandomIndex = rand() % candidates.size();
pConnection = candidates[iRandomIndex];
pConnection->SetInUse(true);
}
if (pConnection)
{
m_Levels[iLevel]--;
}
}
m_mutexConnections.Unlock();
return pConnection;
return nullptr;
}
void ServerPool::FreeConnection(NNTPConnection* pConnection, bool bUsed)
NntpConnection* ServerPool::LockedGetConnection(int level, NewsServer* wantServer, RawServerList* ignoreServers)
{
if (bUsed)
if (level >= (int)m_levels.size() || m_levels[level] == 0)
{
return nullptr;
}
PooledConnection* connection = nullptr;
std::vector<PooledConnection*> candidates;
candidates.reserve(m_connections.size());
for (PooledConnection* candidateConnection : &m_connections)
{
NewsServer* candidateServer = candidateConnection->GetNewsServer();
if (!candidateConnection->GetInUse() && candidateServer->GetActive() &&
candidateServer->GetNormLevel() == level &&
(!wantServer || candidateServer == wantServer ||
(wantServer->GetGroup() > 0 && wantServer->GetGroup() == candidateServer->GetGroup())) &&
(candidateConnection->GetStatus() == Connection::csConnected ||
!IsServerBlocked(candidateServer)))
{
// free connection found, check if it's not from the server which should be ignored
bool useConnection = true;
if (ignoreServers && !wantServer)
{
for (NewsServer* ignoreServer : ignoreServers)
{
if (ignoreServer == candidateServer ||
(ignoreServer->GetGroup() > 0 && ignoreServer->GetGroup() == candidateServer->GetGroup() &&
ignoreServer->GetNormLevel() == candidateServer->GetNormLevel()))
{
useConnection = false;
break;
}
}
}
candidateServer->SetBlockTime(0);
if (useConnection)
{
candidates.push_back(candidateConnection);
}
}
}
if (!candidates.empty())
{
// Peeking a random free connection. This is better than taking the first
// available connection because provides better distribution across news servers,
// especially when one of servers becomes unavailable or doesn't have requested articles.
int randomIndex = rand() % candidates.size();
connection = candidates[randomIndex];
connection->SetInUse(true);
}
if (connection)
{
m_levels[level]--;
}
return connection;
}
void ServerPool::FreeConnection(NntpConnection* connection, bool used)
{
if (used)
{
debug("Freeing used connection");
}
m_mutexConnections.Lock();
Guard guard(m_connectionsMutex);
((PooledConnection*)pConnection)->SetInUse(false);
if (bUsed)
((PooledConnection*)connection)->SetInUse(false);
if (used)
{
((PooledConnection*)pConnection)->SetFreeTimeNow();
((PooledConnection*)connection)->SetFreeTimeNow();
}
if (pConnection->GetNewsServer()->GetNormLevel() > -1 && pConnection->GetNewsServer()->GetActive())
if (connection->GetNewsServer()->GetNormLevel() > -1 && connection->GetNewsServer()->GetActive())
{
m_Levels[pConnection->GetNewsServer()->GetNormLevel()]++;
m_levels[connection->GetNewsServer()->GetNormLevel()]++;
}
m_mutexConnections.Unlock();
}
void ServerPool::BlockServer(NewsServer* pNewsServer)
void ServerPool::BlockServer(NewsServer* newsServer)
{
m_mutexConnections.Lock();
time_t tCurTime = time(NULL);
bool bNewBlock = pNewsServer->GetBlockTime() != tCurTime;
pNewsServer->SetBlockTime(tCurTime);
m_mutexConnections.Unlock();
if (bNewBlock && m_iRetryInterval > 0)
bool newBlock = false;
{
warn("Blocking %s (%s) for %i sec", pNewsServer->GetName(), pNewsServer->GetHost(), m_iRetryInterval);
Guard guard(m_connectionsMutex);
time_t curTime = Util::CurrentTime();
newBlock = newsServer->GetBlockTime() != curTime;
newsServer->SetBlockTime(curTime);
}
if (newBlock && m_retryInterval > 0)
{
warn("Blocking %s (%s) for %i sec", newsServer->GetName(), newsServer->GetHost(), m_retryInterval);
}
}
bool ServerPool::IsServerBlocked(NewsServer* newsServer)
{
if (!newsServer->GetBlockTime())
{
return false;
}
time_t curTime = Util::CurrentTime();
bool blocked = newsServer->GetBlockTime() <= curTime &&
curTime < newsServer->GetBlockTime() + m_retryInterval;
return blocked;
}
void ServerPool::CloseUnusedConnections()
{
m_mutexConnections.Lock();
Guard guard(m_connectionsMutex);
time_t curtime = ::time(NULL);
time_t curtime = Util::CurrentTime();
// close and free all connections of servers which were disabled since the last check
int i = 0;
for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); )
{
PooledConnection* pConnection = *it;
bool bDeleted = false;
if (!pConnection->GetInUse() &&
(pConnection->GetNewsServer()->GetNormLevel() == -1 ||
!pConnection->GetNewsServer()->GetActive()))
m_connections.erase(std::remove_if(m_connections.begin(), m_connections.end(),
[](std::unique_ptr<PooledConnection>& connection)
{
debug("Closing (and deleting) unused connection to server%i", pConnection->GetNewsServer()->GetID());
if (pConnection->GetStatus() == Connection::csConnected)
if (!connection->GetInUse() &&
(connection->GetNewsServer()->GetNormLevel() == -1 ||
!connection->GetNewsServer()->GetActive()))
{
pConnection->Disconnect();
debug("Closing (and deleting) unused connection to server%i", connection->GetNewsServer()->GetId());
if (connection->GetStatus() == Connection::csConnected)
{
connection->Disconnect();
}
return true;
}
delete pConnection;
m_Connections.erase(it);
it = m_Connections.begin() + i;
bDeleted = true;
}
if (!bDeleted)
{
it++;
i++;
}
}
return false;
}),
m_connections.end());
// close all opened connections on levels not having any in-use connections
for (int iLevel = 0; iLevel <= m_iMaxNormLevel; iLevel++)
for (int level = 0; level <= m_maxNormLevel; level++)
{
// check if we have in-use connections on the level
bool bHasInUseConnections = false;
int iInactiveTime = 0;
for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++)
bool hasInUseConnections = false;
int inactiveTime = 0;
for (PooledConnection* connection : &m_connections)
{
PooledConnection* pConnection = *it;
if (pConnection->GetNewsServer()->GetNormLevel() == iLevel)
if (connection->GetNewsServer()->GetNormLevel() == level)
{
if (pConnection->GetInUse())
if (connection->GetInUse())
{
bHasInUseConnections = true;
hasInUseConnections = true;
break;
}
else
{
int tdiff = (int)(curtime - pConnection->GetFreeTime());
if (tdiff > iInactiveTime)
int tdiff = (int)(curtime - connection->GetFreeTime());
if (tdiff > inactiveTime)
{
iInactiveTime = tdiff;
inactiveTime = tdiff;
}
}
}
@@ -371,22 +332,19 @@ void ServerPool::CloseUnusedConnections()
// if there are no in-use connections on the level and the hold time out has
// expired - close all connections of the level.
if (!bHasInUseConnections && iInactiveTime > CONNECTION_HOLD_SECODNS)
if (!hasInUseConnections && inactiveTime > CONNECTION_HOLD_SECODNS)
{
for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++)
for (PooledConnection* connection : &m_connections)
{
PooledConnection* pConnection = *it;
if (pConnection->GetNewsServer()->GetNormLevel() == iLevel &&
pConnection->GetStatus() == Connection::csConnected)
if (connection->GetNewsServer()->GetNormLevel() == level &&
connection->GetStatus() == Connection::csConnected)
{
debug("Closing (and keeping) unused connection to server%i", pConnection->GetNewsServer()->GetID());
pConnection->Disconnect();
debug("Closing (and keeping) unused connection to server%i", connection->GetNewsServer()->GetId());
connection->Disconnect();
}
}
}
}
m_mutexConnections.Unlock();
}
void ServerPool::Changed()
@@ -401,39 +359,35 @@ void ServerPool::LogDebugInfo()
{
info(" ---------- ServerPool");
info(" Max-Level: %i", m_iMaxNormLevel);
info(" Max-Level: %i", m_maxNormLevel);
m_mutexConnections.Lock();
Guard guard(m_connectionsMutex);
time_t tCurTime = time(NULL);
time_t curTime = Util::CurrentTime();
info(" Servers: %i", m_Servers.size());
for (Servers::iterator it = m_Servers.begin(); it != m_Servers.end(); it++)
info(" Servers: %i", (int)m_servers.size());
for (NewsServer* newsServer : &m_servers)
{
NewsServer* pNewsServer = *it;
info(" %i) %s (%s): Level=%i, NormLevel=%i, BlockSec=%i", pNewsServer->GetID(), pNewsServer->GetName(),
pNewsServer->GetHost(), pNewsServer->GetLevel(), pNewsServer->GetNormLevel(),
pNewsServer->GetBlockTime() && pNewsServer->GetBlockTime() + m_iRetryInterval > tCurTime ?
pNewsServer->GetBlockTime() + m_iRetryInterval - tCurTime : 0);
info(" %i) %s (%s): Level=%i, NormLevel=%i, BlockSec=%i", newsServer->GetId(), newsServer->GetName(),
newsServer->GetHost(), newsServer->GetLevel(), newsServer->GetNormLevel(),
newsServer->GetBlockTime() && newsServer->GetBlockTime() + m_retryInterval > curTime ?
(int)(newsServer->GetBlockTime() + m_retryInterval - curTime) : 0);
}
info(" Levels: %i", m_Levels.size());
info(" Levels: %i", (int)m_levels.size());
int index = 0;
for (Levels::iterator it = m_Levels.begin(); it != m_Levels.end(); it++, index++)
for (int size : m_levels)
{
int iSize = *it;
info(" %i: Free connections=%i", index, iSize);
info(" %i: Free connections=%i", index, size);
index++;
}
info(" Connections: %i", m_Connections.size());
for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++)
info(" Connections: %i", (int)m_connections.size());
for (PooledConnection* connection : &m_connections)
{
PooledConnection* pConnection = *it;
info(" %i) %s (%s): Level=%i, NormLevel=%i, InUse:%i", pConnection->GetNewsServer()->GetID(),
pConnection->GetNewsServer()->GetName(), pConnection->GetNewsServer()->GetHost(),
pConnection->GetNewsServer()->GetLevel(), pConnection->GetNewsServer()->GetNormLevel(),
(int)pConnection->GetInUse());
info(" %i) %s (%s): Level=%i, NormLevel=%i, InUse:%i", connection->GetNewsServer()->GetId(),
connection->GetNewsServer()->GetName(), connection->GetNewsServer()->GetHost(),
connection->GetNewsServer()->GetLevel(), connection->GetNewsServer()->GetNormLevel(),
(int)connection->GetInUse());
}
m_mutexConnections.Unlock();
}

View File

@@ -1,8 +1,8 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,78 +15,72 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SERVERPOOL_H
#define SERVERPOOL_H
#include <vector>
#include <time.h>
#include "Log.h"
#include "Container.h"
#include "Thread.h"
#include "NewsServer.h"
#include "NNTPConnection.h"
#include "NntpConnection.h"
class ServerPool : public Debuggable
{
private:
class PooledConnection : public NNTPConnection
{
private:
bool m_bInUse;
time_t m_tFreeTime;
public:
PooledConnection(NewsServer* server);
bool GetInUse() { return m_bInUse; }
void SetInUse(bool bInUse) { m_bInUse = bInUse; }
time_t GetFreeTime() { return m_tFreeTime; }
void SetFreeTimeNow() { m_tFreeTime = ::time(NULL); }
};
public:
typedef std::vector<NewsServer*> RawServerList;
typedef std::vector<int> Levels;
typedef std::vector<PooledConnection*> Connections;
Servers m_Servers;
Servers m_SortedServers;
Connections m_Connections;
Levels m_Levels;
int m_iMaxNormLevel;
Mutex m_mutexConnections;
int m_iTimeout;
int m_iRetryInterval;
int m_iGeneration;
void NormalizeLevels();
static bool CompareServers(NewsServer* pServer1, NewsServer* pServer2);
void SetTimeout(int timeout) { m_timeout = timeout; }
void SetRetryInterval(int retryInterval) { m_retryInterval = retryInterval; }
void AddServer(std::unique_ptr<NewsServer> newsServer);
void InitConnections();
int GetMaxNormLevel() { return m_maxNormLevel; }
Servers* GetServers() { return &m_servers; } // Only for read access (no lockings)
NntpConnection* GetConnection(int level, NewsServer* wantServer, RawServerList* ignoreServers);
void FreeConnection(NntpConnection* connection, bool used);
void CloseUnusedConnections();
void Changed();
int GetGeneration() { return m_generation; }
void BlockServer(NewsServer* newsServer);
bool IsServerBlocked(NewsServer* newsServer);
protected:
virtual void LogDebugInfo();
virtual void LogDebugInfo();
public:
ServerPool();
~ServerPool();
void SetTimeout(int iTimeout) { m_iTimeout = iTimeout; }
void SetRetryInterval(int iRetryInterval) { m_iRetryInterval = iRetryInterval; }
void AddServer(NewsServer* pNewsServer);
void InitConnections();
int GetMaxNormLevel() { return m_iMaxNormLevel; }
Servers* GetServers() { return &m_Servers; } // Only for read access (no lockings)
NNTPConnection* GetConnection(int iLevel, NewsServer* pWantServer, Servers* pIgnoreServers);
void FreeConnection(NNTPConnection* pConnection, bool bUsed);
void CloseUnusedConnections();
void Changed();
int GetGeneration() { return m_iGeneration; }
void BlockServer(NewsServer* pNewsServer);
private:
class PooledConnection : public NntpConnection
{
public:
using NntpConnection::NntpConnection;
bool GetInUse() { return m_inUse; }
void SetInUse(bool inUse) { m_inUse = inUse; }
time_t GetFreeTime() { return m_freeTime; }
void SetFreeTimeNow();
private:
bool m_inUse = false;
time_t m_freeTime = 0;
};
typedef std::vector<int> Levels;
typedef std::vector<std::unique_ptr<PooledConnection>> Connections;
Servers m_servers;
RawServerList m_sortedServers;
Connections m_connections;
Levels m_levels;
int m_maxNormLevel = 0;
Mutex m_connectionsMutex;
int m_timeout = 60;
int m_retryInterval = 0;
int m_generation = 0;
void NormalizeLevels();
NntpConnection* LockedGetConnection(int level, NewsServer* wantServer, RawServerList* ignoreServers);
};
extern ServerPool* g_pServerPool;
extern ServerPool* g_ServerPool;
#endif

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2014-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2014-2019 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,30 +14,14 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "nzbget.h"
#include "StatMeter.h"
#include "Options.h"
#include "WorkState.h"
#include "ServerPool.h"
#include "DiskState.h"
#include "Util.h"
@@ -45,123 +29,106 @@
static const int DAYS_UP_TO_2013_JAN_1 = 15706;
static const int DAYS_IN_TWENTY_YEARS = 366*20;
ServerVolume::ServerVolume()
void ServerVolume::CalcSlots(time_t locCurTime)
{
m_BytesPerSeconds.resize(60);
m_BytesPerMinutes.resize(60);
m_BytesPerHours.resize(24);
m_BytesPerDays.resize(0);
m_iFirstDay = 0;
m_tDataTime = 0;
m_lTotalBytes = 0;
m_lCustomBytes = 0;
m_tCustomTime = time(NULL);
m_iSecSlot = 0;
m_iMinSlot = 0;
m_iHourSlot = 0;
m_iDaySlot = 0;
}
void ServerVolume::CalcSlots(time_t tLocCurTime)
{
m_iSecSlot = (int)tLocCurTime % 60;
m_iMinSlot = ((int)tLocCurTime / 60) % 60;
m_iHourSlot = ((int)tLocCurTime % 86400) / 3600;
int iDaysSince1970 = (int)tLocCurTime / 86400;
m_iDaySlot = iDaysSince1970 - DAYS_UP_TO_2013_JAN_1 + 1;
if (0 <= m_iDaySlot && m_iDaySlot < DAYS_IN_TWENTY_YEARS)
m_secSlot = (int)locCurTime % 60;
m_minSlot = ((int)locCurTime / 60) % 60;
m_hourSlot = ((int)locCurTime % 86400) / 3600;
int daysSince1970 = (int)locCurTime / 86400;
m_daySlot = daysSince1970 - DAYS_UP_TO_2013_JAN_1 + 1;
if (0 <= m_daySlot && m_daySlot < DAYS_IN_TWENTY_YEARS)
{
int iCurDay = iDaysSince1970;
if (m_iFirstDay == 0 || m_iFirstDay > iCurDay)
int curDay = daysSince1970;
if (m_firstDay == 0 || m_firstDay > curDay)
{
m_iFirstDay = iCurDay;
m_firstDay = curDay;
}
m_iDaySlot = iCurDay - m_iFirstDay;
if (m_iDaySlot + 1 > (int)m_BytesPerDays.size())
m_daySlot = curDay - m_firstDay;
if (m_daySlot + 1 > (int)m_bytesPerDays.size())
{
m_BytesPerDays.resize(m_iDaySlot + 1);
m_bytesPerDays.resize(m_daySlot + 1);
}
}
else
{
m_iDaySlot = -1;
m_daySlot = -1;
}
}
void ServerVolume::AddData(int iBytes)
void ServerVolume::AddData(int bytes)
{
time_t tCurTime = time(NULL);
time_t tLocCurTime = tCurTime + g_pOptions->GetLocalTimeOffset();
time_t tLocDataTime = m_tDataTime + g_pOptions->GetLocalTimeOffset();
time_t curTime = Util::CurrentTime();
time_t locCurTime = curTime + g_WorkState->GetLocalTimeOffset();
time_t locDataTime = m_dataTime + g_WorkState->GetLocalTimeOffset();
int iLastMinSlot = m_iMinSlot;
int iLastHourSlot = m_iHourSlot;
int lastMinSlot = m_minSlot;
int lastHourSlot = m_hourSlot;
CalcSlots(tLocCurTime);
CalcSlots(locCurTime);
if (tLocCurTime != tLocDataTime)
if (locCurTime != locDataTime)
{
// clear seconds/minutes/hours slots if necessary
// also handle the backwards changes of system clock
int iTotalDelta = (int)(tLocCurTime - tLocDataTime);
int iDeltaSign = iTotalDelta >= 0 ? 1 : -1;
iTotalDelta = abs(iTotalDelta);
int totalDelta = (int)(locCurTime - locDataTime);
int deltaSign = totalDelta >= 0 ? 1 : -1;
totalDelta = abs(totalDelta);
int iSecDelta = iTotalDelta;
if (iDeltaSign < 0) iSecDelta++;
if (iSecDelta >= 60) iSecDelta = 60;
for (int i = 0; i < iSecDelta; i++)
int secDelta = totalDelta;
if (deltaSign < 0) secDelta++;
if (secDelta >= 60) secDelta = 60;
for (int i = 0; i < secDelta; i++)
{
int iNulSlot = m_iSecSlot - i * iDeltaSign;
if (iNulSlot < 0) iNulSlot += 60;
if (iNulSlot >= 60) iNulSlot -= 60;
m_BytesPerSeconds[iNulSlot] = 0;
int nulSlot = m_secSlot - i * deltaSign;
if (nulSlot < 0) nulSlot += 60;
if (nulSlot >= 60) nulSlot -= 60;
m_bytesPerSeconds[nulSlot] = 0;
}
int iMinDelta = iTotalDelta / 60;
if (iDeltaSign < 0) iMinDelta++;
if (abs(iMinDelta) >= 60) iMinDelta = 60;
if (iMinDelta == 0 && m_iMinSlot != iLastMinSlot) iMinDelta = 1;
for (int i = 0; i < iMinDelta; i++)
int minDelta = totalDelta / 60;
if (deltaSign < 0) minDelta++;
if (abs(minDelta) >= 60) minDelta = 60;
if (minDelta == 0 && m_minSlot != lastMinSlot) minDelta = 1;
for (int i = 0; i < minDelta; i++)
{
int iNulSlot = m_iMinSlot - i * iDeltaSign;
if (iNulSlot < 0) iNulSlot += 60;
if (iNulSlot >= 60) iNulSlot -= 60;
m_BytesPerMinutes[iNulSlot] = 0;
int nulSlot = m_minSlot - i * deltaSign;
if (nulSlot < 0) nulSlot += 60;
if (nulSlot >= 60) nulSlot -= 60;
m_bytesPerMinutes[nulSlot] = 0;
}
int iHourDelta = iTotalDelta / (60 * 60);
if (iDeltaSign < 0) iHourDelta++;
if (iHourDelta >= 24) iHourDelta = 24;
if (iHourDelta == 0 && m_iHourSlot != iLastHourSlot) iHourDelta = 1;
for (int i = 0; i < iHourDelta; i++)
int hourDelta = totalDelta / (60 * 60);
if (deltaSign < 0) hourDelta++;
if (hourDelta >= 24) hourDelta = 24;
if (hourDelta == 0 && m_hourSlot != lastHourSlot) hourDelta = 1;
for (int i = 0; i < hourDelta; i++)
{
int iNulSlot = m_iHourSlot - i * iDeltaSign;
if (iNulSlot < 0) iNulSlot += 24;
if (iNulSlot >= 24) iNulSlot -= 24;
m_BytesPerHours[iNulSlot] = 0;
int nulSlot = m_hourSlot - i * deltaSign;
if (nulSlot < 0) nulSlot += 24;
if (nulSlot >= 24) nulSlot -= 24;
m_bytesPerHours[nulSlot] = 0;
}
}
// add bytes to every slot
m_BytesPerSeconds[m_iSecSlot] += iBytes;
m_BytesPerMinutes[m_iMinSlot] += iBytes;
m_BytesPerHours[m_iHourSlot] += iBytes;
if (m_iDaySlot >= 0)
m_bytesPerSeconds[m_secSlot] += bytes;
m_bytesPerMinutes[m_minSlot] += bytes;
m_bytesPerHours[m_hourSlot] += bytes;
if (m_daySlot >= 0)
{
m_BytesPerDays[m_iDaySlot] += iBytes;
m_bytesPerDays[m_daySlot] += bytes;
}
m_lTotalBytes += iBytes;
m_lCustomBytes += iBytes;
m_totalBytes += bytes;
m_customBytes += bytes;
m_tDataTime = tCurTime;
m_dataTime = curTime;
}
void ServerVolume::ResetCustom()
{
m_lCustomBytes = 0;
m_tCustomTime = time(NULL);
m_customBytes = 0;
m_customTime = Util::CurrentTime();
}
void ServerVolume::LogDebugInfo()
@@ -172,38 +139,30 @@ void ServerVolume::LogDebugInfo()
for (int i = 0; i < 60; i++)
{
char szNum[30];
snprintf(szNum, 30, "[%i]=%lli ", i, m_BytesPerSeconds[i]);
msg.Append(szNum);
msg.AppendFmt("[%i]=%" PRIi64 " ", i, m_bytesPerSeconds[i]);
}
info("Secs: %s", msg.GetBuffer());
info("Secs: %s", *msg);
msg.Clear();
for (int i = 0; i < 60; i++)
{
char szNum[30];
snprintf(szNum, 30, "[%i]=%lli ", i, m_BytesPerMinutes[i]);
msg.Append(szNum);
msg.AppendFmt("[%i]=%" PRIi64 " ", i, m_bytesPerMinutes[i]);
}
info("Mins: %s", msg.GetBuffer());
info("Mins: %s", *msg);
msg.Clear();
for (int i = 0; i < 24; i++)
{
char szNum[30];
snprintf(szNum, 30, "[%i]=%lli ", i, m_BytesPerHours[i]);
msg.Append(szNum);
msg.AppendFmt("[%i]=%" PRIi64 " ", i, m_bytesPerHours[i]);
}
info("Hours: %s", msg.GetBuffer());
info("Hours: %s", *msg);
msg.Clear();
for (int i = 0; i < (int)m_BytesPerDays.size(); i++)
for (int i = 0; i < (int)m_bytesPerDays.size(); i++)
{
char szNum[30];
snprintf(szNum, 30, "[%i]=%lli ", m_iFirstDay + i, m_BytesPerDays[i]);
msg.Append(szNum);
msg.AppendFmt("[%i]=%" PRIi64 " ", m_firstDay + i, m_bytesPerDays[i]);
}
info("Days: %s", msg.GetBuffer());
info("Days: %s", *msg);
}
StatMeter::StatMeter()
@@ -211,61 +170,29 @@ StatMeter::StatMeter()
debug("Creating StatMeter");
ResetSpeedStat();
m_iAllBytes = 0;
m_tStartDownload = 0;
m_tPausedFrom = 0;
m_bStandBy = true;
m_tStartServer = 0;
m_tLastCheck = 0;
m_tLastTimeOffset = 0;
m_bStatChanged = false;
g_pLog->RegisterDebuggable(this);
}
StatMeter::~StatMeter()
{
debug("Destroying StatMeter");
// Cleanup
g_pLog->UnregisterDebuggable(this);
for (ServerVolumes::iterator it = m_ServerVolumes.begin(); it != m_ServerVolumes.end(); it++)
{
delete *it;
}
debug("StatMeter destroyed");
}
void StatMeter::Init()
{
m_tStartServer = time(NULL);
m_tLastCheck = m_tStartServer;
m_startServer = Util::CurrentTime();
m_lastCheck = m_startServer;
AdjustTimeOffset();
m_ServerVolumes.resize(1 + g_pServerPool->GetServers()->size());
m_ServerVolumes[0] = new ServerVolume();
for (Servers::iterator it = g_pServerPool->GetServers()->begin(); it != g_pServerPool->GetServers()->end(); it++)
{
NewsServer* pServer = *it;
m_ServerVolumes[pServer->GetID()] = new ServerVolume();
}
m_serverVolumes.resize(1 + g_ServerPool->GetServers()->size());
}
void StatMeter::AdjustTimeOffset()
{
time_t tUtcTime = time(NULL);
time_t utcTime = Util::CurrentTime();
tm tmSplittedTime;
gmtime_r(&tUtcTime, &tmSplittedTime);
gmtime_r(&utcTime, &tmSplittedTime);
tmSplittedTime.tm_isdst = -1;
time_t tLocTime = mktime(&tmSplittedTime);
time_t tLocalTimeDelta = tUtcTime - tLocTime;
g_pOptions->SetLocalTimeOffset((int)tLocalTimeDelta + g_pOptions->GetTimeCorrection());
m_tLastTimeOffset = tUtcTime;
time_t locTime = mktime(&tmSplittedTime);
time_t localTimeDelta = utcTime - locTime;
g_WorkState->SetLocalTimeOffset((int)localTimeDelta + g_Options->GetTimeCorrection());
m_lastTimeOffset = utcTime;
debug("UTC delta: %i (%i+%i)", g_pOptions->GetLocalTimeOffset(), (int)tLocalTimeDelta, g_pOptions->GetTimeCorrection());
debug("UTC delta: %i (%i+%i)", g_WorkState->GetLocalTimeOffset(), (int)localTimeDelta, g_Options->GetTimeCorrection());
}
/*
@@ -275,20 +202,20 @@ void StatMeter::AdjustTimeOffset()
*/
void StatMeter::IntervalCheck()
{
time_t m_tCurTime = time(NULL);
time_t tDiff = m_tCurTime - m_tLastCheck;
if (tDiff > 60 || tDiff < 0)
time_t m_curTime = Util::CurrentTime();
time_t diff = m_curTime - m_lastCheck;
if (diff > 60 || diff < 0)
{
m_tStartServer += tDiff + 1; // "1" because the method is called once per second
if (m_tStartDownload != 0 && !m_bStandBy)
m_startServer += diff + 1; // "1" because the method is called once per second
if (m_startDownload != 0 && !m_standBy)
{
m_tStartDownload += tDiff + 1;
m_startDownload += diff + 1;
}
AdjustTimeOffset();
}
else if (m_tLastTimeOffset > m_tCurTime ||
m_tCurTime - m_tLastTimeOffset > 60 * 60 * 3 ||
(m_tCurTime - m_tLastTimeOffset > 60 && !m_bStandBy))
else if (m_lastTimeOffset > m_curTime ||
m_curTime - m_lastTimeOffset > 60 * 60 * 3 ||
(m_curTime - m_lastTimeOffset > 60 && !m_standBy))
{
// checking time zone settings may prevent the device from entering sleep/hibernate mode
// check every minute if not in standby
@@ -296,254 +223,322 @@ void StatMeter::IntervalCheck()
AdjustTimeOffset();
}
m_tLastCheck = m_tCurTime;
m_lastCheck = m_curTime;
if (m_bStatChanged)
CheckQuota();
if (m_statChanged)
{
Save();
}
}
void StatMeter::EnterLeaveStandBy(bool bEnter)
void StatMeter::EnterLeaveStandBy(bool enter)
{
m_mutexStat.Lock();
m_bStandBy = bEnter;
if (bEnter)
Guard guard(m_statMutex);
m_standBy = enter;
if (enter)
{
m_tPausedFrom = time(NULL);
m_pausedFrom = Util::CurrentTime();
}
else
{
if (m_tStartDownload == 0)
if (m_startDownload == 0)
{
m_tStartDownload = time(NULL);
m_startDownload = Util::CurrentTime();
}
else
{
m_tStartDownload += time(NULL) - m_tPausedFrom;
m_startDownload += Util::CurrentTime() - m_pausedFrom;
}
m_tPausedFrom = 0;
m_pausedFrom = 0;
ResetSpeedStat();
}
m_mutexStat.Unlock();
}
void StatMeter::CalcTotalStat(int* iUpTimeSec, int* iDnTimeSec, long long* iAllBytes, bool* bStandBy)
void StatMeter::CalcTotalStat(int* upTimeSec, int* dnTimeSec, int64* allBytes, bool* standBy)
{
m_mutexStat.Lock();
if (m_tStartServer > 0)
Guard guard(m_statMutex);
if (m_startServer > 0)
{
*iUpTimeSec = (int)(time(NULL) - m_tStartServer);
*upTimeSec = (int)(Util::CurrentTime() - m_startServer);
}
else
{
*iUpTimeSec = 0;
*upTimeSec = 0;
}
*bStandBy = m_bStandBy;
if (m_bStandBy)
*standBy = m_standBy;
if (m_standBy)
{
*iDnTimeSec = (int)(m_tPausedFrom - m_tStartDownload);
*dnTimeSec = (int)(m_pausedFrom - m_startDownload);
}
else
{
*iDnTimeSec = (int)(time(NULL) - m_tStartDownload);
*dnTimeSec = (int)(Util::CurrentTime() - m_startDownload);
}
*iAllBytes = m_iAllBytes;
m_mutexStat.Unlock();
*allBytes = m_allBytes;
}
// Average speed in last 30 seconds
int StatMeter::CalcCurrentDownloadSpeed()
{
if (m_bStandBy)
if (m_standBy)
{
return 0;
}
int iTimeDiff = (int)time(NULL) - m_iSpeedStartTime * SPEEDMETER_SLOTSIZE;
if (iTimeDiff == 0)
int timeDiff = (int)Util::CurrentTime() - m_speedStartTime * SPEEDMETER_SLOTSIZE;
if (timeDiff == 0)
{
return 0;
}
return (int)(m_iSpeedTotalBytes / iTimeDiff);
return (int)(m_speedTotalBytes / timeDiff);
}
// Amount of data downloaded in current second
int StatMeter::CalcMomentaryDownloadSpeed()
{
time_t tCurTime = time(NULL);
int iSpeed = tCurTime == m_tCurSecTime ? m_iCurSecBytes : 0;
return iSpeed;
time_t curTime = Util::CurrentTime();
int speed = curTime == m_curSecTime ? m_curSecBytes : 0;
return speed;
}
void StatMeter::AddSpeedReading(int iBytes)
void StatMeter::AddSpeedReading(int bytes)
{
time_t tCurTime = time(NULL);
int iNowSlot = (int)tCurTime / SPEEDMETER_SLOTSIZE;
time_t curTime = Util::CurrentTime();
int nowSlot = (int)curTime / SPEEDMETER_SLOTSIZE;
if (g_pOptions->GetAccurateRate())
if (curTime != m_curSecTime)
{
m_mutexSpeed.Lock();
m_curSecTime = curTime;
m_curSecBytes = 0;
}
m_curSecBytes += bytes;
if (tCurTime != m_tCurSecTime)
{
m_tCurSecTime = tCurTime;
m_iCurSecBytes = 0;
}
m_iCurSecBytes += iBytes;
while (iNowSlot > m_iSpeedTime[m_iSpeedBytesIndex])
while (nowSlot > m_speedTime[m_speedBytesIndex])
{
//record bytes in next slot
m_iSpeedBytesIndex++;
if (m_iSpeedBytesIndex >= SPEEDMETER_SLOTS)
m_speedBytesIndex++;
if (m_speedBytesIndex >= SPEEDMETER_SLOTS)
{
m_iSpeedBytesIndex = 0;
m_speedBytesIndex = 0;
}
//Adjust counters with outgoing information.
m_iSpeedTotalBytes = m_iSpeedTotalBytes - (long long)m_iSpeedBytes[m_iSpeedBytesIndex];
m_speedTotalBytes = m_speedTotalBytes - (int64)m_speedBytes[m_speedBytesIndex];
//Note we should really use the start time of the next slot
//but its easier to just use the outgoing slot time. This
//will result in a small error.
m_iSpeedStartTime = m_iSpeedTime[m_iSpeedBytesIndex];
m_speedStartTime = m_speedTime[m_speedBytesIndex];
//Now reset.
m_iSpeedBytes[m_iSpeedBytesIndex] = 0;
m_iSpeedTime[m_iSpeedBytesIndex] = iNowSlot;
m_speedBytes[m_speedBytesIndex] = 0;
m_speedTime[m_speedBytesIndex] = nowSlot;
}
// Once per second recalculate summary field "m_iSpeedTotalBytes" to recover from possible synchronisation errors
if (tCurTime > m_tSpeedCorrection)
if (curTime > m_speedCorrection)
{
long long iSpeedTotalBytes = 0;
int64 speedTotalBytes = 0;
for (int i = 0; i < SPEEDMETER_SLOTS; i++)
{
iSpeedTotalBytes += m_iSpeedBytes[i];
speedTotalBytes += m_speedBytes[i];
}
m_iSpeedTotalBytes = iSpeedTotalBytes;
m_tSpeedCorrection = tCurTime;
m_speedTotalBytes = speedTotalBytes;
m_speedCorrection = curTime;
}
if (m_iSpeedTotalBytes == 0)
if (m_speedTotalBytes == 0)
{
m_iSpeedStartTime = iNowSlot;
}
m_iSpeedBytes[m_iSpeedBytesIndex] += iBytes;
m_iSpeedTotalBytes += iBytes;
m_iAllBytes += iBytes;
if (g_pOptions->GetAccurateRate())
{
m_mutexSpeed.Unlock();
m_speedStartTime = nowSlot;
}
m_speedBytes[m_speedBytesIndex] += bytes;
m_speedTotalBytes += bytes;
m_allBytes += bytes;
}
void StatMeter::ResetSpeedStat()
{
time_t tCurTime = time(NULL);
m_iSpeedStartTime = (int)tCurTime / SPEEDMETER_SLOTSIZE;
time_t curTime = Util::CurrentTime();
m_speedStartTime = (int)curTime / SPEEDMETER_SLOTSIZE;
for (int i = 0; i < SPEEDMETER_SLOTS; i++)
{
m_iSpeedBytes[i] = 0;
m_iSpeedTime[i] = m_iSpeedStartTime;
m_speedBytes[i] = 0;
m_speedTime[i] = m_speedStartTime;
}
m_iSpeedBytesIndex = 0;
m_iSpeedTotalBytes = 0;
m_tSpeedCorrection = tCurTime;
m_tCurSecTime = 0;
m_iCurSecBytes = 0;
m_speedBytesIndex = 0;
m_speedTotalBytes = 0;
m_speedCorrection = curTime;
m_curSecTime = 0;
m_curSecBytes = 0;
}
void StatMeter::LogDebugInfo()
{
info(" ---------- SpeedMeter");
int iSpeed = CalcCurrentDownloadSpeed() / 1024;
int iTimeDiff = (int)time(NULL) - m_iSpeedStartTime * SPEEDMETER_SLOTSIZE;
info(" Speed: %i", iSpeed);
info(" SpeedStartTime: %i", m_iSpeedStartTime);
info(" SpeedTotalBytes: %i", m_iSpeedTotalBytes);
info(" SpeedBytesIndex: %i", m_iSpeedBytesIndex);
info(" AllBytes: %i", m_iAllBytes);
info(" Time: %i", (int)time(NULL));
info(" TimeDiff: %i", iTimeDiff);
int speed = CalcCurrentDownloadSpeed() / 1024;
int timeDiff = (int)Util::CurrentTime() - m_speedStartTime * SPEEDMETER_SLOTSIZE;
info(" Speed: %i", speed);
info(" SpeedStartTime: %i", m_speedStartTime);
info(" SpeedTotalBytes: %" PRIi64, m_speedTotalBytes);
info(" SpeedBytesIndex: %i", m_speedBytesIndex);
info(" AllBytes: %" PRIi64, m_allBytes);
info(" Time: %i", (int)Util::CurrentTime());
info(" TimeDiff: %i", timeDiff);
for (int i=0; i < SPEEDMETER_SLOTS; i++)
{
info(" Bytes[%i]: %i, Time[%i]: %i", i, m_iSpeedBytes[i], i, m_iSpeedTime[i]);
info(" Bytes[%i]: %i, Time[%i]: %i", i, m_speedBytes[i], i, m_speedTime[i]);
}
m_mutexVolume.Lock();
Guard guard(m_volumeMutex);
int index = 0;
for (ServerVolumes::iterator it = m_ServerVolumes.begin(); it != m_ServerVolumes.end(); it++, index++)
for (ServerVolume& serverVolume : m_serverVolumes)
{
ServerVolume* pServerVolume = *it;
info(" ServerVolume %i", index);
pServerVolume->LogDebugInfo();
serverVolume.LogDebugInfo();
index++;
}
m_mutexVolume.Unlock();
}
void StatMeter::AddServerData(int iBytes, int iServerID)
void StatMeter::AddServerData(int bytes, int serverId)
{
if (iBytes == 0)
if (bytes == 0)
{
return;
}
m_mutexVolume.Lock();
m_ServerVolumes[0]->AddData(iBytes);
m_ServerVolumes[iServerID]->AddData(iBytes);
m_bStatChanged = true;
m_mutexVolume.Unlock();
Guard guard(m_volumeMutex);
m_serverVolumes[0].AddData(bytes);
m_serverVolumes[serverId].AddData(bytes);
m_statChanged = true;
}
ServerVolumes* StatMeter::LockServerVolumes()
GuardedServerVolumes StatMeter::GuardServerVolumes()
{
m_mutexVolume.Lock();
GuardedServerVolumes serverVolumes(&m_serverVolumes, &m_volumeMutex);
// update slots
for (ServerVolumes::iterator it = m_ServerVolumes.begin(); it != m_ServerVolumes.end(); it++)
for (ServerVolume& serverVolume : m_serverVolumes)
{
ServerVolume* pServerVolume = *it;
pServerVolume->AddData(0);
serverVolume.AddData(0);
}
return &m_ServerVolumes;
}
void StatMeter::UnlockServerVolumes()
{
m_mutexVolume.Unlock();
return serverVolumes;
}
void StatMeter::Save()
{
if (!g_pOptions->GetServerMode())
if (!g_Options->GetServerMode())
{
return;
}
m_mutexVolume.Lock();
g_pDiskState->SaveStats(g_pServerPool->GetServers(), &m_ServerVolumes);
m_bStatChanged = false;
m_mutexVolume.Unlock();
Guard guard(m_volumeMutex);
g_DiskState->SaveStats(g_ServerPool->GetServers(), &m_serverVolumes);
m_statChanged = false;
}
bool StatMeter::Load(bool* pPerfectServerMatch)
bool StatMeter::Load(bool* perfectServerMatch)
{
m_mutexVolume.Lock();
Guard guard(m_volumeMutex);
bool bOK = g_pDiskState->LoadStats(g_pServerPool->GetServers(), &m_ServerVolumes, pPerfectServerMatch);
bool ok = g_DiskState->LoadStats(g_ServerPool->GetServers(), &m_serverVolumes, perfectServerMatch);
for (ServerVolumes::iterator it = m_ServerVolumes.begin(); it != m_ServerVolumes.end(); it++)
for (ServerVolume& serverVolume : m_serverVolumes)
{
ServerVolume* pServerVolume = *it;
pServerVolume->CalcSlots(pServerVolume->GetDataTime() + g_pOptions->GetLocalTimeOffset());
serverVolume.CalcSlots(serverVolume.GetDataTime() + g_WorkState->GetLocalTimeOffset());
}
m_mutexVolume.Unlock();
return bOK;
return ok;
}
void StatMeter::CheckQuota()
{
if ((g_Options->GetDailyQuota() == 0 && g_Options->GetMonthlyQuota() == 0))
{
return;
}
int64 monthBytes, dayBytes;
CalcQuotaUsage(monthBytes, dayBytes);
bool monthlyQuotaReached = g_Options->GetMonthlyQuota() > 0 && monthBytes >= (int64)g_Options->GetMonthlyQuota() * 1024 * 1024;
bool dailyQuotaReached = g_Options->GetDailyQuota() > 0 && dayBytes >= (int64)g_Options->GetDailyQuota() * 1024 * 1024;
if (monthlyQuotaReached && !g_WorkState->GetQuotaReached())
{
warn("Monthly quota reached at %s", *Util::FormatSize(monthBytes));
}
else if (dailyQuotaReached && !g_WorkState->GetQuotaReached())
{
warn("Daily quota reached at %s", *Util::FormatSize(dayBytes));
}
else if (!monthlyQuotaReached && !dailyQuotaReached && g_WorkState->GetQuotaReached())
{
info("Quota lifted");
}
g_WorkState->SetQuotaReached(monthlyQuotaReached || dailyQuotaReached);
}
void StatMeter::CalcQuotaUsage(int64& monthBytes, int64& dayBytes)
{
Guard guard(m_volumeMutex);
ServerVolume totalVolume = m_serverVolumes[0];
time_t locTime = Util::CurrentTime() + g_WorkState->GetLocalTimeOffset();
int daySlot = (int)(locTime / 86400) - totalVolume.GetFirstDay();
dayBytes = 0;
if (daySlot < (int)totalVolume.BytesPerDays()->size())
{
dayBytes = totalVolume.BytesPerDays()->at(daySlot);
}
int elapsedSlots = CalcMonthSlots(totalVolume);
monthBytes = 0;
int endSlot = std::max(daySlot - elapsedSlots, -1);
for (int slot = daySlot; slot >= 0 && slot > endSlot; slot--)
{
if (slot < (int)totalVolume.BytesPerDays()->size())
{
monthBytes += totalVolume.BytesPerDays()->at(slot);
debug("adding slot %i: %i", slot, (int)(totalVolume.BytesPerDays()->at(slot) / 1024 / 1024));
}
}
debug("month volume: %i MB", (int)(monthBytes / 1024 / 1024));
}
int StatMeter::CalcMonthSlots(ServerVolume& volume)
{
int elapsedDays;
time_t locCurTime = Util::CurrentTime() + g_WorkState->GetLocalTimeOffset();
tm dayparts;
gmtime_r(&locCurTime, &dayparts);
if (g_Options->GetQuotaStartDay() > dayparts.tm_mday)
{
dayparts.tm_mon--;
dayparts.tm_mday = g_Options->GetQuotaStartDay();
time_t prevMonth = Util::Timegm(&dayparts);
tm prevparts;
gmtime_r(&prevMonth, &prevparts);
if (prevparts.tm_mday != g_Options->GetQuotaStartDay())
{
dayparts.tm_mday = 1;
dayparts.tm_mon++;
prevMonth = Util::Timegm(&dayparts);
}
elapsedDays = (int)(locCurTime - prevMonth) / 60 / 60 / 24 + 1;
}
else
{
elapsedDays = dayparts.tm_mday - g_Options->GetQuotaStartDay() + 1;
}
return elapsedDays;
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2014-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2014-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,128 +14,119 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef STATMETER_H
#define STATMETER_H
#include <vector>
#include <time.h>
#include "Log.h"
#include "Thread.h"
#include "Util.h"
class ServerVolume
{
public:
typedef std::vector<long long> VolumeArray;
typedef std::vector<int64> VolumeArray;
VolumeArray* BytesPerSeconds() { return &m_bytesPerSeconds; }
VolumeArray* BytesPerMinutes() { return &m_bytesPerMinutes; }
VolumeArray* BytesPerHours() { return &m_bytesPerHours; }
VolumeArray* BytesPerDays() { return &m_bytesPerDays; }
void SetFirstDay(int firstDay) { m_firstDay = firstDay; }
int GetFirstDay() { return m_firstDay; }
void SetTotalBytes(int64 totalBytes) { m_totalBytes = totalBytes; }
int64 GetTotalBytes() { return m_totalBytes; }
void SetCustomBytes(int64 customBytes) { m_customBytes = customBytes; }
int64 GetCustomBytes() { return m_customBytes; }
int GetSecSlot() { return m_secSlot; }
int GetMinSlot() { return m_minSlot; }
int GetHourSlot() { return m_hourSlot; }
int GetDaySlot() { return m_daySlot; }
time_t GetDataTime() { return m_dataTime; }
void SetDataTime(time_t dataTime) { m_dataTime = dataTime; }
time_t GetCustomTime() { return m_customTime; }
void SetCustomTime(time_t customTime) { m_customTime = customTime; }
void AddData(int bytes);
void CalcSlots(time_t locCurTime);
void ResetCustom();
void LogDebugInfo();
private:
VolumeArray m_BytesPerSeconds;
VolumeArray m_BytesPerMinutes;
VolumeArray m_BytesPerHours;
VolumeArray m_BytesPerDays;
int m_iFirstDay;
long long m_lTotalBytes;
long long m_lCustomBytes;
time_t m_tDataTime;
time_t m_tCustomTime;
int m_iSecSlot;
int m_iMinSlot;
int m_iHourSlot;
int m_iDaySlot;
public:
ServerVolume();
VolumeArray* BytesPerSeconds() { return &m_BytesPerSeconds; }
VolumeArray* BytesPerMinutes() { return &m_BytesPerMinutes; }
VolumeArray* BytesPerHours() { return &m_BytesPerHours; }
VolumeArray* BytesPerDays() { return &m_BytesPerDays; }
void SetFirstDay(int iFirstDay) { m_iFirstDay = iFirstDay; }
int GetFirstDay() { return m_iFirstDay; }
void SetTotalBytes(long long lTotalBytes) { m_lTotalBytes = lTotalBytes; }
long long GetTotalBytes() { return m_lTotalBytes; }
void SetCustomBytes(long long lCustomBytes) { m_lCustomBytes = lCustomBytes; }
long long GetCustomBytes() { return m_lCustomBytes; }
int GetSecSlot() { return m_iSecSlot; }
int GetMinSlot() { return m_iMinSlot; }
int GetHourSlot() { return m_iHourSlot; }
int GetDaySlot() { return m_iDaySlot; }
time_t GetDataTime() { return m_tDataTime; }
void SetDataTime(time_t tDataTime) { m_tDataTime = tDataTime; }
time_t GetCustomTime() { return m_tCustomTime; }
void SetCustomTime(time_t tCustomTime) { m_tCustomTime = tCustomTime; }
void AddData(int iBytes);
void CalcSlots(time_t tLocCurTime);
void ResetCustom();
void LogDebugInfo();
VolumeArray m_bytesPerSeconds = VolumeArray(60);
VolumeArray m_bytesPerMinutes = VolumeArray(60);
VolumeArray m_bytesPerHours = VolumeArray(24);
VolumeArray m_bytesPerDays;
int m_firstDay = 0;
int64 m_totalBytes = 0;
int64 m_customBytes = 0;
time_t m_dataTime = 0;
time_t m_customTime = Util::CurrentTime();
int m_secSlot = 0;
int m_minSlot = 0;
int m_hourSlot = 0;
int m_daySlot = 0;
};
typedef std::vector<ServerVolume*> ServerVolumes;
typedef std::vector<ServerVolume> ServerVolumes;
typedef GuardedPtr<ServerVolumes> GuardedServerVolumes;
class StatMeter : public Debuggable
{
private:
// speed meter
static const int SPEEDMETER_SLOTS = 30;
static const int SPEEDMETER_SLOTSIZE = 1; //Split elapsed time into this number of secs.
int m_iSpeedBytes[SPEEDMETER_SLOTS];
long long m_iSpeedTotalBytes;
int m_iSpeedTime[SPEEDMETER_SLOTS];
int m_iSpeedStartTime;
time_t m_tSpeedCorrection;
int m_iSpeedBytesIndex;
int m_iCurSecBytes;
time_t m_tCurSecTime;
Mutex m_mutexSpeed;
// time
long long m_iAllBytes;
time_t m_tStartServer;
time_t m_tLastCheck;
time_t m_tLastTimeOffset;
time_t m_tStartDownload;
time_t m_tPausedFrom;
bool m_bStandBy;
Mutex m_mutexStat;
// data volume
bool m_bStatChanged;
ServerVolumes m_ServerVolumes;
Mutex m_mutexVolume;
void ResetSpeedStat();
void AdjustTimeOffset();
public:
StatMeter();
void Init();
int CalcCurrentDownloadSpeed();
int CalcMomentaryDownloadSpeed();
void AddSpeedReading(int bytes);
void AddServerData(int bytes, int serverId);
void CalcTotalStat(int* upTimeSec, int* dnTimeSec, int64* allBytes, bool* standBy);
void CalcQuotaUsage(int64& monthBytes, int64& dayBytes);
void IntervalCheck();
void EnterLeaveStandBy(bool enter);
GuardedServerVolumes GuardServerVolumes();
void Save();
bool Load(bool* perfectServerMatch);
protected:
virtual void LogDebugInfo();
virtual void LogDebugInfo();
public:
StatMeter();
~StatMeter();
void Init();
int CalcCurrentDownloadSpeed();
int CalcMomentaryDownloadSpeed();
void AddSpeedReading(int iBytes);
void AddServerData(int iBytes, int iServerID);
void CalcTotalStat(int* iUpTimeSec, int* iDnTimeSec, long long* iAllBytes, bool* bStandBy);
bool GetStandBy() { return m_bStandBy; }
void IntervalCheck();
void EnterLeaveStandBy(bool bEnter);
ServerVolumes* LockServerVolumes();
void UnlockServerVolumes();
void Save();
bool Load(bool* pPerfectServerMatch);
private:
// speed meter
static const int SPEEDMETER_SLOTS = 30;
static const int SPEEDMETER_SLOTSIZE = 1; //Split elapsed time into this number of secs.
int m_speedBytes[SPEEDMETER_SLOTS];
int64 m_speedTotalBytes;
int m_speedTime[SPEEDMETER_SLOTS];
int m_speedStartTime;
time_t m_speedCorrection;
int m_speedBytesIndex;
int m_curSecBytes;
time_t m_curSecTime;
// time
int64 m_allBytes = 0;
time_t m_startServer = 0;
time_t m_lastCheck = 0;
time_t m_lastTimeOffset = 0;
time_t m_startDownload = 0;
time_t m_pausedFrom = 0;
bool m_standBy = true;
Mutex m_statMutex;
// data volume
bool m_statChanged = false;
ServerVolumes m_serverVolumes;
Mutex m_volumeMutex;
void ResetSpeedStat();
void AdjustTimeOffset();
void CheckQuota();
int CalcMonthSlots(ServerVolume& volume);
};
extern StatMeter* g_pStatMeter;
extern StatMeter* g_StatMeter;
#endif

View File

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

View File

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

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

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

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

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

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

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

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

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

View File

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

View File

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

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

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

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

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

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2013-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2013-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,265 +14,214 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include <errno.h>
#include "nzbget.h"
#include "Cleanup.h"
#include "Log.h"
#include "Util.h"
#include "FileSystem.h"
#include "ParParser.h"
#include "Options.h"
void MoveController::StartJob(PostInfo* pPostInfo)
void MoveController::StartJob(PostInfo* postInfo)
{
MoveController* pMoveController = new MoveController();
pMoveController->m_pPostInfo = pPostInfo;
pMoveController->SetAutoDestroy(false);
MoveController* moveController = new MoveController();
moveController->m_postInfo = postInfo;
moveController->SetAutoDestroy(false);
pPostInfo->SetPostThread(pMoveController);
postInfo->SetPostThread(moveController);
pMoveController->Start();
moveController->Start();
}
void MoveController::Run()
{
// the locking is needed for accessing the members of NZBInfo
DownloadQueue::Lock();
char szNZBName[1024];
strncpy(szNZBName, m_pPostInfo->GetNZBInfo()->GetName(), 1024);
szNZBName[1024-1] = '\0';
char szInfoName[1024];
snprintf(szInfoName, 1024, "move for %s", m_pPostInfo->GetNZBInfo()->GetName());
szInfoName[1024-1] = '\0';
SetInfoName(szInfoName);
strncpy(m_szInterDir, m_pPostInfo->GetNZBInfo()->GetDestDir(), 1024);
m_szInterDir[1024-1] = '\0';
m_pPostInfo->GetNZBInfo()->BuildFinalDirName(m_szDestDir, 1024);
m_szDestDir[1024-1] = '\0';
DownloadQueue::Unlock();
PrintMessage(Message::mkInfo, "Moving completed files for %s", szNZBName);
bool bOK = MoveFiles();
szInfoName[0] = 'M'; // uppercase
if (bOK)
BString<1024> nzbName;
{
PrintMessage(Message::mkInfo, "%s successful", szInfoName);
GuardedDownloadQueue guard = DownloadQueue::Guard();
nzbName = m_postInfo->GetNzbInfo()->GetName();
m_interDir = m_postInfo->GetNzbInfo()->GetDestDir();
m_destDir = m_postInfo->GetNzbInfo()->GetFinalDir();
}
BString<1024> infoName("move for %s", *nzbName);
SetInfoName(infoName);
if (m_destDir.Empty())
{
m_destDir = m_postInfo->GetNzbInfo()->BuildFinalDirName();
}
PrintMessage(Message::mkInfo, "Moving completed files for %s", *nzbName);
bool ok = MoveFiles();
infoName[0] = 'M'; // uppercase
if (ok)
{
PrintMessage(Message::mkInfo, "%s successful", *infoName);
// save new dest dir
DownloadQueue::Lock();
m_pPostInfo->GetNZBInfo()->SetDestDir(m_szDestDir);
m_pPostInfo->GetNZBInfo()->SetMoveStatus(NZBInfo::msSuccess);
DownloadQueue::Unlock();
GuardedDownloadQueue guard = DownloadQueue::Guard();
m_postInfo->GetNzbInfo()->SetDestDir(m_destDir);
m_postInfo->GetNzbInfo()->SetFinalDir("");
m_postInfo->GetNzbInfo()->SetMoveStatus(NzbInfo::msSuccess);
}
else
{
PrintMessage(Message::mkError, "%s failed", szInfoName);
m_pPostInfo->GetNZBInfo()->SetMoveStatus(NZBInfo::msFailure);
PrintMessage(Message::mkError, "%s failed", *infoName);
m_postInfo->GetNzbInfo()->SetMoveStatus(NzbInfo::msFailure);
}
m_pPostInfo->SetStage(PostInfo::ptQueued);
m_pPostInfo->SetWorking(false);
m_postInfo->SetWorking(false);
}
bool MoveController::MoveFiles()
{
char szErrBuf[1024];
if (!Util::ForceDirectories(m_szDestDir, szErrBuf, sizeof(szErrBuf)))
CString errmsg;
if (!FileSystem::ForceDirectories(m_destDir, errmsg))
{
PrintMessage(Message::mkError, "Could not create directory %s: %s", m_szDestDir, szErrBuf);
PrintMessage(Message::mkError, "Could not create directory %s: %s", *m_destDir, *errmsg);
return false;
}
bool bOK = true;
DirBrowser dir(m_szInterDir);
while (const char* filename = dir.Next())
bool ok = true;
{
if (strcmp(filename, ".") && strcmp(filename, ".."))
DirBrowser dir(m_interDir);
while (const char* filename = dir.Next())
{
char szSrcFile[1024];
snprintf(szSrcFile, 1024, "%s%c%s", m_szInterDir, PATH_SEPARATOR, filename);
szSrcFile[1024-1] = '\0';
BString<1024> srcFile("%s%c%s",* m_interDir, PATH_SEPARATOR, filename);
CString dstFile = FileSystem::MakeUniqueFilename(m_destDir, FileSystem::MakeValidFilename(filename));
bool hiddenFile = filename[0] == '.';
char szDstFile[1024];
Util::MakeUniqueFilename(szDstFile, 1024, m_szDestDir, filename);
bool bHiddenFile = filename[0] == '.';
if (!bHiddenFile)
if (!hiddenFile)
{
PrintMessage(Message::mkInfo, "Moving file %s to %s", Util::BaseFileName(szSrcFile), m_szDestDir);
PrintMessage(Message::mkInfo, "Moving file %s to %s", FileSystem::BaseFileName(srcFile), *m_destDir);
}
if (!Util::MoveFile(szSrcFile, szDstFile) && !bHiddenFile)
if (!FileSystem::MoveFile(srcFile, dstFile) && !hiddenFile)
{
char szErrBuf[256];
PrintMessage(Message::mkError, "Could not move file %s to %s: %s", szSrcFile, szDstFile,
Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf)));
bOK = false;
PrintMessage(Message::mkError, "Could not move file %s to %s: %s",
*srcFile, *dstFile, *FileSystem::GetLastErrorMessage());
ok = false;
}
}
}
} // make sure "DirBrowser dir" is destroyed (and has closed its handle) before we trying to delete the directory
if (bOK && !Util::DeleteDirectoryWithContent(m_szInterDir, szErrBuf, sizeof(szErrBuf)))
if (ok && !FileSystem::DeleteDirectoryWithContent(m_interDir, errmsg))
{
PrintMessage(Message::mkWarning, "Could not delete intermediate directory %s: %s", m_szInterDir, szErrBuf);
PrintMessage(Message::mkWarning, "Could not delete intermediate directory %s: %s", *m_interDir, *errmsg);
}
return bOK;
return ok;
}
void MoveController::AddMessage(Message::EKind eKind, const char* szText)
void MoveController::AddMessage(Message::EKind kind, const char* text)
{
m_pPostInfo->GetNZBInfo()->AddMessage(eKind, szText);
m_postInfo->GetNzbInfo()->AddMessage(kind, text);
}
void CleanupController::StartJob(PostInfo* pPostInfo)
void CleanupController::StartJob(PostInfo* postInfo)
{
CleanupController* pCleanupController = new CleanupController();
pCleanupController->m_pPostInfo = pPostInfo;
pCleanupController->SetAutoDestroy(false);
CleanupController* cleanupController = new CleanupController();
cleanupController->m_postInfo = postInfo;
cleanupController->SetAutoDestroy(false);
pPostInfo->SetPostThread(pCleanupController);
postInfo->SetPostThread(cleanupController);
pCleanupController->Start();
cleanupController->Start();
}
void CleanupController::Run()
{
// the locking is needed for accessing the members of NZBInfo
DownloadQueue::Lock();
char szNZBName[1024];
strncpy(szNZBName, m_pPostInfo->GetNZBInfo()->GetName(), 1024);
szNZBName[1024-1] = '\0';
char szInfoName[1024];
snprintf(szInfoName, 1024, "cleanup for %s", m_pPostInfo->GetNZBInfo()->GetName());
szInfoName[1024-1] = '\0';
SetInfoName(szInfoName);
strncpy(m_szDestDir, m_pPostInfo->GetNZBInfo()->GetDestDir(), 1024);
m_szDestDir[1024-1] = '\0';
bool bInterDir = strlen(g_pOptions->GetInterDir()) > 0 &&
!strncmp(m_szDestDir, g_pOptions->GetInterDir(), strlen(g_pOptions->GetInterDir()));
if (bInterDir)
BString<1024> nzbName;
CString destDir;
CString finalDir;
{
m_pPostInfo->GetNZBInfo()->BuildFinalDirName(m_szFinalDir, 1024);
m_szFinalDir[1024-1] = '\0';
GuardedDownloadQueue guard = DownloadQueue::Guard();
nzbName = m_postInfo->GetNzbInfo()->GetName();
destDir = m_postInfo->GetNzbInfo()->GetDestDir();
finalDir = m_postInfo->GetNzbInfo()->GetFinalDir();
}
BString<1024> infoName("cleanup for %s", *nzbName);
SetInfoName(infoName);
PrintMessage(Message::mkInfo, "Cleaning up %s", *nzbName);
bool deleted = false;
bool ok = Cleanup(destDir, &deleted);
if (ok && !finalDir.Empty())
{
bool deleted2 = false;
ok = Cleanup(finalDir, &deleted2);
deleted = deleted || deleted2;
}
infoName[0] = 'C'; // uppercase
if (ok && deleted)
{
PrintMessage(Message::mkInfo, "%s successful", *infoName);
m_postInfo->GetNzbInfo()->SetCleanupStatus(NzbInfo::csSuccess);
}
else if (ok)
{
PrintMessage(Message::mkInfo, "Nothing to cleanup for %s", *nzbName);
m_postInfo->GetNzbInfo()->SetCleanupStatus(NzbInfo::csSuccess);
}
else
{
m_szFinalDir[0] = '\0';
PrintMessage(Message::mkError, "%s failed", *infoName);
m_postInfo->GetNzbInfo()->SetCleanupStatus(NzbInfo::csFailure);
}
DownloadQueue::Unlock();
PrintMessage(Message::mkInfo, "Cleaning up %s", szNZBName);
bool bDeleted = false;
bool bOK = Cleanup(m_szDestDir, &bDeleted);
if (bOK && m_szFinalDir[0] != '\0')
{
bool bDeleted2 = false;
bOK = Cleanup(m_szFinalDir, &bDeleted2);
bDeleted = bDeleted || bDeleted2;
}
szInfoName[0] = 'C'; // uppercase
if (bOK && bDeleted)
{
PrintMessage(Message::mkInfo, "%s successful", szInfoName);
m_pPostInfo->GetNZBInfo()->SetCleanupStatus(NZBInfo::csSuccess);
}
else if (bOK)
{
PrintMessage(Message::mkInfo, "Nothing to cleanup for %s", szNZBName);
m_pPostInfo->GetNZBInfo()->SetCleanupStatus(NZBInfo::csSuccess);
}
else
{
PrintMessage(Message::mkError, "%s failed", szInfoName);
m_pPostInfo->GetNZBInfo()->SetCleanupStatus(NZBInfo::csFailure);
}
m_pPostInfo->SetStage(PostInfo::ptQueued);
m_pPostInfo->SetWorking(false);
m_postInfo->SetWorking(false);
}
bool CleanupController::Cleanup(const char* szDestDir, bool *bDeleted)
bool CleanupController::Cleanup(const char* destDir, bool *deleted)
{
*bDeleted = false;
bool bOK = true;
*deleted = false;
bool ok = true;
DirBrowser dir(szDestDir);
DirBrowser dir(destDir);
while (const char* filename = dir.Next())
{
char szFullFilename[1024];
snprintf(szFullFilename, 1024, "%s%c%s", szDestDir, PATH_SEPARATOR, filename);
szFullFilename[1024-1] = '\0';
BString<1024> fullFilename("%s%c%s", destDir, PATH_SEPARATOR, filename);
bool bIsDir = Util::DirectoryExists(szFullFilename);
bool isDir = FileSystem::DirectoryExists(fullFilename);
if (strcmp(filename, ".") && strcmp(filename, "..") && bIsDir)
if (isDir)
{
bOK &= Cleanup(szFullFilename, bDeleted);
ok &= Cleanup(fullFilename, deleted);
}
// check file extension
bool bDeleteIt = Util::MatchFileExt(filename, g_pOptions->GetExtCleanupDisk(), ",;") && !bIsDir;
bool deleteIt = Util::MatchFileExt(filename, g_Options->GetExtCleanupDisk(), ",;") && !isDir;
if (bDeleteIt)
if (deleteIt)
{
PrintMessage(Message::mkInfo, "Deleting file %s", filename);
if (remove(szFullFilename) != 0)
if (!FileSystem::DeleteFile(fullFilename))
{
char szErrBuf[256];
PrintMessage(Message::mkError, "Could not delete file %s: %s", szFullFilename, Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf)));
bOK = false;
PrintMessage(Message::mkError, "Could not delete file %s: %s", *fullFilename,
*FileSystem::GetLastErrorMessage());
ok = false;
}
*bDeleted = true;
*deleted = true;
}
}
return bOK;
return ok;
}
void CleanupController::AddMessage(Message::EKind eKind, const char* szText)
void CleanupController::AddMessage(Message::EKind kind, const char* text)
{
m_pPostInfo->GetNZBInfo()->AddMessage(eKind, szText);
m_postInfo->GetNzbInfo()->AddMessage(kind, text);
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2013-2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2013-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,18 +14,14 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CLEANUP_H
#define CLEANUP_H
#include "NString.h"
#include "Log.h"
#include "Thread.h"
#include "DownloadInfo.h"
@@ -33,36 +29,36 @@
class MoveController : public Thread, public ScriptController
{
private:
PostInfo* m_pPostInfo;
char m_szInterDir[1024];
char m_szDestDir[1024];
bool MoveFiles();
public:
virtual void Run();
static void StartJob(PostInfo* postInfo);
protected:
virtual void AddMessage(Message::EKind eKind, const char* szText);
virtual void AddMessage(Message::EKind kind, const char* text);
public:
virtual void Run();
static void StartJob(PostInfo* pPostInfo);
private:
PostInfo* m_postInfo;
CString m_interDir;
CString m_destDir;
bool MoveFiles();
};
class CleanupController : public Thread, public ScriptController
{
private:
PostInfo* m_pPostInfo;
char m_szDestDir[1024];
char m_szFinalDir[1024];
bool Cleanup(const char* szDestDir, bool *bDeleted);
public:
virtual void Run();
static void StartJob(PostInfo* postInfo);
protected:
virtual void AddMessage(Message::EKind eKind, const char* szText);
virtual void AddMessage(Message::EKind kind, const char* text);
public:
virtual void Run();
static void StartJob(PostInfo* pPostInfo);
private:
PostInfo* m_postInfo;
CString m_destDir;
CString m_finalDir;
bool Cleanup(const char* destDir, bool *deleted);
};
#endif

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
* This file is part of nzbget. See <http://nzbget.net>.
*
* Copyright (C) 2015 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2015-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -14,36 +14,15 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include <errno.h>
#include "nzbget.h"
#include "DupeMatcher.h"
#include "Log.h"
#include "Util.h"
#include "FileSystem.h"
#include "Options.h"
#include "Script.h"
#include "Thread.h"
@@ -51,64 +30,57 @@
class RarLister : public Thread, public ScriptController
{
private:
DupeMatcher* m_pOwner;
long long m_lMaxSize;
bool m_bCompressed;
bool m_bLastSizeMax;
long long m_lExpectedSize;
char* m_szFilenameBuf;
int m_iFilenameBufLen;
char m_szLastFilename[1024];
DupeMatcher* m_owner;
int64 m_maxSize;
bool m_compressed;
bool m_lastSizeMax;
int64 m_expectedSize;
char* m_filenameBuf;
int m_filenameBufLen;
BString<1024> m_lastFilename;
protected:
virtual void AddMessage(Message::EKind eKind, const char* szText);
virtual void AddMessage(Message::EKind kind, const char* text);
public:
virtual void Run();
static bool FindLargestFile(DupeMatcher* pOwner, const char* szDirectory,
char* szFilenameBuf, int iFilenameBufLen, long long lExpectedSize,
int iTimeoutSec, long long* pMaxSize, bool* pCompressed);
virtual void Run();
static bool FindLargestFile(DupeMatcher* owner, const char* directory,
char* filenameBuf, int filenameBufLen, int64 expectedSize,
int timeoutSec, int64* maxSize, bool* compressed);
};
bool RarLister::FindLargestFile(DupeMatcher* pOwner, const char* szDirectory,
char* szFilenameBuf, int iFilenameBufLen, long long lExpectedSize,
int iTimeoutSec, long long* pMaxSize, bool* pCompressed)
bool RarLister::FindLargestFile(DupeMatcher* owner, const char* directory,
char* filenameBuf, int filenameBufLen, int64 expectedSize,
int timeoutSec, int64* maxSize, bool* compressed)
{
RarLister unrar;
unrar.m_pOwner = pOwner;
unrar.m_lExpectedSize = lExpectedSize;
unrar.m_lMaxSize = -1;
unrar.m_bCompressed = false;
unrar.m_bLastSizeMax = false;
unrar.m_szFilenameBuf = szFilenameBuf;
unrar.m_iFilenameBufLen = iFilenameBufLen;
unrar.m_owner = owner;
unrar.m_expectedSize = expectedSize;
unrar.m_maxSize = -1;
unrar.m_compressed = false;
unrar.m_lastSizeMax = false;
unrar.m_filenameBuf = filenameBuf;
unrar.m_filenameBufLen = filenameBufLen;
char** pCmdArgs = NULL;
if (!Util::SplitCommandLine(g_pOptions->GetUnrarCmd(), &pCmdArgs))
std::vector<CString> cmdArgs = Util::SplitCommandLine(g_Options->GetUnrarCmd());
if (cmdArgs.empty())
{
return false;
}
const char* szUnrarPath = *pCmdArgs;
unrar.SetScript(szUnrarPath);
const char* unrarPath = cmdArgs[0];
unrar.SetArgs({unrarPath, "lt", "*.rar"});
unrar.SetWorkingDir(directory);
const char* szArgs[4];
szArgs[0] = szUnrarPath;
szArgs[1] = "lt";
szArgs[2] = "*.rar";
szArgs[3] = NULL;
unrar.SetArgs(szArgs, false);
unrar.SetWorkingDir(szDirectory);
time_t curTime = time(NULL);
time_t curTime = Util::CurrentTime();
unrar.Start();
// wait up to iTimeoutSec for unrar output
while (unrar.IsRunning() &&
curTime + iTimeoutSec > time(NULL) &&
curTime >= time(NULL)) // in a case clock was changed
curTime + timeoutSec > Util::CurrentTime() &&
curTime >= Util::CurrentTime()) // in a case clock was changed
{
usleep(200 * 1000);
Util::Sleep(200);
}
if (unrar.IsRunning())
@@ -119,17 +91,11 @@ bool RarLister::FindLargestFile(DupeMatcher* pOwner, const char* szDirectory,
// wait until terminated or killed
while (unrar.IsRunning())
{
usleep(200 * 1000);
Util::Sleep(200);
}
for (char** szArgPtr = pCmdArgs; *szArgPtr; szArgPtr++)
{
free(*szArgPtr);
}
free(pCmdArgs);
*pMaxSize = unrar.m_lMaxSize;
*pCompressed = unrar.m_bCompressed;
*maxSize = unrar.m_maxSize;
*compressed = unrar.m_compressed;
return true;
}
@@ -139,36 +105,35 @@ void RarLister::Run()
Execute();
}
void RarLister::AddMessage(Message::EKind eKind, const char* szText)
void RarLister::AddMessage(Message::EKind kind, const char* text)
{
if (!strncasecmp(szText, "Archive: ", 9))
if (!strncasecmp(text, "Archive: ", 9))
{
m_pOwner->PrintMessage(Message::mkDetail, "Reading file %s", szText + 9);
m_owner->PrintMessage(Message::mkDetail, "Reading file %s", text + 9);
}
else if (!strncasecmp(szText, " Name: ", 14))
else if (!strncasecmp(text, " Name: ", 14))
{
strncpy(m_szLastFilename, szText + 14, sizeof(m_szLastFilename));
m_szLastFilename[sizeof(m_szLastFilename)-1] = '\0';
m_lastFilename = text + 14;
}
else if (!strncasecmp(szText, " Size: ", 14))
else if (!strncasecmp(text, " Size: ", 14))
{
m_bLastSizeMax = false;
long long lSize = atoll(szText + 14);
if (lSize > m_lMaxSize)
m_lastSizeMax = false;
int64 size = atoll(text + 14);
if (size > m_maxSize)
{
m_lMaxSize = lSize;
m_bLastSizeMax = true;
strncpy(m_szFilenameBuf, m_szLastFilename, m_iFilenameBufLen);
m_szFilenameBuf[m_iFilenameBufLen-1] = '\0';
m_maxSize = size;
m_lastSizeMax = true;
strncpy(m_filenameBuf, m_lastFilename, m_filenameBufLen);
m_filenameBuf[m_filenameBufLen-1] = '\0';
}
return;
}
if (m_bLastSizeMax && !strncasecmp(szText, " Compression: ", 14))
if (m_lastSizeMax && !strncasecmp(text, " Compression: ", 14))
{
m_bCompressed = !strstr(szText, " -m0");
if (m_lMaxSize > m_lExpectedSize ||
DupeMatcher::SizeDiffOK(m_lMaxSize, m_lExpectedSize, 20))
m_compressed = !strstr(text, " -m0");
if (m_maxSize > m_expectedSize ||
DupeMatcher::SizeDiffOK(m_maxSize, m_expectedSize, 20))
{
// alread found the largest file, aborting unrar
Terminate();
@@ -177,84 +142,66 @@ void RarLister::AddMessage(Message::EKind eKind, const char* szText)
}
DupeMatcher::DupeMatcher(const char* szDestDir, long long lExpectedSize)
bool DupeMatcher::SizeDiffOK(int64 size1, int64 size2, int maxDiffPercent)
{
m_szDestDir = strdup(szDestDir);
m_lExpectedSize = lExpectedSize;
m_lMaxSize = -1;
m_bCompressed = false;
}
DupeMatcher::~DupeMatcher()
{
free(m_szDestDir);
}
bool DupeMatcher::SizeDiffOK(long long lSize1, long long lSize2, int iMaxDiffPercent)
{
if (lSize1 == 0 || lSize2 == 0)
if (size1 == 0 || size2 == 0)
{
return false;
}
long long lDiff = lSize1 - lSize2;
lDiff = lDiff > 0 ? lDiff : -lDiff;
long long lMax = lSize1 > lSize2 ? lSize1 : lSize2;
int lDiffPercent = (int)(lDiff * 100 / lMax);
return lDiffPercent < iMaxDiffPercent;
int64 diff = size1 - size2;
diff = diff > 0 ? diff : -diff;
int64 max = size1 > size2 ? size1 : size2;
int diffPercent = (int)(diff * 100 / max);
return diffPercent < maxDiffPercent;
}
bool DupeMatcher::Prepare()
{
char szFilename[1024];
FindLargestFile(m_szDestDir, szFilename, sizeof(szFilename), &m_lMaxSize, &m_bCompressed);
bool bSizeOK = SizeDiffOK(m_lMaxSize, m_lExpectedSize, 20);
PrintMessage(Message::mkDetail, "Found main file %s with size %lli bytes%s",
szFilename, m_lMaxSize, bSizeOK ? "" : ", size mismatch");
return bSizeOK;
char filename[1024];
FindLargestFile(m_destDir, filename, sizeof(filename), &m_maxSize, &m_compressed);
bool sizeOK = SizeDiffOK(m_maxSize, m_expectedSize, 20);
PrintMessage(Message::mkDetail, "Found main file %s with size %" PRIi64 " bytes%s",
filename, m_maxSize, sizeOK ? "" : ", size mismatch");
return sizeOK;
}
bool DupeMatcher::MatchDupeContent(const char* szDupeDir)
bool DupeMatcher::MatchDupeContent(const char* dupeDir)
{
long long lDupeMaxSize = 0;
bool lDupeCompressed = false;
char szFilename[1024];
FindLargestFile(szDupeDir, szFilename, sizeof(szFilename), &lDupeMaxSize, &lDupeCompressed);
bool bOK = lDupeMaxSize == m_lMaxSize && lDupeCompressed == m_bCompressed;
PrintMessage(Message::mkDetail, "Found main file %s with size %lli bytes%s",
szFilename, m_lMaxSize, bOK ? "" : ", size mismatch");
return bOK;
int64 dupeMaxSize = 0;
bool dupeCompressed = false;
char filename[1024];
FindLargestFile(dupeDir, filename, sizeof(filename), &dupeMaxSize, &dupeCompressed);
bool ok = dupeMaxSize == m_maxSize && dupeCompressed == m_compressed;
PrintMessage(Message::mkDetail, "Found main file %s with size %" PRIi64 " bytes%s",
filename, m_maxSize, ok ? "" : ", size mismatch");
return ok;
}
void DupeMatcher::FindLargestFile(const char* szDirectory, char* szFilenameBuf, int iBufLen,
long long* pMaxSize, bool* pCompressed)
void DupeMatcher::FindLargestFile(const char* directory, char* filenameBuf, int bufLen,
int64* maxSize, bool* compressed)
{
*pMaxSize = 0;
*pCompressed = false;
*maxSize = 0;
*compressed = false;
DirBrowser dir(szDirectory);
DirBrowser dir(directory);
while (const char* filename = dir.Next())
{
if (strcmp(filename, ".") && strcmp(filename, ".."))
BString<1024> fullFilename("%s%c%s", directory, PATH_SEPARATOR, filename);
int64 fileSize = FileSystem::FileSize(fullFilename);
if (fileSize > *maxSize)
{
char szFullFilename[1024];
snprintf(szFullFilename, 1024, "%s%c%s", szDirectory, PATH_SEPARATOR, filename);
szFullFilename[1024-1] = '\0';
*maxSize = fileSize;
strncpy(filenameBuf, filename, bufLen);
filenameBuf[bufLen-1] = '\0';
}
long long lFileSize = Util::FileSize(szFullFilename);
if (lFileSize > *pMaxSize)
{
*pMaxSize = lFileSize;
strncpy(szFilenameBuf, filename, iBufLen);
szFilenameBuf[iBufLen-1] = '\0';
}
if (Util::MatchFileExt(filename, ".rar", ","))
{
RarLister::FindLargestFile(this, szDirectory, szFilenameBuf, iBufLen,
m_lMaxSize, 60, pMaxSize, pCompressed);
return;
}
if (Util::MatchFileExt(filename, ".rar", ","))
{
RarLister::FindLargestFile(this, directory, filenameBuf, bufLen,
m_maxSize, 60, maxSize, compressed);
return;
}
}
}

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