Codebase list nzbget / eb0bf72
Merge tag 'upstream/18.1+dfsg' Upstream version 18.1+dfsg Andreas Moog 6 years ago
167 changed file(s) with 9636 addition(s) and 3930 deletion(s). Raw diff Collapse all Expand all
6060 # NZBGet specific
6161 nzbget
6262 code_revision.cpp
63 *.temp
64 *.pyc
65 pytest.ini
0 nzbget-18.1:
1 - fixed: crash during download caused by a race condition;
2 - fixed: sleep mode did not work on Windows;
3 - fixed: queue was not saved after deleting of queued post-jobs;
4 - fixed: possible crash at the end of post-processing;
5 - fixed: "undefined" in reorder extension scripts.
6
7 nzbget-18.0:
8 - automatic deobfuscation of rar-archives without par-files:
9 - obfuscated downloads not having par-files can now be successfully
10 unpacked;
11 - also helps with downloads where rar-files were obfuscated before
12 creating par-files;
13 - new options "RarRename" and "UnpackIgnoreExt";
14 - multi post-processing:
15 - in addition to classic post-processing strategy where items are
16 processed one after another it is now possible to post-process
17 multiple items at the same time;
18 - new option "PostStrategy" to choose from four: sequential, balanced,
19 aggressive, rocket;
20 - in "balanced" strategy downloads needing repair do not block other
21 items which are processed sequentially but simultaneously with
22 repairing item;
23 - in "aggressive" mode up to three items are post-processed at the same
24 time and in "rocket" mode up to six items (including up to two repair
25 tasks);
26 - unified extension scripts settings:
27 - options "PostScript", "QueueScript", "ScanScript" and "FeedScript"
28 were replaced with one option "Extensions";
29 - users don't need to know the technical details os extension scripts as
30 all scripts are now can be selected at one place;
31 - easier activation of complex extension scripts which previously needed
32 to be selected in multiple options for their proper work;
33 - reordering download queue with drag and drop in web-interface:
34 - new actions "GroupMoveBefore" and "GroupMoveAfter" in API-method
35 "editqueue";
36 - priorities are now displayed as a column instead of badge; that makes it
37 possible to manually sort on priority;
38 - removed vertical lines in tables; looks better in combination with new
39 priority column;
40 - keyboard shortcuts in web-interface;
41 - improved UI to prevent accidental deletion of many items:
42 - visual indication of records selected on other pages;
43 - extra warning when deleting many records from history;
44 - additional options in "custom pause dialog";
45 - better handing of damaged par2-files in par-renamer:
46 - if par-renamer can't load a (damaged) par2-file then another par2-file
47 is downloaded and par-renamer tries again;
48 - reverted non-strict par2-filename matching to handle article subjects
49 with non-parseable filenames;
50 - better handling of obfuscated par-files;
51 - splitted option "Retries" into "ArticleRetries" and "UrlRetries"; option
52 "RetryInterval" into "ArticleInterval" and "UrlInterval";
53 - scheduler tasks can be started at program launch:
54 - use asterisk as TaskX.Time;
55 - graceful termination of scheduler scripts:
56 - scripts receive signal SIGINT (CTRL+BREAK on Windows) before
57 termination;
58 - added support for nZEDb attributes in rss feeds;
59 - better cleanup handling: if parameter "unpack" is disabled for an nzb-file
60 the cleanup isn't performed for it;
61 - fields containing passwords are now displayed as protected fields;
62 - showing password-badge for nzbs with passwords;
63 - allow control of what tab is shown when opening web-interface:
64 add "#downloads", "#history", "#messages" or "#settings" to the URL,
65 for example "http://localhost:6789/#history" or
66 "http://localhost:6789/index.html#history";
67 - functional testing to ensure program quality:
68 - implemented built-in simple nntp server to be used for functional
69 testing;
70 - created a number of tests;
71 - new features come with additional tests;
72 - improved API-method "append" in combination with duplicate check; method
73 returns nzb-id also for items added straight to history;
74 - removed parameter "offset" from api-method "editqueue":
75 - when needed the "offset" is now passed within parameter "Args" as
76 string;
77 - old method signature is supported for compatibility;
78 - improved error reporting on feed parse errors;
79 - highlighting selected rows with alternative colors;
80 - improved selecting of par2-file for repair;
81 - splitted config section "Download Queue" and moved many options into new
82 section "Connection";
83 - disabled SSLv3 in built-in web-server;
84 - multiple recipients in the example pp-script "EMail.py";
85 - added compatibility with openssl 1.1.0;
86 - fixed TLS handshake error when using GnuTLS;
87 - fixed: sorting of selected items may give wrong results;
88 - fixed: search box filter in feed view were not reset.
89
090 nzbget-17.1:
191 - adjustments and fixes for "Retry failed articles" function, better handling
292 of certain corner cases;
8989 daemon/postprocess/DupeMatcher.h \
9090 daemon/postprocess/ParChecker.cpp \
9191 daemon/postprocess/ParChecker.h \
92 daemon/postprocess/ParCoordinator.cpp \
93 daemon/postprocess/ParCoordinator.h \
9492 daemon/postprocess/ParParser.cpp \
9593 daemon/postprocess/ParParser.h \
9694 daemon/postprocess/ParRenamer.cpp \
9795 daemon/postprocess/ParRenamer.h \
9896 daemon/postprocess/PrePostProcessor.cpp \
9997 daemon/postprocess/PrePostProcessor.h \
98 daemon/postprocess/RarRenamer.cpp \
99 daemon/postprocess/RarRenamer.h \
100 daemon/postprocess/RarReader.cpp \
101 daemon/postprocess/RarReader.h \
102 daemon/postprocess/Rename.cpp \
103 daemon/postprocess/Rename.h \
104 daemon/postprocess/Repair.cpp \
105 daemon/postprocess/Repair.h \
100106 daemon/postprocess/Unpack.cpp \
101107 daemon/postprocess/Unpack.h \
102108 daemon/queue/DiskState.cpp \
145151 daemon/util/FileSystem.h \
146152 daemon/util/Util.cpp \
147153 daemon/util/Util.h \
154 daemon/nserv/NServMain.h \
155 daemon/nserv/NServMain.cpp \
156 daemon/nserv/NServFrontend.h \
157 daemon/nserv/NServFrontend.cpp \
158 daemon/nserv/NntpServer.h \
159 daemon/nserv/NntpServer.cpp \
160 daemon/nserv/NzbGenerator.h \
161 daemon/nserv/NzbGenerator.cpp \
162 daemon/nserv/YEncoder.h \
163 daemon/nserv/YEncoder.cpp \
148164 code_revision.cpp
149165
150166 if WITH_PAR2
173189 lib/par2/md5.cpp \
174190 lib/par2/md5.h \
175191 lib/par2/par2cmdline.h \
176 lib/par2/par2creatorsourcefile.cpp \
177 lib/par2/par2creatorsourcefile.h \
178192 lib/par2/par2fileformat.cpp \
179193 lib/par2/par2fileformat.h \
180194 lib/par2/par2repairer.cpp \
204218 -I$(srcdir)/daemon/queue \
205219 -I$(srcdir)/daemon/remote \
206220 -I$(srcdir)/daemon/util \
221 -I$(srcdir)/daemon/nserv \
207222 -I$(srcdir)/lib/par2
208223
209224 if WITH_TESTS
217232 tests/main/OptionsTest.cpp \
218233 tests/feed/FeedFilterTest.cpp \
219234 tests/postprocess/DupeMatcherTest.cpp \
235 tests/postprocess/RarRenamerTest.cpp \
236 tests/postprocess/RarReaderTest.cpp \
220237 tests/queue/NzbFileTest.cpp \
221238 tests/nntp/ServerPoolTest.cpp \
222239 tests/util/FileSystemTest.cpp \
365382 tests/testdata/parchecker/testfile.par2 \
366383 tests/testdata/parchecker/testfile.vol00+1.PAR2 \
367384 tests/testdata/parchecker/testfile.vol01+2.PAR2 \
368 tests/testdata/parchecker/testfile.vol03+3.PAR2
385 tests/testdata/parchecker/testfile.vol03+3.PAR2 \
386 tests/testdata/rarrenamer/testfile3.part01.rar \
387 tests/testdata/rarrenamer/testfile3.part02.rar \
388 tests/testdata/rarrenamer/testfile3.part03.rar \
389 tests/testdata/rarrenamer/testfile5.part01.rar \
390 tests/testdata/rarrenamer/testfile5.part02.rar \
391 tests/testdata/rarrenamer/testfile5.part03.rar \
392 tests/testdata/rarrenamer/testfile3oldnam.rar \
393 tests/testdata/rarrenamer/testfile3oldnam.r00 \
394 tests/testdata/rarrenamer/testfile3oldnam.r01 \
395 tests/testdata/rarrenamer/testfile3encdata.part01.rar \
396 tests/testdata/rarrenamer/testfile3encdata.part02.rar \
397 tests/testdata/rarrenamer/testfile3encdata.part03.rar \
398 tests/testdata/rarrenamer/testfile3encnam.part01.rar \
399 tests/testdata/rarrenamer/testfile3encnam.part02.rar \
400 tests/testdata/rarrenamer/testfile3encnam.part03.rar \
401 tests/testdata/rarrenamer/testfile5encdata.part01.rar \
402 tests/testdata/rarrenamer/testfile5encdata.part02.rar \
403 tests/testdata/rarrenamer/testfile5encdata.part03.rar \
404 tests/testdata/rarrenamer/testfile5encnam.part01.rar \
405 tests/testdata/rarrenamer/testfile5encnam.part02.rar \
406 tests/testdata/rarrenamer/testfile5encnam.part03.rar
369407
370408 # Install
371409 dist_doc_DATA = $(doc_FILES)
8383 @WITH_PAR2_TRUE@ lib/par2/md5.cpp \
8484 @WITH_PAR2_TRUE@ lib/par2/md5.h \
8585 @WITH_PAR2_TRUE@ lib/par2/par2cmdline.h \
86 @WITH_PAR2_TRUE@ lib/par2/par2creatorsourcefile.cpp \
87 @WITH_PAR2_TRUE@ lib/par2/par2creatorsourcefile.h \
8886 @WITH_PAR2_TRUE@ lib/par2/par2fileformat.cpp \
8987 @WITH_PAR2_TRUE@ lib/par2/par2fileformat.h \
9088 @WITH_PAR2_TRUE@ lib/par2/par2repairer.cpp \
111109 @WITH_TESTS_TRUE@ tests/main/CommandLineParserTest.cpp \
112110 @WITH_TESTS_TRUE@ tests/main/OptionsTest.cpp \
113111 @WITH_TESTS_TRUE@ tests/feed/FeedFilterTest.cpp \
114 @WITH_TESTS_TRUE@ tests/postprocess/ParCheckerTest.cpp \
115 @WITH_TESTS_TRUE@ tests/postprocess/ParRenamerTest.cpp \
116112 @WITH_TESTS_TRUE@ tests/postprocess/DupeMatcherTest.cpp \
113 @WITH_TESTS_TRUE@ tests/postprocess/RarRenamerTest.cpp \
114 @WITH_TESTS_TRUE@ tests/postprocess/RarReaderTest.cpp \
117115 @WITH_TESTS_TRUE@ tests/queue/NzbFileTest.cpp \
118116 @WITH_TESTS_TRUE@ tests/nntp/ServerPoolTest.cpp \
119117 @WITH_TESTS_TRUE@ tests/util/FileSystemTest.cpp \
120118 @WITH_TESTS_TRUE@ tests/util/NStringTest.cpp \
121119 @WITH_TESTS_TRUE@ tests/util/UtilTest.cpp
122120
123 @WITH_TESTS_TRUE@am__append_3 = \
121 @WITH_PAR2_TRUE@@WITH_TESTS_TRUE@am__append_3 = \
122 @WITH_PAR2_TRUE@@WITH_TESTS_TRUE@ tests/postprocess/ParCheckerTest.cpp \
123 @WITH_PAR2_TRUE@@WITH_TESTS_TRUE@ tests/postprocess/ParRenamerTest.cpp
124
125 @WITH_TESTS_TRUE@am__append_4 = \
124126 @WITH_TESTS_TRUE@ -I$(srcdir)/lib/catch \
125127 @WITH_TESTS_TRUE@ -I$(srcdir)/tests/suite
126128
190192 daemon/postprocess/DupeMatcher.h \
191193 daemon/postprocess/ParChecker.cpp \
192194 daemon/postprocess/ParChecker.h \
193 daemon/postprocess/ParCoordinator.cpp \
194 daemon/postprocess/ParCoordinator.h \
195195 daemon/postprocess/ParParser.cpp \
196196 daemon/postprocess/ParParser.h \
197197 daemon/postprocess/ParRenamer.cpp \
198198 daemon/postprocess/ParRenamer.h \
199199 daemon/postprocess/PrePostProcessor.cpp \
200200 daemon/postprocess/PrePostProcessor.h \
201 daemon/postprocess/Unpack.cpp daemon/postprocess/Unpack.h \
202 daemon/queue/DiskState.cpp daemon/queue/DiskState.h \
203 daemon/queue/DownloadInfo.cpp daemon/queue/DownloadInfo.h \
204 daemon/queue/DupeCoordinator.cpp \
201 daemon/postprocess/RarRenamer.cpp \
202 daemon/postprocess/RarRenamer.h \
203 daemon/postprocess/RarReader.cpp \
204 daemon/postprocess/RarReader.h daemon/postprocess/Rename.cpp \
205 daemon/postprocess/Rename.h daemon/postprocess/Repair.cpp \
206 daemon/postprocess/Repair.h daemon/postprocess/Unpack.cpp \
207 daemon/postprocess/Unpack.h daemon/queue/DiskState.cpp \
208 daemon/queue/DiskState.h daemon/queue/DownloadInfo.cpp \
209 daemon/queue/DownloadInfo.h daemon/queue/DupeCoordinator.cpp \
205210 daemon/queue/DupeCoordinator.h \
206211 daemon/queue/HistoryCoordinator.cpp \
207212 daemon/queue/HistoryCoordinator.h daemon/queue/NzbFile.cpp \
222227 daemon/util/Thread.cpp daemon/util/Thread.h \
223228 daemon/util/Service.cpp daemon/util/Service.h \
224229 daemon/util/FileSystem.cpp daemon/util/FileSystem.h \
225 daemon/util/Util.cpp daemon/util/Util.h code_revision.cpp \
226 lib/par2/commandline.cpp lib/par2/commandline.h \
227 lib/par2/crc.cpp lib/par2/crc.h lib/par2/creatorpacket.cpp \
228 lib/par2/creatorpacket.h lib/par2/criticalpacket.cpp \
229 lib/par2/criticalpacket.h lib/par2/datablock.cpp \
230 lib/par2/datablock.h lib/par2/descriptionpacket.cpp \
231 lib/par2/descriptionpacket.h lib/par2/diskfile.cpp \
232 lib/par2/diskfile.h lib/par2/filechecksummer.cpp \
233 lib/par2/filechecksummer.h lib/par2/galois.cpp \
234 lib/par2/galois.h lib/par2/letype.h lib/par2/mainpacket.cpp \
235 lib/par2/mainpacket.h lib/par2/md5.cpp lib/par2/md5.h \
236 lib/par2/par2cmdline.h lib/par2/par2creatorsourcefile.cpp \
237 lib/par2/par2creatorsourcefile.h lib/par2/par2fileformat.cpp \
238 lib/par2/par2fileformat.h lib/par2/par2repairer.cpp \
239 lib/par2/par2repairer.h lib/par2/par2repairersourcefile.cpp \
230 daemon/util/Util.cpp daemon/util/Util.h \
231 daemon/nserv/NServMain.h daemon/nserv/NServMain.cpp \
232 daemon/nserv/NServFrontend.h daemon/nserv/NServFrontend.cpp \
233 daemon/nserv/NntpServer.h daemon/nserv/NntpServer.cpp \
234 daemon/nserv/NzbGenerator.h daemon/nserv/NzbGenerator.cpp \
235 daemon/nserv/YEncoder.h daemon/nserv/YEncoder.cpp \
236 code_revision.cpp lib/par2/commandline.cpp \
237 lib/par2/commandline.h lib/par2/crc.cpp lib/par2/crc.h \
238 lib/par2/creatorpacket.cpp lib/par2/creatorpacket.h \
239 lib/par2/criticalpacket.cpp lib/par2/criticalpacket.h \
240 lib/par2/datablock.cpp lib/par2/datablock.h \
241 lib/par2/descriptionpacket.cpp lib/par2/descriptionpacket.h \
242 lib/par2/diskfile.cpp lib/par2/diskfile.h \
243 lib/par2/filechecksummer.cpp lib/par2/filechecksummer.h \
244 lib/par2/galois.cpp lib/par2/galois.h lib/par2/letype.h \
245 lib/par2/mainpacket.cpp lib/par2/mainpacket.h lib/par2/md5.cpp \
246 lib/par2/md5.h lib/par2/par2cmdline.h \
247 lib/par2/par2fileformat.cpp lib/par2/par2fileformat.h \
248 lib/par2/par2repairer.cpp lib/par2/par2repairer.h \
249 lib/par2/par2repairersourcefile.cpp \
240250 lib/par2/par2repairersourcefile.h lib/par2/parheaders.cpp \
241251 lib/par2/parheaders.h lib/par2/recoverypacket.cpp \
242252 lib/par2/recoverypacket.h lib/par2/reedsolomon.cpp \
247257 tests/suite/TestMain.h tests/suite/TestUtil.cpp \
248258 tests/suite/TestUtil.h tests/main/CommandLineParserTest.cpp \
249259 tests/main/OptionsTest.cpp tests/feed/FeedFilterTest.cpp \
250 tests/postprocess/ParCheckerTest.cpp \
251 tests/postprocess/ParRenamerTest.cpp \
252260 tests/postprocess/DupeMatcherTest.cpp \
261 tests/postprocess/RarRenamerTest.cpp \
262 tests/postprocess/RarReaderTest.cpp \
253263 tests/queue/NzbFileTest.cpp tests/nntp/ServerPoolTest.cpp \
254264 tests/util/FileSystemTest.cpp tests/util/NStringTest.cpp \
255 tests/util/UtilTest.cpp
265 tests/util/UtilTest.cpp tests/postprocess/ParCheckerTest.cpp \
266 tests/postprocess/ParRenamerTest.cpp
256267 @WITH_PAR2_TRUE@am__objects_1 = commandline.$(OBJEXT) crc.$(OBJEXT) \
257268 @WITH_PAR2_TRUE@ creatorpacket.$(OBJEXT) \
258269 @WITH_PAR2_TRUE@ criticalpacket.$(OBJEXT) datablock.$(OBJEXT) \
259270 @WITH_PAR2_TRUE@ descriptionpacket.$(OBJEXT) diskfile.$(OBJEXT) \
260271 @WITH_PAR2_TRUE@ filechecksummer.$(OBJEXT) galois.$(OBJEXT) \
261272 @WITH_PAR2_TRUE@ mainpacket.$(OBJEXT) md5.$(OBJEXT) \
262 @WITH_PAR2_TRUE@ par2creatorsourcefile.$(OBJEXT) \
263273 @WITH_PAR2_TRUE@ par2fileformat.$(OBJEXT) \
264274 @WITH_PAR2_TRUE@ par2repairer.$(OBJEXT) \
265275 @WITH_PAR2_TRUE@ par2repairersourcefile.$(OBJEXT) \
271281 @WITH_TESTS_TRUE@ CommandLineParserTest.$(OBJEXT) \
272282 @WITH_TESTS_TRUE@ OptionsTest.$(OBJEXT) \
273283 @WITH_TESTS_TRUE@ FeedFilterTest.$(OBJEXT) \
274 @WITH_TESTS_TRUE@ ParCheckerTest.$(OBJEXT) \
275 @WITH_TESTS_TRUE@ ParRenamerTest.$(OBJEXT) \
276284 @WITH_TESTS_TRUE@ DupeMatcherTest.$(OBJEXT) \
277 @WITH_TESTS_TRUE@ NzbFileTest.$(OBJEXT) \
285 @WITH_TESTS_TRUE@ RarRenamerTest.$(OBJEXT) \
286 @WITH_TESTS_TRUE@ RarReaderTest.$(OBJEXT) NzbFileTest.$(OBJEXT) \
278287 @WITH_TESTS_TRUE@ ServerPoolTest.$(OBJEXT) \
279288 @WITH_TESTS_TRUE@ FileSystemTest.$(OBJEXT) \
280289 @WITH_TESTS_TRUE@ NStringTest.$(OBJEXT) UtilTest.$(OBJEXT)
290 @WITH_PAR2_TRUE@@WITH_TESTS_TRUE@am__objects_3 = \
291 @WITH_PAR2_TRUE@@WITH_TESTS_TRUE@ ParCheckerTest.$(OBJEXT) \
292 @WITH_PAR2_TRUE@@WITH_TESTS_TRUE@ ParRenamerTest.$(OBJEXT)
281293 am_nzbget_OBJECTS = Connection.$(OBJEXT) TlsSocket.$(OBJEXT) \
282294 WebDownloader.$(OBJEXT) FeedScript.$(OBJEXT) \
283295 NzbScript.$(OBJEXT) PostScript.$(OBJEXT) QueueScript.$(OBJEXT) \
293305 Decoder.$(OBJEXT) NewsServer.$(OBJEXT) \
294306 NntpConnection.$(OBJEXT) ServerPool.$(OBJEXT) \
295307 StatMeter.$(OBJEXT) Cleanup.$(OBJEXT) DupeMatcher.$(OBJEXT) \
296 ParChecker.$(OBJEXT) ParCoordinator.$(OBJEXT) \
297 ParParser.$(OBJEXT) ParRenamer.$(OBJEXT) \
298 PrePostProcessor.$(OBJEXT) Unpack.$(OBJEXT) \
299 DiskState.$(OBJEXT) DownloadInfo.$(OBJEXT) \
308 ParChecker.$(OBJEXT) ParParser.$(OBJEXT) ParRenamer.$(OBJEXT) \
309 PrePostProcessor.$(OBJEXT) RarRenamer.$(OBJEXT) \
310 RarReader.$(OBJEXT) Rename.$(OBJEXT) Repair.$(OBJEXT) \
311 Unpack.$(OBJEXT) DiskState.$(OBJEXT) DownloadInfo.$(OBJEXT) \
300312 DupeCoordinator.$(OBJEXT) HistoryCoordinator.$(OBJEXT) \
301313 NzbFile.$(OBJEXT) QueueCoordinator.$(OBJEXT) \
302314 QueueEditor.$(OBJEXT) Scanner.$(OBJEXT) \
305317 WebServer.$(OBJEXT) XmlRpc.$(OBJEXT) Log.$(OBJEXT) \
306318 NString.$(OBJEXT) Observer.$(OBJEXT) Script.$(OBJEXT) \
307319 Thread.$(OBJEXT) Service.$(OBJEXT) FileSystem.$(OBJEXT) \
308 Util.$(OBJEXT) code_revision.$(OBJEXT) $(am__objects_1) \
309 $(am__objects_2)
320 Util.$(OBJEXT) NServMain.$(OBJEXT) NServFrontend.$(OBJEXT) \
321 NntpServer.$(OBJEXT) NzbGenerator.$(OBJEXT) YEncoder.$(OBJEXT) \
322 code_revision.$(OBJEXT) $(am__objects_1) $(am__objects_2) \
323 $(am__objects_3)
310324 nzbget_OBJECTS = $(am_nzbget_OBJECTS)
311325 nzbget_LDADD = $(LDADD)
312326 am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
442456 mkdir_p = @mkdir_p@
443457 ncurses_CFLAGS = @ncurses_CFLAGS@
444458 ncurses_LIBS = @ncurses_LIBS@
459 nettle_CFLAGS = @nettle_CFLAGS@
460 nettle_LIBS = @nettle_LIBS@
445461 oldincludedir = @oldincludedir@
446462 openssl_CFLAGS = @openssl_CFLAGS@
447463 openssl_LIBS = @openssl_LIBS@
503519 daemon/postprocess/DupeMatcher.h \
504520 daemon/postprocess/ParChecker.cpp \
505521 daemon/postprocess/ParChecker.h \
506 daemon/postprocess/ParCoordinator.cpp \
507 daemon/postprocess/ParCoordinator.h \
508522 daemon/postprocess/ParParser.cpp \
509523 daemon/postprocess/ParParser.h \
510524 daemon/postprocess/ParRenamer.cpp \
511525 daemon/postprocess/ParRenamer.h \
512526 daemon/postprocess/PrePostProcessor.cpp \
513527 daemon/postprocess/PrePostProcessor.h \
514 daemon/postprocess/Unpack.cpp daemon/postprocess/Unpack.h \
515 daemon/queue/DiskState.cpp daemon/queue/DiskState.h \
516 daemon/queue/DownloadInfo.cpp daemon/queue/DownloadInfo.h \
517 daemon/queue/DupeCoordinator.cpp \
528 daemon/postprocess/RarRenamer.cpp \
529 daemon/postprocess/RarRenamer.h \
530 daemon/postprocess/RarReader.cpp \
531 daemon/postprocess/RarReader.h daemon/postprocess/Rename.cpp \
532 daemon/postprocess/Rename.h daemon/postprocess/Repair.cpp \
533 daemon/postprocess/Repair.h daemon/postprocess/Unpack.cpp \
534 daemon/postprocess/Unpack.h daemon/queue/DiskState.cpp \
535 daemon/queue/DiskState.h daemon/queue/DownloadInfo.cpp \
536 daemon/queue/DownloadInfo.h daemon/queue/DupeCoordinator.cpp \
518537 daemon/queue/DupeCoordinator.h \
519538 daemon/queue/HistoryCoordinator.cpp \
520539 daemon/queue/HistoryCoordinator.h daemon/queue/NzbFile.cpp \
535554 daemon/util/Thread.cpp daemon/util/Thread.h \
536555 daemon/util/Service.cpp daemon/util/Service.h \
537556 daemon/util/FileSystem.cpp daemon/util/FileSystem.h \
538 daemon/util/Util.cpp daemon/util/Util.h code_revision.cpp \
539 $(am__append_1) $(am__append_2)
557 daemon/util/Util.cpp daemon/util/Util.h \
558 daemon/nserv/NServMain.h daemon/nserv/NServMain.cpp \
559 daemon/nserv/NServFrontend.h daemon/nserv/NServFrontend.cpp \
560 daemon/nserv/NntpServer.h daemon/nserv/NntpServer.cpp \
561 daemon/nserv/NzbGenerator.h daemon/nserv/NzbGenerator.cpp \
562 daemon/nserv/YEncoder.h daemon/nserv/YEncoder.cpp \
563 code_revision.cpp $(am__append_1) $(am__append_2) \
564 $(am__append_3)
540565 AM_CPPFLAGS = -I$(srcdir)/daemon/connect -I$(srcdir)/daemon/extension \
541566 -I$(srcdir)/daemon/feed -I$(srcdir)/daemon/frontend \
542567 -I$(srcdir)/daemon/main -I$(srcdir)/daemon/nntp \
543568 -I$(srcdir)/daemon/postprocess -I$(srcdir)/daemon/queue \
544569 -I$(srcdir)/daemon/remote -I$(srcdir)/daemon/util \
545 -I$(srcdir)/lib/par2 $(am__append_3)
570 -I$(srcdir)/daemon/nserv -I$(srcdir)/lib/par2 $(am__append_4)
546571 EXTRA_DIST = \
547572 $(windows_FILES) \
548573 $(osx_FILES) \
674699 tests/testdata/parchecker/testfile.par2 \
675700 tests/testdata/parchecker/testfile.vol00+1.PAR2 \
676701 tests/testdata/parchecker/testfile.vol01+2.PAR2 \
677 tests/testdata/parchecker/testfile.vol03+3.PAR2
702 tests/testdata/parchecker/testfile.vol03+3.PAR2 \
703 tests/testdata/rarrenamer/testfile3.part01.rar \
704 tests/testdata/rarrenamer/testfile3.part02.rar \
705 tests/testdata/rarrenamer/testfile3.part03.rar \
706 tests/testdata/rarrenamer/testfile5.part01.rar \
707 tests/testdata/rarrenamer/testfile5.part02.rar \
708 tests/testdata/rarrenamer/testfile5.part03.rar \
709 tests/testdata/rarrenamer/testfile3oldnam.rar \
710 tests/testdata/rarrenamer/testfile3oldnam.r00 \
711 tests/testdata/rarrenamer/testfile3oldnam.r01 \
712 tests/testdata/rarrenamer/testfile3encdata.part01.rar \
713 tests/testdata/rarrenamer/testfile3encdata.part02.rar \
714 tests/testdata/rarrenamer/testfile3encdata.part03.rar \
715 tests/testdata/rarrenamer/testfile3encnam.part01.rar \
716 tests/testdata/rarrenamer/testfile3encnam.part02.rar \
717 tests/testdata/rarrenamer/testfile3encnam.part03.rar \
718 tests/testdata/rarrenamer/testfile5encdata.part01.rar \
719 tests/testdata/rarrenamer/testfile5encdata.part02.rar \
720 tests/testdata/rarrenamer/testfile5encdata.part03.rar \
721 tests/testdata/rarrenamer/testfile5encnam.part01.rar \
722 tests/testdata/rarrenamer/testfile5encnam.part02.rar \
723 tests/testdata/rarrenamer/testfile5encnam.part03.rar
678724
679725
680726 # Install
833879 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/LoggableFrontend.Po@am__quote@
834880 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Maintenance.Po@am__quote@
835881 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NCursesFrontend.Po@am__quote@
882 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NServFrontend.Po@am__quote@
883 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NServMain.Po@am__quote@
836884 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NString.Po@am__quote@
837885 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NStringTest.Po@am__quote@
838886 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NewsServer.Po@am__quote@
839887 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NntpConnection.Po@am__quote@
888 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NntpServer.Po@am__quote@
840889 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NzbFile.Po@am__quote@
841890 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NzbFileTest.Po@am__quote@
891 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NzbGenerator.Po@am__quote@
842892 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NzbScript.Po@am__quote@
843893 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Observer.Po@am__quote@
844894 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Options.Po@am__quote@
845895 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/OptionsTest.Po@am__quote@
846896 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ParChecker.Po@am__quote@
847897 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ParCheckerTest.Po@am__quote@
848 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ParCoordinator.Po@am__quote@
849898 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ParParser.Po@am__quote@
850899 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ParRenamer.Po@am__quote@
851900 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ParRenamerTest.Po@am__quote@
854903 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/QueueCoordinator.Po@am__quote@
855904 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/QueueEditor.Po@am__quote@
856905 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/QueueScript.Po@am__quote@
906 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/RarReader.Po@am__quote@
907 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/RarReaderTest.Po@am__quote@
908 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/RarRenamer.Po@am__quote@
909 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/RarRenamerTest.Po@am__quote@
857910 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/RemoteClient.Po@am__quote@
858911 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/RemoteServer.Po@am__quote@
912 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Rename.Po@am__quote@
913 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Repair.Po@am__quote@
859914 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ScanScript.Po@am__quote@
860915 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Scanner.Po@am__quote@
861916 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Scheduler.Po@am__quote@
878933 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/WebDownloader.Po@am__quote@
879934 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/WebServer.Po@am__quote@
880935 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/XmlRpc.Po@am__quote@
936 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/YEncoder.Po@am__quote@
881937 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/code_revision.Po@am__quote@
882938 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/commandline.Po@am__quote@
883939 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc.Po@am__quote@
891947 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mainpacket.Po@am__quote@
892948 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/md5.Po@am__quote@
893949 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nzbget.Po@am__quote@
894 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/par2creatorsourcefile.Po@am__quote@
895950 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/par2fileformat.Po@am__quote@
896951 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/par2repairer.Po@am__quote@
897952 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/par2repairersourcefile.Po@am__quote@
14051460 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
14061461 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ParChecker.obj `if test -f 'daemon/postprocess/ParChecker.cpp'; then $(CYGPATH_W) 'daemon/postprocess/ParChecker.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/ParChecker.cpp'; fi`
14071462
1408 ParCoordinator.o: daemon/postprocess/ParCoordinator.cpp
1409 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ParCoordinator.o -MD -MP -MF "$(DEPDIR)/ParCoordinator.Tpo" -c -o ParCoordinator.o `test -f 'daemon/postprocess/ParCoordinator.cpp' || echo '$(srcdir)/'`daemon/postprocess/ParCoordinator.cpp; \
1410 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ParCoordinator.Tpo" "$(DEPDIR)/ParCoordinator.Po"; else rm -f "$(DEPDIR)/ParCoordinator.Tpo"; exit 1; fi
1411 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/ParCoordinator.cpp' object='ParCoordinator.o' libtool=no @AMDEPBACKSLASH@
1412 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
1413 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ParCoordinator.o `test -f 'daemon/postprocess/ParCoordinator.cpp' || echo '$(srcdir)/'`daemon/postprocess/ParCoordinator.cpp
1414
1415 ParCoordinator.obj: daemon/postprocess/ParCoordinator.cpp
1416 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ParCoordinator.obj -MD -MP -MF "$(DEPDIR)/ParCoordinator.Tpo" -c -o ParCoordinator.obj `if test -f 'daemon/postprocess/ParCoordinator.cpp'; then $(CYGPATH_W) 'daemon/postprocess/ParCoordinator.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/ParCoordinator.cpp'; fi`; \
1417 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ParCoordinator.Tpo" "$(DEPDIR)/ParCoordinator.Po"; else rm -f "$(DEPDIR)/ParCoordinator.Tpo"; exit 1; fi
1418 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/ParCoordinator.cpp' object='ParCoordinator.obj' libtool=no @AMDEPBACKSLASH@
1419 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
1420 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ParCoordinator.obj `if test -f 'daemon/postprocess/ParCoordinator.cpp'; then $(CYGPATH_W) 'daemon/postprocess/ParCoordinator.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/ParCoordinator.cpp'; fi`
1421
14221463 ParParser.o: daemon/postprocess/ParParser.cpp
14231464 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ParParser.o -MD -MP -MF "$(DEPDIR)/ParParser.Tpo" -c -o ParParser.o `test -f 'daemon/postprocess/ParParser.cpp' || echo '$(srcdir)/'`daemon/postprocess/ParParser.cpp; \
14241465 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ParParser.Tpo" "$(DEPDIR)/ParParser.Po"; else rm -f "$(DEPDIR)/ParParser.Tpo"; exit 1; fi
14611502 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
14621503 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o PrePostProcessor.obj `if test -f 'daemon/postprocess/PrePostProcessor.cpp'; then $(CYGPATH_W) 'daemon/postprocess/PrePostProcessor.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/PrePostProcessor.cpp'; fi`
14631504
1505 RarRenamer.o: daemon/postprocess/RarRenamer.cpp
1506 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT RarRenamer.o -MD -MP -MF "$(DEPDIR)/RarRenamer.Tpo" -c -o RarRenamer.o `test -f 'daemon/postprocess/RarRenamer.cpp' || echo '$(srcdir)/'`daemon/postprocess/RarRenamer.cpp; \
1507 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/RarRenamer.Tpo" "$(DEPDIR)/RarRenamer.Po"; else rm -f "$(DEPDIR)/RarRenamer.Tpo"; exit 1; fi
1508 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/RarRenamer.cpp' object='RarRenamer.o' libtool=no @AMDEPBACKSLASH@
1509 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
1510 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o RarRenamer.o `test -f 'daemon/postprocess/RarRenamer.cpp' || echo '$(srcdir)/'`daemon/postprocess/RarRenamer.cpp
1511
1512 RarRenamer.obj: daemon/postprocess/RarRenamer.cpp
1513 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT RarRenamer.obj -MD -MP -MF "$(DEPDIR)/RarRenamer.Tpo" -c -o RarRenamer.obj `if test -f 'daemon/postprocess/RarRenamer.cpp'; then $(CYGPATH_W) 'daemon/postprocess/RarRenamer.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/RarRenamer.cpp'; fi`; \
1514 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/RarRenamer.Tpo" "$(DEPDIR)/RarRenamer.Po"; else rm -f "$(DEPDIR)/RarRenamer.Tpo"; exit 1; fi
1515 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/RarRenamer.cpp' object='RarRenamer.obj' libtool=no @AMDEPBACKSLASH@
1516 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
1517 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o RarRenamer.obj `if test -f 'daemon/postprocess/RarRenamer.cpp'; then $(CYGPATH_W) 'daemon/postprocess/RarRenamer.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/RarRenamer.cpp'; fi`
1518
1519 RarReader.o: daemon/postprocess/RarReader.cpp
1520 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT RarReader.o -MD -MP -MF "$(DEPDIR)/RarReader.Tpo" -c -o RarReader.o `test -f 'daemon/postprocess/RarReader.cpp' || echo '$(srcdir)/'`daemon/postprocess/RarReader.cpp; \
1521 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/RarReader.Tpo" "$(DEPDIR)/RarReader.Po"; else rm -f "$(DEPDIR)/RarReader.Tpo"; exit 1; fi
1522 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/RarReader.cpp' object='RarReader.o' libtool=no @AMDEPBACKSLASH@
1523 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
1524 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o RarReader.o `test -f 'daemon/postprocess/RarReader.cpp' || echo '$(srcdir)/'`daemon/postprocess/RarReader.cpp
1525
1526 RarReader.obj: daemon/postprocess/RarReader.cpp
1527 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT RarReader.obj -MD -MP -MF "$(DEPDIR)/RarReader.Tpo" -c -o RarReader.obj `if test -f 'daemon/postprocess/RarReader.cpp'; then $(CYGPATH_W) 'daemon/postprocess/RarReader.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/RarReader.cpp'; fi`; \
1528 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/RarReader.Tpo" "$(DEPDIR)/RarReader.Po"; else rm -f "$(DEPDIR)/RarReader.Tpo"; exit 1; fi
1529 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/RarReader.cpp' object='RarReader.obj' libtool=no @AMDEPBACKSLASH@
1530 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
1531 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o RarReader.obj `if test -f 'daemon/postprocess/RarReader.cpp'; then $(CYGPATH_W) 'daemon/postprocess/RarReader.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/RarReader.cpp'; fi`
1532
1533 Rename.o: daemon/postprocess/Rename.cpp
1534 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Rename.o -MD -MP -MF "$(DEPDIR)/Rename.Tpo" -c -o Rename.o `test -f 'daemon/postprocess/Rename.cpp' || echo '$(srcdir)/'`daemon/postprocess/Rename.cpp; \
1535 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Rename.Tpo" "$(DEPDIR)/Rename.Po"; else rm -f "$(DEPDIR)/Rename.Tpo"; exit 1; fi
1536 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/Rename.cpp' object='Rename.o' libtool=no @AMDEPBACKSLASH@
1537 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
1538 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Rename.o `test -f 'daemon/postprocess/Rename.cpp' || echo '$(srcdir)/'`daemon/postprocess/Rename.cpp
1539
1540 Rename.obj: daemon/postprocess/Rename.cpp
1541 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Rename.obj -MD -MP -MF "$(DEPDIR)/Rename.Tpo" -c -o Rename.obj `if test -f 'daemon/postprocess/Rename.cpp'; then $(CYGPATH_W) 'daemon/postprocess/Rename.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/Rename.cpp'; fi`; \
1542 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Rename.Tpo" "$(DEPDIR)/Rename.Po"; else rm -f "$(DEPDIR)/Rename.Tpo"; exit 1; fi
1543 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/Rename.cpp' object='Rename.obj' libtool=no @AMDEPBACKSLASH@
1544 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
1545 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Rename.obj `if test -f 'daemon/postprocess/Rename.cpp'; then $(CYGPATH_W) 'daemon/postprocess/Rename.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/Rename.cpp'; fi`
1546
1547 Repair.o: daemon/postprocess/Repair.cpp
1548 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Repair.o -MD -MP -MF "$(DEPDIR)/Repair.Tpo" -c -o Repair.o `test -f 'daemon/postprocess/Repair.cpp' || echo '$(srcdir)/'`daemon/postprocess/Repair.cpp; \
1549 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Repair.Tpo" "$(DEPDIR)/Repair.Po"; else rm -f "$(DEPDIR)/Repair.Tpo"; exit 1; fi
1550 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/Repair.cpp' object='Repair.o' libtool=no @AMDEPBACKSLASH@
1551 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
1552 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Repair.o `test -f 'daemon/postprocess/Repair.cpp' || echo '$(srcdir)/'`daemon/postprocess/Repair.cpp
1553
1554 Repair.obj: daemon/postprocess/Repair.cpp
1555 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Repair.obj -MD -MP -MF "$(DEPDIR)/Repair.Tpo" -c -o Repair.obj `if test -f 'daemon/postprocess/Repair.cpp'; then $(CYGPATH_W) 'daemon/postprocess/Repair.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/Repair.cpp'; fi`; \
1556 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Repair.Tpo" "$(DEPDIR)/Repair.Po"; else rm -f "$(DEPDIR)/Repair.Tpo"; exit 1; fi
1557 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/Repair.cpp' object='Repair.obj' libtool=no @AMDEPBACKSLASH@
1558 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
1559 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Repair.obj `if test -f 'daemon/postprocess/Repair.cpp'; then $(CYGPATH_W) 'daemon/postprocess/Repair.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/Repair.cpp'; fi`
1560
14641561 Unpack.o: daemon/postprocess/Unpack.cpp
14651562 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Unpack.o -MD -MP -MF "$(DEPDIR)/Unpack.Tpo" -c -o Unpack.o `test -f 'daemon/postprocess/Unpack.cpp' || echo '$(srcdir)/'`daemon/postprocess/Unpack.cpp; \
14661563 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Unpack.Tpo" "$(DEPDIR)/Unpack.Po"; else rm -f "$(DEPDIR)/Unpack.Tpo"; exit 1; fi
17831880 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
17841881 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Util.obj `if test -f 'daemon/util/Util.cpp'; then $(CYGPATH_W) 'daemon/util/Util.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/util/Util.cpp'; fi`
17851882
1883 NServMain.o: daemon/nserv/NServMain.cpp
1884 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NServMain.o -MD -MP -MF "$(DEPDIR)/NServMain.Tpo" -c -o NServMain.o `test -f 'daemon/nserv/NServMain.cpp' || echo '$(srcdir)/'`daemon/nserv/NServMain.cpp; \
1885 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NServMain.Tpo" "$(DEPDIR)/NServMain.Po"; else rm -f "$(DEPDIR)/NServMain.Tpo"; exit 1; fi
1886 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nserv/NServMain.cpp' object='NServMain.o' libtool=no @AMDEPBACKSLASH@
1887 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
1888 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NServMain.o `test -f 'daemon/nserv/NServMain.cpp' || echo '$(srcdir)/'`daemon/nserv/NServMain.cpp
1889
1890 NServMain.obj: daemon/nserv/NServMain.cpp
1891 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NServMain.obj -MD -MP -MF "$(DEPDIR)/NServMain.Tpo" -c -o NServMain.obj `if test -f 'daemon/nserv/NServMain.cpp'; then $(CYGPATH_W) 'daemon/nserv/NServMain.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nserv/NServMain.cpp'; fi`; \
1892 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NServMain.Tpo" "$(DEPDIR)/NServMain.Po"; else rm -f "$(DEPDIR)/NServMain.Tpo"; exit 1; fi
1893 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nserv/NServMain.cpp' object='NServMain.obj' libtool=no @AMDEPBACKSLASH@
1894 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
1895 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NServMain.obj `if test -f 'daemon/nserv/NServMain.cpp'; then $(CYGPATH_W) 'daemon/nserv/NServMain.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nserv/NServMain.cpp'; fi`
1896
1897 NServFrontend.o: daemon/nserv/NServFrontend.cpp
1898 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NServFrontend.o -MD -MP -MF "$(DEPDIR)/NServFrontend.Tpo" -c -o NServFrontend.o `test -f 'daemon/nserv/NServFrontend.cpp' || echo '$(srcdir)/'`daemon/nserv/NServFrontend.cpp; \
1899 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NServFrontend.Tpo" "$(DEPDIR)/NServFrontend.Po"; else rm -f "$(DEPDIR)/NServFrontend.Tpo"; exit 1; fi
1900 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nserv/NServFrontend.cpp' object='NServFrontend.o' libtool=no @AMDEPBACKSLASH@
1901 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
1902 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NServFrontend.o `test -f 'daemon/nserv/NServFrontend.cpp' || echo '$(srcdir)/'`daemon/nserv/NServFrontend.cpp
1903
1904 NServFrontend.obj: daemon/nserv/NServFrontend.cpp
1905 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NServFrontend.obj -MD -MP -MF "$(DEPDIR)/NServFrontend.Tpo" -c -o NServFrontend.obj `if test -f 'daemon/nserv/NServFrontend.cpp'; then $(CYGPATH_W) 'daemon/nserv/NServFrontend.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nserv/NServFrontend.cpp'; fi`; \
1906 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NServFrontend.Tpo" "$(DEPDIR)/NServFrontend.Po"; else rm -f "$(DEPDIR)/NServFrontend.Tpo"; exit 1; fi
1907 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nserv/NServFrontend.cpp' object='NServFrontend.obj' libtool=no @AMDEPBACKSLASH@
1908 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
1909 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NServFrontend.obj `if test -f 'daemon/nserv/NServFrontend.cpp'; then $(CYGPATH_W) 'daemon/nserv/NServFrontend.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nserv/NServFrontend.cpp'; fi`
1910
1911 NntpServer.o: daemon/nserv/NntpServer.cpp
1912 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NntpServer.o -MD -MP -MF "$(DEPDIR)/NntpServer.Tpo" -c -o NntpServer.o `test -f 'daemon/nserv/NntpServer.cpp' || echo '$(srcdir)/'`daemon/nserv/NntpServer.cpp; \
1913 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NntpServer.Tpo" "$(DEPDIR)/NntpServer.Po"; else rm -f "$(DEPDIR)/NntpServer.Tpo"; exit 1; fi
1914 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nserv/NntpServer.cpp' object='NntpServer.o' libtool=no @AMDEPBACKSLASH@
1915 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
1916 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NntpServer.o `test -f 'daemon/nserv/NntpServer.cpp' || echo '$(srcdir)/'`daemon/nserv/NntpServer.cpp
1917
1918 NntpServer.obj: daemon/nserv/NntpServer.cpp
1919 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NntpServer.obj -MD -MP -MF "$(DEPDIR)/NntpServer.Tpo" -c -o NntpServer.obj `if test -f 'daemon/nserv/NntpServer.cpp'; then $(CYGPATH_W) 'daemon/nserv/NntpServer.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nserv/NntpServer.cpp'; fi`; \
1920 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NntpServer.Tpo" "$(DEPDIR)/NntpServer.Po"; else rm -f "$(DEPDIR)/NntpServer.Tpo"; exit 1; fi
1921 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nserv/NntpServer.cpp' object='NntpServer.obj' libtool=no @AMDEPBACKSLASH@
1922 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
1923 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NntpServer.obj `if test -f 'daemon/nserv/NntpServer.cpp'; then $(CYGPATH_W) 'daemon/nserv/NntpServer.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nserv/NntpServer.cpp'; fi`
1924
1925 NzbGenerator.o: daemon/nserv/NzbGenerator.cpp
1926 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NzbGenerator.o -MD -MP -MF "$(DEPDIR)/NzbGenerator.Tpo" -c -o NzbGenerator.o `test -f 'daemon/nserv/NzbGenerator.cpp' || echo '$(srcdir)/'`daemon/nserv/NzbGenerator.cpp; \
1927 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NzbGenerator.Tpo" "$(DEPDIR)/NzbGenerator.Po"; else rm -f "$(DEPDIR)/NzbGenerator.Tpo"; exit 1; fi
1928 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nserv/NzbGenerator.cpp' object='NzbGenerator.o' libtool=no @AMDEPBACKSLASH@
1929 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
1930 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NzbGenerator.o `test -f 'daemon/nserv/NzbGenerator.cpp' || echo '$(srcdir)/'`daemon/nserv/NzbGenerator.cpp
1931
1932 NzbGenerator.obj: daemon/nserv/NzbGenerator.cpp
1933 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NzbGenerator.obj -MD -MP -MF "$(DEPDIR)/NzbGenerator.Tpo" -c -o NzbGenerator.obj `if test -f 'daemon/nserv/NzbGenerator.cpp'; then $(CYGPATH_W) 'daemon/nserv/NzbGenerator.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nserv/NzbGenerator.cpp'; fi`; \
1934 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NzbGenerator.Tpo" "$(DEPDIR)/NzbGenerator.Po"; else rm -f "$(DEPDIR)/NzbGenerator.Tpo"; exit 1; fi
1935 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nserv/NzbGenerator.cpp' object='NzbGenerator.obj' libtool=no @AMDEPBACKSLASH@
1936 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
1937 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NzbGenerator.obj `if test -f 'daemon/nserv/NzbGenerator.cpp'; then $(CYGPATH_W) 'daemon/nserv/NzbGenerator.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nserv/NzbGenerator.cpp'; fi`
1938
1939 YEncoder.o: daemon/nserv/YEncoder.cpp
1940 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT YEncoder.o -MD -MP -MF "$(DEPDIR)/YEncoder.Tpo" -c -o YEncoder.o `test -f 'daemon/nserv/YEncoder.cpp' || echo '$(srcdir)/'`daemon/nserv/YEncoder.cpp; \
1941 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/YEncoder.Tpo" "$(DEPDIR)/YEncoder.Po"; else rm -f "$(DEPDIR)/YEncoder.Tpo"; exit 1; fi
1942 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nserv/YEncoder.cpp' object='YEncoder.o' libtool=no @AMDEPBACKSLASH@
1943 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
1944 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o YEncoder.o `test -f 'daemon/nserv/YEncoder.cpp' || echo '$(srcdir)/'`daemon/nserv/YEncoder.cpp
1945
1946 YEncoder.obj: daemon/nserv/YEncoder.cpp
1947 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT YEncoder.obj -MD -MP -MF "$(DEPDIR)/YEncoder.Tpo" -c -o YEncoder.obj `if test -f 'daemon/nserv/YEncoder.cpp'; then $(CYGPATH_W) 'daemon/nserv/YEncoder.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nserv/YEncoder.cpp'; fi`; \
1948 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/YEncoder.Tpo" "$(DEPDIR)/YEncoder.Po"; else rm -f "$(DEPDIR)/YEncoder.Tpo"; exit 1; fi
1949 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nserv/YEncoder.cpp' object='YEncoder.obj' libtool=no @AMDEPBACKSLASH@
1950 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
1951 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o YEncoder.obj `if test -f 'daemon/nserv/YEncoder.cpp'; then $(CYGPATH_W) 'daemon/nserv/YEncoder.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nserv/YEncoder.cpp'; fi`
1952
17861953 commandline.o: lib/par2/commandline.cpp
17871954 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT commandline.o -MD -MP -MF "$(DEPDIR)/commandline.Tpo" -c -o commandline.o `test -f 'lib/par2/commandline.cpp' || echo '$(srcdir)/'`lib/par2/commandline.cpp; \
17881955 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/commandline.Tpo" "$(DEPDIR)/commandline.Po"; else rm -f "$(DEPDIR)/commandline.Tpo"; exit 1; fi
19372104 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
19382105 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o md5.obj `if test -f 'lib/par2/md5.cpp'; then $(CYGPATH_W) 'lib/par2/md5.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/md5.cpp'; fi`
19392106
1940 par2creatorsourcefile.o: lib/par2/par2creatorsourcefile.cpp
1941 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT par2creatorsourcefile.o -MD -MP -MF "$(DEPDIR)/par2creatorsourcefile.Tpo" -c -o par2creatorsourcefile.o `test -f 'lib/par2/par2creatorsourcefile.cpp' || echo '$(srcdir)/'`lib/par2/par2creatorsourcefile.cpp; \
1942 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/par2creatorsourcefile.Tpo" "$(DEPDIR)/par2creatorsourcefile.Po"; else rm -f "$(DEPDIR)/par2creatorsourcefile.Tpo"; exit 1; fi
1943 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/par2creatorsourcefile.cpp' object='par2creatorsourcefile.o' libtool=no @AMDEPBACKSLASH@
1944 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
1945 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o par2creatorsourcefile.o `test -f 'lib/par2/par2creatorsourcefile.cpp' || echo '$(srcdir)/'`lib/par2/par2creatorsourcefile.cpp
1946
1947 par2creatorsourcefile.obj: lib/par2/par2creatorsourcefile.cpp
1948 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT par2creatorsourcefile.obj -MD -MP -MF "$(DEPDIR)/par2creatorsourcefile.Tpo" -c -o par2creatorsourcefile.obj `if test -f 'lib/par2/par2creatorsourcefile.cpp'; then $(CYGPATH_W) 'lib/par2/par2creatorsourcefile.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/par2creatorsourcefile.cpp'; fi`; \
1949 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/par2creatorsourcefile.Tpo" "$(DEPDIR)/par2creatorsourcefile.Po"; else rm -f "$(DEPDIR)/par2creatorsourcefile.Tpo"; exit 1; fi
1950 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/par2creatorsourcefile.cpp' object='par2creatorsourcefile.obj' libtool=no @AMDEPBACKSLASH@
1951 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
1952 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o par2creatorsourcefile.obj `if test -f 'lib/par2/par2creatorsourcefile.cpp'; then $(CYGPATH_W) 'lib/par2/par2creatorsourcefile.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/par2creatorsourcefile.cpp'; fi`
1953
19542107 par2fileformat.o: lib/par2/par2fileformat.cpp
19552108 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT par2fileformat.o -MD -MP -MF "$(DEPDIR)/par2fileformat.Tpo" -c -o par2fileformat.o `test -f 'lib/par2/par2fileformat.cpp' || echo '$(srcdir)/'`lib/par2/par2fileformat.cpp; \
19562109 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/par2fileformat.Tpo" "$(DEPDIR)/par2fileformat.Po"; else rm -f "$(DEPDIR)/par2fileformat.Tpo"; exit 1; fi
21332286 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
21342287 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o FeedFilterTest.obj `if test -f 'tests/feed/FeedFilterTest.cpp'; then $(CYGPATH_W) 'tests/feed/FeedFilterTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/feed/FeedFilterTest.cpp'; fi`
21352288
2289 DupeMatcherTest.o: tests/postprocess/DupeMatcherTest.cpp
2290 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT DupeMatcherTest.o -MD -MP -MF "$(DEPDIR)/DupeMatcherTest.Tpo" -c -o DupeMatcherTest.o `test -f 'tests/postprocess/DupeMatcherTest.cpp' || echo '$(srcdir)/'`tests/postprocess/DupeMatcherTest.cpp; \
2291 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/DupeMatcherTest.Tpo" "$(DEPDIR)/DupeMatcherTest.Po"; else rm -f "$(DEPDIR)/DupeMatcherTest.Tpo"; exit 1; fi
2292 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/postprocess/DupeMatcherTest.cpp' object='DupeMatcherTest.o' libtool=no @AMDEPBACKSLASH@
2293 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
2294 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o DupeMatcherTest.o `test -f 'tests/postprocess/DupeMatcherTest.cpp' || echo '$(srcdir)/'`tests/postprocess/DupeMatcherTest.cpp
2295
2296 DupeMatcherTest.obj: tests/postprocess/DupeMatcherTest.cpp
2297 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT DupeMatcherTest.obj -MD -MP -MF "$(DEPDIR)/DupeMatcherTest.Tpo" -c -o DupeMatcherTest.obj `if test -f 'tests/postprocess/DupeMatcherTest.cpp'; then $(CYGPATH_W) 'tests/postprocess/DupeMatcherTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/postprocess/DupeMatcherTest.cpp'; fi`; \
2298 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/DupeMatcherTest.Tpo" "$(DEPDIR)/DupeMatcherTest.Po"; else rm -f "$(DEPDIR)/DupeMatcherTest.Tpo"; exit 1; fi
2299 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/postprocess/DupeMatcherTest.cpp' object='DupeMatcherTest.obj' libtool=no @AMDEPBACKSLASH@
2300 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
2301 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o DupeMatcherTest.obj `if test -f 'tests/postprocess/DupeMatcherTest.cpp'; then $(CYGPATH_W) 'tests/postprocess/DupeMatcherTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/postprocess/DupeMatcherTest.cpp'; fi`
2302
2303 RarRenamerTest.o: tests/postprocess/RarRenamerTest.cpp
2304 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT RarRenamerTest.o -MD -MP -MF "$(DEPDIR)/RarRenamerTest.Tpo" -c -o RarRenamerTest.o `test -f 'tests/postprocess/RarRenamerTest.cpp' || echo '$(srcdir)/'`tests/postprocess/RarRenamerTest.cpp; \
2305 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/RarRenamerTest.Tpo" "$(DEPDIR)/RarRenamerTest.Po"; else rm -f "$(DEPDIR)/RarRenamerTest.Tpo"; exit 1; fi
2306 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/postprocess/RarRenamerTest.cpp' object='RarRenamerTest.o' libtool=no @AMDEPBACKSLASH@
2307 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
2308 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o RarRenamerTest.o `test -f 'tests/postprocess/RarRenamerTest.cpp' || echo '$(srcdir)/'`tests/postprocess/RarRenamerTest.cpp
2309
2310 RarRenamerTest.obj: tests/postprocess/RarRenamerTest.cpp
2311 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT RarRenamerTest.obj -MD -MP -MF "$(DEPDIR)/RarRenamerTest.Tpo" -c -o RarRenamerTest.obj `if test -f 'tests/postprocess/RarRenamerTest.cpp'; then $(CYGPATH_W) 'tests/postprocess/RarRenamerTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/postprocess/RarRenamerTest.cpp'; fi`; \
2312 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/RarRenamerTest.Tpo" "$(DEPDIR)/RarRenamerTest.Po"; else rm -f "$(DEPDIR)/RarRenamerTest.Tpo"; exit 1; fi
2313 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/postprocess/RarRenamerTest.cpp' object='RarRenamerTest.obj' libtool=no @AMDEPBACKSLASH@
2314 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
2315 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o RarRenamerTest.obj `if test -f 'tests/postprocess/RarRenamerTest.cpp'; then $(CYGPATH_W) 'tests/postprocess/RarRenamerTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/postprocess/RarRenamerTest.cpp'; fi`
2316
2317 RarReaderTest.o: tests/postprocess/RarReaderTest.cpp
2318 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT RarReaderTest.o -MD -MP -MF "$(DEPDIR)/RarReaderTest.Tpo" -c -o RarReaderTest.o `test -f 'tests/postprocess/RarReaderTest.cpp' || echo '$(srcdir)/'`tests/postprocess/RarReaderTest.cpp; \
2319 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/RarReaderTest.Tpo" "$(DEPDIR)/RarReaderTest.Po"; else rm -f "$(DEPDIR)/RarReaderTest.Tpo"; exit 1; fi
2320 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/postprocess/RarReaderTest.cpp' object='RarReaderTest.o' libtool=no @AMDEPBACKSLASH@
2321 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
2322 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o RarReaderTest.o `test -f 'tests/postprocess/RarReaderTest.cpp' || echo '$(srcdir)/'`tests/postprocess/RarReaderTest.cpp
2323
2324 RarReaderTest.obj: tests/postprocess/RarReaderTest.cpp
2325 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT RarReaderTest.obj -MD -MP -MF "$(DEPDIR)/RarReaderTest.Tpo" -c -o RarReaderTest.obj `if test -f 'tests/postprocess/RarReaderTest.cpp'; then $(CYGPATH_W) 'tests/postprocess/RarReaderTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/postprocess/RarReaderTest.cpp'; fi`; \
2326 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/RarReaderTest.Tpo" "$(DEPDIR)/RarReaderTest.Po"; else rm -f "$(DEPDIR)/RarReaderTest.Tpo"; exit 1; fi
2327 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/postprocess/RarReaderTest.cpp' object='RarReaderTest.obj' libtool=no @AMDEPBACKSLASH@
2328 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
2329 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o RarReaderTest.obj `if test -f 'tests/postprocess/RarReaderTest.cpp'; then $(CYGPATH_W) 'tests/postprocess/RarReaderTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/postprocess/RarReaderTest.cpp'; fi`
2330
2331 NzbFileTest.o: tests/queue/NzbFileTest.cpp
2332 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NzbFileTest.o -MD -MP -MF "$(DEPDIR)/NzbFileTest.Tpo" -c -o NzbFileTest.o `test -f 'tests/queue/NzbFileTest.cpp' || echo '$(srcdir)/'`tests/queue/NzbFileTest.cpp; \
2333 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NzbFileTest.Tpo" "$(DEPDIR)/NzbFileTest.Po"; else rm -f "$(DEPDIR)/NzbFileTest.Tpo"; exit 1; fi
2334 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/queue/NzbFileTest.cpp' object='NzbFileTest.o' libtool=no @AMDEPBACKSLASH@
2335 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
2336 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NzbFileTest.o `test -f 'tests/queue/NzbFileTest.cpp' || echo '$(srcdir)/'`tests/queue/NzbFileTest.cpp
2337
2338 NzbFileTest.obj: tests/queue/NzbFileTest.cpp
2339 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NzbFileTest.obj -MD -MP -MF "$(DEPDIR)/NzbFileTest.Tpo" -c -o NzbFileTest.obj `if test -f 'tests/queue/NzbFileTest.cpp'; then $(CYGPATH_W) 'tests/queue/NzbFileTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/queue/NzbFileTest.cpp'; fi`; \
2340 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NzbFileTest.Tpo" "$(DEPDIR)/NzbFileTest.Po"; else rm -f "$(DEPDIR)/NzbFileTest.Tpo"; exit 1; fi
2341 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/queue/NzbFileTest.cpp' object='NzbFileTest.obj' libtool=no @AMDEPBACKSLASH@
2342 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
2343 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NzbFileTest.obj `if test -f 'tests/queue/NzbFileTest.cpp'; then $(CYGPATH_W) 'tests/queue/NzbFileTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/queue/NzbFileTest.cpp'; fi`
2344
2345 ServerPoolTest.o: tests/nntp/ServerPoolTest.cpp
2346 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ServerPoolTest.o -MD -MP -MF "$(DEPDIR)/ServerPoolTest.Tpo" -c -o ServerPoolTest.o `test -f 'tests/nntp/ServerPoolTest.cpp' || echo '$(srcdir)/'`tests/nntp/ServerPoolTest.cpp; \
2347 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ServerPoolTest.Tpo" "$(DEPDIR)/ServerPoolTest.Po"; else rm -f "$(DEPDIR)/ServerPoolTest.Tpo"; exit 1; fi
2348 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/nntp/ServerPoolTest.cpp' object='ServerPoolTest.o' libtool=no @AMDEPBACKSLASH@
2349 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
2350 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ServerPoolTest.o `test -f 'tests/nntp/ServerPoolTest.cpp' || echo '$(srcdir)/'`tests/nntp/ServerPoolTest.cpp
2351
2352 ServerPoolTest.obj: tests/nntp/ServerPoolTest.cpp
2353 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ServerPoolTest.obj -MD -MP -MF "$(DEPDIR)/ServerPoolTest.Tpo" -c -o ServerPoolTest.obj `if test -f 'tests/nntp/ServerPoolTest.cpp'; then $(CYGPATH_W) 'tests/nntp/ServerPoolTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/nntp/ServerPoolTest.cpp'; fi`; \
2354 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ServerPoolTest.Tpo" "$(DEPDIR)/ServerPoolTest.Po"; else rm -f "$(DEPDIR)/ServerPoolTest.Tpo"; exit 1; fi
2355 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/nntp/ServerPoolTest.cpp' object='ServerPoolTest.obj' libtool=no @AMDEPBACKSLASH@
2356 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
2357 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ServerPoolTest.obj `if test -f 'tests/nntp/ServerPoolTest.cpp'; then $(CYGPATH_W) 'tests/nntp/ServerPoolTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/nntp/ServerPoolTest.cpp'; fi`
2358
2359 FileSystemTest.o: tests/util/FileSystemTest.cpp
2360 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT FileSystemTest.o -MD -MP -MF "$(DEPDIR)/FileSystemTest.Tpo" -c -o FileSystemTest.o `test -f 'tests/util/FileSystemTest.cpp' || echo '$(srcdir)/'`tests/util/FileSystemTest.cpp; \
2361 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/FileSystemTest.Tpo" "$(DEPDIR)/FileSystemTest.Po"; else rm -f "$(DEPDIR)/FileSystemTest.Tpo"; exit 1; fi
2362 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/util/FileSystemTest.cpp' object='FileSystemTest.o' libtool=no @AMDEPBACKSLASH@
2363 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
2364 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o FileSystemTest.o `test -f 'tests/util/FileSystemTest.cpp' || echo '$(srcdir)/'`tests/util/FileSystemTest.cpp
2365
2366 FileSystemTest.obj: tests/util/FileSystemTest.cpp
2367 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT FileSystemTest.obj -MD -MP -MF "$(DEPDIR)/FileSystemTest.Tpo" -c -o FileSystemTest.obj `if test -f 'tests/util/FileSystemTest.cpp'; then $(CYGPATH_W) 'tests/util/FileSystemTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/util/FileSystemTest.cpp'; fi`; \
2368 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/FileSystemTest.Tpo" "$(DEPDIR)/FileSystemTest.Po"; else rm -f "$(DEPDIR)/FileSystemTest.Tpo"; exit 1; fi
2369 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/util/FileSystemTest.cpp' object='FileSystemTest.obj' libtool=no @AMDEPBACKSLASH@
2370 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
2371 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o FileSystemTest.obj `if test -f 'tests/util/FileSystemTest.cpp'; then $(CYGPATH_W) 'tests/util/FileSystemTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/util/FileSystemTest.cpp'; fi`
2372
2373 NStringTest.o: tests/util/NStringTest.cpp
2374 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NStringTest.o -MD -MP -MF "$(DEPDIR)/NStringTest.Tpo" -c -o NStringTest.o `test -f 'tests/util/NStringTest.cpp' || echo '$(srcdir)/'`tests/util/NStringTest.cpp; \
2375 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NStringTest.Tpo" "$(DEPDIR)/NStringTest.Po"; else rm -f "$(DEPDIR)/NStringTest.Tpo"; exit 1; fi
2376 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/util/NStringTest.cpp' object='NStringTest.o' libtool=no @AMDEPBACKSLASH@
2377 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
2378 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NStringTest.o `test -f 'tests/util/NStringTest.cpp' || echo '$(srcdir)/'`tests/util/NStringTest.cpp
2379
2380 NStringTest.obj: tests/util/NStringTest.cpp
2381 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NStringTest.obj -MD -MP -MF "$(DEPDIR)/NStringTest.Tpo" -c -o NStringTest.obj `if test -f 'tests/util/NStringTest.cpp'; then $(CYGPATH_W) 'tests/util/NStringTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/util/NStringTest.cpp'; fi`; \
2382 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NStringTest.Tpo" "$(DEPDIR)/NStringTest.Po"; else rm -f "$(DEPDIR)/NStringTest.Tpo"; exit 1; fi
2383 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/util/NStringTest.cpp' object='NStringTest.obj' libtool=no @AMDEPBACKSLASH@
2384 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
2385 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NStringTest.obj `if test -f 'tests/util/NStringTest.cpp'; then $(CYGPATH_W) 'tests/util/NStringTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/util/NStringTest.cpp'; fi`
2386
2387 UtilTest.o: tests/util/UtilTest.cpp
2388 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT UtilTest.o -MD -MP -MF "$(DEPDIR)/UtilTest.Tpo" -c -o UtilTest.o `test -f 'tests/util/UtilTest.cpp' || echo '$(srcdir)/'`tests/util/UtilTest.cpp; \
2389 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/UtilTest.Tpo" "$(DEPDIR)/UtilTest.Po"; else rm -f "$(DEPDIR)/UtilTest.Tpo"; exit 1; fi
2390 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/util/UtilTest.cpp' object='UtilTest.o' libtool=no @AMDEPBACKSLASH@
2391 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
2392 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o UtilTest.o `test -f 'tests/util/UtilTest.cpp' || echo '$(srcdir)/'`tests/util/UtilTest.cpp
2393
2394 UtilTest.obj: tests/util/UtilTest.cpp
2395 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT UtilTest.obj -MD -MP -MF "$(DEPDIR)/UtilTest.Tpo" -c -o UtilTest.obj `if test -f 'tests/util/UtilTest.cpp'; then $(CYGPATH_W) 'tests/util/UtilTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/util/UtilTest.cpp'; fi`; \
2396 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/UtilTest.Tpo" "$(DEPDIR)/UtilTest.Po"; else rm -f "$(DEPDIR)/UtilTest.Tpo"; exit 1; fi
2397 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/util/UtilTest.cpp' object='UtilTest.obj' libtool=no @AMDEPBACKSLASH@
2398 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
2399 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o UtilTest.obj `if test -f 'tests/util/UtilTest.cpp'; then $(CYGPATH_W) 'tests/util/UtilTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/util/UtilTest.cpp'; fi`
2400
21362401 ParCheckerTest.o: tests/postprocess/ParCheckerTest.cpp
21372402 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ParCheckerTest.o -MD -MP -MF "$(DEPDIR)/ParCheckerTest.Tpo" -c -o ParCheckerTest.o `test -f 'tests/postprocess/ParCheckerTest.cpp' || echo '$(srcdir)/'`tests/postprocess/ParCheckerTest.cpp; \
21382403 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ParCheckerTest.Tpo" "$(DEPDIR)/ParCheckerTest.Po"; else rm -f "$(DEPDIR)/ParCheckerTest.Tpo"; exit 1; fi
21602425 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/postprocess/ParRenamerTest.cpp' object='ParRenamerTest.obj' libtool=no @AMDEPBACKSLASH@
21612426 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
21622427 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ParRenamerTest.obj `if test -f 'tests/postprocess/ParRenamerTest.cpp'; then $(CYGPATH_W) 'tests/postprocess/ParRenamerTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/postprocess/ParRenamerTest.cpp'; fi`
2163
2164 DupeMatcherTest.o: tests/postprocess/DupeMatcherTest.cpp
2165 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT DupeMatcherTest.o -MD -MP -MF "$(DEPDIR)/DupeMatcherTest.Tpo" -c -o DupeMatcherTest.o `test -f 'tests/postprocess/DupeMatcherTest.cpp' || echo '$(srcdir)/'`tests/postprocess/DupeMatcherTest.cpp; \
2166 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/DupeMatcherTest.Tpo" "$(DEPDIR)/DupeMatcherTest.Po"; else rm -f "$(DEPDIR)/DupeMatcherTest.Tpo"; exit 1; fi
2167 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/postprocess/DupeMatcherTest.cpp' object='DupeMatcherTest.o' libtool=no @AMDEPBACKSLASH@
2168 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
2169 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o DupeMatcherTest.o `test -f 'tests/postprocess/DupeMatcherTest.cpp' || echo '$(srcdir)/'`tests/postprocess/DupeMatcherTest.cpp
2170
2171 DupeMatcherTest.obj: tests/postprocess/DupeMatcherTest.cpp
2172 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT DupeMatcherTest.obj -MD -MP -MF "$(DEPDIR)/DupeMatcherTest.Tpo" -c -o DupeMatcherTest.obj `if test -f 'tests/postprocess/DupeMatcherTest.cpp'; then $(CYGPATH_W) 'tests/postprocess/DupeMatcherTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/postprocess/DupeMatcherTest.cpp'; fi`; \
2173 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/DupeMatcherTest.Tpo" "$(DEPDIR)/DupeMatcherTest.Po"; else rm -f "$(DEPDIR)/DupeMatcherTest.Tpo"; exit 1; fi
2174 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/postprocess/DupeMatcherTest.cpp' object='DupeMatcherTest.obj' libtool=no @AMDEPBACKSLASH@
2175 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
2176 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o DupeMatcherTest.obj `if test -f 'tests/postprocess/DupeMatcherTest.cpp'; then $(CYGPATH_W) 'tests/postprocess/DupeMatcherTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/postprocess/DupeMatcherTest.cpp'; fi`
2177
2178 NzbFileTest.o: tests/queue/NzbFileTest.cpp
2179 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NzbFileTest.o -MD -MP -MF "$(DEPDIR)/NzbFileTest.Tpo" -c -o NzbFileTest.o `test -f 'tests/queue/NzbFileTest.cpp' || echo '$(srcdir)/'`tests/queue/NzbFileTest.cpp; \
2180 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NzbFileTest.Tpo" "$(DEPDIR)/NzbFileTest.Po"; else rm -f "$(DEPDIR)/NzbFileTest.Tpo"; exit 1; fi
2181 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/queue/NzbFileTest.cpp' object='NzbFileTest.o' libtool=no @AMDEPBACKSLASH@
2182 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
2183 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NzbFileTest.o `test -f 'tests/queue/NzbFileTest.cpp' || echo '$(srcdir)/'`tests/queue/NzbFileTest.cpp
2184
2185 NzbFileTest.obj: tests/queue/NzbFileTest.cpp
2186 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NzbFileTest.obj -MD -MP -MF "$(DEPDIR)/NzbFileTest.Tpo" -c -o NzbFileTest.obj `if test -f 'tests/queue/NzbFileTest.cpp'; then $(CYGPATH_W) 'tests/queue/NzbFileTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/queue/NzbFileTest.cpp'; fi`; \
2187 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NzbFileTest.Tpo" "$(DEPDIR)/NzbFileTest.Po"; else rm -f "$(DEPDIR)/NzbFileTest.Tpo"; exit 1; fi
2188 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/queue/NzbFileTest.cpp' object='NzbFileTest.obj' libtool=no @AMDEPBACKSLASH@
2189 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
2190 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NzbFileTest.obj `if test -f 'tests/queue/NzbFileTest.cpp'; then $(CYGPATH_W) 'tests/queue/NzbFileTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/queue/NzbFileTest.cpp'; fi`
2191
2192 ServerPoolTest.o: tests/nntp/ServerPoolTest.cpp
2193 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ServerPoolTest.o -MD -MP -MF "$(DEPDIR)/ServerPoolTest.Tpo" -c -o ServerPoolTest.o `test -f 'tests/nntp/ServerPoolTest.cpp' || echo '$(srcdir)/'`tests/nntp/ServerPoolTest.cpp; \
2194 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ServerPoolTest.Tpo" "$(DEPDIR)/ServerPoolTest.Po"; else rm -f "$(DEPDIR)/ServerPoolTest.Tpo"; exit 1; fi
2195 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/nntp/ServerPoolTest.cpp' object='ServerPoolTest.o' libtool=no @AMDEPBACKSLASH@
2196 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
2197 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ServerPoolTest.o `test -f 'tests/nntp/ServerPoolTest.cpp' || echo '$(srcdir)/'`tests/nntp/ServerPoolTest.cpp
2198
2199 ServerPoolTest.obj: tests/nntp/ServerPoolTest.cpp
2200 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ServerPoolTest.obj -MD -MP -MF "$(DEPDIR)/ServerPoolTest.Tpo" -c -o ServerPoolTest.obj `if test -f 'tests/nntp/ServerPoolTest.cpp'; then $(CYGPATH_W) 'tests/nntp/ServerPoolTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/nntp/ServerPoolTest.cpp'; fi`; \
2201 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ServerPoolTest.Tpo" "$(DEPDIR)/ServerPoolTest.Po"; else rm -f "$(DEPDIR)/ServerPoolTest.Tpo"; exit 1; fi
2202 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/nntp/ServerPoolTest.cpp' object='ServerPoolTest.obj' libtool=no @AMDEPBACKSLASH@
2203 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
2204 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ServerPoolTest.obj `if test -f 'tests/nntp/ServerPoolTest.cpp'; then $(CYGPATH_W) 'tests/nntp/ServerPoolTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/nntp/ServerPoolTest.cpp'; fi`
2205
2206 FileSystemTest.o: tests/util/FileSystemTest.cpp
2207 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT FileSystemTest.o -MD -MP -MF "$(DEPDIR)/FileSystemTest.Tpo" -c -o FileSystemTest.o `test -f 'tests/util/FileSystemTest.cpp' || echo '$(srcdir)/'`tests/util/FileSystemTest.cpp; \
2208 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/FileSystemTest.Tpo" "$(DEPDIR)/FileSystemTest.Po"; else rm -f "$(DEPDIR)/FileSystemTest.Tpo"; exit 1; fi
2209 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/util/FileSystemTest.cpp' object='FileSystemTest.o' libtool=no @AMDEPBACKSLASH@
2210 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
2211 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o FileSystemTest.o `test -f 'tests/util/FileSystemTest.cpp' || echo '$(srcdir)/'`tests/util/FileSystemTest.cpp
2212
2213 FileSystemTest.obj: tests/util/FileSystemTest.cpp
2214 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT FileSystemTest.obj -MD -MP -MF "$(DEPDIR)/FileSystemTest.Tpo" -c -o FileSystemTest.obj `if test -f 'tests/util/FileSystemTest.cpp'; then $(CYGPATH_W) 'tests/util/FileSystemTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/util/FileSystemTest.cpp'; fi`; \
2215 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/FileSystemTest.Tpo" "$(DEPDIR)/FileSystemTest.Po"; else rm -f "$(DEPDIR)/FileSystemTest.Tpo"; exit 1; fi
2216 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/util/FileSystemTest.cpp' object='FileSystemTest.obj' libtool=no @AMDEPBACKSLASH@
2217 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
2218 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o FileSystemTest.obj `if test -f 'tests/util/FileSystemTest.cpp'; then $(CYGPATH_W) 'tests/util/FileSystemTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/util/FileSystemTest.cpp'; fi`
2219
2220 NStringTest.o: tests/util/NStringTest.cpp
2221 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NStringTest.o -MD -MP -MF "$(DEPDIR)/NStringTest.Tpo" -c -o NStringTest.o `test -f 'tests/util/NStringTest.cpp' || echo '$(srcdir)/'`tests/util/NStringTest.cpp; \
2222 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NStringTest.Tpo" "$(DEPDIR)/NStringTest.Po"; else rm -f "$(DEPDIR)/NStringTest.Tpo"; exit 1; fi
2223 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/util/NStringTest.cpp' object='NStringTest.o' libtool=no @AMDEPBACKSLASH@
2224 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
2225 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NStringTest.o `test -f 'tests/util/NStringTest.cpp' || echo '$(srcdir)/'`tests/util/NStringTest.cpp
2226
2227 NStringTest.obj: tests/util/NStringTest.cpp
2228 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NStringTest.obj -MD -MP -MF "$(DEPDIR)/NStringTest.Tpo" -c -o NStringTest.obj `if test -f 'tests/util/NStringTest.cpp'; then $(CYGPATH_W) 'tests/util/NStringTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/util/NStringTest.cpp'; fi`; \
2229 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NStringTest.Tpo" "$(DEPDIR)/NStringTest.Po"; else rm -f "$(DEPDIR)/NStringTest.Tpo"; exit 1; fi
2230 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/util/NStringTest.cpp' object='NStringTest.obj' libtool=no @AMDEPBACKSLASH@
2231 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
2232 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NStringTest.obj `if test -f 'tests/util/NStringTest.cpp'; then $(CYGPATH_W) 'tests/util/NStringTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/util/NStringTest.cpp'; fi`
2233
2234 UtilTest.o: tests/util/UtilTest.cpp
2235 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT UtilTest.o -MD -MP -MF "$(DEPDIR)/UtilTest.Tpo" -c -o UtilTest.o `test -f 'tests/util/UtilTest.cpp' || echo '$(srcdir)/'`tests/util/UtilTest.cpp; \
2236 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/UtilTest.Tpo" "$(DEPDIR)/UtilTest.Po"; else rm -f "$(DEPDIR)/UtilTest.Tpo"; exit 1; fi
2237 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/util/UtilTest.cpp' object='UtilTest.o' libtool=no @AMDEPBACKSLASH@
2238 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
2239 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o UtilTest.o `test -f 'tests/util/UtilTest.cpp' || echo '$(srcdir)/'`tests/util/UtilTest.cpp
2240
2241 UtilTest.obj: tests/util/UtilTest.cpp
2242 @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT UtilTest.obj -MD -MP -MF "$(DEPDIR)/UtilTest.Tpo" -c -o UtilTest.obj `if test -f 'tests/util/UtilTest.cpp'; then $(CYGPATH_W) 'tests/util/UtilTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/util/UtilTest.cpp'; fi`; \
2243 @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/UtilTest.Tpo" "$(DEPDIR)/UtilTest.Po"; else rm -f "$(DEPDIR)/UtilTest.Tpo"; exit 1; fi
2244 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/util/UtilTest.cpp' object='UtilTest.obj' libtool=no @AMDEPBACKSLASH@
2245 @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
2246 @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o UtilTest.obj `if test -f 'tests/util/UtilTest.cpp'; then $(CYGPATH_W) 'tests/util/UtilTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/util/UtilTest.cpp'; fi`
22472428 uninstall-info-am:
22482429 install-dist_docDATA: $(dist_doc_DATA)
22492430 @$(NORMAL_INSTALL)
23502531 distdir: $(DISTFILES)
23512532 $(am__remove_distdir)
23522533 mkdir $(distdir)
2353 $(mkdir_p) $(distdir)/daemon/windows $(distdir)/lib/par2 $(distdir)/linux $(distdir)/osx $(distdir)/osx/NZBGet.xcodeproj $(distdir)/osx/Resources $(distdir)/osx/Resources/Images $(distdir)/osx/Resources/licenses $(distdir)/posix $(distdir)/scripts $(distdir)/tests/testdata/dupematcher1 $(distdir)/tests/testdata/dupematcher2 $(distdir)/tests/testdata/nzbfile $(distdir)/tests/testdata/parchecker $(distdir)/webui $(distdir)/webui/img $(distdir)/webui/lib $(distdir)/windows $(distdir)/windows/resources $(distdir)/windows/setup
2534 $(mkdir_p) $(distdir)/daemon/windows $(distdir)/lib/par2 $(distdir)/linux $(distdir)/osx $(distdir)/osx/NZBGet.xcodeproj $(distdir)/osx/Resources $(distdir)/osx/Resources/Images $(distdir)/osx/Resources/licenses $(distdir)/posix $(distdir)/scripts $(distdir)/tests/testdata/dupematcher1 $(distdir)/tests/testdata/dupematcher2 $(distdir)/tests/testdata/nzbfile $(distdir)/tests/testdata/parchecker $(distdir)/tests/testdata/rarrenamer $(distdir)/webui $(distdir)/webui/img $(distdir)/webui/lib $(distdir)/windows $(distdir)/windows/resources $(distdir)/windows/setup
23542535 @srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; \
23552536 topsrcdirstrip=`echo "$(top_srcdir)" | sed 's|.|.|g'`; \
23562537 list='$(DISTFILES)'; for file in $$list; do \
9191 /* Define to 1 if you have the <ncurses/ncurses.h> header file. */
9292 #undef HAVE_NCURSES_NCURSES_H
9393
94 /* Define to 1 to use OpenSSL library for TLS/SSL-support. */
94 /* Define to 1 to use Nettle library for decryption. */
95 #undef HAVE_NETTLE
96
97 /* Define to 1 to use OpenSSL library for TLS/SSL-support and decryption. */
9598 #undef HAVE_OPENSSL
9699
97100 /* Define to 1 if you have the <regex.h> header file. */
00 #! /bin/sh
11 # Guess values for system-dependent variables and create Makefiles.
2 # Generated by GNU Autoconf 2.61 for nzbget 17.1.
2 # Generated by GNU Autoconf 2.61 for nzbget 18.1.
33 #
44 # Report bugs to <hugbug@users.sourceforge.net>.
55 #
573573 # Identity of this package.
574574 PACKAGE_NAME='nzbget'
575575 PACKAGE_TARNAME='nzbget'
576 PACKAGE_VERSION='17.1'
577 PACKAGE_STRING='nzbget 17.1'
576 PACKAGE_VERSION='18.1'
577 PACKAGE_STRING='nzbget 18.1'
578578 PACKAGE_BUGREPORT='hugbug@users.sourceforge.net'
579579
580580 ac_unique_file="daemon/main/nzbget.cpp"
720720 openssl_LIBS
721721 gnutls_CFLAGS
722722 gnutls_LIBS
723 nettle_CFLAGS
724 nettle_LIBS
723725 zlib_CFLAGS
724726 zlib_LIBS
725727 WITH_TESTS_TRUE
746748 openssl_LIBS
747749 gnutls_CFLAGS
748750 gnutls_LIBS
751 nettle_CFLAGS
752 nettle_LIBS
749753 zlib_CFLAGS
750754 zlib_LIBS'
751755
12501254 # Omit some internal or obsolete options to make the list less imposing.
12511255 # This message is too long to be a string in the A/UX 3.1 sh.
12521256 cat <<_ACEOF
1253 \`configure' configures nzbget 17.1 to adapt to many kinds of systems.
1257 \`configure' configures nzbget 18.1 to adapt to many kinds of systems.
12541258
12551259 Usage: $0 [OPTION]... [VAR=VALUE]...
12561260
13211325
13221326 if test -n "$ac_init_help"; then
13231327 case $ac_init_help in
1324 short | recursive ) echo "Configuration of nzbget 17.1:";;
1328 short | recursive ) echo "Configuration of nzbget 18.1:";;
13251329 esac
13261330 cat <<\_ACEOF
13271331
13681372 GnuTLS include directory
13691373 --with-libgnutls-libraries=DIR
13701374 GnuTLS library directory
1375 --with-libnettle-includes=DIR
1376 Nettle include directory
1377 --with-libnettle-libraries=DIR
1378 Nettle library directory
13711379 --with-zlib-includes=DIR
13721380 zlib include directory
13731381 --with-zlib-libraries=DIR
13981406 gnutls_CFLAGS
13991407 C compiler flags for gnutls, overriding pkg-config
14001408 gnutls_LIBS linker flags for gnutls, overriding pkg-config
1409 nettle_CFLAGS
1410 C compiler flags for nettle, overriding pkg-config
1411 nettle_LIBS linker flags for nettle, overriding pkg-config
14011412 zlib_CFLAGS C compiler flags for zlib, overriding pkg-config
14021413 zlib_LIBS linker flags for zlib, overriding pkg-config
14031414
14651476 test -n "$ac_init_help" && exit $ac_status
14661477 if $ac_init_version; then
14671478 cat <<\_ACEOF
1468 nzbget configure 17.1
1479 nzbget configure 18.1
14691480 generated by GNU Autoconf 2.61
14701481
14711482 Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
14791490 This file contains any messages produced by compilers while
14801491 running configure, to aid debugging if configure makes a mistake.
14811492
1482 It was created by nzbget $as_me 17.1, which was
1493 It was created by nzbget $as_me 18.1, which was
14831494 generated by GNU Autoconf 2.61. Invocation command line was
14841495
14851496 $ $0 $@
22752286
22762287 # Define the identity of the package.
22772288 PACKAGE='nzbget'
2278 VERSION='17.1'
2289 VERSION='18.1'
22792290
22802291
22812292 cat >>confdefs.h <<_ACEOF
97969807 { (exit 1); exit 1; }; }
97979808 fi
97989809 if test "$FOUND" = "yes"; then
9799 { echo "$as_me:$LINENO: checking for library containing CRYPTO_set_locking_callback" >&5
9800 echo $ECHO_N "checking for library containing CRYPTO_set_locking_callback... $ECHO_C" >&6; }
9801 if test "${ac_cv_search_CRYPTO_set_locking_callback+set}" = set; then
9810 { echo "$as_me:$LINENO: checking for library containing ASN1_OBJECT_free" >&5
9811 echo $ECHO_N "checking for library containing ASN1_OBJECT_free... $ECHO_C" >&6; }
9812 if test "${ac_cv_search_ASN1_OBJECT_free+set}" = set; then
98029813 echo $ECHO_N "(cached) $ECHO_C" >&6
98039814 else
98049815 ac_func_search_save_LIBS=$LIBS
98159826 #ifdef __cplusplus
98169827 extern "C"
98179828 #endif
9818 char CRYPTO_set_locking_callback ();
9829 char ASN1_OBJECT_free ();
98199830 int
98209831 main ()
98219832 {
9822 return CRYPTO_set_locking_callback ();
9833 return ASN1_OBJECT_free ();
98239834 ;
98249835 return 0;
98259836 }
98499860 test ! -s conftest.err
98509861 } && test -s conftest$ac_exeext &&
98519862 $as_test_x conftest$ac_exeext; then
9852 ac_cv_search_CRYPTO_set_locking_callback=$ac_res
9863 ac_cv_search_ASN1_OBJECT_free=$ac_res
98539864 else
98549865 echo "$as_me: failed program was:" >&5
98559866 sed 's/^/| /' conftest.$ac_ext >&5
98599870
98609871 rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \
98619872 conftest$ac_exeext
9862 if test "${ac_cv_search_CRYPTO_set_locking_callback+set}" = set; then
9873 if test "${ac_cv_search_ASN1_OBJECT_free+set}" = set; then
98639874 break
98649875 fi
98659876 done
9866 if test "${ac_cv_search_CRYPTO_set_locking_callback+set}" = set; then
9877 if test "${ac_cv_search_ASN1_OBJECT_free+set}" = set; then
98679878 :
98689879 else
9869 ac_cv_search_CRYPTO_set_locking_callback=no
9880 ac_cv_search_ASN1_OBJECT_free=no
98709881 fi
98719882 rm conftest.$ac_ext
98729883 LIBS=$ac_func_search_save_LIBS
98739884 fi
9874 { echo "$as_me:$LINENO: result: $ac_cv_search_CRYPTO_set_locking_callback" >&5
9875 echo "${ECHO_T}$ac_cv_search_CRYPTO_set_locking_callback" >&6; }
9876 ac_res=$ac_cv_search_CRYPTO_set_locking_callback
9885 { echo "$as_me:$LINENO: result: $ac_cv_search_ASN1_OBJECT_free" >&5
9886 echo "${ECHO_T}$ac_cv_search_ASN1_OBJECT_free" >&6; }
9887 ac_res=$ac_cv_search_ASN1_OBJECT_free
98779888 if test "$ac_res" != no; then
98789889 test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
9879 { echo "$as_me:$LINENO: checking for library containing SSL_library_init" >&5
9880 echo $ECHO_N "checking for library containing SSL_library_init... $ECHO_C" >&6; }
9881 if test "${ac_cv_search_SSL_library_init+set}" = set; then
9890 { echo "$as_me:$LINENO: checking for library containing SSL_CTX_new" >&5
9891 echo $ECHO_N "checking for library containing SSL_CTX_new... $ECHO_C" >&6; }
9892 if test "${ac_cv_search_SSL_CTX_new+set}" = set; then
98829893 echo $ECHO_N "(cached) $ECHO_C" >&6
98839894 else
98849895 ac_func_search_save_LIBS=$LIBS
98959906 #ifdef __cplusplus
98969907 extern "C"
98979908 #endif
9898 char SSL_library_init ();
9909 char SSL_CTX_new ();
98999910 int
99009911 main ()
99019912 {
9902 return SSL_library_init ();
9913 return SSL_CTX_new ();
99039914 ;
99049915 return 0;
99059916 }
99299940 test ! -s conftest.err
99309941 } && test -s conftest$ac_exeext &&
99319942 $as_test_x conftest$ac_exeext; then
9932 ac_cv_search_SSL_library_init=$ac_res
9943 ac_cv_search_SSL_CTX_new=$ac_res
99339944 else
99349945 echo "$as_me: failed program was:" >&5
99359946 sed 's/^/| /' conftest.$ac_ext >&5
99399950
99409951 rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \
99419952 conftest$ac_exeext
9942 if test "${ac_cv_search_SSL_library_init+set}" = set; then
9953 if test "${ac_cv_search_SSL_CTX_new+set}" = set; then
99439954 break
99449955 fi
99459956 done
9946 if test "${ac_cv_search_SSL_library_init+set}" = set; then
9957 if test "${ac_cv_search_SSL_CTX_new+set}" = set; then
99479958 :
99489959 else
9949 ac_cv_search_SSL_library_init=no
9960 ac_cv_search_SSL_CTX_new=no
99509961 fi
99519962 rm conftest.$ac_ext
99529963 LIBS=$ac_func_search_save_LIBS
99539964 fi
9954 { echo "$as_me:$LINENO: result: $ac_cv_search_SSL_library_init" >&5
9955 echo "${ECHO_T}$ac_cv_search_SSL_library_init" >&6; }
9956 ac_res=$ac_cv_search_SSL_library_init
9965 { echo "$as_me:$LINENO: result: $ac_cv_search_SSL_CTX_new" >&5
9966 echo "${ECHO_T}$ac_cv_search_SSL_CTX_new" >&6; }
9967 ac_res=$ac_cv_search_SSL_CTX_new
99579968 if test "$ac_res" != no; then
99589969 test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
99599970 FOUND=yes
1062910640
1063010641 cat >>confdefs.h <<\_ACEOF
1063110642 #define HAVE_LIBGNUTLS 1
10643 _ACEOF
10644
10645 fi
10646 fi
10647
10648 if test "$TLSLIB" = "GnuTLS"; then
10649
10650 # Check whether --with-libnettle_includes was given.
10651 if test "${with_libnettle_includes+set}" = set; then
10652 withval=$with_libnettle_includes; CPPFLAGS="${CPPFLAGS} -I${withval}"
10653 INCVAL="yes"
10654 else
10655 INCVAL="no"
10656 fi
10657
10658
10659 # Check whether --with-libnettle_libraries was given.
10660 if test "${with_libnettle_libraries+set}" = set; then
10661 withval=$with_libnettle_libraries; LDFLAGS="${LDFLAGS} -L${withval}"
10662 LIBVAL="yes"
10663 else
10664 LIBVAL="no"
10665 fi
10666
10667 if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then
10668
10669 pkg_failed=no
10670 { echo "$as_me:$LINENO: checking for nettle" >&5
10671 echo $ECHO_N "checking for nettle... $ECHO_C" >&6; }
10672
10673 if test -n "$PKG_CONFIG"; then
10674 if test -n "$nettle_CFLAGS"; then
10675 pkg_cv_nettle_CFLAGS="$nettle_CFLAGS"
10676 else
10677 if test -n "$PKG_CONFIG" && \
10678 { (echo "$as_me:$LINENO: \$PKG_CONFIG --exists --print-errors \"nettle\"") >&5
10679 ($PKG_CONFIG --exists --print-errors "nettle") 2>&5
10680 ac_status=$?
10681 echo "$as_me:$LINENO: \$? = $ac_status" >&5
10682 (exit $ac_status); }; then
10683 pkg_cv_nettle_CFLAGS=`$PKG_CONFIG --cflags "nettle" 2>/dev/null`
10684 else
10685 pkg_failed=yes
10686 fi
10687 fi
10688 else
10689 pkg_failed=untried
10690 fi
10691 if test -n "$PKG_CONFIG"; then
10692 if test -n "$nettle_LIBS"; then
10693 pkg_cv_nettle_LIBS="$nettle_LIBS"
10694 else
10695 if test -n "$PKG_CONFIG" && \
10696 { (echo "$as_me:$LINENO: \$PKG_CONFIG --exists --print-errors \"nettle\"") >&5
10697 ($PKG_CONFIG --exists --print-errors "nettle") 2>&5
10698 ac_status=$?
10699 echo "$as_me:$LINENO: \$? = $ac_status" >&5
10700 (exit $ac_status); }; then
10701 pkg_cv_nettle_LIBS=`$PKG_CONFIG --libs "nettle" 2>/dev/null`
10702 else
10703 pkg_failed=yes
10704 fi
10705 fi
10706 else
10707 pkg_failed=untried
10708 fi
10709
10710
10711
10712 if test $pkg_failed = yes; then
10713
10714 if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
10715 _pkg_short_errors_supported=yes
10716 else
10717 _pkg_short_errors_supported=no
10718 fi
10719 if test $_pkg_short_errors_supported = yes; then
10720 nettle_PKG_ERRORS=`$PKG_CONFIG --short-errors --errors-to-stdout --print-errors "nettle"`
10721 else
10722 nettle_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "nettle"`
10723 fi
10724 # Put the nasty error message in config.log where it belongs
10725 echo "$nettle_PKG_ERRORS" >&5
10726
10727 { { echo "$as_me:$LINENO: error: Package requirements (nettle) were not met:
10728
10729 $nettle_PKG_ERRORS
10730
10731 Consider adjusting the PKG_CONFIG_PATH environment variable if you
10732 installed software in a non-standard prefix.
10733
10734 Alternatively, you may set the environment variables nettle_CFLAGS
10735 and nettle_LIBS to avoid the need to call pkg-config.
10736 See the pkg-config man page for more details.
10737 " >&5
10738 echo "$as_me: error: Package requirements (nettle) were not met:
10739
10740 $nettle_PKG_ERRORS
10741
10742 Consider adjusting the PKG_CONFIG_PATH environment variable if you
10743 installed software in a non-standard prefix.
10744
10745 Alternatively, you may set the environment variables nettle_CFLAGS
10746 and nettle_LIBS to avoid the need to call pkg-config.
10747 See the pkg-config man page for more details.
10748 " >&2;}
10749 { (exit 1); exit 1; }; }
10750 elif test $pkg_failed = untried; then
10751 { { echo "$as_me:$LINENO: error: The pkg-config script could not be found or is too old. Make sure it
10752 is in your PATH or set the PKG_CONFIG environment variable to the full
10753 path to pkg-config.
10754
10755 Alternatively, you may set the environment variables nettle_CFLAGS
10756 and nettle_LIBS to avoid the need to call pkg-config.
10757 See the pkg-config man page for more details.
10758
10759 To get pkg-config, see <http://pkg-config.freedesktop.org/>.
10760 See \`config.log' for more details." >&5
10761 echo "$as_me: error: The pkg-config script could not be found or is too old. Make sure it
10762 is in your PATH or set the PKG_CONFIG environment variable to the full
10763 path to pkg-config.
10764
10765 Alternatively, you may set the environment variables nettle_CFLAGS
10766 and nettle_LIBS to avoid the need to call pkg-config.
10767 See the pkg-config man page for more details.
10768
10769 To get pkg-config, see <http://pkg-config.freedesktop.org/>.
10770 See \`config.log' for more details." >&2;}
10771 { (exit 1); exit 1; }; }
10772 else
10773 nettle_CFLAGS=$pkg_cv_nettle_CFLAGS
10774 nettle_LIBS=$pkg_cv_nettle_LIBS
10775 { echo "$as_me:$LINENO: result: yes" >&5
10776 echo "${ECHO_T}yes" >&6; }
10777 LIBS="${LIBS} $nettle_LIBS"
10778 CPPFLAGS="${CPPFLAGS} $nettle_CFLAGS"
10779 fi
10780 fi
10781 if test "${ac_cv_header_nettle_sha_h+set}" = set; then
10782 { echo "$as_me:$LINENO: checking for nettle/sha.h" >&5
10783 echo $ECHO_N "checking for nettle/sha.h... $ECHO_C" >&6; }
10784 if test "${ac_cv_header_nettle_sha_h+set}" = set; then
10785 echo $ECHO_N "(cached) $ECHO_C" >&6
10786 fi
10787 { echo "$as_me:$LINENO: result: $ac_cv_header_nettle_sha_h" >&5
10788 echo "${ECHO_T}$ac_cv_header_nettle_sha_h" >&6; }
10789 else
10790 # Is the header compilable?
10791 { echo "$as_me:$LINENO: checking nettle/sha.h usability" >&5
10792 echo $ECHO_N "checking nettle/sha.h usability... $ECHO_C" >&6; }
10793 cat >conftest.$ac_ext <<_ACEOF
10794 /* confdefs.h. */
10795 _ACEOF
10796 cat confdefs.h >>conftest.$ac_ext
10797 cat >>conftest.$ac_ext <<_ACEOF
10798 /* end confdefs.h. */
10799 $ac_includes_default
10800 #include <nettle/sha.h>
10801 _ACEOF
10802 rm -f conftest.$ac_objext
10803 if { (ac_try="$ac_compile"
10804 case "(($ac_try" in
10805 *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
10806 *) ac_try_echo=$ac_try;;
10807 esac
10808 eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
10809 (eval "$ac_compile") 2>conftest.er1
10810 ac_status=$?
10811 grep -v '^ *+' conftest.er1 >conftest.err
10812 rm -f conftest.er1
10813 cat conftest.err >&5
10814 echo "$as_me:$LINENO: \$? = $ac_status" >&5
10815 (exit $ac_status); } && {
10816 test -z "$ac_cxx_werror_flag" ||
10817 test ! -s conftest.err
10818 } && test -s conftest.$ac_objext; then
10819 ac_header_compiler=yes
10820 else
10821 echo "$as_me: failed program was:" >&5
10822 sed 's/^/| /' conftest.$ac_ext >&5
10823
10824 ac_header_compiler=no
10825 fi
10826
10827 rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
10828 { echo "$as_me:$LINENO: result: $ac_header_compiler" >&5
10829 echo "${ECHO_T}$ac_header_compiler" >&6; }
10830
10831 # Is the header present?
10832 { echo "$as_me:$LINENO: checking nettle/sha.h presence" >&5
10833 echo $ECHO_N "checking nettle/sha.h presence... $ECHO_C" >&6; }
10834 cat >conftest.$ac_ext <<_ACEOF
10835 /* confdefs.h. */
10836 _ACEOF
10837 cat confdefs.h >>conftest.$ac_ext
10838 cat >>conftest.$ac_ext <<_ACEOF
10839 /* end confdefs.h. */
10840 #include <nettle/sha.h>
10841 _ACEOF
10842 if { (ac_try="$ac_cpp conftest.$ac_ext"
10843 case "(($ac_try" in
10844 *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
10845 *) ac_try_echo=$ac_try;;
10846 esac
10847 eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
10848 (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1
10849 ac_status=$?
10850 grep -v '^ *+' conftest.er1 >conftest.err
10851 rm -f conftest.er1
10852 cat conftest.err >&5
10853 echo "$as_me:$LINENO: \$? = $ac_status" >&5
10854 (exit $ac_status); } >/dev/null && {
10855 test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" ||
10856 test ! -s conftest.err
10857 }; then
10858 ac_header_preproc=yes
10859 else
10860 echo "$as_me: failed program was:" >&5
10861 sed 's/^/| /' conftest.$ac_ext >&5
10862
10863 ac_header_preproc=no
10864 fi
10865
10866 rm -f conftest.err conftest.$ac_ext
10867 { echo "$as_me:$LINENO: result: $ac_header_preproc" >&5
10868 echo "${ECHO_T}$ac_header_preproc" >&6; }
10869
10870 # So? What about this header?
10871 case $ac_header_compiler:$ac_header_preproc:$ac_cxx_preproc_warn_flag in
10872 yes:no: )
10873 { echo "$as_me:$LINENO: WARNING: nettle/sha.h: accepted by the compiler, rejected by the preprocessor!" >&5
10874 echo "$as_me: WARNING: nettle/sha.h: accepted by the compiler, rejected by the preprocessor!" >&2;}
10875 { echo "$as_me:$LINENO: WARNING: nettle/sha.h: proceeding with the compiler's result" >&5
10876 echo "$as_me: WARNING: nettle/sha.h: proceeding with the compiler's result" >&2;}
10877 ac_header_preproc=yes
10878 ;;
10879 no:yes:* )
10880 { echo "$as_me:$LINENO: WARNING: nettle/sha.h: present but cannot be compiled" >&5
10881 echo "$as_me: WARNING: nettle/sha.h: present but cannot be compiled" >&2;}
10882 { echo "$as_me:$LINENO: WARNING: nettle/sha.h: check for missing prerequisite headers?" >&5
10883 echo "$as_me: WARNING: nettle/sha.h: check for missing prerequisite headers?" >&2;}
10884 { echo "$as_me:$LINENO: WARNING: nettle/sha.h: see the Autoconf documentation" >&5
10885 echo "$as_me: WARNING: nettle/sha.h: see the Autoconf documentation" >&2;}
10886 { echo "$as_me:$LINENO: WARNING: nettle/sha.h: section \"Present But Cannot Be Compiled\"" >&5
10887 echo "$as_me: WARNING: nettle/sha.h: section \"Present But Cannot Be Compiled\"" >&2;}
10888 { echo "$as_me:$LINENO: WARNING: nettle/sha.h: proceeding with the preprocessor's result" >&5
10889 echo "$as_me: WARNING: nettle/sha.h: proceeding with the preprocessor's result" >&2;}
10890 { echo "$as_me:$LINENO: WARNING: nettle/sha.h: in the future, the compiler will take precedence" >&5
10891 echo "$as_me: WARNING: nettle/sha.h: in the future, the compiler will take precedence" >&2;}
10892 ( cat <<\_ASBOX
10893 ## ------------------------------------------- ##
10894 ## Report this to hugbug@users.sourceforge.net ##
10895 ## ------------------------------------------- ##
10896 _ASBOX
10897 ) | sed "s/^/$as_me: WARNING: /" >&2
10898 ;;
10899 esac
10900 { echo "$as_me:$LINENO: checking for nettle/sha.h" >&5
10901 echo $ECHO_N "checking for nettle/sha.h... $ECHO_C" >&6; }
10902 if test "${ac_cv_header_nettle_sha_h+set}" = set; then
10903 echo $ECHO_N "(cached) $ECHO_C" >&6
10904 else
10905 ac_cv_header_nettle_sha_h=$ac_header_preproc
10906 fi
10907 { echo "$as_me:$LINENO: result: $ac_cv_header_nettle_sha_h" >&5
10908 echo "${ECHO_T}$ac_cv_header_nettle_sha_h" >&6; }
10909
10910 fi
10911 if test $ac_cv_header_nettle_sha_h = yes; then
10912 FOUND=yes
10913 else
10914 FOUND=no
10915 fi
10916
10917
10918 if test "$FOUND" = "no"; then
10919 { { echo "$as_me:$LINENO: error: Couldn't find Nettle headers (sha.h)" >&5
10920 echo "$as_me: error: Couldn't find Nettle headers (sha.h)" >&2;}
10921 { (exit 1); exit 1; }; }
10922 fi
10923 { echo "$as_me:$LINENO: checking for library containing nettle_pbkdf2_hmac_sha256" >&5
10924 echo $ECHO_N "checking for library containing nettle_pbkdf2_hmac_sha256... $ECHO_C" >&6; }
10925 if test "${ac_cv_search_nettle_pbkdf2_hmac_sha256+set}" = set; then
10926 echo $ECHO_N "(cached) $ECHO_C" >&6
10927 else
10928 ac_func_search_save_LIBS=$LIBS
10929 cat >conftest.$ac_ext <<_ACEOF
10930 /* confdefs.h. */
10931 _ACEOF
10932 cat confdefs.h >>conftest.$ac_ext
10933 cat >>conftest.$ac_ext <<_ACEOF
10934 /* end confdefs.h. */
10935
10936 /* Override any GCC internal prototype to avoid an error.
10937 Use char because int might match the return type of a GCC
10938 builtin and then its argument prototype would still apply. */
10939 #ifdef __cplusplus
10940 extern "C"
10941 #endif
10942 char nettle_pbkdf2_hmac_sha256 ();
10943 int
10944 main ()
10945 {
10946 return nettle_pbkdf2_hmac_sha256 ();
10947 ;
10948 return 0;
10949 }
10950 _ACEOF
10951 for ac_lib in '' nettle; do
10952 if test -z "$ac_lib"; then
10953 ac_res="none required"
10954 else
10955 ac_res=-l$ac_lib
10956 LIBS="-l$ac_lib $ac_func_search_save_LIBS"
10957 fi
10958 rm -f conftest.$ac_objext conftest$ac_exeext
10959 if { (ac_try="$ac_link"
10960 case "(($ac_try" in
10961 *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
10962 *) ac_try_echo=$ac_try;;
10963 esac
10964 eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
10965 (eval "$ac_link") 2>conftest.er1
10966 ac_status=$?
10967 grep -v '^ *+' conftest.er1 >conftest.err
10968 rm -f conftest.er1
10969 cat conftest.err >&5
10970 echo "$as_me:$LINENO: \$? = $ac_status" >&5
10971 (exit $ac_status); } && {
10972 test -z "$ac_cxx_werror_flag" ||
10973 test ! -s conftest.err
10974 } && test -s conftest$ac_exeext &&
10975 $as_test_x conftest$ac_exeext; then
10976 ac_cv_search_nettle_pbkdf2_hmac_sha256=$ac_res
10977 else
10978 echo "$as_me: failed program was:" >&5
10979 sed 's/^/| /' conftest.$ac_ext >&5
10980
10981
10982 fi
10983
10984 rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \
10985 conftest$ac_exeext
10986 if test "${ac_cv_search_nettle_pbkdf2_hmac_sha256+set}" = set; then
10987 break
10988 fi
10989 done
10990 if test "${ac_cv_search_nettle_pbkdf2_hmac_sha256+set}" = set; then
10991 :
10992 else
10993 ac_cv_search_nettle_pbkdf2_hmac_sha256=no
10994 fi
10995 rm conftest.$ac_ext
10996 LIBS=$ac_func_search_save_LIBS
10997 fi
10998 { echo "$as_me:$LINENO: result: $ac_cv_search_nettle_pbkdf2_hmac_sha256" >&5
10999 echo "${ECHO_T}$ac_cv_search_nettle_pbkdf2_hmac_sha256" >&6; }
11000 ac_res=$ac_cv_search_nettle_pbkdf2_hmac_sha256
11001 if test "$ac_res" != no; then
11002 test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
11003 FOUND=yes
11004 else
11005 FOUND=no
11006 fi
11007
11008 if test "$FOUND" = "no"; then
11009 { { echo "$as_me:$LINENO: error: Couldn't find Nettle library, required when using GnuTLS" >&5
11010 echo "$as_me: error: Couldn't find Nettle library, required when using GnuTLS" >&2;}
11011 { (exit 1); exit 1; }; }
11012 fi
11013 if test "$FOUND" = "yes"; then
11014
11015 cat >>confdefs.h <<\_ACEOF
11016 #define HAVE_NETTLE 1
1063211017 _ACEOF
1063311018
1063411019 fi
1182412209 # report actual input values of CONFIG_FILES etc. instead of their
1182512210 # values after options handling.
1182612211 ac_log="
11827 This file was extended by nzbget $as_me 17.1, which was
12212 This file was extended by nzbget $as_me 18.1, which was
1182812213 generated by GNU Autoconf 2.61. Invocation command line was
1182912214
1183012215 CONFIG_FILES = $CONFIG_FILES
1187712262 _ACEOF
1187812263 cat >>$CONFIG_STATUS <<_ACEOF
1187912264 ac_cs_version="\\
11880 nzbget config.status 17.1
12265 nzbget config.status 18.1
1188112266 configured by $0, generated by GNU Autoconf 2.61,
1188212267 with options \\"`echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`\\"
1188312268
1220212587 openssl_LIBS!$openssl_LIBS$ac_delim
1220312588 gnutls_CFLAGS!$gnutls_CFLAGS$ac_delim
1220412589 gnutls_LIBS!$gnutls_LIBS$ac_delim
12590 nettle_CFLAGS!$nettle_CFLAGS$ac_delim
12591 nettle_LIBS!$nettle_LIBS$ac_delim
1220512592 zlib_CFLAGS!$zlib_CFLAGS$ac_delim
1220612593 zlib_LIBS!$zlib_LIBS$ac_delim
1220712594 WITH_TESTS_TRUE!$WITH_TESTS_TRUE$ac_delim
1221012597 LTLIBOBJS!$LTLIBOBJS$ac_delim
1221112598 _ACEOF
1221212599
12213 if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 15; then
12600 if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 17; then
1221412601 break
1221512602 elif $ac_last_try; then
1221612603 { { echo "$as_me:$LINENO: error: could not make $CONFIG_STATUS" >&5
00 #
11 # This file is part of nzbget. See <http://nzbget.net>.
22 #
3 # Copyright (C) 2008-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
3 # Copyright (C) 2008-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
44 #
55 # This program is free software; you can redistribute it and/or modify
66 # it under the terms of the GNU General Public License as published by
2020 # Process this file with autoconf to produce a configure script.
2121
2222 AC_PREREQ(2.59)
23 AC_INIT(nzbget, 17.1, hugbug@users.sourceforge.net)
23 AC_INIT(nzbget, 18.1, hugbug@users.sourceforge.net)
2424 AC_CONFIG_AUX_DIR(posix)
2525 AC_CANONICAL_TARGET
2626 AM_INIT_AUTOMAKE([foreign])
384384 AC_MSG_ERROR([Couldn't find OpenSSL headers (ssl.h)])
385385 fi
386386 if test "$FOUND" = "yes"; then
387 AC_SEARCH_LIBS([CRYPTO_set_locking_callback], [crypto],
388 AC_SEARCH_LIBS([SSL_library_init], [ssl],
387 AC_SEARCH_LIBS([ASN1_OBJECT_free], [crypto],
388 AC_SEARCH_LIBS([SSL_CTX_new], [ssl],
389389 FOUND=yes,
390390 FOUND=no),
391391 FOUND=no)
394394 fi
395395 if test "$FOUND" = "yes"; then
396396 TLSLIB="OpenSSL"
397 AC_DEFINE([HAVE_OPENSSL],1,[Define to 1 to use OpenSSL library for TLS/SSL-support.])
397 AC_DEFINE([HAVE_OPENSSL],1,[Define to 1 to use OpenSSL library for TLS/SSL-support and decryption.])
398398 fi
399399 fi
400400 fi
456456 AC_DEFINE([HAVE_LIBGNUTLS],1,[Define to 1 to use GnuTLS library for TLS/SSL-support.])
457457 fi
458458 fi
459
460 if test "$TLSLIB" = "GnuTLS"; then
461 AC_ARG_WITH(libnettle_includes,
462 [AS_HELP_STRING([--with-libnettle-includes=DIR], [Nettle include directory])],
463 [CPPFLAGS="${CPPFLAGS} -I${withval}"]
464 [INCVAL="yes"],
465 [INCVAL="no"])
466 AC_ARG_WITH(libnettle_libraries,
467 [AS_HELP_STRING([--with-libnettle-libraries=DIR], [Nettle library directory])],
468 [LDFLAGS="${LDFLAGS} -L${withval}"]
469 [LIBVAL="yes"],
470 [LIBVAL="no"])
471 if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then
472 PKG_CHECK_MODULES([nettle], [nettle],
473 [LIBS="${LIBS} $nettle_LIBS"]
474 [CPPFLAGS="${CPPFLAGS} $nettle_CFLAGS"])
475 fi
476 AC_CHECK_HEADER(nettle/sha.h,
477 FOUND=yes,
478 FOUND=no)
479 if test "$FOUND" = "no"; then
480 AC_MSG_ERROR([Couldn't find Nettle headers (sha.h)])
481 fi
482 AC_SEARCH_LIBS([nettle_pbkdf2_hmac_sha256], [nettle],
483 FOUND=yes,
484 FOUND=no)
485 if test "$FOUND" = "no"; then
486 AC_MSG_ERROR([Couldn't find Nettle library, required when using GnuTLS])
487 fi
488 if test "$FOUND" = "yes"; then
489 AC_DEFINE([HAVE_NETTLE],1,[Define to 1 to use Nettle library for decryption.])
490 fi
491 fi
459492 fi
460493
461494 if test "$TLSLIB" = ""; then
8888
8989 #ifdef HAVE_OPENSSL
9090
91 #ifndef CRYPTO_set_locking_callback
92 #define NEED_CRYPTO_LOCKING
93 #endif
94
95 #ifdef NEED_CRYPTO_LOCKING
96
9197 /**
9298 * Mutexes for OpenSSL
9399 */
107113 }
108114 }
109115
110 /*
111 static uint32 openssl_thread_id(void)
112 {
113 #ifdef WIN32
114 return (uint32)GetCurrentThreadId();
115 #else
116 return (uint32)pthread_self();
117 #endif
118 }
119 */
120
121116 static struct CRYPTO_dynlock_value* openssl_dynlock_create(const char *file, int line)
122117 {
123118 return (CRYPTO_dynlock_value*)new Mutex();
142137 }
143138 }
144139
140 #endif /* NEED_CRYPTO_LOCKING */
145141 #endif /* HAVE_OPENSSL */
146142
147143
171167 #endif /* HAVE_LIBGNUTLS */
172168
173169 #ifdef HAVE_OPENSSL
170
171 #ifdef NEED_CRYPTO_LOCKING
174172 for (int i = 0, num = CRYPTO_num_locks(); i < num; i++)
175173 {
176174 g_OpenSSLMutexes.emplace_back(std::make_unique<Mutex>());
177175 }
176
177 CRYPTO_set_locking_callback(openssl_locking);
178 CRYPTO_set_dynlock_create_callback(openssl_dynlock_create);
179 CRYPTO_set_dynlock_destroy_callback(openssl_dynlock_destroy);
180 CRYPTO_set_dynlock_lock_callback(openssl_dynlock_lock);
181 #endif /* NEED_CRYPTO_LOCKING */
178182
179183 SSL_load_error_strings();
180184 SSL_library_init();
181185 OpenSSL_add_all_algorithms();
182186
183 CRYPTO_set_locking_callback(openssl_locking);
184 //CRYPTO_set_id_callback(openssl_thread_id);
185 CRYPTO_set_dynlock_create_callback(openssl_dynlock_create);
186 CRYPTO_set_dynlock_destroy_callback(openssl_dynlock_destroy);
187 CRYPTO_set_dynlock_lock_callback(openssl_dynlock_lock);
188
189187 #endif /* HAVE_OPENSSL */
190188
191189 m_tlsSocketFinalizer = std::make_unique<TlsSocketFinalizer>();
286284
287285 m_initialized = true;
288286
289 const char* priority = !m_cipher.Empty() ? m_cipher.Str() : "NORMAL";
287 const char* priority = !m_cipher.Empty() ? m_cipher.Str() :
288 (m_certFile && m_keyFile ? "NORMAL:!VERS-SSL3.0" : "NORMAL");
290289
291290 m_retCode = gnutls_priority_set_direct((gnutls_session_t)m_session, priority, nullptr);
292291 if (m_retCode != 0)
353352 Close();
354353 return false;
355354 }
355 if (!SSL_CTX_set_options((SSL_CTX*)m_context, SSL_OP_NO_SSLv3))
356 {
357 ReportError("Could not select minimum protocol version for TLS");
358 Close();
359 return false;
360 }
356361 }
357362
358363 m_session = SSL_new((SSL_CTX*)m_context);
5353
5454 SetStatus(adRunning);
5555
56 int remainedDownloadRetries = g_Options->GetRetries() > 0 ? g_Options->GetRetries() : 1;
56 int remainedDownloadRetries = g_Options->GetUrlRetries() > 0 ? g_Options->GetUrlRetries() : 1;
5757 int remainedConnectRetries = remainedDownloadRetries > 10 ? remainedDownloadRetries : 10;
5858 if (!m_retry)
5959 {
7373 ((Status == adConnectError) && (remainedConnectRetries > 1)))
7474 && !IsStopped() && !(!m_force && g_Options->GetPauseDownload()))
7575 {
76 detail("Waiting %i sec to retry", g_Options->GetRetryInterval());
76 detail("Waiting %i sec to retry", g_Options->GetUrlInterval());
7777 int msec = 0;
78 while (!IsStopped() && (msec < g_Options->GetRetryInterval() * 1000) &&
78 while (!IsStopped() && (msec < g_Options->GetUrlInterval() * 1000) &&
7979 !(!m_force && g_Options->GetPauseDownload()))
8080 {
8181 usleep(100 * 1000);
6161 if (exitCode != FEED_SUCCESS)
6262 {
6363 infoName[0] = 'F'; // uppercase
64 PrintMessage(Message::mkError, "%s failed", GetInfoName());
64 PrintMessage(Message::mkError, "%s failed", *infoName);
6565 m_success = false;
6666 }
6767
9999 infoName[0] = 'P'; // uppercase
100100
101101 SetLogPrefix(nullptr);
102 ScriptStatus::EStatus status = AnalyseExitCode(exitCode);
102 ScriptStatus::EStatus status = AnalyseExitCode(exitCode, infoName);
103103
104104 {
105105 GuardedDownloadQueue guard = DownloadQueue::Guard();
175175 PrepareEnvScript(m_postInfo->GetNzbInfo()->GetParameters(), scriptName);
176176 }
177177
178 ScriptStatus::EStatus PostScriptController::AnalyseExitCode(int exitCode)
178 ScriptStatus::EStatus PostScriptController::AnalyseExitCode(int exitCode, const char* upInfoName)
179179 {
180180 // The ScriptStatus is accumulated for all scripts:
181181 // If any script has failed the status is "failure", etc.
183183 switch (exitCode)
184184 {
185185 case POSTPROCESS_SUCCESS:
186 PrintMessage(Message::mkInfo, "%s successful", GetInfoName());
186 PrintMessage(Message::mkInfo, "%s successful", upInfoName);
187187 return ScriptStatus::srSuccess;
188188
189189 case POSTPROCESS_ERROR:
190190 case -1: // Execute() returns -1 if the process could not be started (file not found or other problem)
191 PrintMessage(Message::mkError, "%s failed", GetInfoName());
191 PrintMessage(Message::mkError, "%s failed", upInfoName);
192192 return ScriptStatus::srFailure;
193193
194194 case POSTPROCESS_NONE:
195 PrintMessage(Message::mkInfo, "%s skipped", GetInfoName());
195 PrintMessage(Message::mkInfo, "%s skipped", upInfoName);
196196 return ScriptStatus::srNone;
197197
198198 #ifndef DISABLE_PARCHECK
199199 case POSTPROCESS_PARCHECK:
200200 if (m_postInfo->GetNzbInfo()->GetParStatus() > NzbInfo::psSkipped)
201201 {
202 PrintMessage(Message::mkError, "%s requested par-check/repair, but the collection was already checked", GetInfoName());
202 PrintMessage(Message::mkError, "%s requested par-check/repair, but the collection was already checked", upInfoName);
203203 return ScriptStatus::srFailure;
204204 }
205205 else
206206 {
207 PrintMessage(Message::mkInfo, "%s requested par-check/repair", GetInfoName());
207 PrintMessage(Message::mkInfo, "%s requested par-check/repair", upInfoName);
208208 m_postInfo->SetRequestParCheck(true);
209209 m_postInfo->SetForceRepair(true);
210210 return ScriptStatus::srSuccess;
213213 #endif
214214
215215 default:
216 PrintMessage(Message::mkError, "%s failed (terminated with unknown status)", GetInfoName());
216 PrintMessage(Message::mkError, "%s failed (terminated with unknown status)", upInfoName);
217217 return ScriptStatus::srFailure;
218218 }
219219 }
4040 ScriptConfig::Script* m_script;
4141
4242 void PrepareParams(const char* scriptName);
43 ScriptStatus::EStatus AnalyseExitCode(int exitCode);
43 ScriptStatus::EStatus AnalyseExitCode(int exitCode, const char* upInfoName);
4444 };
4545
4646 #endif
111111 {
112112 nzbInfo->PrintMessage(Message::mkWarning, "Cancelling download and deleting %s", *m_nzbName);
113113 nzbInfo->SetDeleteStatus(NzbInfo::dsBad);
114 downloadQueue->EditEntry(m_id, DownloadQueue::eaGroupDelete, 0, nullptr);
114 downloadQueue->EditEntry(m_id, DownloadQueue::eaGroupDelete, nullptr);
115115 }
116116 }
117117
335335 return false;
336336 }
337337
338 // check queue-scripts
339 const char* queueScript = g_Options->GetQueueScript();
340 if (!Util::EmptyStr(queueScript))
341 {
342 Tokenizer tok(queueScript, ",;");
343 while (const char* scriptName = tok.Next())
344 {
345 if (FileSystem::SameFilename(scriptName, script.GetName()))
346 {
347 return true;
348 }
349 }
350 }
351
352 // check post-processing-scripts assigned for that nzb
338 // check extension scripts assigned for that nzb
353339 for (NzbParameter& parameter : nzbInfo->GetParameters())
354340 {
355341 const char* varname = parameter.GetName();
367353 }
368354 }
369355
370 // for URL-events the post-processing scripts are not assigned yet;
371 // instead we take the default post-processing scripts for the category (or global)
356 // for URL-events the extension scripts are not assigned yet;
357 // instead we take the default extension scripts for the category (or global)
372358 if (event == qeUrlCompleted)
373359 {
374 const char* postScript = g_Options->GetPostScript();
360 const char* postScript = g_Options->GetExtensions();
375361 if (!Util::EmptyStr(nzbInfo->GetCategory()))
376362 {
377363 Options::Category* categoryObj = g_Options->FindCategory(nzbInfo->GetCategory(), false);
378 if (categoryObj && !Util::EmptyStr(categoryObj->GetPostScript()))
379 {
380 postScript = categoryObj->GetPostScript();
364 if (categoryObj && !Util::EmptyStr(categoryObj->GetExtensions()))
365 {
366 postScript = categoryObj->GetExtensions();
381367 }
382368 }
383369
2323 #include "Options.h"
2424 #include "Log.h"
2525 #include "FileSystem.h"
26
27 class ScanScriptCheck : public NzbScriptController
28 {
29 protected:
30 virtual void ExecuteScript(ScriptConfig::Script* script) { has |= script->GetScanScript(); }
31 bool has = false;
32 friend class ScanScriptController;
33 };
34
35
36 bool ScanScriptController::HasScripts()
37 {
38 ScanScriptCheck check;
39 check.ExecuteScriptList(g_Options->GetExtensions());
40 return check.has;
41 }
2642
2743 void ScanScriptController::ExecuteScripts(const char* nzbFilename,
2844 const char* url, const char* directory, CString* nzbName, CString* category,
4460 scriptController.m_dupeMode = dupeMode;
4561 scriptController.m_prefixLen = 0;
4662
47 scriptController.ExecuteScriptList(g_Options->GetScanScript());
63 const char* extensions = g_Options->GetExtensions();
64
65 if (!Util::EmptyStr(*category))
66 {
67 Options::Category* categoryObj = g_Options->FindCategory(*category, false);
68 if (categoryObj && !Util::EmptyStr(categoryObj->GetExtensions()))
69 {
70 extensions = categoryObj->GetExtensions();
71 }
72 }
73
74 scriptController.ExecuteScriptList(extensions);
4875 }
4976
5077 void ScanScriptController::ExecuteScript(ScriptConfig::Script* script)
2929 const char* directory, CString* nzbName, CString* category, int* priority,
3030 NzbParameterList* parameters, bool* addTop, bool* addPaused,
3131 CString* dupeKey, int* dupeScore, EDupeMode* dupeMode);
32 static bool HasScripts();
3233
3334 protected:
3435 virtual void ExecuteScript(ScriptConfig::Script* script);
6868 return;
6969 }
7070
71 PrintMessage(Message::mkInfo, "Executing scheduler-script %s for Task%i", script->GetName(), m_taskId);
71 BString<1024> taskName(" for Task%i", m_taskId);
72 if (m_taskId == 0)
73 {
74 taskName = "";
75 }
76
77 PrintMessage(Message::mkInfo, "Executing scheduler-script %s%s", script->GetName(), *taskName);
7278
7379 SetArgs({script->GetLocation()});
7480
75 BString<1024> infoName("scheduler-script %s for Task%i", script->GetName(), m_taskId);
81 BString<1024> infoName("scheduler-script %s%s", script->GetName(), *taskName);
7682 SetInfoName(infoName);
7783
7884 SetLogPrefix(script->GetDisplayName());
3232 static const char* FEED_SCRIPT_SIGNATURE = "FEED";
3333 static const char* END_SCRIPT_SIGNATURE = " SCRIPT";
3434 static const char* QUEUE_EVENTS_SIGNATURE = "### QUEUE EVENTS:";
35
36
37 ScriptConfig::Script::Script(const char* name, const char* location)
38 {
39 m_name = name;
40 m_location = location;
41 m_displayName = name;
42 m_postScript = false;
43 m_scanScript = false;
44 m_queueScript = false;
45 m_schedulerScript = false;
46 m_feedScript = false;
47 }
48
35 static const char* TASK_TIME_SIGNATURE = "### TASK TIME:";
36 static const char* DEFINITION_SIGNATURE = "###";
4937
5038 void ScriptConfig::InitOptions()
5139 {
5240 InitScripts();
5341 InitConfigTemplates();
42 CreateTasks();
5443 }
5544
5645 bool ScriptConfig::LoadConfig(Options::OptEntries* optEntries)
8069
8170 CString optname;
8271 CString optvalue;
83 if (g_Options->SplitOptionString(buf, optname, optvalue))
72 if (Options::SplitOptionString(buf, optname, optvalue))
8473 {
8574 optEntries->emplace_back(optname, optvalue);
8675 }
8776 }
8877
8978 infile.Close();
79
80 Options::ConvertOldOptions(optEntries);
9081
9182 return true;
9283 }
179170 LoadScripts(&scriptList);
180171
181172 const int beginSignatureLen = strlen(BEGIN_SCRIPT_SIGNATURE);
182 const int queueEventsSignatureLen = strlen(QUEUE_EVENTS_SIGNATURE);
173 const int definitionSignatureLen = strlen(DEFINITION_SIGNATURE);
183174
184175 for (Script& script : scriptList)
185176 {
193184 StringBuilder templ;
194185 char buf[1024];
195186 bool inConfig = false;
187 bool inHeader = false;
196188
197189 while (infile.ReadLine(buf, sizeof(buf) - 1))
198190 {
209201 break;
210202 }
211203 inConfig = true;
204 inHeader = true;
212205 continue;
213206 }
214207
215 bool skip = !strncmp(buf, QUEUE_EVENTS_SIGNATURE, queueEventsSignatureLen);
216
217 if (inConfig && !skip)
208 inHeader &= !strncmp(buf, DEFINITION_SIGNATURE, definitionSignatureLen);
209
210 if (inConfig && !inHeader)
218211 {
219212 templ.Append(buf);
220213 }
286279
287280 void ScriptConfig::LoadScriptDir(Scripts* scripts, const char* directory, bool isSubDir)
288281 {
289 CharBuffer buffer(1024*10 + 1);
282 DirBrowser dir(directory);
283 while (const char* filename = dir.Next())
284 {
285 if (filename[0] != '.' && filename[0] != '_')
286 {
287 BString<1024> fullFilename("%s%c%s", directory, PATH_SEPARATOR, filename);
288
289 if (!FileSystem::DirectoryExists(fullFilename))
290 {
291 BString<1024> scriptName = BuildScriptName(directory, filename, isSubDir);
292 if (ScriptExists(scripts, scriptName))
293 {
294 continue;
295 }
296
297 Script script(scriptName, fullFilename);
298 if (LoadScriptFile(&script))
299 {
300 scripts->push_back(std::move(script));
301 }
302 }
303 else if (!isSubDir)
304 {
305 LoadScriptDir(scripts, fullFilename, true);
306 }
307 }
308 }
309 }
310
311 bool ScriptConfig::LoadScriptFile(Script* script)
312 {
313 DiskFile infile;
314 if (!infile.Open(script->GetLocation(), DiskFile::omRead))
315 {
316 return false;
317 }
318
319 CharBuffer buffer(1024 * 10 + 1);
290320
291321 const int beginSignatureLen = strlen(BEGIN_SCRIPT_SIGNATURE);
292322 const int queueEventsSignatureLen = strlen(QUEUE_EVENTS_SIGNATURE);
293
294 DirBrowser dir(directory);
295 while (const char* filename = dir.Next())
296 {
297 if (filename[0] != '.' && filename[0] != '_')
298 {
299 BString<1024> fullFilename("%s%c%s", directory, PATH_SEPARATOR, filename);
300
301 if (!FileSystem::DirectoryExists(fullFilename))
302 {
303 BString<1024> scriptName = BuildScriptName(directory, filename, isSubDir);
304 if (ScriptExists(scripts, scriptName))
305 {
306 continue;
307 }
308
309 // check if the file contains pp-script-signature
310 DiskFile infile;
311 if (infile.Open(fullFilename, DiskFile::omRead))
312 {
313 // read first 10KB of the file and look for signature
314 int readBytes = (int)infile.Read(buffer, buffer.Size() - 1);
315 infile.Close();
316 buffer[readBytes] = '\0';
317
318 // split buffer into lines
319 Tokenizer tok(buffer, "\n\r", true);
320 while (char* line = tok.Next())
321 {
322 if (!strncmp(line, BEGIN_SCRIPT_SIGNATURE, beginSignatureLen) &&
323 strstr(line, END_SCRIPT_SIGNATURE))
324 {
325 bool postScript = strstr(line, POST_SCRIPT_SIGNATURE);
326 bool scanScript = strstr(line, SCAN_SCRIPT_SIGNATURE);
327 bool queueScript = strstr(line, QUEUE_SCRIPT_SIGNATURE);
328 bool schedulerScript = strstr(line, SCHEDULER_SCRIPT_SIGNATURE);
329 bool feedScript = strstr(line, FEED_SCRIPT_SIGNATURE);
330 if (postScript || scanScript || queueScript || schedulerScript || feedScript)
331 {
332 char* queueEvents = nullptr;
333 if (queueScript)
334 {
335 while (char* line = tok.Next())
336 {
337 if (!strncmp(line, QUEUE_EVENTS_SIGNATURE, queueEventsSignatureLen))
338 {
339 queueEvents = line + queueEventsSignatureLen;
340 break;
341 }
342 }
343 }
344
345 scripts->emplace_back(scriptName, fullFilename);
346 Script& script = scripts->back();
347 script.SetPostScript(postScript);
348 script.SetScanScript(scanScript);
349 script.SetQueueScript(queueScript);
350 script.SetSchedulerScript(schedulerScript);
351 script.SetFeedScript(feedScript);
352 script.SetQueueEvents(queueEvents);
353 break;
354 }
355 }
356 }
357 }
358 }
359 else if (!isSubDir)
360 {
361 LoadScriptDir(scripts, fullFilename, true);
362 }
363 }
364 }
365 }
366
323 const int taskTimeSignatureLen = strlen(TASK_TIME_SIGNATURE);
324 const int definitionSignatureLen = strlen(DEFINITION_SIGNATURE);
325
326 // check if the file contains pp-script-signature
327 // read first 10KB of the file and look for signature
328 int readBytes = (int)infile.Read(buffer, buffer.Size() - 1);
329 infile.Close();
330 buffer[readBytes] = '\0';
331
332 bool postScript = false;
333 bool scanScript = false;
334 bool queueScript = false;
335 bool schedulerScript = false;
336 bool feedScript = false;
337 char* queueEvents = nullptr;
338 char* taskTime = nullptr;
339
340 bool inConfig = false;
341 bool afterConfig = false;
342
343 // Declarations "QUEUE EVENT:" and "TASK TIME:" can be placed:
344 // - in script definition body (between opening and closing script signatures);
345 // - immediately before script definition (before opening script signature);
346 // - immediately after script definition (after closing script signature).
347 // The last two pissibilities are provided to increase compatibility of scripts with older
348 // nzbget versions which do not expect the extra declarations in the script defintion body.
349
350 Tokenizer tok(buffer, "\n\r", true);
351 while (char* line = tok.Next())
352 {
353 if (!strncmp(line, QUEUE_EVENTS_SIGNATURE, queueEventsSignatureLen))
354 {
355 queueEvents = line + queueEventsSignatureLen;
356 }
357 else if (!strncmp(line, TASK_TIME_SIGNATURE, taskTimeSignatureLen))
358 {
359 taskTime = line + taskTimeSignatureLen;
360 }
361
362 bool header = !strncmp(line, DEFINITION_SIGNATURE, definitionSignatureLen);
363 if (!header && !inConfig)
364 {
365 queueEvents = nullptr;
366 taskTime = nullptr;
367 }
368
369 if (!header && afterConfig)
370 {
371 break;
372 }
373
374 if (!strncmp(line, BEGIN_SCRIPT_SIGNATURE, beginSignatureLen) && strstr(line, END_SCRIPT_SIGNATURE))
375 {
376 if (!inConfig)
377 {
378 inConfig = true;
379 postScript = strstr(line, POST_SCRIPT_SIGNATURE);
380 scanScript = strstr(line, SCAN_SCRIPT_SIGNATURE);
381 queueScript = strstr(line, QUEUE_SCRIPT_SIGNATURE);
382 schedulerScript = strstr(line, SCHEDULER_SCRIPT_SIGNATURE);
383 feedScript = strstr(line, FEED_SCRIPT_SIGNATURE);
384 }
385 else
386 {
387 afterConfig = true;
388 }
389 }
390 }
391
392 if (!(postScript || scanScript || queueScript || schedulerScript || feedScript))
393 {
394 return false;
395 }
396
397 // trim decorations
398 char* p;
399 while (queueEvents && *queueEvents && *(p = queueEvents + strlen(queueEvents) - 1) == '#') *p = '\0';
400 if (queueEvents) queueEvents = Util::Trim(queueEvents);
401 while (taskTime && *taskTime && *(p = taskTime + strlen(taskTime) - 1) == '#') *p = '\0';
402 if (taskTime) taskTime = Util::Trim(taskTime);
403
404 script->SetPostScript(postScript);
405 script->SetScanScript(scanScript);
406 script->SetQueueScript(queueScript);
407 script->SetSchedulerScript(schedulerScript);
408 script->SetFeedScript(feedScript);
409 script->SetQueueEvents(queueEvents);
410 script->SetTaskTime(taskTime);
411
412 return true;
413 }
367414
368415 BString<1024> ScriptConfig::BuildScriptName(const char* directory, const char* filename, bool isSubDir)
369416 {
430477 script.SetDisplayName(displayName);
431478 }
432479 }
480
481 void ScriptConfig::CreateTasks()
482 {
483 for (Script& script : m_scripts)
484 {
485 if (script.GetSchedulerScript() && !Util::EmptyStr(script.GetTaskTime()))
486 {
487 Tokenizer tok(g_Options->GetExtensions(), ",;");
488 while (const char* scriptName = tok.Next())
489 {
490 if (FileSystem::SameFilename(scriptName, script.GetName()))
491 {
492 g_Options->CreateSchedulerTask(0, script.GetTaskTime(),
493 nullptr, Options::scScript, script.GetName());
494 break;
495 }
496 }
497 }
498 }
499 }
3030 class Script
3131 {
3232 public:
33 Script(const char* name, const char* location);
33 Script(const char* name, const char* location) :
34 m_name(name), m_location(location), m_displayName(name) {};
3435 Script(Script&&) = default;
3536 const char* GetName() { return m_name; }
3637 const char* GetLocation() { return m_location; }
4849 void SetFeedScript(bool feedScript) { m_feedScript = feedScript; }
4950 void SetQueueEvents(const char* queueEvents) { m_queueEvents = queueEvents; }
5051 const char* GetQueueEvents() { return m_queueEvents; }
52 void SetTaskTime(const char* taskTime) { m_taskTime = taskTime; }
53 const char* GetTaskTime() { return m_taskTime; }
5154
5255 private:
5356 CString m_name;
5457 CString m_location;
5558 CString m_displayName;
56 bool m_postScript;
57 bool m_scanScript;
58 bool m_queueScript;
59 bool m_schedulerScript;
60 bool m_feedScript;
59 bool m_postScript = false;
60 bool m_scanScript = false;
61 bool m_queueScript = false;
62 bool m_schedulerScript = false;
63 bool m_feedScript = false;
6164 CString m_queueEvents;
65 CString m_taskTime;
6266 };
6367
6468 typedef std::list<Script> Scripts;
7478 private:
7579 Script m_script;
7680 CString m_template;
77
78 friend class Options;
7981 };
8082
8183 typedef std::deque<ConfigTemplate> ConfigTemplates;
9395
9496 void InitScripts();
9597 void InitConfigTemplates();
98 void CreateTasks();
9699 void LoadScriptDir(Scripts* scripts, const char* directory, bool isSubDir);
97100 void BuildScriptDisplayNames(Scripts* scripts);
98101 void LoadScripts(Scripts* scripts);
102 bool LoadScriptFile(Script* script);
99103 BString<1024>BuildScriptName(const char* directory, const char* filename, bool isSubDir);
100104 bool ScriptExists(Scripts* scripts, const char* scriptName);
101105 };
238238 feedDownloader->Attach(this);
239239 feedDownloader->SetFeedInfo(feedInfo);
240240 feedDownloader->SetUrl(feedInfo->GetUrl());
241 if (strlen(feedInfo->GetName()) > 0)
242 {
243 feedDownloader->SetInfoName(feedInfo->GetName());
244 }
245 else
246 {
247 feedDownloader->SetInfoName(NzbInfo::MakeNiceUrlName(feedInfo->GetUrl(), ""));
248 }
241 feedDownloader->SetInfoName(feedInfo->GetName());
249242 feedDownloader->SetForce(force || g_Options->GetUrlForce());
250243
251244 BString<1024> outFilename;
303296 {
304297 bool scriptSuccess = true;
305298 FeedScriptController::ExecuteScripts(
306 !Util::EmptyStr(feedInfo->GetFeedScript()) ? feedInfo->GetFeedScript(): g_Options->GetFeedScript(),
299 !Util::EmptyStr(feedInfo->GetExtensions()) ? feedInfo->GetExtensions(): g_Options->GetExtensions(),
307300 feedInfo->GetOutputFilename(), feedInfo->GetId(), &scriptSuccess);
308301 if (!scriptSuccess)
309302 {
311304 return;
312305 }
313306
314 std::unique_ptr<FeedFile> feedFile = std::make_unique<FeedFile>(feedInfo->GetOutputFilename());
315 bool parsed = feedFile->Parse();
316 FileSystem::DeleteFile(feedInfo->GetOutputFilename());
307 std::unique_ptr<FeedFile> feedFile = parseFeed(feedInfo);
317308
318309 std::vector<std::unique_ptr<NzbInfo>> addedNzbs;
319310
320311 {
321312 Guard guard(m_downloadsMutex);
322 if (parsed)
313 if (feedFile)
323314 {
324315 std::unique_ptr<FeedItemList> feedItems = feedFile->DetachFeedItems();
325316 addedNzbs = ProcessFeed(feedInfo, feedItems.get());
468459
469460 return PreviewFeed(feedInfo->GetId(), feedInfo->GetName(), feedInfo->GetUrl(), feedInfo->GetFilter(),
470461 feedInfo->GetBacklog(), feedInfo->GetPauseNzb(), feedInfo->GetCategory(),
471 feedInfo->GetPriority(), feedInfo->GetInterval(), feedInfo->GetFeedScript(), 0, nullptr);
462 feedInfo->GetPriority(), feedInfo->GetInterval(), feedInfo->GetExtensions(), 0, nullptr);
472463 }
473464
474465 std::shared_ptr<FeedItemList> FeedCoordinator::PreviewFeed(int id,
534525 }
535526
536527 FeedScriptController::ExecuteScripts(
537 !Util::EmptyStr(feedInfo->GetFeedScript()) ? feedInfo->GetFeedScript(): g_Options->GetFeedScript(),
528 !Util::EmptyStr(feedInfo->GetExtensions()) ? feedInfo->GetExtensions(): g_Options->GetExtensions(),
538529 feedInfo->GetOutputFilename(), feedInfo->GetId(), nullptr);
539530
540 std::unique_ptr<FeedFile> feedFile = std::make_unique<FeedFile>(feedInfo->GetOutputFilename());
541 bool parsed = feedFile->Parse();
542 FileSystem::DeleteFile(feedInfo->GetOutputFilename());
543
544 if (!parsed)
531 std::unique_ptr<FeedFile> feedFile = parseFeed(feedInfo.get());
532 if (!feedFile)
545533 {
546534 return nullptr;
547535 }
587575 }
588576 }
589577
578 std::unique_ptr<FeedFile> FeedCoordinator::parseFeed(FeedInfo* feedInfo)
579 {
580 std::unique_ptr<FeedFile> feedFile = std::make_unique<FeedFile>(feedInfo->GetOutputFilename(), feedInfo->GetName());
581 if (feedFile->Parse())
582 {
583 FileSystem::DeleteFile(feedInfo->GetOutputFilename());
584 }
585 else
586 {
587 error("Feed file %s kept for troubleshooting (will be deleted on next successful feed fetch)", feedInfo->GetOutputFilename());
588 feedFile.reset();
589 }
590 return std::move(feedFile);
591 }
592
590593 void FeedCoordinator::DownloadQueueUpdate(Subject* caller, void* aspect)
591594 {
592595 debug("Notification from URL-Coordinator received");
2525 #include "Thread.h"
2626 #include "WebDownloader.h"
2727 #include "DownloadInfo.h"
28 #include "FeedFile.h"
2829 #include "FeedInfo.h"
2930 #include "Observer.h"
3031 #include "Util.h"
118119 void CleanupHistory();
119120 void CleanupCache();
120121 void CheckSaveFeeds();
122 std::unique_ptr<FeedFile> parseFeed(FeedInfo* feedInfo);
121123 };
122124
123125 extern FeedCoordinator* g_FeedCoordinator;
2424 #include "Options.h"
2525 #include "Util.h"
2626
27 FeedFile::FeedFile(const char* fileName) :
28 m_fileName(fileName)
27 FeedFile::FeedFile(const char* fileName, const char* infoName) :
28 m_fileName(fileName), m_infoName(infoName)
2929 {
3030 debug("Creating FeedFile");
3131
112112 {
113113 _bstr_t r(doc->GetparseError()->reason);
114114 const char* errMsg = r;
115 error("Error parsing rss feed: %s", errMsg);
115 error("Error parsing rss feed %s: %s", *m_infoName, errMsg);
116116 return false;
117117 }
118118
247247 //<newznab:attr name="size" value="5423523453534" />
248248 if (feedItemInfo.GetSize() == 0)
249249 {
250 tag = node->selectSingleNode("newznab:attr[@name='size']");
250 tag = node->selectSingleNode("newznab:attr[@name='size'] | nZEDb:attr[@name='size']");
251251 if (tag)
252252 {
253253 attr = tag->Getattributes()->getNamedItem("value");
261261 }
262262
263263 //<newznab:attr name="imdb" value="1588173"/>
264 tag = node->selectSingleNode("newznab:attr[@name='imdb']");
264 tag = node->selectSingleNode("newznab:attr[@name='imdb'] | nZEDb:attr[@name='imdb']");
265265 if (tag)
266266 {
267267 attr = tag->Getattributes()->getNamedItem("value");
274274 }
275275
276276 //<newznab:attr name="rageid" value="33877"/>
277 tag = node->selectSingleNode("newznab:attr[@name='rageid']");
277 tag = node->selectSingleNode("newznab:attr[@name='rageid'] | nZEDb:attr[@name='rageid']");
278278 if (tag)
279279 {
280280 attr = tag->Getattributes()->getNamedItem("value");
287287 }
288288
289289 //<newznab:attr name="tdvdbid" value="33877"/>
290 tag = node->selectSingleNode("newznab:attr[@name='tvdbid']");
290 tag = node->selectSingleNode("newznab:attr[@name='tvdbid'] | nZEDb:attr[@name='tvdbid']");
291291 if (tag)
292292 {
293293 attr = tag->Getattributes()->getNamedItem("value");
300300 }
301301
302302 //<newznab:attr name="tvmazeid" value="33877"/>
303 tag = node->selectSingleNode("newznab:attr[@name='tvmazeid']");
303 tag = node->selectSingleNode("newznab:attr[@name='tvmazeid'] | nZEDb:attr[@name='tvmazeid']");
304304 if (tag)
305305 {
306306 attr = tag->Getattributes()->getNamedItem("value");
314314
315315 //<newznab:attr name="episode" value="E09"/>
316316 //<newznab:attr name="episode" value="9"/>
317 tag = node->selectSingleNode("newznab:attr[@name='episode']");
317 tag = node->selectSingleNode("newznab:attr[@name='episode'] | nZEDb:attr[@name='episode']");
318318 if (tag)
319319 {
320320 attr = tag->Getattributes()->getNamedItem("value");
327327
328328 //<newznab:attr name="season" value="S03"/>
329329 //<newznab:attr name="season" value="3"/>
330 tag = node->selectSingleNode("newznab:attr[@name='season']");
330 tag = node->selectSingleNode("newznab:attr[@name='season'] | nZEDb:attr[@name='season']");
331331 if (tag)
332332 {
333333 attr = tag->Getattributes()->getNamedItem("value");
338338 }
339339 }
340340
341 MSXML::IXMLDOMNodeListPtr itemList = node->selectNodes("newznab:attr");
341 MSXML::IXMLDOMNodeListPtr itemList = node->selectNodes("newznab:attr | nZEDb:attr");
342342 for (int i = 0; i < itemList->Getlength(); i++)
343343 {
344344 MSXML::IXMLDOMNodePtr node = itemList->Getitem(i);
372372
373373 if (ret != 0)
374374 {
375 error("Failed to parse rss feed");
375 error("Failed to parse rss feed %s", *m_infoName);
376376 return false;
377377 }
378378
406406 }
407407 }
408408 }
409 else if (m_feedItemInfo && !strcmp("newznab:attr", name) &&
409 else if (m_feedItemInfo &&
410 (!strcmp("newznab:attr", name) || !strcmp("nZEDb:attr", name)) &&
410411 atts[0] && atts[1] && atts[2] && atts[3] &&
411412 !strcmp("name", atts[0]) && !strcmp("value", atts[2]))
412413 {
591592
592593 // remove trailing CRLF
593594 for (char* pend = errMsg + strlen(errMsg) - 1; pend >= errMsg && (*pend == '\n' || *pend == '\r' || *pend == ' '); pend--) *pend = '\0';
594 error("Error parsing rss feed: %s", errMsg);
595 error("Error parsing rss feed %s: %s", *file->m_infoName, errMsg);
595596 }
596597 #endif
2626 class FeedFile
2727 {
2828 public:
29 FeedFile(const char* fileName);
29 FeedFile(const char* fileName, const char* infoName);
3030 bool Parse();
3131 std::unique_ptr<FeedItemList> DetachFeedItems() { return std::move(m_feedItems); }
3232
3535 private:
3636 std::unique_ptr<FeedItemList> m_feedItems;
3737 CString m_fileName;
38 CString m_infoName;
3839
3940 void ParseSubject(FeedItemInfo& feedItemInfo);
4041 #ifdef WIN32
2222 #include "Util.h"
2323
2424 FeedInfo::FeedInfo(int id, const char* name, const char* url, bool backlog, int interval,
25 const char* filter, bool pauseNzb, const char* category, int priority, const char* feedScript) :
25 const char* filter, bool pauseNzb, const char* category, int priority, const char* extensions) :
2626 m_backlog(backlog), m_interval(interval), m_pauseNzb(pauseNzb), m_priority(priority)
2727 {
2828 m_id = id;
2929 m_name = name ? name : "";
30 if (m_name.Length() == 0)
31 {
32 m_name.Format("Feed%i", m_id);
33 }
3034 m_url = url ? url : "";
3135 m_filter = filter ? filter : "";
3236 m_filterHash = Util::HashBJ96(m_filter, strlen(m_filter), 0);
3337 m_category = category ? category : "";
34 m_feedScript = feedScript ? feedScript : "";
38 m_extensions = extensions ? extensions : "";
3539 }
3640
3741
3737
3838 FeedInfo(int id, const char* name, const char* url, bool backlog, int interval,
3939 const char* filter, bool pauseNzb, const char* category, int priority,
40 const char* feedScript);
40 const char* extensions);
4141 int GetId() { return m_id; }
4242 const char* GetName() { return m_name; }
4343 const char* GetUrl() { return m_url; }
4747 bool GetPauseNzb() { return m_pauseNzb; }
4848 const char* GetCategory() { return m_category; }
4949 int GetPriority() { return m_priority; }
50 const char* GetFeedScript() { return m_feedScript; }
50 const char* GetExtensions() { return m_extensions; }
5151 time_t GetLastUpdate() { return m_lastUpdate; }
5252 void SetLastUpdate(time_t lastUpdate) { m_lastUpdate = lastUpdate; }
5353 bool GetPreview() { return m_preview; }
7272 uint32 m_filterHash;
7373 bool m_pauseNzb;
7474 CString m_category;
75 CString m_feedScript;
75 CString m_extensions;
7676 int m_priority;
7777 time_t m_lastUpdate = 0;
7878 bool m_preview = false;
132132 }
133133 else
134134 {
135 return DownloadQueue::Guard()->EditEntry(id, action, offset, nullptr);
135 return DownloadQueue::Guard()->EditEntry(id, action, CString::FormatStr("%i", offset));
136136 }
137137 }
138138
7171 static const char* OPTION_BROKENLOG = "BrokenLog";
7272 static const char* OPTION_NZBLOG = "NzbLog";
7373 static const char* OPTION_DECODE = "Decode";
74 static const char* OPTION_RETRIES = "Retries";
75 static const char* OPTION_RETRYINTERVAL = "RetryInterval";
74 static const char* OPTION_ARTICLERETRIES = "ArticleRetries";
75 static const char* OPTION_ARTICLEINTERVAL = "ArticleInterval";
76 static const char* OPTION_URLRETRIES = "UrlRetries";
77 static const char* OPTION_URLINTERVAL = "UrlInterval";
7678 static const char* OPTION_TERMINATETIMEOUT = "TerminateTimeout";
7779 static const char* OPTION_CONTINUEPARTIAL = "ContinuePartial";
7880 static const char* OPTION_URLCONNECTIONS = "UrlConnections";
8688 static const char* OPTION_PARREPAIR = "ParRepair";
8789 static const char* OPTION_PARSCAN = "ParScan";
8890 static const char* OPTION_PARQUICK = "ParQuick";
91 static const char* OPTION_POSTSTRATEGY = "PostStrategy";
8992 static const char* OPTION_PARRENAME = "ParRename";
9093 static const char* OPTION_PARBUFFER = "ParBuffer";
9194 static const char* OPTION_PARTHREADS = "ParThreads";
95 static const char* OPTION_RARRENAME = "RarRename";
9296 static const char* OPTION_HEALTHCHECK = "HealthCheck";
93 static const char* OPTION_SCANSCRIPT = "ScanScript";
94 static const char* OPTION_QUEUESCRIPT = "QueueScript";
95 static const char* OPTION_FEEDSCRIPT = "FeedScript";
9697 static const char* OPTION_UMASK = "UMask";
9798 static const char* OPTION_UPDATEINTERVAL = "UpdateInterval";
9899 static const char* OPTION_CURSESNZBNAME = "CursesNzbName";
118119 static const char* OPTION_UNPACKPASSFILE = "UnpackPassFile";
119120 static const char* OPTION_UNPACKPAUSEQUEUE = "UnpackPauseQueue";
120121 static const char* OPTION_SCRIPTORDER = "ScriptOrder";
121 static const char* OPTION_POSTSCRIPT = "PostScript";
122 static const char* OPTION_EXTENSIONS = "Extensions";
122123 static const char* OPTION_EXTCLEANUPDISK = "ExtCleanupDisk";
123124 static const char* OPTION_PARIGNOREEXT = "ParIgnoreExt";
125 static const char* OPTION_UNPACKIGNOREEXT = "UnpackIgnoreExt";
124126 static const char* OPTION_FEEDHISTORY = "FeedHistory";
125127 static const char* OPTION_URLFORCE = "UrlForce";
126128 static const char* OPTION_TIMECORRECTION = "TimeCorrection";
154156 static const char* OPTION_PARCLEANUPQUEUE = "ParCleanupQueue";
155157 static const char* OPTION_DELETECLEANUPDISK = "DeleteCleanupDisk";
156158 static const char* OPTION_HISTORYCLEANUPDISK = "HistoryCleanupDisk";
159 static const char* OPTION_SCANSCRIPT = "ScanScript";
160 static const char* OPTION_QUEUESCRIPT = "QueueScript";
161 static const char* OPTION_FEEDSCRIPT = "FeedScript";
157162
158163 const char* BoolNames[] = { "yes", "no", "true", "false", "1", "0", "on", "off", "enable", "disable", "enabled", "disabled" };
159164 const int BoolValues[] = { 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 };
324329 return;
325330 }
326331
332 ConvertOldOptions(&m_optEntries);
327333 InitOptions();
328334 CheckOptions();
329335
428434 SetOption(OPTION_BROKENLOG, "yes");
429435 SetOption(OPTION_NZBLOG, "yes");
430436 SetOption(OPTION_DECODE, "yes");
431 SetOption(OPTION_RETRIES, "3");
432 SetOption(OPTION_RETRYINTERVAL, "10");
437 SetOption(OPTION_ARTICLERETRIES, "3");
438 SetOption(OPTION_ARTICLEINTERVAL, "10");
439 SetOption(OPTION_URLRETRIES, "3");
440 SetOption(OPTION_URLINTERVAL, "10");
433441 SetOption(OPTION_TERMINATETIMEOUT, "600");
434442 SetOption(OPTION_CONTINUEPARTIAL, "no");
435443 SetOption(OPTION_URLCONNECTIONS, "4");
443451 SetOption(OPTION_PARREPAIR, "yes");
444452 SetOption(OPTION_PARSCAN, "extended");
445453 SetOption(OPTION_PARQUICK, "yes");
454 SetOption(OPTION_POSTSTRATEGY, "sequential");
446455 SetOption(OPTION_PARRENAME, "yes");
447456 SetOption(OPTION_PARBUFFER, "16");
448457 SetOption(OPTION_PARTHREADS, "1");
458 SetOption(OPTION_RARRENAME, "yes");
449459 SetOption(OPTION_HEALTHCHECK, "none");
450460 SetOption(OPTION_SCRIPTORDER, "");
451 SetOption(OPTION_POSTSCRIPT, "");
452 SetOption(OPTION_SCANSCRIPT, "");
453 SetOption(OPTION_QUEUESCRIPT, "");
454 SetOption(OPTION_FEEDSCRIPT, "");
461 SetOption(OPTION_EXTENSIONS, "");
455462 SetOption(OPTION_DAEMONUSERNAME, "root");
456463 SetOption(OPTION_UMASK, "1000");
457464 SetOption(OPTION_UPDATEINTERVAL, "200");
484491 SetOption(OPTION_UNPACKPAUSEQUEUE, "no");
485492 SetOption(OPTION_EXTCLEANUPDISK, "");
486493 SetOption(OPTION_PARIGNOREEXT, "");
494 SetOption(OPTION_UNPACKIGNOREEXT, "");
487495 SetOption(OPTION_FEEDHISTORY, "7");
488496 SetOption(OPTION_URLFORCE, "yes");
489497 SetOption(OPTION_TIMECORRECTION, "0");
640648
641649 m_configTemplate = GetOption(OPTION_CONFIGTEMPLATE);
642650 m_scriptOrder = GetOption(OPTION_SCRIPTORDER);
643 m_postScript = GetOption(OPTION_POSTSCRIPT);
644 m_scanScript = GetOption(OPTION_SCANSCRIPT);
645 m_queueScript = GetOption(OPTION_QUEUESCRIPT);
646 m_feedScript = GetOption(OPTION_FEEDSCRIPT);
651 m_extensions = GetOption(OPTION_EXTENSIONS);
647652 m_controlIp = GetOption(OPTION_CONTROLIP);
648653 m_controlUsername = GetOption(OPTION_CONTROLUSERNAME);
649654 m_controlPassword = GetOption(OPTION_CONTROLPASSWORD);
662667 m_unpackPassFile = GetOption(OPTION_UNPACKPASSFILE);
663668 m_extCleanupDisk = GetOption(OPTION_EXTCLEANUPDISK);
664669 m_parIgnoreExt = GetOption(OPTION_PARIGNOREEXT);
670 m_unpackIgnoreExt = GetOption(OPTION_UNPACKIGNOREEXT);
665671 m_shellOverride = GetOption(OPTION_SHELLOVERRIDE);
666672
667673 m_downloadRate = ParseIntValue(OPTION_DOWNLOADRATE, 10) * 1024;
668674 m_articleTimeout = ParseIntValue(OPTION_ARTICLETIMEOUT, 10);
669675 m_urlTimeout = ParseIntValue(OPTION_URLTIMEOUT, 10);
670676 m_terminateTimeout = ParseIntValue(OPTION_TERMINATETIMEOUT, 10);
671 m_retries = ParseIntValue(OPTION_RETRIES, 10);
672 m_retryInterval = ParseIntValue(OPTION_RETRYINTERVAL, 10);
677 m_articleRetries = ParseIntValue(OPTION_ARTICLERETRIES, 10);
678 m_articleInterval = ParseIntValue(OPTION_ARTICLEINTERVAL, 10);
679 m_urlRetries = ParseIntValue(OPTION_URLRETRIES, 10);
680 m_urlInterval = ParseIntValue(OPTION_URLINTERVAL, 10);
673681 m_controlPort = ParseIntValue(OPTION_CONTROLPORT, 10);
674682 m_securePort = ParseIntValue(OPTION_SECUREPORT, 10);
675683 m_urlConnections = ParseIntValue(OPTION_URLCONNECTIONS, 10);
709717 m_parRepair = (bool)ParseEnumValue(OPTION_PARREPAIR, BoolCount, BoolNames, BoolValues);
710718 m_parQuick = (bool)ParseEnumValue(OPTION_PARQUICK, BoolCount, BoolNames, BoolValues);
711719 m_parRename = (bool)ParseEnumValue(OPTION_PARRENAME, BoolCount, BoolNames, BoolValues);
720 m_rarRename = (bool)ParseEnumValue(OPTION_RARRENAME, BoolCount, BoolNames, BoolValues);
712721 m_reloadQueue = (bool)ParseEnumValue(OPTION_RELOADQUEUE, BoolCount, BoolNames, BoolValues);
713722 m_cursesNzbName = (bool)ParseEnumValue(OPTION_CURSESNZBNAME, BoolCount, BoolNames, BoolValues);
714723 m_cursesTime = (bool)ParseEnumValue(OPTION_CURSESTIME, BoolCount, BoolNames, BoolValues);
742751 const int ParScanCount = 4;
743752 m_parScan = (EParScan)ParseEnumValue(OPTION_PARSCAN, ParScanCount, ParScanNames, ParScanValues);
744753
754 const char* PostStrategyNames[] = { "sequential", "balanced", "aggressive", "rocket" };
755 const int PostStrategyValues[] = { ppSequential, ppBalanced, ppAggressive, ppRocket };
756 const int PostStrategyCount = 4;
757 m_postStrategy = (EPostStrategy)ParseEnumValue(OPTION_POSTSTRATEGY, PostStrategyCount, PostStrategyNames, PostStrategyValues);
758
745759 const char* HealthCheckNames[] = { "pause", "delete", "park", "none" };
746760 const int HealthCheckValues[] = { hcPause, hcDelete, hcPark, hcNone };
747761 const int HealthCheckCount = 4;
10171031 unpack = (bool)ParseEnumValue(BString<100>("Category%i.Unpack", n), BoolCount, BoolNames, BoolValues);
10181032 }
10191033
1020 const char* npostscript = GetOption(BString<100>("Category%i.PostScript", n));
1034 const char* nextensions = GetOption(BString<100>("Category%i.Extensions", n));
10211035 const char* naliases = GetOption(BString<100>("Category%i.Aliases", n));
10221036
1023 bool definition = nname || ndestdir || nunpack || npostscript || naliases;
1037 bool definition = nname || ndestdir || nunpack || nextensions || naliases;
10241038 bool completed = nname && strlen(nname) > 0;
10251039
10261040 if (!definition)
10361050 CheckDir(destDir, BString<100>("Category%i.DestDir", n), m_destDir, false, false);
10371051 }
10381052
1039 m_categories.emplace_back(nname, destDir, unpack, npostscript);
1053 m_categories.emplace_back(nname, destDir, unpack, nextensions);
10401054 Category& category = m_categories.back();
10411055
10421056 // split Aliases into tokens and create items for each token
10671081 const char* nurl = GetOption(BString<100>("Feed%i.URL", n));
10681082 const char* nfilter = GetOption(BString<100>("Feed%i.Filter", n));
10691083 const char* ncategory = GetOption(BString<100>("Feed%i.Category", n));
1070 const char* nfeedscript = GetOption(BString<100>("Feed%i.FeedScript", n));
1084 const char* nextensions = GetOption(BString<100>("Feed%i.Extensions", n));
10711085
10721086 const char* nbacklog = GetOption(BString<100>("Feed%i.Backlog", n));
10731087 bool backlog = true;
10871101 const char* npriority = GetOption(BString<100>("Feed%i.Priority", n));
10881102
10891103 bool definition = nname || nurl || nfilter || ncategory || nbacklog || npausenzb ||
1090 ninterval || npriority || nfeedscript;
1104 ninterval || npriority || nextensions;
10911105 bool completed = nurl;
10921106
10931107 if (!definition)
11001114 if (m_extender)
11011115 {
11021116 m_extender->AddFeed(n, nname, nurl, ninterval ? atoi(ninterval) : 0, nfilter,
1103 backlog, pauseNzb, ncategory, npriority ? atoi(npriority) : 0, nfeedscript);
1117 backlog, pauseNzb, ncategory, npriority ? atoi(npriority) : 0, nextensions);
11041118 }
11051119 }
11061120 else
11691183 continue;
11701184 }
11711185
1172 int weekDaysVal = 0;
1173 if (weekDays && !ParseWeekDays(weekDays, &weekDaysVal))
1174 {
1175 ConfigError("Invalid value for option \"Task%i.WeekDays\": \"%s\"", n, weekDays);
1176 continue;
1177 }
1178
11791186 if (taskCommand == scDownloadRate)
11801187 {
11811188 if (param)
12061213 continue;
12071214 }
12081215
1209 int hours, minutes;
1210 Tokenizer tok(time, ";,");
1211 while (const char* oneTime = tok.Next())
1212 {
1213 if (!ParseTime(oneTime, &hours, &minutes))
1216 CreateSchedulerTask(n, time, weekDays, taskCommand, param);
1217 }
1218 }
1219
1220 void Options::CreateSchedulerTask(int id, const char* time, const char* weekDays,
1221 ESchedulerCommand command, const char* param)
1222 {
1223 if (!id)
1224 {
1225 m_configLine = 0;
1226 }
1227
1228 int weekDaysVal = 0;
1229 if (weekDays && !ParseWeekDays(weekDays, &weekDaysVal))
1230 {
1231 ConfigError("Invalid value for option \"Task%i.WeekDays\": \"%s\"", id, weekDays);
1232 return;
1233 }
1234
1235 int hours, minutes;
1236 Tokenizer tok(time, ";,");
1237 while (const char* oneTime = tok.Next())
1238 {
1239 if (!ParseTime(oneTime, &hours, &minutes))
1240 {
1241 ConfigError("Invalid value for option \"Task%i.Time\": \"%s\"", id, oneTime);
1242 return;
1243 }
1244
1245 if (m_extender)
1246 {
1247 if (hours == -2)
12141248 {
1215 ConfigError("Invalid value for option \"Task%i.Time\": \"%s\"", n, oneTime);
1216 break;
1217 }
1218
1219 if (m_extender)
1220 {
1221 if (hours == -1)
1249 for (int everyHour = 0; everyHour < 24; everyHour++)
12221250 {
1223 for (int everyHour = 0; everyHour < 24; everyHour++)
1224 {
1225 m_extender->AddTask(n, everyHour, minutes, weekDaysVal, taskCommand, param);
1226 }
1227 }
1228 else
1229 {
1230 m_extender->AddTask(n, hours, minutes, weekDaysVal, taskCommand, param);
1251 m_extender->AddTask(id, everyHour, minutes, weekDaysVal, command, param);
12311252 }
12321253 }
1254 else
1255 {
1256 m_extender->AddTask(id, hours, minutes, weekDaysVal, command, param);
1257 }
12331258 }
12341259 }
12351260 }
12361261
12371262 bool Options::ParseTime(const char* time, int* hours, int* minutes)
12381263 {
1264 if (!strcmp(time, "*"))
1265 {
1266 *hours = -1;
1267 return true;
1268 }
1269
12391270 int colons = 0;
12401271 const char* p = time;
12411272 while (*p)
12641295
12651296 if (time[0] == '*')
12661297 {
1267 *hours = -1;
1298 *hours = -2;
12681299 }
12691300 else
12701301 {
14851516 {
14861517 char* p = (char*)optname + 8;
14871518 while (*p >= '0' && *p <= '9') p++;
1488 if (p && (!strcasecmp(p, ".name") || !strcasecmp(p, ".destdir") || !strcasecmp(p, ".postscript") ||
1519 if (p && (!strcasecmp(p, ".name") || !strcasecmp(p, ".destdir") || !strcasecmp(p, ".extensions") ||
14891520 !strcasecmp(p, ".unpack") || !strcasecmp(p, ".aliases")))
14901521 {
14911522 return true;
14981529 while (*p >= '0' && *p <= '9') p++;
14991530 if (p && (!strcasecmp(p, ".name") || !strcasecmp(p, ".url") || !strcasecmp(p, ".interval") ||
15001531 !strcasecmp(p, ".filter") || !strcasecmp(p, ".backlog") || !strcasecmp(p, ".pausenzb") ||
1501 !strcasecmp(p, ".category") || !strcasecmp(p, ".priority") || !strcasecmp(p, ".feedscript")))
1532 !strcasecmp(p, ".category") || !strcasecmp(p, ".priority") || !strcasecmp(p, ".extensions")))
15021533 {
15031534 return true;
15041535 }
15311562 ConfigWarn("Option \"%s\" is obsolete, ignored", optname);
15321563 return true;
15331564 }
1565
15341566 if (!strcasecmp(optname, OPTION_POSTPROCESS) ||
15351567 !strcasecmp(optname, OPTION_NZBPROCESS) ||
15361568 !strcasecmp(optname, OPTION_NZBADDEDPROCESS))
15371569 {
15381570 if (optvalue && strlen(optvalue) > 0)
15391571 {
1540 ConfigError("Option \"%s\" is obsolete, ignored, use \"%s\" and \"%s\" instead", optname, OPTION_SCRIPTDIR,
1541 !strcasecmp(optname, OPTION_POSTPROCESS) ? OPTION_POSTSCRIPT :
1542 !strcasecmp(optname, OPTION_NZBPROCESS) ? OPTION_SCANSCRIPT :
1543 !strcasecmp(optname, OPTION_NZBADDEDPROCESS) ? OPTION_QUEUESCRIPT :
1544 "ERROR");
1545 }
1572 ConfigError("Option \"%s\" is obsolete, ignored, use \"%s\" and \"%s\" instead",
1573 optname, OPTION_SCRIPTDIR, OPTION_EXTENSIONS);
1574 }
1575 return true;
1576 }
1577
1578 if (!strcasecmp(optname, OPTION_SCANSCRIPT) ||
1579 !strcasecmp(optname, OPTION_QUEUESCRIPT) ||
1580 !strcasecmp(optname, OPTION_FEEDSCRIPT))
1581 {
1582 // will be automatically converted into "Extensions"
15461583 return true;
15471584 }
15481585
15991636 value = "extended";
16001637 }
16011638
1602 if (!strcasecmp(option, "DefScript"))
1603 {
1604 option = "PostScript";
1639 if (!strcasecmp(option, "DefScript") || !strcasecmp(option, "PostScript"))
1640 {
1641 option = "Extensions";
16051642 }
16061643
16071644 int nameLen = strlen(option);
1608 if (!strncasecmp(option, "Category", 8) && nameLen > 10 &&
1609 !strcasecmp(option + nameLen - 10, ".DefScript"))
1610 {
1611 option.Replace(".DefScript", ".PostScript");
1645 if (!strncasecmp(option, "Category", 8) &&
1646 ((nameLen > 10 && !strcasecmp(option + nameLen - 10, ".DefScript")) ||
1647 (nameLen > 11 && !strcasecmp(option + nameLen - 11, ".PostScript"))))
1648 {
1649 option.Replace(".DefScript", ".Extensions");
1650 option.Replace(".PostScript", ".Extensions");
1651 }
1652 if (!strncasecmp(option, "Feed", 4) && nameLen > 11 && !strcasecmp(option + nameLen - 11, ".FeedScript"))
1653 {
1654 option.Replace(".FeedScript", ".Extensions");
16121655 }
16131656
16141657 if (!strcasecmp(option, "WriteBufferSize"))
16241667 option = "ArticleTimeout";
16251668 }
16261669
1670 if (!strcasecmp(option, "Retries"))
1671 {
1672 option = "ArticleRetries";
1673 }
1674
1675 if (!strcasecmp(option, "RetryInterval"))
1676 {
1677 option = "ArticleInterval";
1678 }
1679
16271680 if (!strcasecmp(option, "CreateBrokenLog"))
16281681 {
16291682 option = "BrokenLog";
16951748 if (sizeof(void*) == 4 && m_parBuffer + m_articleCache > 1900)
16961749 {
16971750 ConfigError("Options \"ArticleCache\" and \"ParBuffer\" in total cannot use more than 1900MB of memory in 32-Bit mode. Changed to 1500 and 400");
1698 m_articleCache = 1900;
1751 m_articleCache = 1500;
16991752 m_parBuffer = 400;
17001753 }
17011754
17041757 ConfigError("Invalid value for option \"UnpackPassFile\": %s. File not found", *m_unpackPassFile);
17051758 }
17061759 }
1760
1761 void Options::ConvertOldOptions(OptEntries* optEntries)
1762 {
1763 MergeOldScriptOption(optEntries, OPTION_SCANSCRIPT, true);
1764 MergeOldScriptOption(optEntries, OPTION_QUEUESCRIPT, true);
1765 MergeOldScriptOption(optEntries, OPTION_FEEDSCRIPT, false);
1766 }
1767
1768 void Options::MergeOldScriptOption(OptEntries* optEntries, const char* optname, bool mergeCategories)
1769 {
1770 OptEntry* optEntry = optEntries->FindOption(optname);
1771 if (!optEntry || Util::EmptyStr(optEntry->GetValue()))
1772 {
1773 return;
1774 }
1775
1776 OptEntry* extensionsOpt = optEntries->FindOption(OPTION_EXTENSIONS);
1777 if (!extensionsOpt)
1778 {
1779 optEntries->emplace_back(OPTION_EXTENSIONS, "");
1780 extensionsOpt = optEntries->FindOption(OPTION_EXTENSIONS);
1781 }
1782
1783 const char* scriptList = optEntry->GetValue();
1784
1785 Tokenizer tok(scriptList, ",;");
1786 while (const char* scriptName = tok.Next())
1787 {
1788 // merge into global "Extensions"
1789 if (!HasScript(extensionsOpt->m_value, scriptName))
1790 {
1791 if (!extensionsOpt->m_value.Empty())
1792 {
1793 extensionsOpt->m_value.Append(",");
1794 }
1795 extensionsOpt->m_value.Append(scriptName);
1796 }
1797
1798 // merge into categories' "Extensions" (if not empty)
1799 if (mergeCategories)
1800 {
1801 for (OptEntry& opt : optEntries)
1802 {
1803 const char* optname = opt.GetName();
1804 if (!strncasecmp(optname, "category", 8))
1805 {
1806 char* p = (char*)optname + 8;
1807 while (*p >= '0' && *p <= '9') p++;
1808 if (p && (!strcasecmp(p, ".extensions")))
1809 {
1810 if (!opt.m_value.Empty() && !HasScript(opt.m_value, scriptName))
1811 {
1812 opt.m_value.Append(",");
1813 opt.m_value.Append(scriptName);
1814 }
1815 }
1816 }
1817 }
1818 }
1819 }
1820 }
1821
1822 bool Options::HasScript(const char* scriptList, const char* scriptName)
1823 {
1824 Tokenizer tok(scriptList, ",;");
1825 while (const char* scriptName2 = tok.Next())
1826 {
1827 if (!strcasecmp(scriptName2, scriptName))
1828 {
1829 return true;
1830 }
1831 }
1832 return false;
1833 };
8484 scDeactivateServer,
8585 scFetchFeed
8686 };
87 enum EPostStrategy
88 {
89 ppSequential,
90 ppBalanced,
91 ppAggressive,
92 ppRocket
93 };
8794
8895 class OptEntry
8996 {
125132 class Category
126133 {
127134 public:
128 Category(const char* name, const char* destDir, bool unpack, const char* postScript) :
129 m_name(name), m_destDir(destDir), m_unpack(unpack), m_postScript(postScript) {}
135 Category(const char* name, const char* destDir, bool unpack, const char* extensions) :
136 m_name(name), m_destDir(destDir), m_unpack(unpack), m_extensions(extensions) {}
130137 const char* GetName() { return m_name; }
131138 const char* GetDestDir() { return m_destDir; }
132139 bool GetUnpack() { return m_unpack; }
133 const char* GetPostScript() { return m_postScript; }
140 const char* GetExtensions() { return m_extensions; }
134141 NameList* GetAliases() { return &m_aliases; }
135142
136143 private:
137144 CString m_name;
138145 CString m_destDir;
139146 bool m_unpack;
140 CString m_postScript;
147 CString m_extensions;
141148 NameList m_aliases;
142149 };
143150
158165 int level, int group, bool optional) = 0;
159166 virtual void AddFeed(int id, const char* name, const char* url, int interval,
160167 const char* filter, bool backlog, bool pauseNzb, const char* category,
161 int priority, const char* feedScript) {}
168 int priority, const char* extensions) {}
162169 virtual void AddTask(int id, int hours, int minutes, int weekDaysBits, ESchedulerCommand command,
163170 const char* param) {}
164171 virtual void SetupFirstStart() {}
169176 Options(CmdOptList* commandLineOptions, Extender* extender);
170177 ~Options();
171178
172 bool SplitOptionString(const char* option, CString& optName, CString& optValue);
179 static bool SplitOptionString(const char* option, CString& optName, CString& optValue);
180 static void ConvertOldOptions(OptEntries* optEntries);
173181 bool GetFatalError() { return m_fatalError; }
174182 GuardedOptEntries GuardOptEntries() { return GuardedOptEntries(&m_optEntries, &m_optEntriesMutex); }
183 void CreateSchedulerTask(int id, const char* time, const char* weekDays,
184 ESchedulerCommand command, const char* param);
175185
176186 // Options
177187 const char* GetConfigFilename() { return m_configFilename; }
199209 bool GetDecode() { return m_decode; };
200210 bool GetAppendCategoryDir() { return m_appendCategoryDir; }
201211 bool GetContinuePartial() { return m_continuePartial; }
202 int GetRetries() { return m_retries; }
203 int GetRetryInterval() { return m_retryInterval; }
212 int GetArticleRetries() { return m_articleRetries; }
213 int GetArticleInterval() { return m_articleInterval; }
214 int GetUrlRetries() { return m_urlRetries; }
215 int GetUrlInterval() { return m_urlInterval; }
204216 bool GetSaveQueue() { return m_saveQueue; }
205217 bool GetFlushQueue() { return m_flushQueue; }
206218 bool GetDupeCheck() { return m_dupeCheck; }
230242 bool GetParRepair() { return m_parRepair; }
231243 EParScan GetParScan() { return m_parScan; }
232244 bool GetParQuick() { return m_parQuick; }
245 EPostStrategy GetPostStrategy() { return m_postStrategy; }
233246 bool GetParRename() { return m_parRename; }
234247 int GetParBuffer() { return m_parBuffer; }
235248 int GetParThreads() { return m_parThreads; }
249 bool GetRarRename() { return m_rarRename; }
236250 EHealthCheck GetHealthCheck() { return m_healthCheck; }
237251 const char* GetScriptOrder() { return m_scriptOrder; }
238 const char* GetPostScript() { return m_postScript; }
239 const char* GetScanScript() { return m_scanScript; }
240 const char* GetQueueScript() { return m_queueScript; }
241 const char* GetFeedScript() { return m_feedScript; }
252 const char* GetExtensions() { return m_extensions; }
242253 int GetUMask() { return m_umask; }
243254 int GetUpdateInterval() {return m_updateInterval; }
244255 bool GetCursesNzbName() { return m_cursesNzbName; }
266277 bool GetUnpackPauseQueue() { return m_unpackPauseQueue; }
267278 const char* GetExtCleanupDisk() { return m_extCleanupDisk; }
268279 const char* GetParIgnoreExt() { return m_parIgnoreExt; }
280 const char* GetUnpackIgnoreExt() { return m_unpackIgnoreExt; }
269281 int GetFeedHistory() { return m_feedHistory; }
270282 bool GetUrlForce() { return m_urlForce; }
271283 int GetTimeCorrection() { return m_timeCorrection; }
342354 int m_terminateTimeout = 0;
343355 bool m_appendCategoryDir = false;
344356 bool m_continuePartial = false;
345 int m_retries = 0;
346 int m_retryInterval = 0;
357 int m_articleRetries = 0;
358 int m_articleInterval = 0;
359 int m_urlRetries = 0;
360 int m_urlInterval = 0;
347361 bool m_saveQueue = false;
348362 bool m_flushQueue = false;
349363 bool m_dupeCheck = false;
373387 bool m_parRepair = false;
374388 EParScan m_parScan = psLimited;
375389 bool m_parQuick = true;
390 EPostStrategy m_postStrategy = ppSequential;
376391 bool m_parRename = false;
377392 int m_parBuffer = 0;
378393 int m_parThreads = 0;
394 bool m_rarRename = false;
379395 EHealthCheck m_healthCheck = hcNone;
380 CString m_postScript;
396 CString m_extensions;
381397 CString m_scriptOrder;
382 CString m_scanScript;
383 CString m_queueScript;
384 CString m_feedScript;
385398 int m_umask = 0;
386399 int m_updateInterval = 0;
387400 bool m_cursesNzbName = false;
409422 bool m_unpackPauseQueue;
410423 CString m_extCleanupDisk;
411424 CString m_parIgnoreExt;
425 CString m_unpackIgnoreExt;
412426 int m_feedHistory = 0;
413427 bool m_urlForce = false;
414428 int m_timeCorrection = 0;
460474 void ConfigError(const char* msg, ...);
461475 void ConfigWarn(const char* msg, ...);
462476 void LocateOptionSrcPos(const char *optionName);
463 void ConvertOldOption(CString& option, CString& value);
477 static void ConvertOldOption(CString& option, CString& value);
478 static void MergeOldScriptOption(OptEntries* optEntries, const char* optname, bool mergeCategories);
479 static bool HasScript(const char* scriptList, const char* scriptName);
464480 };
465481
466482 extern Options* g_Options;
132132 }
133133
134134 bool weekDayOK = task->m_weekDaysBits == 0 || (task->m_weekDaysBits & (1 << (weekDay - 1)));
135 bool doTask = weekDayOK && localLastCheck < appoint && appoint <= localCurrent;
136
137 //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);
135 bool doTask = (task->m_hours >= 0 && weekDayOK && localLastCheck < appoint && appoint <= localCurrent) ||
136 (task->m_hours == Task::STARTUP_TASK && task->m_lastExecuted == 0);
138137
139138 if (doTask)
140139 {
161160 "Pause Scan", "Unpause Scan", "Enable Server", "Disable Server", "Fetch Feed" };
162161 debug("Executing scheduled command: %s", commandName[task->m_command]);
163162
163 bool executeProcess = m_executeProcess || task->m_hours == Task::STARTUP_TASK;
164
164165 switch (task->m_command)
165166 {
166167 case scDownloadRate:
189190 m_pauseScanChanged = true;
190191 break;
191192
192 case scScript:
193 case scExtensions:
193194 case scProcess:
194 if (m_executeProcess)
195 if (executeProcess)
195196 {
196197 SchedulerScriptController::StartScript(task->m_param, task->m_command == scProcess, task->m_id);
197198 }
203204 break;
204205
205206 case scFetchFeed:
206 if (m_executeProcess)
207 if (executeProcess)
207208 {
208209 FetchFeed(task->m_param);
209210 break;
3434 scPausePostProcess,
3535 scUnpausePostProcess,
3636 scDownloadRate,
37 scScript,
37 scExtensions,
3838 scProcess,
3939 scPauseScan,
4040 scUnpauseScan,
5151 m_id(id), m_hours(hours), m_minutes(minutes),
5252 m_weekDaysBits(weekDaysBits), m_command(command), m_param(param) {}
5353 friend class Scheduler;
54
54 static const int STARTUP_TASK = -1;
5555 private:
5656 int m_id;
5757 int m_hours;
5959 #ifdef ENABLE_TESTS
6060 #include "TestMain.h"
6161 #endif
62 #ifndef DISABLE_NSERV
63 #include "NServMain.h"
64 #endif
6265
6366 // Prototypes
6467 void RunMain();
126129 TestCleanup();
127130 #endif
128131
132 if (argc > 1 && (!strcmp(argv[1], "--nserv")))
133 {
134 #ifndef DISABLE_NSERV
135 return NServMain(argc, argv);
136 #else
137 printf("ERROR: Could not start NServ, the program was compiled without NServ\n");
138 return 1;
139 #endif
140 }
141
129142 #ifdef WIN32
130143 InstallUninstallServiceCheck(argc, argv);
131144 #endif
137150 {
138151 if (!strcmp(argv[i], "-D"))
139152 {
153 AllocConsole(); // needed for sending CTRL+BREAK signal to child processes
140154 StartService(RunMain);
141155 return 0;
142156 }
393407 }
394408
395409 m_serverPool->SetTimeout(m_options->GetArticleTimeout());
396 m_serverPool->SetRetryInterval(m_options->GetRetryInterval());
410 m_serverPool->SetRetryInterval(m_options->GetArticleInterval());
397411
398412 m_scriptConfig->InitOptions();
399413 }
864878 // obtain a new process group
865879 setsid();
866880
867 // close all descriptors
868 for (int i = getdtablesize(); i >= 0; --i)
869 {
870 close(i);
871 }
872
873881 // handle standart I/O
874882 int d = open("/dev/null", O_RDWR);
875 dup(d);
876 dup(d);
883 dup2(d, 0);
884 dup2(d, 1);
885 dup2(d, 2);
886 close(d);
877887
878888 // set up lock-file
879889 int lfp = -1;
195195 #include <iostream>
196196 #include <fstream>
197197 #include <memory>
198 #include <functional>
198199
199200 #ifdef HAVE_LIBGNUTLS
200201 #ifdef WIN32
209210 #ifdef NEED_GCRYPT_LOCKING
210211 #include <gcrypt.h>
211212 #endif /* NEED_GCRYPT_LOCKING */
213 #include <nettle/sha.h>
214 #include <nettle/pbkdf2.h>
215 #include <nettle/aes.h>
212216 #endif /* HAVE_LIBGNUTLS */
213217
214218 #ifdef HAVE_OPENSSL
317321
318322 #ifdef HAVE_STDINT_H
319323 typedef uint8_t uint8;
324 typedef int16_t int16;
325 typedef uint16_t uint16;
320326 typedef uint32_t int32;
321327 typedef uint32_t uint32;
322328 typedef int64_t int64;
323329 typedef uint64_t uint64;
324330 #else
325331 typedef unsigned char uint8;
332 typedef signed short int16;
333 typedef unsigned short uint16;
326334 typedef signed int int32;
327335 typedef unsigned int uint32;
328336 typedef signed long long int64;
6262 - if download fails with error "Not-Found" (article or group not found) or with CRC error,
6363 add the server to failed server list;
6464 - if download fails with general failure error (article incomplete, other unknown error
65 codes), try the same server again as many times as defined by option <Retries>; if all attempts
66 fail, add the server to failed server list;
65 codes), try the same server again as many times as defined by option <ArticleRetries>;
66 if all attempts fail, add the server to failed server list;
6767 - if all servers from current level were tried, increase level;
6868 - if all servers from all levels were tried, break the loop with failure status.
6969 <end-loop>
7979 m_articleWriter.Prepare();
8080
8181 EStatus status = adFailed;
82 int retries = g_Options->GetRetries() > 0 ? g_Options->GetRetries() : 1;
82 int retries = g_Options->GetArticleRetries() > 0 ? g_Options->GetArticleRetries() : 1;
8383 int remainedRetries = retries;
8484 ServerPool::RawServerList failedServers;
8585 failedServers.reserve(g_ServerPool->GetServers()->size());
0 /*
1 * This file is part of nzbget. See <http://nzbget.net>.
2 *
3 * Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19
20 #include "nzbget.h"
21 #include "NServFrontend.h"
22 #include "Util.h"
23
24 NServFrontend::NServFrontend()
25 {
26 #ifdef WIN32
27 m_console = GetStdHandle(STD_OUTPUT_HANDLE);
28 #endif
29 }
30
31 void NServFrontend::Run()
32 {
33 while (!IsStopped())
34 {
35 Update();
36 usleep(100 * 1000);
37 }
38 // Printing the last messages
39 Update();
40 }
41
42 void NServFrontend::Update()
43 {
44 BeforePrint();
45
46 {
47 GuardedMessageList messages = g_Log->GuardMessages();
48 if (!messages->empty())
49 {
50 Message& firstMessage = messages->front();
51 int start = m_neededLogFirstId - firstMessage.GetId() + 1;
52 if (start < 0)
53 {
54 PrintSkip();
55 start = 0;
56 }
57 for (uint32 i = (uint32)start; i < messages->size(); i++)
58 {
59 PrintMessage(messages->at(i));
60 m_neededLogFirstId = messages->at(i).GetId();
61 }
62 }
63 }
64
65 fflush(stdout);
66 }
67
68 void NServFrontend::BeforePrint()
69 {
70 if (m_needGoBack)
71 {
72 // go back one line
73 #ifdef WIN32
74 CONSOLE_SCREEN_BUFFER_INFO BufInfo;
75 GetConsoleScreenBufferInfo(m_console, &BufInfo);
76 BufInfo.dwCursorPosition.Y--;
77 SetConsoleCursorPosition(m_console, BufInfo.dwCursorPosition);
78 #else
79 printf("\r\033[1A");
80 #endif
81 m_needGoBack = false;
82 }
83 }
84
85 void NServFrontend::PrintMessage(Message& message)
86 {
87 #ifdef WIN32
88 switch (message.GetKind())
89 {
90 case Message::mkDebug:
91 SetConsoleTextAttribute(m_console, 8);
92 printf("[DEBUG] ");
93 break;
94 case Message::mkError:
95 SetConsoleTextAttribute(m_console, 4);
96 printf("[ERROR] ");
97 break;
98 case Message::mkWarning:
99 SetConsoleTextAttribute(m_console, 5);
100 printf("[WARNING]");
101 break;
102 case Message::mkInfo:
103 SetConsoleTextAttribute(m_console, 2);
104 printf("[INFO] ");
105 break;
106 case Message::mkDetail:
107 SetConsoleTextAttribute(m_console, 2);
108 printf("[DETAIL]");
109 break;
110 }
111 SetConsoleTextAttribute(m_console, 7);
112 CString msg = message.GetText();
113 CharToOem(msg, msg);
114 printf(" %s\n", *msg);
115 #else
116 const char* msg = message.GetText();
117 switch (message.GetKind())
118 {
119 case Message::mkDebug:
120 printf("[DEBUG] %s\033[K\n", msg);
121 break;
122 case Message::mkError:
123 printf("\033[31m[ERROR]\033[39m %s\033[K\n", msg);
124 break;
125 case Message::mkWarning:
126 printf("\033[35m[WARNING]\033[39m %s\033[K\n", msg);
127 break;
128 case Message::mkInfo:
129 printf("\033[32m[INFO]\033[39m %s\033[K\n", msg);
130 break;
131 case Message::mkDetail:
132 printf("\033[32m[DETAIL]\033[39m %s\033[K\n", msg);
133 break;
134 }
135 #endif
136 }
137
138 void NServFrontend::PrintSkip()
139 {
140 #ifdef WIN32
141 printf(".....\n");
142 #else
143 printf(".....\033[K\n");
144 #endif
145 }
0 /*
1 * This file is part of nzbget. See <http://nzbget.net>.
2 *
3 * Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19
20 #ifndef NSERVFRONTEND_H
21 #define NSERVFRONTEND_H
22
23 #include "Thread.h"
24 #include "Log.h"
25
26 class NServFrontend : public Thread
27 {
28 public:
29 NServFrontend();
30
31 private:
32 uint32 m_neededLogEntries = 0;
33 uint32 m_neededLogFirstId = 0;
34 bool m_needGoBack = false;
35
36 #ifdef WIN32
37 HANDLE m_console;
38 #endif
39
40 void Run();
41 void Update();
42 void BeforePrint();
43 void PrintMessage(Message& message);
44 void PrintSkip();
45 };
46
47 #endif
0 /*
1 * This file is part of nzbget. See <http://nzbget.net>.
2 *
3 * Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19
20 #include "nzbget.h"
21
22 #include "Thread.h"
23 #include "Connection.h"
24 #include "Log.h"
25 #include "Util.h"
26 #include "FileSystem.h"
27 #include "NServFrontend.h"
28 #include "NntpServer.h"
29 #include "NzbGenerator.h"
30 #include "Options.h"
31
32 struct NServOpts
33 {
34 CString dataDir;
35 CString cacheDir;
36 CString bindAddress;
37 int firstPort;
38 int instances;
39 CString logFile;
40 CString secureCert;
41 CString secureKey;
42 BString<1024> logOpt;
43 bool generateNzb;
44 int segmentSize;
45 bool quit;
46
47 NServOpts(int argc, char* argv[], Options::CmdOptList& cmdOpts);
48 };
49
50 void NServPrintUsage(const char* com);
51
52 int NServMain(int argc, char* argv[])
53 {
54 Log log;
55
56 info("NServ %s (Test NNTP server)", Util::VersionRevision());
57
58 Options::CmdOptList cmdOpts;
59 NServOpts opts(argc, argv, cmdOpts);
60
61 if (opts.dataDir.Empty())
62 {
63 NServPrintUsage(argv[0]);
64 return 1;
65 }
66
67 if (!FileSystem::DirectoryExists(opts.dataDir))
68 {
69 // dataDir does not exist. Let's find out a bit more, and report:
70 if (FileSystem::FileExists(opts.dataDir))
71 {
72 error("Specified data-dir %s is not a directory, but a file", *opts.dataDir );
73 } else {
74 error("Specified data-dir %s does not exist", *opts.dataDir );
75 }
76 }
77
78 Options options(&cmdOpts, nullptr);
79
80 log.InitOptions();
81 Thread::Init();
82 Connection::Init();
83 #ifndef DISABLE_TLS
84 TlsSocket::Init();
85 #endif
86
87 NServFrontend frontend;
88 frontend.Start();
89
90 if (opts.generateNzb)
91 {
92 NzbGenerator gen(opts.dataDir, opts.segmentSize);
93 gen.Execute();
94 if (opts.quit)
95 {
96 return 0;
97 }
98 }
99
100 CString errmsg;
101 if (opts.cacheDir && !FileSystem::ForceDirectories(opts.cacheDir, errmsg))
102 {
103 error("Could not create directory %s: %s", *opts.cacheDir, *errmsg);
104 }
105
106 std::vector<std::unique_ptr<NntpServer>> instances;
107
108 for (int i = 0; i < opts.instances; i++)
109 {
110 instances.emplace_back(std::make_unique<NntpServer>(i + 1, opts.bindAddress,
111 opts.firstPort + i, opts.secureCert, opts.secureKey, opts.dataDir, opts.cacheDir));
112 instances.back()->Start();
113 }
114
115 info("Press Ctrl+C to quit");
116 while (getchar()) usleep(1000*200);
117
118 for (std::unique_ptr<NntpServer>& serv: instances)
119 {
120 serv->Stop();
121 }
122 frontend.Stop();
123
124 bool hasRunning = false;
125 do
126 {
127 hasRunning = frontend.IsRunning();
128 for (std::unique_ptr<NntpServer>& serv : instances)
129 {
130 hasRunning |= serv->IsRunning();
131 }
132 usleep(50 * 1000);
133 } while (hasRunning);
134
135 return 0;
136 }
137
138 void NServPrintUsage(const char* com)
139 {
140 printf("Usage:\n"
141 " %s --nserv -d <data-dir> [optional switches] \n"
142 " -d <data-dir> - directory whose files will be served\n"
143 " Optional switches:\n"
144 " -c <cache-dir> - directory to store encoded articles\n"
145 " -l <log-file> - write into log-file (disabled by default)\n"
146 " -i <instances> - number of server instances (default is 1)\n"
147 " -b <address> - ip address to bind to (default is 0.0.0.0)\n"
148 " -p <port> - port number for the first instance (default is 6791)\n"
149 " -s <cert> <key> - paths to SSL certificate and key files\n"
150 " -v <verbose> - verbosity level 0..3 (default is 2)\n"
151 " -z <seg-size> - generate nzbs for all files in data-dir (size in bytes)\n"
152 " -q - quit after generating nzbs (in combination with -z)\n"
153 , FileSystem::BaseFileName(com));
154 }
155
156 NServOpts::NServOpts(int argc, char* argv[], Options::CmdOptList& cmdOpts)
157 {
158 instances = 1;
159 bindAddress = "0.0.0.0";
160 firstPort = 6791;
161 generateNzb = false;
162 segmentSize = 500000;
163 quit = false;
164 int verbosity = 2;
165
166 char short_options[] = "b:c:d:l:p:i:s:v:z:q";
167
168 optind = 2;
169 while (true)
170 {
171 int c = getopt(argc, argv, short_options);
172 if (c == -1) break;
173 switch (c)
174 {
175 case 'd':
176 dataDir = optind > argc ? nullptr : argv[optind - 1];
177 break;
178
179 case 'c':
180 cacheDir = optind > argc ? nullptr : argv[optind - 1];
181 break;
182
183 case 'l':
184 logFile = optind > argc ? nullptr : argv[optind - 1];
185 break;
186
187 case 'b':
188 bindAddress= optind > argc ? "0.0.0.0" : argv[optind - 1];
189 break;
190
191 case 'p':
192 firstPort = atoi(optind > argc ? "6791" : argv[optind - 1]);
193 break;
194
195 case 's':
196 secureCert = optind > argc ? nullptr : argv[optind - 1];
197 optind++;
198 secureKey = optind > argc ? nullptr : argv[optind - 1];
199 break;
200
201 case 'i':
202 instances = atoi(optind > argc ? "1" : argv[optind - 1]);
203 break;
204
205 case 'v':
206 verbosity = atoi(optind > argc ? "1" : argv[optind - 1]);
207 break;
208
209 case 'z':
210 generateNzb = true;
211 segmentSize = atoi(optind > argc ? "500000" : argv[optind - 1]);
212 break;
213
214 case 'q':
215 quit = true;
216 break;
217 }
218 }
219
220 if (logFile.Empty())
221 {
222 cmdOpts.push_back("WriteLog=none");
223 }
224 else
225 {
226 cmdOpts.push_back("WriteLog=append");
227 logOpt.Format("LogFile=%s", *logFile);
228 cmdOpts.push_back(logOpt);
229 }
230
231 if (verbosity < 1)
232 {
233 cmdOpts.push_back("InfoTarget=none");
234 cmdOpts.push_back("WarningTarget=none");
235 cmdOpts.push_back("ErrorTarget=none");
236 }
237 if (verbosity < 2)
238 {
239 cmdOpts.push_back("DetailTarget=none");
240 }
241 if (verbosity > 2)
242 {
243 cmdOpts.push_back("DebugTarget=both");
244 }
245 }
0 /*
1 * This file is part of nzbget. See <http://nzbget.net>.
2 *
3 * Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19
20 #ifndef NSERVMAIN_H
21 #define NSERVMAIN_H
22
23 int NServMain(int argc, char * argv[]);
24
25 #endif
0 /*
1 * This file is part of nzbget. See <http://nzbget.net>.
2 *
3 * Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19
20 #include "nzbget.h"
21 #include "NntpServer.h"
22 #include "Log.h"
23 #include "Util.h"
24 #include "YEncoder.h"
25
26 class NntpProcessor : public Thread
27 {
28 public:
29 NntpProcessor(int id, int serverId, const char* dataDir, const char* cacheDir,
30 const char* secureCert, const char* secureKey) :
31 m_id(id), m_serverId(serverId), m_dataDir(dataDir), m_cacheDir(cacheDir),
32 m_secureCert(secureCert), m_secureKey(secureKey) {}
33 ~NntpProcessor() { m_connection->Disconnect(); }
34 virtual void Run();
35 void SetConnection(std::unique_ptr<Connection>&& connection) { m_connection = std::move(connection); }
36
37 private:
38 int m_id;
39 int m_serverId;
40 std::unique_ptr<Connection> m_connection;
41 const char* m_dataDir;
42 const char* m_cacheDir;
43 const char* m_secureCert;
44 const char* m_secureKey;
45 const char* m_messageid;
46 CString m_filename;
47 int m_part;
48 int64 m_offset;
49 int m_size;
50 bool m_sendHeaders;
51
52 void ServArticle();
53 void SendSegment();
54 bool ServerInList(const char* servList);
55 };
56
57 void NntpServer::Run()
58 {
59 debug("Entering NntpServer-loop");
60
61 info("Listening on port %i", m_port);
62
63 int num = 1;
64
65 while (!IsStopped())
66 {
67 bool bind = true;
68
69 if (!m_connection)
70 {
71 m_connection = std::make_unique<Connection>(m_host, m_port, m_secureCert);
72 m_connection->SetTimeout(10);
73 m_connection->SetSuppressErrors(false);
74 bind = m_connection->Bind();
75 }
76
77 // Accept connections and store the new Connection
78 std::unique_ptr<Connection> acceptedConnection;
79 if (bind)
80 {
81 acceptedConnection = m_connection->Accept();
82 }
83 if (!bind || !acceptedConnection)
84 {
85 // Server could not bind or accept connection, waiting 1/2 sec and try again
86 if (IsStopped())
87 {
88 break;
89 }
90 m_connection.reset();
91 usleep(500 * 1000);
92 continue;
93 }
94
95 NntpProcessor* commandThread = new NntpProcessor(num++, m_id,
96 m_dataDir, m_cacheDir, m_secureCert, m_secureKey);
97 commandThread->SetAutoDestroy(true);
98 commandThread->SetConnection(std::move(acceptedConnection));
99 commandThread->Start();
100 }
101
102 if (m_connection)
103 {
104 m_connection->Disconnect();
105 }
106
107 debug("Exiting NntpServer-loop");
108 }
109
110 void NntpServer::Stop()
111 {
112 Thread::Stop();
113 if (m_connection)
114 {
115 m_connection->SetSuppressErrors(true);
116 m_connection->Cancel();
117 #ifdef WIN32
118 m_connection->Disconnect();
119 #endif
120 }
121 }
122
123
124 void NntpProcessor::Run()
125 {
126 m_connection->SetSuppressErrors(false);
127
128 #ifndef DISABLE_TLS
129 if (m_secureCert && !m_connection->StartTls(false, m_secureCert, m_secureKey))
130 {
131 error("Could not establish secure connection to nntp-client: Start TLS failed");
132 return;
133 }
134 #endif
135
136 m_connection->WriteLine("200 Welcome (NServ)\r\n");
137
138 CharBuffer buf(1024);
139 int bytesRead = 0;
140 while (CString line = m_connection->ReadLine(buf, 1024, &bytesRead))
141 {
142 line.TrimRight();
143 detail("[%i] Received: %s", m_id, *line);
144
145 if (!strncasecmp(line, "ARTICLE ", 8))
146 {
147 m_messageid = line + 8;
148 m_sendHeaders = true;
149 ServArticle();
150 }
151 else if (!strncasecmp(line, "BODY ", 5))
152 {
153 m_messageid = line + 5;
154 m_sendHeaders = false;
155 ServArticle();
156 }
157 else if (!strncasecmp(line, "GROUP ", 6))
158 {
159 m_connection->WriteLine(CString::FormatStr("211 0 0 0 %s\r\n", line + 7));
160 }
161 else if (!strncasecmp(line, "AUTHINFO ", 9))
162 {
163 m_connection->WriteLine("281 Authentication accepted\r\n");
164 }
165 else if (!strcasecmp(line, "QUIT"))
166 {
167 detail("[%i] Closing connection", m_id);
168 m_connection->WriteLine("205 Connection closing\r\n");
169 break;
170 }
171 else
172 {
173 warn("[%i] Unknown command: %s", m_id, *line);
174 m_connection->WriteLine("500 Unknown command\r\n");
175 }
176 }
177
178 m_connection->SetGracefull(true);
179 m_connection->Disconnect();
180 }
181
182 /*
183 Message-id format:
184 <file-path-relative-to-dataDir?xxx=yyy:zzz!1,2,3>
185 where:
186 xxx - part number (integer)
187 xxx - offset from which to read the files (integer)
188 yyy - size of file block to return (integer)
189 1,2,3 - list of server ids, which have the article (optional),
190 if the list is given and current server is not in the list
191 the "article not found"-error is returned.
192 Examples:
193 <parchecker/testfile.dat?1=0:50000> - return first 50000 bytes starting from beginning
194 <parchecker/testfile.dat?2=50000:50000> - return 50000 bytes starting from offset 50000
195 <parchecker/testfile.dat?2=50000:50000!2> - article is missing on server 1
196 */
197 void NntpProcessor::ServArticle()
198 {
199 detail("[%i] Serving: %s", m_id, m_messageid);
200
201 bool ok = false;
202
203 const char* from = strchr(m_messageid, '?');
204 const char* off = strchr(m_messageid, '=');
205 const char* to = strchr(m_messageid, ':');
206 const char* end = strchr(m_messageid, '>');
207 const char* serv = strchr(m_messageid, '!');
208
209 if (from && off && to && end)
210 {
211 m_filename.Set(m_messageid + 1, from - m_messageid - 1);
212 m_part = atoi(from + 1);
213 m_offset = atoll(off + 1);
214 m_size = atoi(to + 1);
215
216 ok = !serv || ServerInList(serv + 1);
217
218 if (ok)
219 {
220 SendSegment();
221 return;
222 }
223
224 if (!ok)
225 {
226 m_connection->WriteLine("430 No Such Article Found\r\n");
227 }
228 }
229 else
230 {
231 m_connection->WriteLine("430 No Such Article Found (invalid message id format)\r\n");
232 }
233 }
234
235 bool NntpProcessor::ServerInList(const char* servList)
236 {
237 Tokenizer tok(servList, ",");
238 while (const char* servid = tok.Next())
239 {
240 if (atoi(servid) == m_serverId)
241 {
242 return true;
243 }
244 }
245 return false;
246 }
247
248 void NntpProcessor::SendSegment()
249 {
250 detail("[%i] Sending segment %s (%i=%lli:%i)", m_id, *m_filename, m_part, (long long)m_offset, m_size);
251
252 BString<1024> fullFilename("%s/%s", m_dataDir, *m_filename);
253 BString<1024> cacheFileDir("%s/%s", m_cacheDir, *m_filename);
254 BString<1024> cacheFileName("%i=%lli-%i", m_part, (long long)m_offset, m_size);
255 BString<1024> cacheFullFilename("%s/%s", *cacheFileDir, *cacheFileName);
256
257 DiskFile cacheFile;
258 bool readCache = m_cacheDir && cacheFile.Open(cacheFullFilename, DiskFile::omRead);
259 bool writeCache = m_cacheDir && !readCache;
260
261 CString errmsg;
262 if (writeCache && !FileSystem::ForceDirectories(cacheFileDir, errmsg))
263 {
264 error("Could not create directory %s: %s", *cacheFileDir, *errmsg);
265 }
266
267 if (writeCache && !cacheFile.Open(cacheFullFilename, DiskFile::omWrite))
268 {
269 error("Could not create file %s: %s", *cacheFullFilename, *FileSystem::GetLastErrorMessage());
270 }
271
272 if (!readCache && !FileSystem::FileExists(fullFilename))
273 {
274 m_connection->WriteLine(CString::FormatStr("430 Article not found\r\n"));
275 return;
276 }
277
278 YEncoder encoder(fullFilename, m_part, m_offset, m_size,
279 [con = m_connection.get(), writeCache, &cacheFile](const char* buf, int size)
280 {
281 if (writeCache)
282 {
283 cacheFile.Write(buf, size);
284 }
285 con->Send(buf, size);
286 });
287
288 if (!readCache && !encoder.OpenFile(errmsg))
289 {
290 m_connection->WriteLine(CString::FormatStr("403 %s\r\n", *errmsg));
291 return;
292 }
293
294 m_connection->WriteLine(CString::FormatStr("%i, 0 %s\r\n", m_sendHeaders ? 222 : 220, m_messageid));
295 if (m_sendHeaders)
296 {
297 m_connection->WriteLine(CString::FormatStr("Message-ID: %s\r\n", m_messageid));
298 m_connection->WriteLine(CString::FormatStr("Subject: \"%s\"\r\n", FileSystem::BaseFileName(m_filename)));
299 m_connection->WriteLine("\r\n");
300 }
301
302 if (readCache)
303 {
304 cacheFile.Seek(0, DiskFile::soEnd);
305 int size = (int)cacheFile.Position();
306 CharBuffer buf(size);
307 cacheFile.Seek(0);
308 if (cacheFile.Read((char*)buf, size) != size)
309 {
310 error("Could not read file %s: %s", *cacheFullFilename, *FileSystem::GetLastErrorMessage());
311 }
312 m_connection->Send(buf, size);
313 }
314 else
315 {
316 encoder.WriteSegment();
317 }
318
319 m_connection->WriteLine(".\r\n");
320 }
0 /*
1 * This file is part of nzbget. See <http://nzbget.net>.
2 *
3 * Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19
20 #ifndef NNTPSERVER_H
21 #define NNTPSERVER_H
22
23 #include "Thread.h"
24 #include "Connection.h"
25
26 class NntpServer : public Thread
27 {
28 public:
29 NntpServer(int id, const char* host, int port, const char* secureCert,
30 const char* secureKey, const char* dataDir, const char* cacheDir) :
31 m_id(id), m_host(host), m_port(port), m_secureCert(secureCert),
32 m_secureKey(secureKey), m_dataDir(dataDir), m_cacheDir(cacheDir) {}
33 virtual void Run();
34 virtual void Stop();
35
36 private:
37 int m_id;
38 CString m_host;
39 int m_port;
40 CString m_dataDir;
41 CString m_cacheDir;
42 CString m_secureCert;
43 CString m_secureKey;
44 std::unique_ptr<Connection> m_connection;
45 };
46
47 #endif
0 /*
1 * This file is part of nzbget. See <http://nzbget.net>.
2 *
3 * Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19
20 #include "nzbget.h"
21 #include "NzbGenerator.h"
22 #include "Util.h"
23 #include "FileSystem.h"
24 #include "Log.h"
25
26 void NzbGenerator::Execute()
27 {
28 info("Generating nzbs for %s", *m_dataDir);
29
30 DirBrowser dir(m_dataDir);
31 while (const char* filename = dir.Next())
32 {
33 BString<1024> fullFilename("%s%c%s", *m_dataDir, PATH_SEPARATOR, filename);
34
35 int len = strlen(filename);
36 if (len > 4 && !strcasecmp(filename + len - 4, ".nzb"))
37 {
38 // skip nzb-files
39 continue;
40 }
41
42 GenerateNzb(fullFilename);
43 }
44
45 info("Nzb generation finished");
46 }
47
48 void NzbGenerator::GenerateNzb(const char* path)
49 {
50 BString<1024> nzbFilename("%s%c%s.nzb", *m_dataDir, PATH_SEPARATOR, FileSystem::BaseFileName(path));
51
52 if (FileSystem::FileExists(nzbFilename))
53 {
54 return;
55 }
56
57 info("Generating nzb for %s", FileSystem::BaseFileName(path));
58
59 DiskFile outfile;
60 if (!outfile.Open(nzbFilename, DiskFile::omWrite))
61 {
62 error("Could not create file %s", *nzbFilename);
63 return;
64 }
65
66 outfile.Print("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
67 outfile.Print("<!DOCTYPE nzb PUBLIC \"-//newzBin//DTD NZB 1.0//EN\" \"http://www.newzbin.com/DTD/nzb/nzb-1.0.dtd\">\n");
68 outfile.Print("<nzb xmlns=\"http://www.newzbin.com/DTD/2003/nzb\">\n");
69
70 bool isDir = FileSystem::DirectoryExists(path);
71 if (isDir)
72 {
73 AppendDir(outfile, path);
74 }
75 else
76 {
77 AppendFile(outfile, path, nullptr);
78 }
79
80 outfile.Print("</nzb>\n");
81
82 outfile.Close();
83 }
84
85 void NzbGenerator::AppendDir(DiskFile& outfile, const char* path)
86 {
87 DirBrowser dir(path);
88 while (const char* filename = dir.Next())
89 {
90 BString<1024> fullFilename("%s%c%s", path, PATH_SEPARATOR, filename);
91
92 bool isDir = FileSystem::DirectoryExists(fullFilename);
93 if (!isDir)
94 {
95 AppendFile(outfile, fullFilename, FileSystem::BaseFileName(path));
96 }
97 }
98 }
99
100 void NzbGenerator::AppendFile(DiskFile& outfile, const char* filename, const char* relativePath)
101 {
102 detail("Processing %s", FileSystem::BaseFileName(filename));
103
104 int64 fileSize = FileSystem::FileSize(filename);
105 time_t timestamp = Util::CurrentTime();
106
107 int segmentCount = (int)((fileSize + m_segmentSize - 1) / m_segmentSize);
108
109 outfile.Print("<file poster=\"nserv\" date=\"%i\" subject=\"&quot;%s&quot; yEnc (1/%i)\">\n",
110 (int)timestamp, FileSystem::BaseFileName(filename), segmentCount);
111 outfile.Print("<groups>\n");
112 outfile.Print("<group>alt.binaries.test</group>\n");
113 outfile.Print("</groups>\n");
114 outfile.Print("<segments>\n");
115
116 int64 segOffset = 0;
117 for (int segno = 1; segno <= segmentCount; segno++)
118 {
119 int segSize = (int)(segOffset + m_segmentSize < fileSize ? m_segmentSize : fileSize - segOffset);
120 outfile.Print("<segment bytes=\"%i\" number=\"%i\">%s%s%s?%i=%lli:%i</segment>\n",
121 m_segmentSize, segno,
122 relativePath ? relativePath : "",
123 relativePath ? "/" : "",
124 FileSystem::BaseFileName(filename), segno, (long long)segOffset, (int)segSize);
125 segOffset += segSize;
126 }
127
128 outfile.Print("</segments>\n");
129 outfile.Print("</file>\n");
130 }
0 /*
1 * This file is part of nzbget. See <http://nzbget.net>.
2 *
3 * Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19
20 #ifndef NZBGENERATOR_H
21 #define NZBGENERATOR_H
22
23 #include "NString.h"
24 #include "FileSystem.h"
25
26 class NzbGenerator
27 {
28 public:
29 NzbGenerator(const char* dataDir, int segmentSize) :
30 m_dataDir(dataDir), m_segmentSize(segmentSize) {};
31 void Execute();
32
33 private:
34 CString m_dataDir;
35 int m_segmentSize;
36
37 void GenerateNzb(const char* path);
38 void AppendFile(DiskFile& outfile, const char* filename, const char* relativePath);
39 void AppendDir(DiskFile& outfile, const char* path);
40 };
41
42 #endif
0 /*
1 * This file is part of nzbget. See <http://nzbget.net>.
2 *
3 * Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19
20 #include "nzbget.h"
21 #include "YEncoder.h"
22 #include "Util.h"
23 #include "FileSystem.h"
24 #include "Log.h"
25
26 bool YEncoder::OpenFile(CString& errmsg)
27 {
28 if (m_size < 0)
29 {
30 errmsg = "Invalid segment size";
31 return false;
32 }
33
34 if (!m_diskfile.Open(m_filename, DiskFile::omRead) || !m_diskfile.Seek(0, DiskFile::soEnd))
35 {
36 errmsg = "File not found";
37 return false;
38 }
39
40 m_fileSize = m_diskfile.Position();
41 if (m_size == 0)
42 {
43 m_size = (int)(m_fileSize - m_offset + 1);
44 }
45
46 if (m_fileSize < m_offset + m_size)
47 {
48 errmsg = "Invalid segment size";
49 return false;
50 }
51
52 if (!m_diskfile.Seek(m_offset))
53 {
54 errmsg = "Invalid segment offset";
55 return false;
56 }
57
58 return true;
59 }
60
61 void YEncoder::WriteSegment()
62 {
63 StringBuilder outbuf;
64 outbuf.Reserve(std::max(2048, std::min((int)(m_size * 1.1), 16 * 1024 * 1024)));
65
66 outbuf.Append(CString::FormatStr("=ybegin part=%i line=128 size=%lli name=%s\r\n", m_part, (long long)m_fileSize, FileSystem::BaseFileName(m_filename)));
67 outbuf.Append(CString::FormatStr("=ypart begin=%lli end=%lli\r\n", (long long)(m_offset + 1), (long long)(m_offset + m_size)));
68
69 uint32 crc = 0xFFFFFFFF;
70 CharBuffer inbuf(std::min(m_size, 16 * 1024 * 1024));
71 int lnsz = 0;
72 char* out = (char*)outbuf + outbuf.Length();
73
74 while (m_diskfile.Position() < m_offset + m_size)
75 {
76 int64 needBytes = std::min((int64)inbuf.Size(), m_offset + m_size - m_diskfile.Position());
77 int64 readBytes = m_diskfile.Read(inbuf, needBytes);
78 bool lastblock = m_diskfile.Position() == m_offset + m_size;
79 if (readBytes == 0)
80 {
81 return; // error;
82 }
83
84 crc = Util::Crc32m(crc, (uchar*)(const char*)inbuf, (int)readBytes);
85
86 char* in = inbuf;
87 while (readBytes > 0)
88 {
89 char ch = *in++;
90 readBytes--;
91 ch = (char)(((uchar)(ch) + 42) % 256);
92 if (ch == '\0' || ch == '\n' || ch == '\r' || ch == '=' || ch == ' ' || ch == '\t')
93 {
94 *out++ = '=';
95 lnsz++;
96 ch = (char)(((uchar)ch + 64) % 256);
97 }
98 if (ch == '.' && lnsz == 0)
99 {
100 *out++ = '.';
101 lnsz++;
102 }
103 *out++ = ch;
104 lnsz++;
105
106 if (lnsz >= 128 || (readBytes == 0 && lastblock))
107 {
108 *out++ = '\r';
109 *out++ = '\n';
110 lnsz += 2;
111 outbuf.SetLength(outbuf.Length() + lnsz);
112
113 if (outbuf.Length() > outbuf.Capacity() - 200)
114 {
115 m_writeFunc(outbuf, outbuf.Length());
116 outbuf.SetLength(0);
117 out = (char*)outbuf;
118 }
119
120 lnsz = 0;
121 }
122 }
123 }
124 crc ^= 0xFFFFFFFF;
125
126 m_diskfile.Close();
127
128 outbuf.Append(CString::FormatStr("=yend size=%i part=0 pcrc32=%08x\r\n", m_size, (unsigned int)crc));
129 m_writeFunc(outbuf, outbuf.Length());
130 }
0 /*
1 * This file is part of nzbget. See <http://nzbget.net>.
2 *
3 * Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19
20 #ifndef YENCODER_H
21 #define YENCODER_H
22
23 #include "NString.h"
24 #include "FileSystem.h"
25
26 class YEncoder
27 {
28 public:
29 typedef std::function<void(const char* buf, int size)> WriteFunc;
30
31 YEncoder(const char* filename, int part, int64 offset, int size, WriteFunc writeFunc) :
32 m_filename(filename), m_part(part), m_offset(offset), m_size(size), m_writeFunc(writeFunc) {};
33 bool OpenFile(CString& errmsg);
34 void WriteSegment();
35
36 private:
37 DiskFile m_diskfile;
38 CString m_filename;
39 int m_part;
40 int64 m_offset;
41 int m_size;
42 int64 m_fileSize;
43 WriteFunc m_writeFunc;
44 };
45
46 #endif
7575 m_postInfo->GetNzbInfo()->SetMoveStatus(NzbInfo::msFailure);
7676 }
7777
78 m_postInfo->SetStage(PostInfo::ptQueued);
7978 m_postInfo->SetWorking(false);
8079 }
8180
181180 m_postInfo->GetNzbInfo()->SetCleanupStatus(NzbInfo::csFailure);
182181 }
183182
184 m_postInfo->SetStage(PostInfo::ptQueued);
185183 m_postInfo->SetWorking(false);
186184 }
187185
5454 class Repairer : public Par2::Par2Repairer, public ParChecker::AbstractRepairer
5555 {
5656 public:
57 Repairer(ParChecker* owner) { m_owner = owner; }
57 Repairer(ParChecker* owner):
58 Par2::Par2Repairer(owner->m_parCout, owner->m_parCerr),
59 m_owner(owner), commandLine(owner->m_parCout, owner->m_parCerr) {}
5860 Par2::Result PreProcess(const char *parFilename);
5961 Par2::Result Process(bool dorepair);
6062 virtual Repairer* GetRepairer() { return this; }
363365 }
364366
365367
366 ParChecker::~ParChecker()
367 {
368 debug("Destroying ParChecker");
369
370 Cleanup();
371 }
372
373368 void ParChecker::Cleanup()
374369 {
375370 m_repairer.reset();
380375 m_errMsg = nullptr;
381376 }
382377
383 void ParChecker::Run()
384 {
385 Par2::cout.rdbuf(&m_parOutStream);
386 Par2::cerr.rdbuf(&m_parErrStream);
387
378 void ParChecker::Execute()
379 {
388380 m_status = RunParCheckAll();
389381
390 if (m_status == psRepairNotNeeded && m_parQuick && m_forceRepair && !m_cancelled)
382 if (m_status == psRepairNotNeeded && m_parQuick && m_forceRepair && !IsStopped())
391383 {
392384 PrintMessage(Message::mkInfo, "Performing full par-check for %s", *m_nzbName);
393385 m_parQuick = false;
395387 }
396388
397389 Completed();
398
399 Par2::cout.rdbuf(&Par2::nullStreamBuf);
400 Par2::cerr.rdbuf(&Par2::nullStreamBuf);
401390 }
402391
403392 ParChecker::EStatus ParChecker::RunParCheckAll()
410399 }
411400
412401 EStatus allStatus = psRepairNotNeeded;
413 m_cancelled = false;
414402 m_parFull = true;
415403
416404 for (CString& parFilename : fileList)
417405 {
418406 debug("Found par: %s", *parFilename);
419407
420 if (!IsStopped() && !m_cancelled)
408 if (!IsStopped())
421409 {
422410 BString<1024> fullParFilename( "%s%c%s", *m_destDir, (int)PATH_SEPARATOR, *parFilename);
423411
424412 int baseLen = 0;
425 ParParser::ParseParFilename(parFilename, &baseLen, nullptr);
413 ParParser::ParseParFilename(parFilename, true, &baseLen, nullptr);
426414 BString<1024> infoName;
427415 infoName.Set(parFilename, baseLen);
428416
570558 }
571559 }
572560
573 if (m_cancelled)
561 if (IsStopped())
574562 {
575563 if (m_stage >= ptRepairing)
576564 {
687675 {
688676 // wait until new files are added by "AddParFile" or a change is signaled by "QueueChanged"
689677 bool queuedParFilesChanged = false;
690 while (!queuedParFilesChanged && !IsStopped() && !m_cancelled)
678 while (!queuedParFilesChanged && !IsStopped())
691679 {
692680 {
693681 Guard guard(m_queuedParFilesMutex);
753741 {
754742 // wait until new files are added by "AddParFile" or a change is signaled by "QueueChanged"
755743 bool queuedParFilesChanged = false;
756 while (!queuedParFilesChanged && !IsStopped() && !m_cancelled)
744 while (!queuedParFilesChanged && !IsStopped())
757745 {
758746 {
759747 Guard guard(m_queuedParFilesMutex);
764752 }
765753 }
766754
767 if (IsStopped() || m_cancelled)
755 if (IsStopped())
768756 {
769757 break;
770758 }
972960
973961 // adding files one by one until all missing files are found
974962
975 while (!IsStopped() && !m_cancelled && extrafiles.size() > 0)
963 while (!IsStopped() && extrafiles.size() > 0)
976964 {
977965 std::list<Par2::CommandLine::ExtraFile> extrafiles1;
978966 extrafiles1.splice(extrafiles1.end(), extrafiles, extrafiles.begin());
11981186 void ParChecker::Cancel()
11991187 {
12001188 GetRepairer()->cancelled = true;
1201 m_cancelled = true;
12021189 QueueChanged();
12031190 }
12041191
12131200 {
12141201 if (status == psFailed)
12151202 {
1216 if (m_cancelled)
1203 if (IsStopped())
12171204 {
12181205 file.Print("Repair cancelled for %s\n", *m_infoName);
12191206 }
2424
2525 #include "NString.h"
2626 #include "Container.h"
27 #include "Thread.h"
2827 #include "FileSystem.h"
2928 #include "Log.h"
3029
3130 class Repairer;
3231
33 class ParChecker : public Thread
32 class ParChecker
3433 {
3534 public:
3635 enum EStatus
5655 virtual Repairer* GetRepairer() = 0;
5756 };
5857
59 virtual ~ParChecker();
60 virtual void Run();
58 void Execute();
6159 void SetDestDir(const char* destDir) { m_destDir = destDir; }
6260 const char* GetParFilename() { return m_parFilename; }
6361 const char* GetInfoName() { return m_infoName; }
7371 void AddParFile(const char* parFilename);
7472 void QueueChanged();
7573 void Cancel();
76 bool GetCancelled() { return m_cancelled; }
7774
7875 protected:
7976 class Segment
128125 */
129126 virtual bool RequestMorePars(int blockNeeded, int* blockFound) = 0;
130127 virtual void UpdateProgress() {}
128 virtual bool IsStopped() { return false; };
131129 virtual void Completed() {}
132130 virtual void PrintMessage(Message::EKind kind, const char* format, ...) PRINTF_SYNTAX(3) {}
133131 virtual void RegisterParredFile(const char* filename) {}
185183 DupeSourceList m_dupeSources;
186184 StreamBuf m_parOutStream{this, Message::mkDetail};
187185 StreamBuf m_parErrStream{this, Message::mkError};
186 std::ostream m_parCout{&m_parOutStream};
187 std::ostream m_parCerr{&m_parErrStream};
188188
189189 // "m_repairer" should be of type "Par2::Par2Repairer", however to prevent the
190190 // including of libpar2-headers into this header-file we use an empty abstract class.
+0
-710
daemon/postprocess/ParCoordinator.cpp less more
0 /*
1 * This file is part of nzbget. See <http://nzbget.net>.
2 *
3 * Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19
20 #include "nzbget.h"
21 #include "ParCoordinator.h"
22 #include "DupeCoordinator.h"
23 #include "ParParser.h"
24 #include "Options.h"
25 #include "DiskState.h"
26 #include "Log.h"
27 #include "FileSystem.h"
28
29 #ifndef DISABLE_PARCHECK
30 bool ParCoordinator::PostParChecker::RequestMorePars(int blockNeeded, int* blockFound)
31 {
32 return m_owner->RequestMorePars(m_postInfo->GetNzbInfo(), GetParFilename(), blockNeeded, blockFound);
33 }
34
35 void ParCoordinator::PostParChecker::UpdateProgress()
36 {
37 m_owner->UpdateParCheckProgress();
38 }
39
40 void ParCoordinator::PostParChecker::PrintMessage(Message::EKind kind, const char* format, ...)
41 {
42 char text[1024];
43 va_list args;
44 va_start(args, format);
45 vsnprintf(text, 1024, format, args);
46 va_end(args);
47 text[1024-1] = '\0';
48
49 m_postInfo->GetNzbInfo()->AddMessage(kind, text);
50 }
51
52 void ParCoordinator::PostParChecker::RegisterParredFile(const char* filename)
53 {
54 m_postInfo->GetParredFiles()->push_back(filename);
55 }
56
57 bool ParCoordinator::PostParChecker::IsParredFile(const char* filename)
58 {
59 for (CString& parredFile : m_postInfo->GetParredFiles())
60 {
61 if (!strcasecmp(parredFile, filename))
62 {
63 return true;
64 }
65 }
66 return false;
67 }
68
69 ParChecker::EFileStatus ParCoordinator::PostParChecker::FindFileCrc(const char* filename,
70 uint32* crc, SegmentList* segments)
71 {
72 CompletedFile* completedFile = nullptr;
73
74 for (CompletedFile& completedFile2 : m_postInfo->GetNzbInfo()->GetCompletedFiles())
75 {
76 if (!strcasecmp(completedFile2.GetFileName(), filename))
77 {
78 completedFile = &completedFile2;
79 break;
80 }
81 }
82 if (!completedFile)
83 {
84 return ParChecker::fsUnknown;
85 }
86
87 debug("Found completed file: %s, CRC: %.8x, Status: %i", FileSystem::BaseFileName(completedFile->GetFileName()), completedFile->GetCrc(), (int)completedFile->GetStatus());
88
89 *crc = completedFile->GetCrc();
90
91 if (completedFile->GetStatus() == CompletedFile::cfPartial && completedFile->GetId() > 0 &&
92 !m_postInfo->GetNzbInfo()->GetReprocess())
93 {
94 FileInfo tmpFileInfo(completedFile->GetId());
95
96 if (!g_DiskState->LoadFileState(&tmpFileInfo, nullptr, true))
97 {
98 return ParChecker::fsUnknown;
99 }
100
101 for (ArticleInfo* pa : tmpFileInfo.GetArticles())
102 {
103 segments->emplace_back(pa->GetStatus() == ArticleInfo::aiFinished,
104 pa->GetSegmentOffset(), pa->GetSegmentSize(), pa->GetCrc());
105 }
106 }
107
108 return completedFile->GetStatus() == CompletedFile::cfSuccess ? ParChecker::fsSuccess :
109 completedFile->GetStatus() == CompletedFile::cfFailure &&
110 !m_postInfo->GetNzbInfo()->GetReprocess() ? ParChecker::fsFailure :
111 completedFile->GetStatus() == CompletedFile::cfPartial && segments->size() > 0 &&
112 !m_postInfo->GetNzbInfo()->GetReprocess()? ParChecker::fsPartial :
113 ParChecker::fsUnknown;
114 }
115
116 void ParCoordinator::PostParChecker::RequestDupeSources(DupeSourceList* dupeSourceList)
117 {
118 GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
119
120 RawNzbList dupeList = g_DupeCoordinator->ListHistoryDupes(downloadQueue, m_postInfo->GetNzbInfo());
121
122 if (!dupeList.empty())
123 {
124 PostDupeMatcher dupeMatcher(m_postInfo);
125 PrintMessage(Message::mkInfo, "Checking %s for dupe scan usability", m_postInfo->GetNzbInfo()->GetName());
126 bool sizeComparisonPossible = dupeMatcher.Prepare();
127 for (NzbInfo* dupeNzbInfo : dupeList)
128 {
129 if (sizeComparisonPossible)
130 {
131 PrintMessage(Message::mkInfo, "Checking %s for dupe scan usability", FileSystem::BaseFileName(dupeNzbInfo->GetDestDir()));
132 }
133 bool useDupe = !sizeComparisonPossible || dupeMatcher.MatchDupeContent(dupeNzbInfo->GetDestDir());
134 if (useDupe)
135 {
136 PrintMessage(Message::mkInfo, "Adding %s to dupe scan sources", FileSystem::BaseFileName(dupeNzbInfo->GetDestDir()));
137 dupeSourceList->emplace_back(dupeNzbInfo->GetId(), dupeNzbInfo->GetDestDir());
138 }
139 }
140 if (dupeSourceList->empty())
141 {
142 PrintMessage(Message::mkInfo, "No usable dupe scan sources found");
143 }
144 }
145 }
146
147 void ParCoordinator::PostParChecker::StatDupeSources(DupeSourceList* dupeSourceList)
148 {
149 GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
150
151 int totalExtraParBlocks = 0;
152 for (DupeSource& dupeSource : dupeSourceList)
153 {
154 if (dupeSource.GetUsedBlocks() > 0)
155 {
156 for (HistoryInfo* historyInfo : downloadQueue->GetHistory())
157 {
158 if (historyInfo->GetKind() == HistoryInfo::hkNzb &&
159 historyInfo->GetNzbInfo()->GetId() == dupeSource.GetId())
160 {
161 historyInfo->GetNzbInfo()->SetExtraParBlocks(historyInfo->GetNzbInfo()->GetExtraParBlocks() - dupeSource.GetUsedBlocks());
162 }
163 }
164 }
165 totalExtraParBlocks += dupeSource.GetUsedBlocks();
166 }
167
168 m_postInfo->GetNzbInfo()->SetExtraParBlocks(m_postInfo->GetNzbInfo()->GetExtraParBlocks() + totalExtraParBlocks);
169 }
170
171 void ParCoordinator::PostParRenamer::UpdateProgress()
172 {
173 m_owner->UpdateParRenameProgress();
174 }
175
176 void ParCoordinator::PostParRenamer::PrintMessage(Message::EKind kind, const char* format, ...)
177 {
178 char text[1024];
179 va_list args;
180 va_start(args, format);
181 vsnprintf(text, 1024, format, args);
182 va_end(args);
183 text[1024-1] = '\0';
184
185 m_postInfo->GetNzbInfo()->AddMessage(kind, text);
186 }
187
188 void ParCoordinator::PostParRenamer::RegisterParredFile(const char* filename)
189 {
190 m_postInfo->GetParredFiles()->push_back(filename);
191 }
192
193 /**
194 * Update file name in the CompletedFiles-list of NZBInfo
195 */
196 void ParCoordinator::PostParRenamer::RegisterRenamedFile(const char* oldFilename, const char* newFileName)
197 {
198 for (CompletedFile& completedFile : m_postInfo->GetNzbInfo()->GetCompletedFiles())
199 {
200 if (!strcasecmp(completedFile.GetFileName(), oldFilename))
201 {
202 completedFile.SetFileName(newFileName);
203 break;
204 }
205 }
206 }
207
208 void ParCoordinator::PostDupeMatcher::PrintMessage(Message::EKind kind, const char* format, ...)
209 {
210 char text[1024];
211 va_list args;
212 va_start(args, format);
213 vsnprintf(text, 1024, format, args);
214 va_end(args);
215 text[1024-1] = '\0';
216
217 m_postInfo->GetNzbInfo()->AddMessage(kind, text);
218 }
219
220 #endif
221
222 ParCoordinator::ParCoordinator()
223 {
224 debug("Creating ParCoordinator");
225
226 #ifndef DISABLE_PARCHECK
227 m_parChecker.m_owner = this;
228 m_parRenamer.m_owner = this;
229 #endif
230 }
231
232 ParCoordinator::~ParCoordinator()
233 {
234 debug("Destroying ParCoordinator");
235 }
236
237 #ifndef DISABLE_PARCHECK
238 void ParCoordinator::Stop()
239 {
240 debug("Stopping ParCoordinator");
241
242 m_stopped = true;
243
244 if (m_parChecker.IsRunning())
245 {
246 m_parChecker.Stop();
247 int mSecWait = 5000;
248 while (m_parChecker.IsRunning() && mSecWait > 0)
249 {
250 usleep(50 * 1000);
251 mSecWait -= 50;
252 }
253 if (m_parChecker.IsRunning())
254 {
255 warn("Terminating par-check for %s", m_parChecker.GetInfoName());
256 m_parChecker.Kill();
257 }
258 }
259 }
260 #endif
261
262 void ParCoordinator::PausePars(DownloadQueue* downloadQueue, NzbInfo* nzbInfo)
263 {
264 debug("ParCoordinator: Pausing pars");
265
266 downloadQueue->EditEntry(nzbInfo->GetId(),
267 DownloadQueue::eaGroupPauseExtraPars, 0, nullptr);
268 }
269
270 #ifndef DISABLE_PARCHECK
271
272 /**
273 * DownloadQueue must be locked prior to call of this function.
274 */
275 void ParCoordinator::StartParCheckJob(PostInfo* postInfo)
276 {
277 m_currentJob = jkParCheck;
278 m_parChecker.SetPostInfo(postInfo);
279 m_parChecker.SetDestDir(postInfo->GetNzbInfo()->GetDestDir());
280 m_parChecker.SetNzbName(postInfo->GetNzbInfo()->GetName());
281 m_parChecker.SetParTime(Util::CurrentTime());
282 m_parChecker.SetDownloadSec(postInfo->GetNzbInfo()->GetDownloadSec());
283 m_parChecker.SetParQuick(g_Options->GetParQuick() && !postInfo->GetForceParFull());
284 m_parChecker.SetForceRepair(postInfo->GetForceRepair());
285 m_parChecker.PrintMessage(Message::mkInfo, "Checking pars for %s", postInfo->GetNzbInfo()->GetName());
286 postInfo->SetWorking(true);
287 m_parChecker.Start();
288 }
289
290 /**
291 * DownloadQueue must be locked prior to call of this function.
292 */
293 void ParCoordinator::StartParRenameJob(PostInfo* postInfo)
294 {
295 const char* destDir = postInfo->GetNzbInfo()->GetDestDir();
296
297 if (postInfo->GetNzbInfo()->GetUnpackStatus() == NzbInfo::usSuccess &&
298 !Util::EmptyStr(postInfo->GetNzbInfo()->GetFinalDir()))
299 {
300 destDir = postInfo->GetNzbInfo()->GetFinalDir();
301 }
302
303 m_currentJob = jkParRename;
304 m_parRenamer.SetPostInfo(postInfo);
305 m_parRenamer.SetDestDir(destDir);
306 m_parRenamer.SetInfoName(postInfo->GetNzbInfo()->GetName());
307 m_parRenamer.SetDetectMissing(postInfo->GetNzbInfo()->GetUnpackStatus() == NzbInfo::usNone);
308 m_parRenamer.PrintMessage(Message::mkInfo, "Checking renamed files for %s", postInfo->GetNzbInfo()->GetName());
309 postInfo->SetWorking(true);
310 m_parRenamer.Start();
311 }
312
313 bool ParCoordinator::Cancel()
314 {
315 if (m_currentJob == jkParCheck)
316 {
317 if (!m_parChecker.GetCancelled())
318 {
319 debug("Cancelling par-repair for %s", m_parChecker.GetInfoName());
320 m_parChecker.Cancel();
321 return true;
322 }
323 }
324 else if (m_currentJob == jkParRename)
325 {
326 if (!m_parRenamer.GetCancelled())
327 {
328 debug("Cancelling par-rename for %s", m_parRenamer.GetInfoName());
329 m_parRenamer.Cancel();
330 return true;
331 }
332 }
333 return false;
334 }
335
336 /**
337 * DownloadQueue must be locked prior to call of this function.
338 */
339 bool ParCoordinator::AddPar(FileInfo* fileInfo, bool deleted)
340 {
341 bool sameCollection = m_parChecker.IsRunning() &&
342 fileInfo->GetNzbInfo() == m_parChecker.GetPostInfo()->GetNzbInfo();
343 if (sameCollection && !deleted)
344 {
345 BString<1024> fullFilename("%s%c%s", fileInfo->GetNzbInfo()->GetDestDir(), (int)PATH_SEPARATOR, fileInfo->GetFilename());
346 m_parChecker.AddParFile(fullFilename);
347 }
348 else
349 {
350 m_parChecker.QueueChanged();
351 }
352 return sameCollection;
353 }
354
355 void ParCoordinator::ParCheckCompleted()
356 {
357 GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
358
359 PostInfo* postInfo = m_parChecker.GetPostInfo();
360
361 // Update ParStatus (accumulate result)
362 if ((m_parChecker.GetStatus() == ParChecker::psRepaired ||
363 m_parChecker.GetStatus() == ParChecker::psRepairNotNeeded) &&
364 postInfo->GetNzbInfo()->GetParStatus() <= NzbInfo::psSkipped)
365 {
366 postInfo->GetNzbInfo()->SetParStatus(NzbInfo::psSuccess);
367 postInfo->SetParRepaired(m_parChecker.GetStatus() == ParChecker::psRepaired);
368 }
369 else if (m_parChecker.GetStatus() == ParChecker::psRepairPossible &&
370 postInfo->GetNzbInfo()->GetParStatus() != NzbInfo::psFailure)
371 {
372 postInfo->GetNzbInfo()->SetParStatus(NzbInfo::psRepairPossible);
373 }
374 else
375 {
376 postInfo->GetNzbInfo()->SetParStatus(NzbInfo::psFailure);
377 }
378
379 int waitTime = postInfo->GetNzbInfo()->GetDownloadSec() - m_parChecker.GetDownloadSec();
380 postInfo->SetStartTime(postInfo->GetStartTime() + (time_t)waitTime);
381 int parSec = (int)(Util::CurrentTime() - m_parChecker.GetParTime()) - waitTime;
382 postInfo->GetNzbInfo()->SetParSec(postInfo->GetNzbInfo()->GetParSec() + parSec);
383
384 postInfo->GetNzbInfo()->SetParFull(m_parChecker.GetParFull());
385
386 postInfo->SetWorking(false);
387 postInfo->SetStage(PostInfo::ptQueued);
388
389 downloadQueue->Save();
390 }
391
392 /**
393 * Unpause par2-files
394 * returns true, if the files with required number of blocks were unpaused,
395 * or false if there are no more files in queue for this collection or not enough blocks.
396 * special case: returns true if there are any unpaused par2-files in the queue regardless
397 * of the amount of blocks; this is to keep par-checker wait for download completion.
398 */
399 bool ParCoordinator::RequestMorePars(NzbInfo* nzbInfo, const char* parFilename, int blockNeeded, int* blockFoundOut)
400 {
401 GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
402
403 Blocks blocks;
404 blocks.clear();
405 int blockFound = 0;
406 int curBlockFound = 0;
407
408 FindPars(downloadQueue, nzbInfo, parFilename, blocks, true, true, &curBlockFound);
409 blockFound += curBlockFound;
410 if (blockFound < blockNeeded)
411 {
412 FindPars(downloadQueue, nzbInfo, parFilename, blocks, true, false, &curBlockFound);
413 blockFound += curBlockFound;
414 }
415 if (blockFound < blockNeeded)
416 {
417 FindPars(downloadQueue, nzbInfo, parFilename, blocks, false, false, &curBlockFound);
418 blockFound += curBlockFound;
419 }
420
421 if (blockFound >= blockNeeded)
422 {
423 // 1. first unpause all files with par-blocks less or equal iBlockNeeded
424 // starting from the file with max block count.
425 // if par-collection was built exponentially and all par-files present,
426 // this step selects par-files with exact number of blocks we need.
427 while (blockNeeded > 0)
428 {
429 BlockInfo* bestBlockInfo = nullptr;
430 Blocks::iterator bestBlockIter;
431 for (Blocks::iterator it = blocks.begin(); it != blocks.end(); it++)
432 {
433 BlockInfo& blockInfo = *it;
434 if (blockInfo.m_blockCount <= blockNeeded &&
435 (!bestBlockInfo || bestBlockInfo->m_blockCount < blockInfo.m_blockCount))
436 {
437 bestBlockInfo = &blockInfo;
438 bestBlockIter = it;
439 }
440 }
441 if (bestBlockInfo)
442 {
443 if (bestBlockInfo->m_fileInfo->GetPaused())
444 {
445 m_parChecker.PrintMessage(Message::mkInfo, "Unpausing %s%c%s for par-recovery", nzbInfo->GetName(), (int)PATH_SEPARATOR, bestBlockInfo->m_fileInfo->GetFilename());
446 bestBlockInfo->m_fileInfo->SetPaused(false);
447 bestBlockInfo->m_fileInfo->SetExtraPriority(true);
448 }
449 blockNeeded -= bestBlockInfo->m_blockCount;
450 blocks.erase(bestBlockIter);
451 }
452 else
453 {
454 break;
455 }
456 }
457
458 // 2. then unpause other files
459 // this step only needed if the par-collection was built not exponentially
460 // or not all par-files present (or some of them were corrupted)
461 // this step is not optimal, but we hope, that the first step will work good
462 // in most cases and we will not need the second step often
463 while (blockNeeded > 0)
464 {
465 BlockInfo& blockInfo = blocks.front();
466 if (blockInfo.m_fileInfo->GetPaused())
467 {
468 m_parChecker.PrintMessage(Message::mkInfo, "Unpausing %s%c%s for par-recovery", nzbInfo->GetName(), (int)PATH_SEPARATOR, blockInfo.m_fileInfo->GetFilename());
469 blockInfo.m_fileInfo->SetPaused(false);
470 blockInfo.m_fileInfo->SetExtraPriority(true);
471 }
472 blockNeeded -= blockInfo.m_blockCount;
473 }
474 }
475
476 bool hasUnpausedParFiles = false;
477 for (FileInfo* fileInfo : nzbInfo->GetFileList())
478 {
479 if (fileInfo->GetParFile() && !fileInfo->GetPaused())
480 {
481 hasUnpausedParFiles = true;
482 break;
483 }
484 }
485
486 if (blockFoundOut)
487 {
488 *blockFoundOut = blockFound;
489 }
490
491 bool ok = blockNeeded <= 0 || hasUnpausedParFiles;
492
493 return ok;
494 }
495
496 void ParCoordinator::FindPars(DownloadQueue* downloadQueue, NzbInfo* nzbInfo, const char* parFilename,
497 Blocks& blocks, bool strictParName, bool exactParName, int* blockFound)
498 {
499 *blockFound = 0;
500
501 // extract base name from m_szParFilename (trim .par2-extension and possible .vol-part)
502 char* baseParFilename = FileSystem::BaseFileName(parFilename);
503 int mainBaseLen = 0;
504 if (!ParParser::ParseParFilename(baseParFilename, &mainBaseLen, nullptr))
505 {
506 // should not happen
507 nzbInfo->PrintMessage(Message::mkError, "Internal error: could not parse filename %s", baseParFilename);
508 return;
509 }
510 BString<1024> mainBaseFilename;
511 mainBaseFilename.Set(baseParFilename, mainBaseLen);
512 for (char* p = mainBaseFilename; *p; p++) *p = tolower(*p); // convert string to lowercase
513
514 for (FileInfo* fileInfo : nzbInfo->GetFileList())
515 {
516 int blockCount = 0;
517 if (ParParser::ParseParFilename(fileInfo->GetFilename(), nullptr, &blockCount) &&
518 blockCount > 0)
519 {
520 bool useFile = true;
521
522 if (exactParName)
523 {
524 useFile = ParParser::SameParCollection(fileInfo->GetFilename(), FileSystem::BaseFileName(parFilename));
525 }
526 else if (strictParName)
527 {
528 // the pFileInfo->GetFilename() may be not confirmed and may contain
529 // additional texts if Subject could not be parsed correctly
530
531 BString<1024> loFileName = fileInfo->GetFilename();
532 for (char* p = loFileName; *p; p++) *p = tolower(*p); // convert string to lowercase
533
534 BString<1024> candidateFileName("%s.par2", *mainBaseFilename);
535 if (!strstr(loFileName, candidateFileName))
536 {
537 candidateFileName.Format("%s.vol", *mainBaseFilename);
538 useFile = strstr(loFileName, candidateFileName);
539 }
540 }
541
542 bool alreadyAdded = false;
543 // check if file is not in the list already
544 if (useFile)
545 {
546 for (BlockInfo& blockInfo : blocks)
547 {
548 if (blockInfo.m_fileInfo == fileInfo)
549 {
550 alreadyAdded = true;
551 break;
552 }
553 }
554 }
555
556 // if it is a par2-file with blocks and it was from the same NZB-request
557 // and it belongs to the same file collection (same base name),
558 // then OK, we can use it
559 if (useFile && !alreadyAdded)
560 {
561 blocks.emplace_back(fileInfo, blockCount);
562 *blockFound += blockCount;
563 }
564 }
565 }
566 }
567
568 void ParCoordinator::UpdateParCheckProgress()
569 {
570 PostInfo* postInfo;
571
572 {
573 GuardedDownloadQueue guard = DownloadQueue::Guard();
574
575 postInfo = m_parChecker.GetPostInfo();
576 if (m_parChecker.GetFileProgress() == 0)
577 {
578 postInfo->SetProgressLabel(m_parChecker.GetProgressLabel());
579 }
580 postInfo->SetFileProgress(m_parChecker.GetFileProgress());
581 postInfo->SetStageProgress(m_parChecker.GetStageProgress());
582 PostInfo::EStage StageKind[] = {PostInfo::ptLoadingPars, PostInfo::ptVerifyingSources, PostInfo::ptRepairing, PostInfo::ptVerifyingRepaired};
583 PostInfo::EStage stage = StageKind[m_parChecker.GetStage()];
584 time_t current = Util::CurrentTime();
585
586 if (postInfo->GetStage() != stage)
587 {
588 postInfo->SetStage(stage);
589 postInfo->SetStageTime(current);
590 if (postInfo->GetStage() == PostInfo::ptRepairing)
591 {
592 m_parChecker.SetRepairTime(current);
593 }
594 else if (postInfo->GetStage() == PostInfo::ptVerifyingRepaired)
595 {
596 int repairSec = (int)(current - m_parChecker.GetRepairTime());
597 postInfo->GetNzbInfo()->SetRepairSec(postInfo->GetNzbInfo()->GetRepairSec() + repairSec);
598 }
599 }
600
601 bool parCancel = false;
602 if (!m_parChecker.GetCancelled())
603 {
604 if ((g_Options->GetParTimeLimit() > 0) &&
605 m_parChecker.GetStage() == PostParChecker::ptRepairing &&
606 ((g_Options->GetParTimeLimit() > 5 && current - postInfo->GetStageTime() > 5 * 60) ||
607 (g_Options->GetParTimeLimit() <= 5 && current - postInfo->GetStageTime() > 1 * 60)))
608 {
609 // first five (or one) minutes elapsed, now can check the estimated time
610 int estimatedRepairTime = (int)((current - postInfo->GetStartTime()) * 1000 /
611 (postInfo->GetStageProgress() > 0 ? postInfo->GetStageProgress() : 1));
612 if (estimatedRepairTime > g_Options->GetParTimeLimit() * 60)
613 {
614 debug("Estimated repair time %i seconds", estimatedRepairTime);
615 m_parChecker.PrintMessage(Message::mkWarning, "Cancelling par-repair for %s, estimated repair time (%i minutes) exceeds allowed repair time", m_parChecker.GetInfoName(), estimatedRepairTime / 60);
616 parCancel = true;
617 }
618 }
619 }
620
621 if (parCancel)
622 {
623 m_parChecker.Cancel();
624 }
625 }
626
627 CheckPauseState(postInfo);
628 }
629
630 void ParCoordinator::CheckPauseState(PostInfo* postInfo)
631 {
632 if (g_Options->GetPausePostProcess() && !postInfo->GetNzbInfo()->GetForcePriority())
633 {
634 time_t stageTime = postInfo->GetStageTime();
635 time_t startTime = postInfo->GetStartTime();
636 time_t parTime = m_parChecker.GetParTime();
637 time_t repairTime = m_parChecker.GetRepairTime();
638 time_t waitTime = Util::CurrentTime();
639
640 // wait until Post-processor is unpaused
641 while (g_Options->GetPausePostProcess() && !postInfo->GetNzbInfo()->GetForcePriority() && !m_stopped)
642 {
643 usleep(50 * 1000);
644
645 // update time stamps
646
647 time_t delta = Util::CurrentTime() - waitTime;
648
649 if (stageTime > 0)
650 {
651 postInfo->SetStageTime(stageTime + delta);
652 }
653 if (startTime > 0)
654 {
655 postInfo->SetStartTime(startTime + delta);
656 }
657 if (parTime > 0)
658 {
659 m_parChecker.SetParTime(parTime + delta);
660 }
661 if (repairTime > 0)
662 {
663 m_parChecker.SetRepairTime(repairTime + delta);
664 }
665 }
666 }
667 }
668
669 void ParCoordinator::ParRenameCompleted()
670 {
671 GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
672
673 PostInfo* postInfo = m_parRenamer.GetPostInfo();
674 postInfo->GetNzbInfo()->SetRenameStatus(m_parRenamer.GetStatus() == ParRenamer::psSuccess ? NzbInfo::rsSuccess : NzbInfo::rsFailure);
675
676 if (m_parRenamer.HasMissedFiles() && postInfo->GetNzbInfo()->GetParStatus() <= NzbInfo::psSkipped)
677 {
678 m_parRenamer.PrintMessage(Message::mkInfo, "Requesting par-check/repair for %s to restore missing files ", m_parRenamer.GetInfoName());
679 postInfo->SetRequestParCheck(true);
680 }
681
682 postInfo->SetWorking(false);
683 postInfo->SetStage(PostInfo::ptQueued);
684
685 downloadQueue->Save();
686 }
687
688 void ParCoordinator::UpdateParRenameProgress()
689 {
690 PostInfo* postInfo;
691 {
692 GuardedDownloadQueue guard = DownloadQueue::Guard();
693
694 postInfo = m_parRenamer.GetPostInfo();
695 postInfo->SetProgressLabel(m_parRenamer.GetProgressLabel());
696 postInfo->SetStageProgress(m_parRenamer.GetStageProgress());
697 time_t current = Util::CurrentTime();
698
699 if (postInfo->GetStage() != PostInfo::ptRenaming)
700 {
701 postInfo->SetStage(PostInfo::ptRenaming);
702 postInfo->SetStageTime(current);
703 }
704 }
705
706 CheckPauseState(postInfo);
707 }
708
709 #endif
+0
-143
daemon/postprocess/ParCoordinator.h less more
0 /*
1 * This file is part of nzbget. See <http://nzbget.net>.
2 *
3 * Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19
20 #ifndef PARCOORDINATOR_H
21 #define PARCOORDINATOR_H
22
23 #include "DownloadInfo.h"
24
25 #ifndef DISABLE_PARCHECK
26 #include "ParChecker.h"
27 #include "ParRenamer.h"
28 #include "DupeMatcher.h"
29 #endif
30
31 class ParCoordinator
32 {
33 public:
34 ParCoordinator();
35 virtual ~ParCoordinator();
36 void PausePars(DownloadQueue* downloadQueue, NzbInfo* nzbInfo);
37
38 #ifndef DISABLE_PARCHECK
39 bool AddPar(FileInfo* fileInfo, bool deleted);
40 void StartParCheckJob(PostInfo* postInfo);
41 void StartParRenameJob(PostInfo* postInfo);
42 void Stop();
43 bool Cancel();
44
45 protected:
46 void UpdateParCheckProgress();
47 void UpdateParRenameProgress();
48 void ParCheckCompleted();
49 void ParRenameCompleted();
50 void CheckPauseState(PostInfo* postInfo);
51 bool RequestMorePars(NzbInfo* nzbInfo, const char* parFilename, int blockNeeded, int* blockFound);
52
53 private:
54 class PostParChecker: public ParChecker
55 {
56 public:
57 PostInfo* GetPostInfo() { return m_postInfo; }
58 void SetPostInfo(PostInfo* postInfo) { m_postInfo = postInfo; }
59 time_t GetParTime() { return m_parTime; }
60 void SetParTime(time_t parTime) { m_parTime = parTime; }
61 time_t GetRepairTime() { return m_repairTime; }
62 void SetRepairTime(time_t repairTime) { m_repairTime = repairTime; }
63 int GetDownloadSec() { return m_downloadSec; }
64 void SetDownloadSec(int downloadSec) { m_downloadSec = downloadSec; }
65 protected:
66 virtual bool RequestMorePars(int blockNeeded, int* blockFound);
67 virtual void UpdateProgress();
68 virtual void Completed() { m_owner->ParCheckCompleted(); }
69 virtual void PrintMessage(Message::EKind kind, const char* format, ...) PRINTF_SYNTAX(3);
70 virtual void RegisterParredFile(const char* filename);
71 virtual bool IsParredFile(const char* filename);
72 virtual EFileStatus FindFileCrc(const char* filename, uint32* crc, SegmentList* segments);
73 virtual void RequestDupeSources(DupeSourceList* dupeSourceList);
74 virtual void StatDupeSources(DupeSourceList* dupeSourceList);
75 private:
76 ParCoordinator* m_owner;
77 PostInfo* m_postInfo;
78 time_t m_parTime;
79 time_t m_repairTime;
80 int m_downloadSec;
81
82 friend class ParCoordinator;
83 };
84
85 class PostParRenamer: public ParRenamer
86 {
87 public:
88 PostInfo* GetPostInfo() { return m_postInfo; }
89 void SetPostInfo(PostInfo* postInfo) { m_postInfo = postInfo; }
90 protected:
91 virtual void UpdateProgress();
92 virtual void Completed() { m_owner->ParRenameCompleted(); }
93 virtual void PrintMessage(Message::EKind kind, const char* format, ...) PRINTF_SYNTAX(3);
94 virtual void RegisterParredFile(const char* filename);
95 virtual void RegisterRenamedFile(const char* oldFilename, const char* newFileName);
96 private:
97 ParCoordinator* m_owner;
98 PostInfo* m_postInfo;
99
100 friend class ParCoordinator;
101 };
102
103 class PostDupeMatcher: public DupeMatcher
104 {
105 public:
106 PostDupeMatcher(PostInfo* postInfo):
107 DupeMatcher(postInfo->GetNzbInfo()->GetDestDir(),
108 postInfo->GetNzbInfo()->GetSize() - postInfo->GetNzbInfo()->GetParSize()),
109 m_postInfo(postInfo) {}
110 protected:
111 virtual void PrintMessage(Message::EKind kind, const char* format, ...) PRINTF_SYNTAX(3);
112 private:
113 PostInfo* m_postInfo;
114 };
115
116 struct BlockInfo
117 {
118 FileInfo* m_fileInfo;
119 int m_blockCount;
120 BlockInfo(FileInfo* fileInfo, int blockCount) :
121 m_fileInfo(fileInfo), m_blockCount(blockCount) {}
122 };
123
124 typedef std::deque<BlockInfo> Blocks;
125
126 enum EJobKind
127 {
128 jkParCheck,
129 jkParRename
130 };
131
132 PostParChecker m_parChecker;
133 bool m_stopped = false;
134 PostParRenamer m_parRenamer;
135 EJobKind m_currentJob;
136
137 void FindPars(DownloadQueue* downloadQueue, NzbInfo* nzbInfo, const char* parFilename,
138 Blocks& blocks, bool strictParName, bool exactParName, int* blockFound);
139 #endif
140 };
141
142 #endif
3232 while (const char* filename = dir.Next())
3333 {
3434 int baseLen = 0;
35 if (ParseParFilename(filename, &baseLen, nullptr))
35 if (ParseParFilename(filename, true, &baseLen, nullptr))
3636 {
3737 if (!fileList)
3838 {
6161 bool ParParser::SameParCollection(const char* filename1, const char* filename2)
6262 {
6363 int baseLen1 = 0, baseLen2 = 0;
64 return ParseParFilename(filename1, &baseLen1, nullptr) &&
65 ParseParFilename(filename2, &baseLen2, nullptr) &&
64 return ParseParFilename(filename1, false, &baseLen1, nullptr) &&
65 ParseParFilename(filename2, false, &baseLen2, nullptr) &&
6666 baseLen1 == baseLen2 &&
6767 !strncasecmp(filename1, filename2, baseLen1);
6868 }
6969
70 bool ParParser::ParseParFilename(const char* parFilename, int* baseNameLen, int* blocks)
70 bool ParParser::ParseParFilename(const char* parFilename, bool confirmedFilename, int* baseNameLen, int* blocks)
7171 {
7272 BString<1024> filename = parFilename;
7373 for (char* p = filename; *p; p++) *p = tolower(*p); // convert string to lowercase
7878 return false;
7979 }
8080
81 // find last occurence of ".par2" and trim filename after it
82 char* end = filename;
83 while (char* p = strstr(end, ".par2")) end = p + 5;
84 *end = '\0';
81 if (!confirmedFilename)
82 {
83 // find last occurence of ".par2" and trim filename after it
84 char* end = filename;
85 while (char* p = strstr(end, ".par2")) end = p + 5;
86 *end = '\0';
87 }
8588
8689 len = strlen(filename);
8790 if (len < 6)
2929 typedef std::vector<CString> ParFileList;
3030
3131 static bool FindMainPars(const char* path, ParFileList* fileList);
32 static bool ParseParFilename(const char* parFilename, int* baseNameLen, int* blocks);
32 static bool ParseParFilename(const char* parFilename, bool confirmedFilename, int* baseNameLen, int* blocks);
3333 static bool SameParCollection(const char* filename1, const char* filename2);
3434 };
3535
3535 class ParRenamerRepairer : public Par2::Par2Repairer
3636 {
3737 public:
38 ParRenamerRepairer() : Par2::Par2Repairer(m_nout, m_nout) {};
3839 friend class ParRenamer;
40 private:
41 class NullStreamBuf : public std::streambuf {};
42 NullStreamBuf m_nullbuf;
43 std::ostream m_nout{&m_nullbuf};
3944 };
4045
4146
42 void ParRenamer::Cleanup()
43 {
44 m_dirList.clear();
45 m_fileHashList.clear();
46 }
47
48 void ParRenamer::Cancel()
49 {
50 m_cancelled = true;
51 }
52
53 void ParRenamer::Run()
54 {
55 Cleanup();
56 m_cancelled = false;
57 m_fileCount = 0;
58 m_curFile = 0;
59 m_renamedCount = 0;
60 m_hasMissedFiles = false;
61 m_status = psFailed;
62
47 void ParRenamer::Execute()
48 {
6349 m_progressLabel.Format("Checking renamed files for %s", *m_infoName);
6450 m_stageProgress = 0;
6551 UpdateProgress();
7056 {
7157 debug("Checking %s", *destDir);
7258 m_fileHashList.clear();
73 LoadParFiles(destDir);
74
75 if (m_fileHashList.empty())
76 {
77 int savedCurFile = m_curFile;
78 CheckFiles(destDir, true);
79 m_curFile = savedCurFile; // restore progress indicator
80 LoadParFiles(destDir);
59 m_parInfoList.clear();
60 m_badParList.clear();
61 m_loadedParList.clear();
62
63 CheckFiles(destDir, true);
64 RenameParFiles(destDir);
65
66 LoadMainParFiles(destDir);
67 if (m_hasDamagedParFiles)
68 {
69 LoadExtraParFiles(destDir);
8170 }
8271
8372 CheckFiles(destDir, false);
8675 {
8776 CheckMissing();
8877 }
89 }
90
91 if (m_cancelled)
92 {
93 PrintMessage(Message::mkWarning, "Renaming cancelled for %s", *m_infoName);
94 }
95 else if (m_renamedCount > 0)
96 {
97 PrintMessage(Message::mkInfo, "Successfully renamed %i file(s) for %s", m_renamedCount, *m_infoName);
98 m_status = psSuccess;
99 }
100 else
101 {
102 PrintMessage(Message::mkInfo, "No renamed files found for %s", *m_infoName);
103 }
104
105 Cleanup();
106 Completed();
78
79 if (m_renamedCount > 0 && !m_badParList.empty())
80 {
81 RenameBadParFiles();
82 }
83 }
10784 }
10885
10986 void ParRenamer::BuildDirList(const char* destDir)
11491
11592 while (const char* filename = dirBrowser.Next())
11693 {
117 if (!m_cancelled)
94 if (!IsStopped())
11895 {
11996 BString<1024> fullFilename("%s%c%s", destDir, PATH_SEPARATOR, filename);
12097 if (FileSystem::DirectoryExists(fullFilename))
129106 }
130107 }
131108
132 void ParRenamer::LoadParFiles(const char* destDir)
109 void ParRenamer::LoadMainParFiles(const char* destDir)
133110 {
134111 ParParser::ParFileList parFileList;
135112 ParParser::FindMainPars(destDir, &parFileList);
141118 }
142119 }
143120
121 void ParRenamer::LoadExtraParFiles(const char* destDir)
122 {
123 DirBrowser dir(destDir);
124 while (const char* filename = dir.Next())
125 {
126 BString<1024> fullParFilename("%s%c%s", destDir, PATH_SEPARATOR, filename);
127 if (ParParser::ParseParFilename(fullParFilename, true, nullptr, nullptr))
128 {
129 bool knownBadParFile = std::find_if(m_badParList.begin(), m_badParList.end(),
130 [&fullParFilename](CString& filename)
131 {
132 return !strcmp(filename, fullParFilename);
133 }) != m_badParList.end();
134
135 bool loadedParFile = std::find_if(m_loadedParList.begin(), m_loadedParList.end(),
136 [&fullParFilename](CString& filename)
137 {
138 return !strcmp(filename, fullParFilename);
139 }) != m_loadedParList.end();
140
141 if (!knownBadParFile && !loadedParFile)
142 {
143 LoadParFile(fullParFilename);
144 }
145 }
146 }
147 }
148
144149 void ParRenamer::LoadParFile(const char* parFilename)
145150 {
146151 ParRenamerRepairer repairer;
147152
148 if (!repairer.LoadPacketsFromFile(parFilename))
153 if (!repairer.LoadPacketsFromFile(parFilename) || FileSystem::FileSize(parFilename) == 0)
149154 {
150155 PrintMessage(Message::mkWarning, "Could not load par2-file %s", parFilename);
151 return;
152 }
156 m_hasDamagedParFiles = true;
157 m_badParList.emplace_back(parFilename);
158 return;
159 }
160
161 m_loadedParList.emplace_back(parFilename);
162 PrintMessage(Message::mkInfo, "Loaded par2-file %s for par-rename", FileSystem::BaseFileName(parFilename));
153163
154164 for (std::pair<const Par2::MD5Hash, Par2::Par2RepairerSourceFile*>& entry : repairer.sourcefilemap)
155165 {
156 if (m_cancelled)
166 if (IsStopped())
157167 {
158168 break;
159169 }
161171 Par2::Par2RepairerSourceFile* sourceFile = entry.second;
162172 if (!sourceFile || !sourceFile->GetDescriptionPacket())
163173 {
164 PrintMessage(Message::mkWarning, "Damaged par2-file detected: %s", parFilename);
174 PrintMessage(Message::mkWarning, "Damaged par2-file detected: %s", FileSystem::BaseFileName(parFilename));
175 m_badParList.emplace_back(parFilename);
176 m_hasDamagedParFiles = true;
165177 continue;
166178 }
167179 std::string filename = Par2::DiskFile::TranslateFilename(sourceFile->GetDescriptionPacket()->FileName());
168 m_fileHashList.emplace_back(filename.c_str(), sourceFile->GetDescriptionPacket()->Hash16k().print().c_str());
169 RegisterParredFile(filename.c_str());
170 }
171 }
172
173 void ParRenamer::CheckFiles(const char* destDir, bool renamePars)
180 std::string hash = sourceFile->GetDescriptionPacket()->Hash16k().print();
181
182 bool exists = std::find_if(m_fileHashList.begin(), m_fileHashList.end(),
183 [&hash](FileHash& fileHash)
184 {
185 return !strcmp(fileHash.GetHash(), hash.c_str());
186 })
187 != m_fileHashList.end();
188
189 if (!exists)
190 {
191 m_fileHashList.emplace_back(filename.c_str(), hash.c_str());
192 RegisterParredFile(filename.c_str());
193 }
194 }
195 }
196
197 void ParRenamer::CheckFiles(const char* destDir, bool checkPars)
174198 {
175199 DirBrowser dir(destDir);
176200 while (const char* filename = dir.Next())
177201 {
178 if (!m_cancelled)
202 if (!IsStopped())
179203 {
180204 BString<1024> fullFilename("%s%c%s", destDir, PATH_SEPARATOR, filename);
181205
182206 if (!FileSystem::DirectoryExists(fullFilename))
183207 {
184208 m_progressLabel.Format("Checking file %s", filename);
185 m_stageProgress = m_fileCount > 0 ? m_curFile * 1000 / m_fileCount : 1000;
209 m_stageProgress = m_fileCount > 0 ? m_curFile * 1000 / m_fileCount / 2 : 1000;
186210 UpdateProgress();
187211 m_curFile++;
188212
189 if (renamePars)
213 if (checkPars)
190214 {
191215 CheckParFile(destDir, fullFilename);
192216 }
288312 }
289313 }
290314
291 /*
292 * For files not having par2-extensions: checks if the file is a par2-file and renames
293 * it according to its set-id.
294 */
295315 void ParRenamer::CheckParFile(const char* destDir, const char* filename)
296316 {
297317 debug("Checking par2-header for %s", filename);
298
299 const char* basename = FileSystem::BaseFileName(filename);
300 const char* extension = strrchr(basename, '.');
301 if (extension && !strcasecmp(extension, ".par2"))
302 {
303 // do not process files already having par2-extension
304 return;
305 }
306318
307319 DiskFile file;
308320 if (!file.Open(filename, DiskFile::omRead))
336348 BString<100> setId = header.setid.print().c_str();
337349 for (char* p = setId; *p; p++) *p = tolower(*p); // convert string to lowercase
338350
339 debug("Renaming: %s; setid: %s", FileSystem::BaseFileName(filename), *setId);
351 debug("Storing: %s; setid: %s", FileSystem::BaseFileName(filename), *setId);
352
353 m_parInfoList.emplace_back(filename, setId);
354 }
355
356 void ParRenamer::RenameParFiles(const char* destDir)
357 {
358 if (NeedRenameParFiles())
359 {
360 for (ParInfo& parInfo : m_parInfoList)
361 {
362 RenameParFile(destDir, parInfo.GetFilename(), parInfo.GetSetId());
363 }
364 }
365 }
366
367 bool ParRenamer::NeedRenameParFiles()
368 {
369 for (ParInfoList::iterator it1 = m_parInfoList.begin(); it1 != m_parInfoList.end(); it1++)
370 {
371 ParInfo& parInfo1 = *it1;
372
373 const char* baseName1 = FileSystem::BaseFileName(parInfo1.GetFilename());
374
375 const char* extension = strrchr(baseName1, '.');
376 if (!extension || strcasecmp(extension, ".par2"))
377 {
378 // file doesn't have "par2" extension
379 return true;
380 }
381
382 int baseLen1;
383 ParParser::ParseParFilename(baseName1, true, &baseLen1, nullptr);
384
385 for (ParInfoList::iterator it2 = it1 + 1; it2 != m_parInfoList.end(); it2++)
386 {
387 ParInfo& parInfo2 = *it2;
388
389 if (!strcmp(parInfo1.GetSetId(), parInfo2.GetSetId()))
390 {
391 const char* baseName2 = FileSystem::BaseFileName(parInfo2.GetFilename());
392 int baseLen2;
393 ParParser::ParseParFilename(baseName2, true, &baseLen2, nullptr);
394 if (baseLen1 != baseLen2 || strncasecmp(baseName1, baseName2, baseLen1))
395 {
396 // same setid but different base file names
397 return true;
398 }
399 }
400 }
401 }
402
403 return false;
404 }
405
406 void ParRenamer::RenameParFile(const char* destDir, const char* filename, const char* setId)
407 {
408 debug("Renaming: %s; setid: %s", FileSystem::BaseFileName(filename), setId);
340409
341410 BString<1024> destFileName;
342411 int num = 1;
343412 while (num == 1 || FileSystem::FileExists(destFileName))
344413 {
345 destFileName.Format("%s%c%s.vol%03i+01.PAR2", destDir, PATH_SEPARATOR, *setId, num);
414 destFileName.Format("%s%c%s.vol%03i+01.PAR2", destDir, PATH_SEPARATOR, setId, num);
346415 num++;
347416 }
348417
365434 RegisterRenamedFile(FileSystem::BaseFileName(srcFilename), FileSystem::BaseFileName(destFileName));
366435 }
367436
437 void ParRenamer::RenameBadParFiles()
438 {
439 for (CString& parFilename : m_badParList)
440 {
441 BString<1024> destFileName("%s.bad", *parFilename);
442 RenameFile(parFilename, destFileName);
443 }
444 }
445
368446 #endif
2323 #ifndef DISABLE_PARCHECK
2424
2525 #include "NString.h"
26 #include "Thread.h"
2726 #include "Log.h"
2827
29 class ParRenamer : public Thread
28 class ParRenamer
3029 {
3130 public:
32 enum EStatus
33 {
34 psFailed,
35 psSuccess
36 };
37
38 virtual void Run();
31 void Execute();
3932 void SetDestDir(const char* destDir) { m_destDir = destDir; }
4033 const char* GetInfoName() { return m_infoName; }
4134 void SetInfoName(const char* infoName) { m_infoName = infoName; }
42 void SetStatus(EStatus status);
43 EStatus GetStatus() { return m_status; }
44 void Cancel();
45 bool GetCancelled() { return m_cancelled; }
35 int GetRenamedCount() { return m_renamedCount; }
4636 bool HasMissedFiles() { return m_hasMissedFiles; }
37 bool HasDamagedParFiles() { return m_hasDamagedParFiles; }
4738 void SetDetectMissing(bool detectMissing) { m_detectMissing = detectMissing; }
4839
4940 protected:
5041 virtual void UpdateProgress() {}
51 virtual void Completed() {}
42 virtual bool IsStopped() { return false; };
5243 virtual void PrintMessage(Message::EKind kind, const char* format, ...) PRINTF_SYNTAX(3) {}
5344 virtual void RegisterParredFile(const char* filename) {}
5445 virtual void RegisterRenamedFile(const char* oldFilename, const char* newFileName) {}
7162 bool m_fileExists = false;
7263 };
7364
65 class ParInfo
66 {
67 public:
68 ParInfo(const char* filename, const char* setId) :
69 m_filename(filename), m_setId(setId) {}
70 const char* GetFilename() { return m_filename; }
71 const char* GetSetId() { return m_setId; }
72 private:
73 CString m_filename;
74 CString m_setId;
75 };
76
7477 typedef std::deque<FileHash> FileHashList;
75 typedef std::deque<CString> DirList;
78 typedef std::deque<ParInfo> ParInfoList;
79 typedef std::deque<CString> NameList;
7680
7781 CString m_infoName;
7882 CString m_destDir;
79 EStatus m_status;
8083 CString m_progressLabel;
81 int m_stageProgress;
82 bool m_cancelled;
83 DirList m_dirList;
84 int m_stageProgress = 0;
85 NameList m_dirList;
8486 FileHashList m_fileHashList;
85 int m_fileCount;
86 int m_curFile;
87 int m_renamedCount;
88 bool m_hasMissedFiles;
87 ParInfoList m_parInfoList;
88 NameList m_badParList;
89 NameList m_loadedParList;
90 int m_fileCount = 0;
91 int m_curFile = 0;
92 int m_renamedCount = 0;
93 bool m_hasMissedFiles = false;
8994 bool m_detectMissing = false;
95 bool m_hasDamagedParFiles = false;
9096
9197 void BuildDirList(const char* destDir);
92 void CheckDir(const char* destDir);
93 void LoadParFiles(const char* destDir);
98 void LoadMainParFiles(const char* destDir);
99 void LoadExtraParFiles(const char* destDir);
94100 void LoadParFile(const char* parFilename);
95 void CheckFiles(const char* destDir, bool renamePars);
101 void CheckFiles(const char* destDir, bool checkPars);
96102 void CheckRegularFile(const char* destDir, const char* filename);
97103 void CheckParFile(const char* destDir, const char* filename);
98104 bool IsSplittedFragment(const char* filename, const char* correctName);
99105 void CheckMissing();
106 void RenameParFiles(const char* destDir);
107 void RenameParFile(const char* destDir, const char* filename, const char* setId);
108 bool NeedRenameParFiles();
100109 void RenameFile(const char* srcFilename, const char* destFileName);
101 void Cleanup();
110 void RenameBadParFiles();
102111 };
103112
104113 #endif
2828 #include "FileSystem.h"
2929 #include "Unpack.h"
3030 #include "Cleanup.h"
31 #include "Rename.h"
32 #include "Repair.h"
3133 #include "NzbFile.h"
3234 #include "QueueScript.h"
3335 #include "ParParser.h"
3638 {
3739 debug("Creating PrePostProcessor");
3840
39 m_downloadQueueObserver.m_owner = this;
40 DownloadQueue::Guard()->Attach(&m_downloadQueueObserver);
41 DownloadQueue::Guard()->Attach(this);
4142 }
4243
4344 void PrePostProcessor::Run()
5657
5758 while (!IsStopped())
5859 {
59 if (!g_Options->GetTempPausePostprocess())
60 if (!g_Options->GetTempPausePostprocess() && m_queuedJobs)
6061 {
6162 // check post-queue every 200 msec
6263 CheckPostQueue();
6364 }
6465
65 Util::SetStandByMode(!m_curJob);
66
6766 usleep(200 * 1000);
6867 }
6968
69 WaitJobs();
70
7071 debug("Exiting PrePostProcessor-loop");
72 }
73
74 void PrePostProcessor::WaitJobs()
75 {
76 debug("PrePostProcessor: waiting for jobs to complete");
77
78 // wait 5 seconds until all jobs gracefully finish
79 time_t waitStart = Util::CurrentTime();
80 while (Util::CurrentTime() < waitStart + 5)
81 {
82 {
83 GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
84 if (m_activeJobs.empty())
85 {
86 break;
87 }
88 }
89 CheckPostQueue();
90 usleep(200 * 1000);
91 }
92
93 // kill remaining jobs; not safe but we can't wait any longer
94 {
95 GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
96 for (NzbInfo* postJob : m_activeJobs)
97 {
98 if (postJob->GetPostInfo() && postJob->GetPostInfo()->GetPostThread())
99 {
100 Thread* thread = postJob->GetPostInfo()->GetPostThread();
101 postJob->GetPostInfo()->SetPostThread(nullptr);
102 warn("Terminating active post-process job for %s", postJob->GetName());
103 thread->Kill();
104 delete thread;
105 }
106 }
107 }
108
109 debug("PrePostProcessor: Jobs are completed");
71110 }
72111
73112 void PrePostProcessor::Stop()
75114 Thread::Stop();
76115 GuardedDownloadQueue guard = DownloadQueue::Guard();
77116
78 #ifndef DISABLE_PARCHECK
79 m_parCoordinator.Stop();
80 #endif
81
82 if (m_curJob && m_curJob->GetPostInfo() &&
83 (m_curJob->GetPostInfo()->GetStage() == PostInfo::ptUnpacking ||
84 m_curJob->GetPostInfo()->GetStage() == PostInfo::ptExecutingScript) &&
85 m_curJob->GetPostInfo()->GetPostThread())
86 {
87 Thread* postThread = m_curJob->GetPostInfo()->GetPostThread();
88 m_curJob->GetPostInfo()->SetPostThread(nullptr);
89 postThread->SetAutoDestroy(true);
90 postThread->Stop();
91 }
92 }
93
94 void PrePostProcessor::DownloadQueueUpdate(Subject* Caller, void* Aspect)
95 {
96 if (IsStopped())
97 {
98 return;
99 }
100
101 DownloadQueue::Aspect* queueAspect = (DownloadQueue::Aspect*)Aspect;
102 if (queueAspect->action == DownloadQueue::eaNzbFound)
103 {
104 NzbFound(queueAspect->downloadQueue, queueAspect->nzbInfo);
105 }
106 else if (queueAspect->action == DownloadQueue::eaNzbAdded)
107 {
108 NzbAdded(queueAspect->downloadQueue, queueAspect->nzbInfo);
109 }
110 else if (queueAspect->action == DownloadQueue::eaNzbDeleted &&
111 queueAspect->nzbInfo->GetDeleting() &&
112 !queueAspect->nzbInfo->GetPostInfo() &&
113 queueAspect->nzbInfo->GetFileList()->empty())
114 {
115 // the deleting of nzbs is usually handled via eaFileDeleted-event, but when deleting nzb without
116 // any files left the eaFileDeleted-event is not fired and we need to process eaNzbDeleted-event instead
117 queueAspect->nzbInfo->PrintMessage(Message::mkInfo,
118 "Collection %s deleted from queue", queueAspect->nzbInfo->GetName());
119 NzbDeleted(queueAspect->downloadQueue, queueAspect->nzbInfo);
120 }
121 else if ((queueAspect->action == DownloadQueue::eaFileCompleted ||
122 queueAspect->action == DownloadQueue::eaFileDeleted))
123 {
124 if (queueAspect->action == DownloadQueue::eaFileCompleted && !queueAspect->nzbInfo->GetPostInfo())
125 {
126 g_QueueScriptCoordinator->EnqueueScript(queueAspect->nzbInfo, QueueScriptCoordinator::qeFileDownloaded);
127 }
128
129 #ifndef DISABLE_PARCHECK
130 if (m_parCoordinator.AddPar(queueAspect->fileInfo, queueAspect->action == DownloadQueue::eaFileDeleted))
131 {
132 return;
133 }
134 #endif
135
136 if ((queueAspect->action == DownloadQueue::eaFileCompleted ||
137 queueAspect->fileInfo->GetDupeDeleted()) &&
138 queueAspect->fileInfo->GetNzbInfo()->GetDeleteStatus() != NzbInfo::dsHealth &&
139 !queueAspect->nzbInfo->GetPostInfo() &&
140 IsNzbFileCompleted(queueAspect->nzbInfo, true))
141 {
142 queueAspect->nzbInfo->PrintMessage(Message::mkInfo,
143 "Collection %s completely downloaded", queueAspect->nzbInfo->GetName());
144 g_QueueScriptCoordinator->EnqueueScript(queueAspect->nzbInfo, QueueScriptCoordinator::qeNzbDownloaded);
145 NzbDownloaded(queueAspect->downloadQueue, queueAspect->nzbInfo);
146 }
147 else if ((queueAspect->action == DownloadQueue::eaFileDeleted ||
148 (queueAspect->action == DownloadQueue::eaFileCompleted &&
149 queueAspect->fileInfo->GetNzbInfo()->GetDeleteStatus() > NzbInfo::dsNone)) &&
150 !queueAspect->nzbInfo->GetPostInfo() &&
151 IsNzbFileCompleted(queueAspect->nzbInfo, false))
152 {
153 queueAspect->nzbInfo->PrintMessage(Message::mkInfo,
154 "Collection %s deleted from queue", queueAspect->nzbInfo->GetName());
155 NzbDeleted(queueAspect->downloadQueue, queueAspect->nzbInfo);
156 }
157 }
158 }
159
160 void PrePostProcessor::NzbFound(DownloadQueue* downloadQueue, NzbInfo* nzbInfo)
161 {
162 if (g_Options->GetDupeCheck() && nzbInfo->GetDupeMode() != dmForce)
163 {
164 g_DupeCoordinator->NzbFound(downloadQueue, nzbInfo);
165 }
166 }
167
168 void PrePostProcessor::NzbAdded(DownloadQueue* downloadQueue, NzbInfo* nzbInfo)
169 {
170 if (g_Options->GetParCheck() != Options::pcForce)
171 {
172 m_parCoordinator.PausePars(downloadQueue, nzbInfo);
173 }
174
175 if (nzbInfo->GetDeleteStatus() == NzbInfo::dsDupe ||
176 nzbInfo->GetDeleteStatus() == NzbInfo::dsCopy ||
177 nzbInfo->GetDeleteStatus() == NzbInfo::dsGood ||
178 nzbInfo->GetDeleteStatus() == NzbInfo::dsScan)
179 {
180 NzbCompleted(downloadQueue, nzbInfo, false);
181 }
182 else
183 {
184 g_QueueScriptCoordinator->EnqueueScript(nzbInfo, QueueScriptCoordinator::qeNzbAdded);
185 }
186 }
187
188 void PrePostProcessor::NzbDownloaded(DownloadQueue* downloadQueue, NzbInfo* nzbInfo)
189 {
190 if (nzbInfo->GetDeleteStatus() == NzbInfo::dsHealth ||
191 nzbInfo->GetDeleteStatus() == NzbInfo::dsBad)
192 {
193 g_QueueScriptCoordinator->EnqueueScript(nzbInfo, QueueScriptCoordinator::qeNzbDeleted);
194 }
195
196 if (!nzbInfo->GetPostInfo() && g_Options->GetDecode())
197 {
198 nzbInfo->PrintMessage(Message::mkInfo, "Queueing %s for post-processing", nzbInfo->GetName());
199
200 nzbInfo->EnterPostProcess();
201 m_jobCount++;
202
203 if (nzbInfo->GetParStatus() == NzbInfo::psNone &&
204 g_Options->GetParCheck() != Options::pcAlways &&
205 g_Options->GetParCheck() != Options::pcForce)
206 {
207 nzbInfo->SetParStatus(NzbInfo::psSkipped);
208 }
209
210 if (nzbInfo->GetRenameStatus() == NzbInfo::rsNone && !g_Options->GetParRename())
211 {
212 nzbInfo->SetRenameStatus(NzbInfo::rsSkipped);
213 }
214
215 downloadQueue->Save();
216 }
217 else
218 {
219 NzbCompleted(downloadQueue, nzbInfo, true);
220 }
221 }
222
223 void PrePostProcessor::NzbDeleted(DownloadQueue* downloadQueue, NzbInfo* nzbInfo)
224 {
225 if (nzbInfo->GetDeleteStatus() == NzbInfo::dsNone)
226 {
227 nzbInfo->SetDeleteStatus(NzbInfo::dsManual);
228 }
229 nzbInfo->SetDeleting(false);
230
231 DeleteCleanup(nzbInfo);
232
233 if (nzbInfo->GetDeleteStatus() == NzbInfo::dsHealth ||
234 nzbInfo->GetDeleteStatus() == NzbInfo::dsBad)
235 {
236 NzbDownloaded(downloadQueue, nzbInfo);
237 }
238 else
239 {
240 NzbCompleted(downloadQueue, nzbInfo, true);
241 }
242 }
243
244 void PrePostProcessor::NzbCompleted(DownloadQueue* downloadQueue, NzbInfo* nzbInfo, bool saveQueue)
245 {
246 bool addToHistory = g_Options->GetKeepHistory() > 0 && !nzbInfo->GetAvoidHistory();
247 if (addToHistory)
248 {
249 g_HistoryCoordinator->AddToHistory(downloadQueue, nzbInfo);
250 }
251 nzbInfo->SetAvoidHistory(false);
252
253 bool needSave = addToHistory;
254
255 if (g_Options->GetDupeCheck() && nzbInfo->GetDupeMode() != dmForce &&
256 (nzbInfo->GetDeleteStatus() == NzbInfo::dsNone ||
257 nzbInfo->GetDeleteStatus() == NzbInfo::dsHealth ||
258 nzbInfo->GetDeleteStatus() == NzbInfo::dsBad ||
259 nzbInfo->GetDeleteStatus() == NzbInfo::dsScan))
260 {
261 g_DupeCoordinator->NzbCompleted(downloadQueue, nzbInfo);
262 needSave = true;
263 }
264
265 if (nzbInfo->GetDeleteStatus() > NzbInfo::dsNone &&
266 nzbInfo->GetDeleteStatus() != NzbInfo::dsHealth &&
267 nzbInfo->GetDeleteStatus() != NzbInfo::dsBad)
268 // nzbs deleted by health check or marked as bad are processed as downloaded with failure status
269 {
270 g_QueueScriptCoordinator->EnqueueScript(nzbInfo, QueueScriptCoordinator::qeNzbDeleted);
271 }
272
273 if (!addToHistory)
274 {
275 g_HistoryCoordinator->DeleteDiskFiles(nzbInfo);
276 downloadQueue->GetQueue()->Remove(nzbInfo);
277 }
278
279 if (saveQueue && needSave)
280 {
281 downloadQueue->Save();
282 }
283 }
284
285 void PrePostProcessor::DeleteCleanup(NzbInfo* nzbInfo)
286 {
287 if (nzbInfo->GetCleanupDisk() ||
288 nzbInfo->GetDeleteStatus() == NzbInfo::dsDupe)
289 {
290 // download was cancelled, deleting already downloaded files from disk
291 for (CompletedFile& completedFile: nzbInfo->GetCompletedFiles())
292 {
293 BString<1024> fullFileName("%s%c%s", nzbInfo->GetDestDir(), (int)PATH_SEPARATOR, completedFile.GetFileName());
294 if (FileSystem::FileExists(fullFileName))
295 {
296 detail("Deleting file %s", completedFile.GetFileName());
297 FileSystem::DeleteFile(fullFileName);
298 }
299 }
300
301 // delete .out.tmp-files and _brokenlog.txt
302 DirBrowser dir(nzbInfo->GetDestDir());
303 while (const char* filename = dir.Next())
304 {
305 int len = strlen(filename);
306 if ((len > 8 && !strcmp(filename + len - 8, ".out.tmp")) || !strcmp(filename, "_brokenlog.txt"))
307 {
308 BString<1024> fullFilename("%s%c%s", nzbInfo->GetDestDir(), PATH_SEPARATOR, filename);
309 detail("Deleting file %s", filename);
310 FileSystem::DeleteFile(fullFilename);
311 }
312 }
313
314 // delete old directory (if empty)
315 if (FileSystem::DirEmpty(nzbInfo->GetDestDir()))
316 {
317 FileSystem::RemoveDirectory(nzbInfo->GetDestDir());
318 }
319 }
320 }
321
322 void PrePostProcessor::CheckPostQueue()
323 {
324 GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
325
326 if (!m_curJob && m_jobCount > 0)
327 {
328 m_curJob = GetNextJob(downloadQueue);
329 }
330
331 if (m_curJob)
332 {
333 PostInfo* postInfo = m_curJob->GetPostInfo();
334 if (!postInfo->GetWorking() && !IsNzbFileDownloading(m_curJob))
335 {
336 #ifndef DISABLE_PARCHECK
337 if (postInfo->GetRequestParCheck() &&
338 (postInfo->GetNzbInfo()->GetParStatus() <= NzbInfo::psSkipped ||
339 (postInfo->GetForceRepair() && !postInfo->GetNzbInfo()->GetParFull())) &&
340 g_Options->GetParCheck() != Options::pcManual)
341 {
342 postInfo->SetForceParFull(postInfo->GetNzbInfo()->GetParStatus() > NzbInfo::psSkipped);
343 postInfo->GetNzbInfo()->SetParStatus(NzbInfo::psNone);
344 postInfo->SetRequestParCheck(false);
345 postInfo->SetStage(PostInfo::ptQueued);
346 postInfo->GetNzbInfo()->GetScriptStatuses()->clear();
347 DeletePostThread(postInfo);
348 }
349 else if (postInfo->GetRequestParCheck() && postInfo->GetNzbInfo()->GetParStatus() <= NzbInfo::psSkipped &&
350 g_Options->GetParCheck() == Options::pcManual)
351 {
352 postInfo->SetRequestParCheck(false);
353 postInfo->GetNzbInfo()->SetParStatus(NzbInfo::psManual);
354 DeletePostThread(postInfo);
355
356 if (!postInfo->GetNzbInfo()->GetFileList()->empty())
357 {
358 postInfo->GetNzbInfo()->PrintMessage(Message::mkInfo,
359 "Downloading all remaining files for manual par-check for %s", postInfo->GetNzbInfo()->GetName());
360 downloadQueue->EditEntry(postInfo->GetNzbInfo()->GetId(), DownloadQueue::eaGroupResume, 0, nullptr);
361 postInfo->SetStage(PostInfo::ptFinished);
362 }
363 else
364 {
365 postInfo->GetNzbInfo()->PrintMessage(Message::mkInfo,
366 "There are no par-files remain for download for %s", postInfo->GetNzbInfo()->GetName());
367 postInfo->SetStage(PostInfo::ptQueued);
368 }
369 }
370
371 #endif
372 if (postInfo->GetDeleted())
373 {
374 postInfo->SetStage(PostInfo::ptFinished);
375 }
376
377 if (postInfo->GetStage() == PostInfo::ptQueued &&
378 (!g_Options->GetPausePostProcess() || postInfo->GetNzbInfo()->GetForcePriority()))
379 {
380 DeletePostThread(postInfo);
381 StartJob(downloadQueue, postInfo);
382 }
383 else if (postInfo->GetStage() == PostInfo::ptFinished)
384 {
385 UpdatePauseState(false, nullptr);
386 JobCompleted(downloadQueue, postInfo);
387 }
388 else if (!g_Options->GetPausePostProcess())
389 {
390 error("Internal error: invalid state in post-processor");
391 // TODO: cancel (delete) current job
392 }
393 }
394 }
395 }
396
397 NzbInfo* PrePostProcessor::GetNextJob(DownloadQueue* downloadQueue)
398 {
399 NzbInfo* nzbInfo = nullptr;
400
401 for (NzbInfo* nzbInfo1: downloadQueue->GetQueue())
402 {
403 if (nzbInfo1->GetPostInfo() && !g_QueueScriptCoordinator->HasJob(nzbInfo1->GetId(), nullptr) &&
404 (!nzbInfo || nzbInfo1->GetPriority() > nzbInfo->GetPriority()) &&
405 (!g_Options->GetPausePostProcess() || nzbInfo1->GetForcePriority()))
406 {
407 nzbInfo = nzbInfo1;
408 }
409 }
410
411 return nzbInfo;
117 for (NzbInfo* postJob : m_activeJobs)
118 {
119 if (postJob->GetPostInfo() && postJob->GetPostInfo()->GetPostThread())
120 {
121 postJob->GetPostInfo()->GetPostThread()->Stop();
122 }
123 }
412124 }
413125
414126 /**
424136 PostInfo* postInfo = nzbInfo->GetPostInfo();
425137 if (postInfo)
426138 {
427 m_jobCount++;
139 m_queuedJobs++;
428140 if (postInfo->GetStage() == PostInfo::ptExecutingScript ||
429141 !FileSystem::DirectoryExists(nzbInfo->GetDestDir()))
430142 {
434146 {
435147 postInfo->SetStage(PostInfo::ptQueued);
436148 }
437 }
438 }
439 }
440
441 void PrePostProcessor::DeletePostThread(PostInfo* postInfo)
442 {
443 delete postInfo->GetPostThread();
444 postInfo->SetPostThread(nullptr);
445 }
446
447 void PrePostProcessor::StartJob(DownloadQueue* downloadQueue, PostInfo* postInfo)
149 postInfo->SetWorking(false);
150 }
151 }
152 }
153
154 void PrePostProcessor::DownloadQueueUpdate(void* aspect)
155 {
156 if (IsStopped())
157 {
158 return;
159 }
160
161 DownloadQueue::Aspect* queueAspect = (DownloadQueue::Aspect*)aspect;
162 if (queueAspect->action == DownloadQueue::eaNzbFound)
163 {
164 NzbFound(queueAspect->downloadQueue, queueAspect->nzbInfo);
165 }
166 else if (queueAspect->action == DownloadQueue::eaNzbAdded)
167 {
168 NzbAdded(queueAspect->downloadQueue, queueAspect->nzbInfo);
169 }
170 else if (queueAspect->action == DownloadQueue::eaNzbDeleted &&
171 queueAspect->nzbInfo->GetDeleting() &&
172 !queueAspect->nzbInfo->GetPostInfo() &&
173 queueAspect->nzbInfo->GetFileList()->empty())
174 {
175 // the deleting of nzbs is usually handled via eaFileDeleted-event, but when deleting nzb without
176 // any files left the eaFileDeleted-event is not fired and we need to process eaNzbDeleted-event instead
177 queueAspect->nzbInfo->PrintMessage(Message::mkInfo,
178 "Collection %s deleted from queue", queueAspect->nzbInfo->GetName());
179 NzbDeleted(queueAspect->downloadQueue, queueAspect->nzbInfo);
180 }
181 else if ((queueAspect->action == DownloadQueue::eaFileCompleted ||
182 queueAspect->action == DownloadQueue::eaFileDeleted))
183 {
184 if (queueAspect->action == DownloadQueue::eaFileCompleted && !queueAspect->nzbInfo->GetPostInfo())
185 {
186 g_QueueScriptCoordinator->EnqueueScript(queueAspect->nzbInfo, QueueScriptCoordinator::qeFileDownloaded);
187 }
188
189 #ifndef DISABLE_PARCHECK
190 for (NzbInfo* postJob : m_activeJobs)
191 {
192 if (postJob && queueAspect->fileInfo->GetNzbInfo() == postJob &&
193 postJob->GetPostInfo() && postJob->GetPostInfo()->GetPostThread() &&
194 postJob->GetPostInfo()->GetStage() >= PostInfo::ptLoadingPars &&
195 postJob->GetPostInfo()->GetStage() <= PostInfo::ptVerifyingRepaired &&
196 ((RepairController*)postJob->GetPostInfo()->GetPostThread())->AddPar(
197 queueAspect->fileInfo, queueAspect->action == DownloadQueue::eaFileDeleted))
198 {
199 return;
200 }
201 }
202 #endif
203
204 if ((queueAspect->action == DownloadQueue::eaFileCompleted ||
205 queueAspect->fileInfo->GetDupeDeleted()) &&
206 queueAspect->fileInfo->GetNzbInfo()->GetDeleteStatus() != NzbInfo::dsHealth &&
207 !queueAspect->nzbInfo->GetPostInfo() &&
208 IsNzbFileCompleted(queueAspect->nzbInfo, true))
209 {
210 queueAspect->nzbInfo->PrintMessage(Message::mkInfo,
211 "Collection %s completely downloaded", queueAspect->nzbInfo->GetName());
212 g_QueueScriptCoordinator->EnqueueScript(queueAspect->nzbInfo, QueueScriptCoordinator::qeNzbDownloaded);
213 NzbDownloaded(queueAspect->downloadQueue, queueAspect->nzbInfo);
214 }
215 else if ((queueAspect->action == DownloadQueue::eaFileDeleted ||
216 (queueAspect->action == DownloadQueue::eaFileCompleted &&
217 queueAspect->fileInfo->GetNzbInfo()->GetDeleteStatus() > NzbInfo::dsNone)) &&
218 !queueAspect->nzbInfo->GetPostInfo() &&
219 IsNzbFileCompleted(queueAspect->nzbInfo, false))
220 {
221 queueAspect->nzbInfo->PrintMessage(Message::mkInfo,
222 "Collection %s deleted from queue", queueAspect->nzbInfo->GetName());
223 NzbDeleted(queueAspect->downloadQueue, queueAspect->nzbInfo);
224 }
225 }
226 }
227
228 void PrePostProcessor::NzbFound(DownloadQueue* downloadQueue, NzbInfo* nzbInfo)
229 {
230 if (g_Options->GetDupeCheck() && nzbInfo->GetDupeMode() != dmForce)
231 {
232 g_DupeCoordinator->NzbFound(downloadQueue, nzbInfo);
233 }
234 }
235
236 void PrePostProcessor::NzbAdded(DownloadQueue* downloadQueue, NzbInfo* nzbInfo)
237 {
238 if (g_Options->GetParCheck() != Options::pcForce)
239 {
240 downloadQueue->EditEntry(nzbInfo->GetId(),
241 DownloadQueue::eaGroupPauseExtraPars, nullptr);
242 }
243
244 if (nzbInfo->GetDeleteStatus() == NzbInfo::dsDupe ||
245 nzbInfo->GetDeleteStatus() == NzbInfo::dsCopy ||
246 nzbInfo->GetDeleteStatus() == NzbInfo::dsGood ||
247 nzbInfo->GetDeleteStatus() == NzbInfo::dsScan)
248 {
249 NzbCompleted(downloadQueue, nzbInfo, false);
250 }
251 else
252 {
253 g_QueueScriptCoordinator->EnqueueScript(nzbInfo, QueueScriptCoordinator::qeNzbAdded);
254 }
255 }
256
257 void PrePostProcessor::NzbDownloaded(DownloadQueue* downloadQueue, NzbInfo* nzbInfo)
258 {
259 if (nzbInfo->GetDeleteStatus() == NzbInfo::dsHealth ||
260 nzbInfo->GetDeleteStatus() == NzbInfo::dsBad)
261 {
262 g_QueueScriptCoordinator->EnqueueScript(nzbInfo, QueueScriptCoordinator::qeNzbDeleted);
263 }
264
265 if (!nzbInfo->GetPostInfo() && g_Options->GetDecode())
266 {
267 nzbInfo->PrintMessage(Message::mkInfo, "Queueing %s for post-processing", nzbInfo->GetName());
268
269 nzbInfo->EnterPostProcess();
270 m_queuedJobs++;
271
272 if (nzbInfo->GetParStatus() == NzbInfo::psNone &&
273 g_Options->GetParCheck() != Options::pcAlways &&
274 g_Options->GetParCheck() != Options::pcForce)
275 {
276 nzbInfo->SetParStatus(NzbInfo::psSkipped);
277 }
278
279 downloadQueue->Save();
280 }
281 else
282 {
283 NzbCompleted(downloadQueue, nzbInfo, true);
284 }
285 }
286
287 void PrePostProcessor::NzbDeleted(DownloadQueue* downloadQueue, NzbInfo* nzbInfo)
288 {
289 if (nzbInfo->GetDeleteStatus() == NzbInfo::dsNone)
290 {
291 nzbInfo->SetDeleteStatus(NzbInfo::dsManual);
292 }
293 nzbInfo->SetDeleting(false);
294
295 DeleteCleanup(nzbInfo);
296
297 if (nzbInfo->GetDeleteStatus() == NzbInfo::dsHealth ||
298 nzbInfo->GetDeleteStatus() == NzbInfo::dsBad)
299 {
300 NzbDownloaded(downloadQueue, nzbInfo);
301 }
302 else
303 {
304 NzbCompleted(downloadQueue, nzbInfo, true);
305 }
306 }
307
308 void PrePostProcessor::NzbCompleted(DownloadQueue* downloadQueue, NzbInfo* nzbInfo, bool saveQueue)
309 {
310 bool addToHistory = g_Options->GetKeepHistory() > 0 && !nzbInfo->GetAvoidHistory();
311 if (addToHistory)
312 {
313 g_HistoryCoordinator->AddToHistory(downloadQueue, nzbInfo);
314 }
315 nzbInfo->SetAvoidHistory(false);
316
317 bool needSave = addToHistory;
318
319 if (g_Options->GetDupeCheck() && nzbInfo->GetDupeMode() != dmForce &&
320 (nzbInfo->GetDeleteStatus() == NzbInfo::dsNone ||
321 nzbInfo->GetDeleteStatus() == NzbInfo::dsHealth ||
322 nzbInfo->GetDeleteStatus() == NzbInfo::dsBad ||
323 nzbInfo->GetDeleteStatus() == NzbInfo::dsScan))
324 {
325 g_DupeCoordinator->NzbCompleted(downloadQueue, nzbInfo);
326 needSave = true;
327 }
328
329 if (nzbInfo->GetDeleteStatus() > NzbInfo::dsNone &&
330 nzbInfo->GetDeleteStatus() != NzbInfo::dsHealth &&
331 nzbInfo->GetDeleteStatus() != NzbInfo::dsBad)
332 // nzbs deleted by health check or marked as bad are processed as downloaded with failure status
333 {
334 g_QueueScriptCoordinator->EnqueueScript(nzbInfo, QueueScriptCoordinator::qeNzbDeleted);
335 }
336
337 if (!addToHistory)
338 {
339 g_HistoryCoordinator->DeleteDiskFiles(nzbInfo);
340 downloadQueue->GetQueue()->Remove(nzbInfo);
341 }
342
343 if (saveQueue && needSave)
344 {
345 downloadQueue->Save();
346 }
347 }
348
349 void PrePostProcessor::DeleteCleanup(NzbInfo* nzbInfo)
350 {
351 if (nzbInfo->GetCleanupDisk() ||
352 nzbInfo->GetDeleteStatus() == NzbInfo::dsDupe)
353 {
354 // download was cancelled, deleting already downloaded files from disk
355 for (CompletedFile& completedFile: nzbInfo->GetCompletedFiles())
356 {
357 BString<1024> fullFileName("%s%c%s", nzbInfo->GetDestDir(), (int)PATH_SEPARATOR, completedFile.GetFileName());
358 if (FileSystem::FileExists(fullFileName))
359 {
360 detail("Deleting file %s", completedFile.GetFileName());
361 FileSystem::DeleteFile(fullFileName);
362 }
363 }
364
365 // delete .out.tmp-files and _brokenlog.txt
366 DirBrowser dir(nzbInfo->GetDestDir());
367 while (const char* filename = dir.Next())
368 {
369 int len = strlen(filename);
370 if ((len > 8 && !strcmp(filename + len - 8, ".out.tmp")) || !strcmp(filename, "_brokenlog.txt"))
371 {
372 BString<1024> fullFilename("%s%c%s", nzbInfo->GetDestDir(), PATH_SEPARATOR, filename);
373 detail("Deleting file %s", filename);
374 FileSystem::DeleteFile(fullFilename);
375 }
376 }
377
378 // delete old directory (if empty)
379 if (FileSystem::DirEmpty(nzbInfo->GetDestDir()))
380 {
381 FileSystem::RemoveDirectory(nzbInfo->GetDestDir());
382 }
383 }
384 }
385
386 void PrePostProcessor::CheckRequestPar(DownloadQueue* downloadQueue)
387 {
388 #ifndef DISABLE_PARCHECK
389 for (NzbInfo* postJob : m_activeJobs)
390 {
391 PostInfo* postInfo = postJob->GetPostInfo();
392
393 if (postInfo->GetRequestParCheck() &&
394 (postInfo->GetNzbInfo()->GetParStatus() <= NzbInfo::psSkipped ||
395 (postInfo->GetForceRepair() && !postInfo->GetNzbInfo()->GetParFull())) &&
396 g_Options->GetParCheck() != Options::pcManual)
397 {
398 postInfo->SetForceParFull(postInfo->GetNzbInfo()->GetParStatus() > NzbInfo::psSkipped);
399 postInfo->GetNzbInfo()->SetParStatus(NzbInfo::psNone);
400 postInfo->SetRequestParCheck(false);
401 postInfo->GetNzbInfo()->GetScriptStatuses()->clear();
402 postInfo->SetWorking(false);
403 }
404 else if (postInfo->GetRequestParCheck() &&
405 postInfo->GetNzbInfo()->GetParStatus() <= NzbInfo::psSkipped &&
406 g_Options->GetParCheck() == Options::pcManual)
407 {
408 postInfo->SetRequestParCheck(false);
409 postInfo->GetNzbInfo()->SetParStatus(NzbInfo::psManual);
410
411 if (!postInfo->GetNzbInfo()->GetFileList()->empty())
412 {
413 postInfo->GetNzbInfo()->PrintMessage(Message::mkInfo,
414 "Downloading all remaining files for manual par-check for %s", postInfo->GetNzbInfo()->GetName());
415 downloadQueue->EditEntry(postInfo->GetNzbInfo()->GetId(), DownloadQueue::eaGroupResume, nullptr);
416 postInfo->SetStage(PostInfo::ptFinished);
417 }
418 else
419 {
420 postInfo->GetNzbInfo()->PrintMessage(Message::mkInfo,
421 "There are no par-files remain for download for %s", postInfo->GetNzbInfo()->GetName());
422 }
423 postInfo->SetWorking(false);
424 }
425 }
426 #endif
427 }
428
429 void PrePostProcessor::CleanupJobs(DownloadQueue* downloadQueue)
430 {
431 m_activeJobs.erase(std::remove_if(m_activeJobs.begin(), m_activeJobs.end(),
432 [processor = this, downloadQueue](NzbInfo* postJob)
433 {
434 PostInfo* postInfo = postJob->GetPostInfo();
435 if (!postInfo->GetWorking())
436 {
437 delete postInfo->GetPostThread();
438 postInfo->SetPostThread(nullptr);
439
440 postInfo->SetStageTime(0);
441 postInfo->SetStageProgress(0);
442 postInfo->SetFileProgress(0);
443 postInfo->SetProgressLabel("");
444
445 if (postInfo->GetStartTime() > 0)
446 {
447 postJob->SetPostTotalSec(postJob->GetPostTotalSec() +
448 (int)(Util::CurrentTime() - postInfo->GetStartTime()));
449 postInfo->SetStartTime(0);
450 }
451
452 if (postInfo->GetStage() == PostInfo::ptFinished || postInfo->GetDeleted())
453 {
454 processor->JobCompleted(downloadQueue, postInfo);
455 }
456 else
457 {
458 postInfo->SetStage(PostInfo::ptQueued);
459 }
460 return true;
461 }
462 return false;
463 }),
464 m_activeJobs.end());
465 }
466
467 bool PrePostProcessor::CanRunMoreJobs(bool* allowPar)
468 {
469 int totalJobs = (int)m_activeJobs.size();
470 int parJobs = 0;
471 int otherJobs = 0;
472 bool repairJobs = false;
473
474 for (NzbInfo* postJob : m_activeJobs)
475 {
476 bool parJob = postJob->GetPostInfo()->GetStage() >= PostInfo::ptLoadingPars &&
477 postJob->GetPostInfo()->GetStage() <= PostInfo::ptVerifyingRepaired;
478 repairJobs |= postJob->GetPostInfo()->GetStage() == PostInfo::ptRepairing;
479 parJobs += parJob ? 1 : 0;
480 otherJobs += parJob ? 0 : 1;
481 }
482
483 switch (g_Options->GetPostStrategy())
484 {
485 case Options::ppSequential:
486 *allowPar = true;
487 return totalJobs == 0;
488
489 case Options::ppBalanced:
490 *allowPar = parJobs == 0;
491 return otherJobs == 0 && (parJobs == 0 || repairJobs);
492
493 case Options::ppAggressive:
494 *allowPar = parJobs < 1;
495 return totalJobs < 3;
496
497 case Options::ppRocket:
498 *allowPar = parJobs < 2;
499 return totalJobs < 6;
500 }
501
502 return false;
503 }
504
505 NzbInfo* PrePostProcessor::PickNextJob(DownloadQueue* downloadQueue, bool allowPar)
506 {
507 NzbInfo* nzbInfo = nullptr;
508
509 for (NzbInfo* nzbInfo1: downloadQueue->GetQueue())
510 {
511 if (nzbInfo1->GetPostInfo() && !nzbInfo1->GetPostInfo()->GetWorking() &&
512 !g_QueueScriptCoordinator->HasJob(nzbInfo1->GetId(), nullptr) &&
513 (!nzbInfo || nzbInfo1->GetPriority() > nzbInfo->GetPriority()) &&
514 (!g_Options->GetPausePostProcess() || nzbInfo1->GetForcePriority()) &&
515 (allowPar || !nzbInfo1->GetPostInfo()->GetNeedParCheck()) &&
516 (std::find(m_activeJobs.begin(), m_activeJobs.end(), nzbInfo1) == m_activeJobs.end()) &&
517 IsNzbFileCompleted(nzbInfo1, true))
518 {
519 nzbInfo = nzbInfo1;
520 }
521 }
522
523 return nzbInfo;
524 }
525
526 void PrePostProcessor::CheckPostQueue()
527 {
528 GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
529
530 size_t countBefore = m_activeJobs.size();
531 CheckRequestPar(downloadQueue);
532 CleanupJobs(downloadQueue);
533 bool changed = m_activeJobs.size() != countBefore;
534
535 bool allowPar;
536 while (CanRunMoreJobs(&allowPar) && !IsStopped())
537 {
538 NzbInfo* postJob = PickNextJob(downloadQueue, allowPar);
539 if (!postJob)
540 {
541 break;
542 }
543
544 m_activeJobs.push_back(postJob);
545
546 PostInfo* postInfo = postJob->GetPostInfo();
547 if (postInfo->GetStage() == PostInfo::ptQueued &&
548 (!g_Options->GetPausePostProcess() || postInfo->GetNzbInfo()->GetForcePriority()))
549 {
550 StartJob(downloadQueue, postInfo, allowPar);
551 CheckRequestPar(downloadQueue);
552 CleanupJobs(downloadQueue);
553 changed = true;
554 }
555 }
556
557 if (changed)
558 {
559 downloadQueue->Save();
560 UpdatePauseState();
561 }
562
563 Util::SetStandByMode(m_activeJobs.empty());
564 }
565
566 void PrePostProcessor::StartJob(DownloadQueue* downloadQueue, PostInfo* postInfo, bool allowPar)
448567 {
449568 if (!postInfo->GetStartTime())
450569 {
451570 postInfo->SetStartTime(Util::CurrentTime());
452571 }
572 postInfo->SetStageTime(Util::CurrentTime());
573 postInfo->SetStageProgress(0);
574 postInfo->SetFileProgress(0);
575 postInfo->SetProgressLabel("");
576
577 if (postInfo->GetNzbInfo()->GetParRenameStatus() == NzbInfo::rsNone &&
578 postInfo->GetNzbInfo()->GetDeleteStatus() == NzbInfo::dsNone &&
579 g_Options->GetParRename())
580 {
581 EnterStage(downloadQueue, postInfo, PostInfo::ptParRenaming);
582 RenameController::StartJob(postInfo, RenameController::jkPar);
583 return;
584 }
453585
454586 #ifndef DISABLE_PARCHECK
455 if (postInfo->GetNzbInfo()->GetRenameStatus() == NzbInfo::rsNone &&
587 if (postInfo->GetNzbInfo()->GetParStatus() == NzbInfo::psNone &&
456588 postInfo->GetNzbInfo()->GetDeleteStatus() == NzbInfo::dsNone)
457589 {
458 UpdatePauseState(g_Options->GetParPauseQueue(), "par-rename");
459 m_parCoordinator.StartParRenameJob(postInfo);
460 return;
461 }
462 else if (postInfo->GetNzbInfo()->GetParStatus() == NzbInfo::psNone &&
463 postInfo->GetNzbInfo()->GetDeleteStatus() == NzbInfo::dsNone)
464 {
465590 if (ParParser::FindMainPars(postInfo->GetNzbInfo()->GetDestDir(), nullptr))
466591 {
467 UpdatePauseState(g_Options->GetParPauseQueue(), "par-check");
468 m_parCoordinator.StartParCheckJob(postInfo);
592 if (!allowPar)
593 {
594 postInfo->SetNeedParCheck(true);
595 return;
596 }
597
598 EnterStage(downloadQueue, postInfo, PostInfo::ptLoadingPars);
599 postInfo->SetNeedParCheck(false);
600 RepairController::StartJob(postInfo);
469601 }
470602 else
471603 {
472604 postInfo->GetNzbInfo()->PrintMessage(Message::mkInfo,
473605 "Nothing to par-check for %s", postInfo->GetNzbInfo()->GetName());
474606 postInfo->GetNzbInfo()->SetParStatus(NzbInfo::psSkipped);
475 postInfo->SetWorking(false);
476 postInfo->SetStage(PostInfo::ptQueued);
477607 }
478608 return;
479609 }
480 else if (postInfo->GetNzbInfo()->GetParStatus() == NzbInfo::psSkipped &&
610
611 if (postInfo->GetNzbInfo()->GetParStatus() == NzbInfo::psSkipped &&
481612 ((g_Options->GetParScan() != Options::psDupe &&
482613 postInfo->GetNzbInfo()->CalcHealth() < postInfo->GetNzbInfo()->CalcCriticalHealth(false) &&
483614 postInfo->GetNzbInfo()->CalcCriticalHealth(false) < 1000) ||
499630 postInfo->GetNzbInfo()->SetParStatus(NzbInfo::psFailure);
500631 return;
501632 }
502 else if (postInfo->GetNzbInfo()->GetParStatus() == NzbInfo::psSkipped &&
633
634 if (postInfo->GetNzbInfo()->GetParStatus() == NzbInfo::psSkipped &&
503635 postInfo->GetNzbInfo()->GetFailedSize() - postInfo->GetNzbInfo()->GetParFailedSize() > 0 &&
504636 ParParser::FindMainPars(postInfo->GetNzbInfo()->GetDestDir(), nullptr))
505637 {
512644 #endif
513645
514646 NzbParameter* unpackParameter = postInfo->GetNzbInfo()->GetParameters()->Find("*Unpack:", false);
515 bool unpackParam = !(unpackParameter && !strcasecmp(unpackParameter->GetValue(), "no"));
516 bool unpack = unpackParam && postInfo->GetNzbInfo()->GetUnpackStatus() == NzbInfo::usNone &&
647 bool wantUnpack = !(unpackParameter && !strcasecmp(unpackParameter->GetValue(), "no"));
648 bool unpack = wantUnpack && postInfo->GetNzbInfo()->GetUnpackStatus() == NzbInfo::usNone &&
517649 postInfo->GetNzbInfo()->GetDeleteStatus() == NzbInfo::dsNone;
650
651 if (postInfo->GetNzbInfo()->GetRarRenameStatus() == NzbInfo::rsNone &&
652 unpack && g_Options->GetRarRename())
653 {
654 EnterStage(downloadQueue, postInfo, PostInfo::ptRarRenaming);
655 RenameController::StartJob(postInfo, RenameController::jkRar);
656 return;
657 }
518658
519659 bool parFailed = postInfo->GetNzbInfo()->GetParStatus() == NzbInfo::psFailure ||
520660 postInfo->GetNzbInfo()->GetParStatus() == NzbInfo::psRepairPossible ||
521661 postInfo->GetNzbInfo()->GetParStatus() == NzbInfo::psManual;
522662
523 bool cleanup = !unpack &&
663 bool cleanup = !unpack && wantUnpack &&
524664 postInfo->GetNzbInfo()->GetCleanupStatus() == NzbInfo::csNone &&
525665 !Util::EmptyStr(g_Options->GetExtCleanupDisk()) &&
526666 ((postInfo->GetNzbInfo()->GetParStatus() == NzbInfo::psSuccess &&
547687 !strncmp(postInfo->GetNzbInfo()->GetDestDir(), g_Options->GetInterDir(), strlen(g_Options->GetInterDir())) &&
548688 postInfo->GetNzbInfo()->GetDestDir()[strlen(g_Options->GetInterDir())] == PATH_SEPARATOR;
549689
550 bool postScript = true;
551
552690 if (unpack && parFailed)
553691 {
554692 postInfo->GetNzbInfo()->PrintMessage(Message::mkWarning,
558696 unpack = false;
559697 }
560698
561 if (!unpack && !moveInter && !postScript)
562 {
563 postInfo->SetStage(PostInfo::ptFinished);
564 return;
565 }
566
567 postInfo->SetProgressLabel(unpack ? "Unpacking" : moveInter ? "Moving" : "Executing post-process-script");
699 if (unpack)
700 {
701 EnterStage(downloadQueue, postInfo, PostInfo::ptUnpacking);
702 UnpackController::StartJob(postInfo);
703 }
704 else if (cleanup)
705 {
706 EnterStage(downloadQueue, postInfo, PostInfo::ptCleaningUp);
707 CleanupController::StartJob(postInfo);
708 }
709 else if (moveInter)
710 {
711 EnterStage(downloadQueue, postInfo, PostInfo::ptMoving);
712 MoveController::StartJob(postInfo);
713 }
714 else
715 {
716 EnterStage(downloadQueue, postInfo, PostInfo::ptExecutingScript);
717 PostScriptController::StartJob(postInfo);
718 }
719 }
720
721 void PrePostProcessor::EnterStage(DownloadQueue* downloadQueue, PostInfo* postInfo, PostInfo::EStage stage)
722 {
568723 postInfo->SetWorking(true);
569 postInfo->SetStage(unpack ? PostInfo::ptUnpacking : moveInter ? PostInfo::ptMoving : PostInfo::ptExecutingScript);
570 postInfo->SetFileProgress(0);
571 postInfo->SetStageProgress(0);
572
573 downloadQueue->Save();
574
575 postInfo->SetStageTime(Util::CurrentTime());
576
577 if (unpack)
578 {
579 UpdatePauseState(g_Options->GetUnpackPauseQueue(), "unpack");
580 UnpackController::StartJob(postInfo);
581 }
582 else if (cleanup)
583 {
584 UpdatePauseState(g_Options->GetUnpackPauseQueue() || g_Options->GetScriptPauseQueue(), "cleanup");
585 CleanupController::StartJob(postInfo);
586 }
587 else if (moveInter)
588 {
589 UpdatePauseState(g_Options->GetUnpackPauseQueue() || g_Options->GetScriptPauseQueue(), "move");
590 MoveController::StartJob(postInfo);
591 }
592 else
593 {
594 UpdatePauseState(g_Options->GetScriptPauseQueue(), "post-process-script");
595 PostScriptController::StartJob(postInfo);
596 }
724 postInfo->SetStage(stage);
597725 }
598726
599727 void PrePostProcessor::JobCompleted(DownloadQueue* downloadQueue, PostInfo* postInfo)
600728 {
601729 NzbInfo* nzbInfo = postInfo->GetNzbInfo();
602730
603 if (postInfo->GetStartTime() > 0)
604 {
605 nzbInfo->SetPostTotalSec((int)(Util::CurrentTime() - postInfo->GetStartTime()));
606 postInfo->SetStartTime(0);
607 }
608
609 DeletePostThread(postInfo);
610731 nzbInfo->LeavePostProcess();
611732
612733 if (IsNzbFileCompleted(nzbInfo, true))
614735 NzbCompleted(downloadQueue, nzbInfo, false);
615736 }
616737
617 if (nzbInfo == m_curJob)
618 {
619 m_curJob = nullptr;
620 }
621 m_jobCount--;
622
623 downloadQueue->Save();
738 m_queuedJobs--;
624739 }
625740
626741 bool PrePostProcessor::IsNzbFileCompleted(NzbInfo* nzbInfo, bool ignorePausedPars)
642757 return true;
643758 }
644759
645 bool PrePostProcessor::IsNzbFileDownloading(NzbInfo* nzbInfo)
646 {
647 if (nzbInfo->GetActiveDownloads())
648 {
649 return true;
650 }
651
652 for (FileInfo* fileInfo : nzbInfo->GetFileList())
653 {
654 if (!fileInfo->GetPaused())
655 {
656 return true;
657 }
658 }
659
660 return false;
661 }
662
663 void PrePostProcessor::UpdatePauseState(bool needPause, const char* reason)
664 {
760 void PrePostProcessor::UpdatePauseState()
761 {
762 bool needPause = false;
763 for (NzbInfo* postJob : m_activeJobs)
764 {
765 switch (postJob->GetPostInfo()->GetStage())
766 {
767 case PostInfo::ptLoadingPars:
768 case PostInfo::ptVerifyingSources:
769 case PostInfo::ptRepairing:
770 case PostInfo::ptVerifyingRepaired:
771 case PostInfo::ptParRenaming:
772 needPause |= g_Options->GetParPauseQueue();
773 break;
774
775 case PostInfo::ptRarRenaming:
776 case PostInfo::ptUnpacking:
777 case PostInfo::ptCleaningUp:
778 case PostInfo::ptMoving:
779 needPause |= g_Options->GetUnpackPauseQueue();
780 break;
781
782 case PostInfo::ptExecutingScript:
783 needPause |= g_Options->GetScriptPauseQueue();
784 break;
785
786 case PostInfo::ptQueued:
787 case PostInfo::ptFinished:
788 break;
789 }
790 }
791
665792 if (needPause && !g_Options->GetTempPauseDownload())
666793 {
667 info("Pausing download before %s", reason);
794 info("Pausing download before post-processing");
668795 }
669796 else if (!needPause && g_Options->GetTempPauseDownload())
670797 {
671 info("Unpausing download after %s", m_pauseReason);
672 }
798 info("Unpausing download after post-processing");
799 }
800
673801 g_Options->SetTempPauseDownload(needPause);
674 m_pauseReason = reason;
675 }
676
677 bool PrePostProcessor::EditList(DownloadQueue* downloadQueue, IdList* idList, DownloadQueue::EEditAction action, int offset, const char* text)
802 }
803
804 bool PrePostProcessor::EditList(DownloadQueue* downloadQueue, IdList* idList,
805 DownloadQueue::EEditAction action, const char* args)
678806 {
679807 debug("Edit-command for post-processor received");
680808 switch (action)
703831 postInfo->GetNzbInfo()->PrintMessage(Message::mkInfo,
704832 "Deleting active post-job %s", postInfo->GetNzbInfo()->GetName());
705833 postInfo->SetDeleted(true);
706 #ifndef DISABLE_PARCHECK
707 if (PostInfo::ptLoadingPars <= postInfo->GetStage() && postInfo->GetStage() <= PostInfo::ptRenaming)
708 {
709 if (m_parCoordinator.Cancel())
710 {
711 ok = true;
712 }
713 }
714 else
715 #endif
716834 if (postInfo->GetPostThread())
717835 {
718 debug("Terminating %s for %s", (postInfo->GetStage() == PostInfo::ptUnpacking ? "unpack" : "post-process-script"), postInfo->GetNzbInfo()->GetName());
836 debug("Terminating post-process thread for %s", postInfo->GetNzbInfo()->GetName());
719837 postInfo->GetPostThread()->Stop();
720838 ok = true;
721839 }
729847 postInfo->GetNzbInfo()->PrintMessage(Message::mkInfo,
730848 "Deleting queued post-job %s", postInfo->GetNzbInfo()->GetName());
731849 JobCompleted(downloadQueue, postInfo);
850
851 m_activeJobs.erase(std::remove_if(m_activeJobs.begin(), m_activeJobs.end(),
852 [postInfo](NzbInfo* postJob)
853 {
854 return postInfo == postJob->GetPostInfo();
855 }),
856 m_activeJobs.end());
857
732858 ok = true;
733859 }
734860 break;
736862 }
737863 }
738864
865 if (ok)
866 {
867 downloadQueue->Save();
868 }
869
739870 return ok;
740871 }
2323 #include "Thread.h"
2424 #include "Observer.h"
2525 #include "DownloadInfo.h"
26 #include "ParCoordinator.h"
2726
28 class PrePostProcessor : public Thread
27 class PrePostProcessor : public Thread, public Observer
2928 {
3029 public:
3130 PrePostProcessor();
3231 virtual void Run();
3332 virtual void Stop();
34 bool HasMoreJobs() { return m_jobCount > 0; }
35 int GetJobCount() { return m_jobCount; }
33 bool HasMoreJobs() { return m_queuedJobs > 0; }
34 int GetJobCount() { return m_queuedJobs; }
3635 bool EditList(DownloadQueue* downloadQueue, IdList* idList, DownloadQueue::EEditAction action,
37 int offset, const char* text);
36 const char* args);
3837 void NzbAdded(DownloadQueue* downloadQueue, NzbInfo* nzbInfo);
3938 void NzbDownloaded(DownloadQueue* downloadQueue, NzbInfo* nzbInfo);
4039
40 protected:
41 virtual void Update(Subject* caller, void* aspect) { DownloadQueueUpdate(aspect); }
42
4143 private:
42 class DownloadQueueObserver: public Observer
43 {
44 public:
45 PrePostProcessor* m_owner;
46 virtual void Update(Subject* Caller, void* Aspect) { m_owner->DownloadQueueUpdate(Caller, Aspect); }
47 };
44 int m_queuedJobs = 0;
45 RawNzbList m_activeJobs;
4846
49 ParCoordinator m_parCoordinator;
50 DownloadQueueObserver m_downloadQueueObserver;
51 int m_jobCount = 0;
52 NzbInfo* m_curJob = nullptr;
53 const char* m_pauseReason = nullptr;
54
55 bool IsNzbFileCompleted(NzbInfo* nzbInfo, bool ignorePausedPars);
56 bool IsNzbFileDownloading(NzbInfo* nzbInfo);
5747 void CheckPostQueue();
58 void JobCompleted(DownloadQueue* downloadQueue, PostInfo* postInfo);
59 void StartJob(DownloadQueue* downloadQueue, PostInfo* postInfo);
48 void CheckRequestPar(DownloadQueue* downloadQueue);
49 void CleanupJobs(DownloadQueue* downloadQueue);
50 bool CanRunMoreJobs(bool* allowPar);
51 NzbInfo* PickNextJob(DownloadQueue* downloadQueue, bool allowPar);
52 void StartJob(DownloadQueue* downloadQueue, PostInfo* postInfo, bool allowPar);
53 void EnterStage(DownloadQueue* downloadQueue, PostInfo* postInfo, PostInfo::EStage stage);
6054 void SanitisePostQueue();
61 void UpdatePauseState(bool needPause, const char* reason);
55 void UpdatePauseState();
6256 void NzbFound(DownloadQueue* downloadQueue, NzbInfo* nzbInfo);
6357 void NzbDeleted(DownloadQueue* downloadQueue, NzbInfo* nzbInfo);
6458 void NzbCompleted(DownloadQueue* downloadQueue, NzbInfo* nzbInfo, bool saveQueue);
59 void JobCompleted(DownloadQueue* downloadQueue, PostInfo* postInfo);
6560 bool PostQueueDelete(DownloadQueue* downloadQueue, IdList* idList);
66 void DeletePostThread(PostInfo* postInfo);
67 NzbInfo* GetNextJob(DownloadQueue* downloadQueue);
68 void DownloadQueueUpdate(Subject* Caller, void* Aspect);
61 void DownloadQueueUpdate(void* aspect);
6962 void DeleteCleanup(NzbInfo* nzbInfo);
63 bool IsNzbFileCompleted(NzbInfo* nzbInfo, bool ignorePausedPars);
64 void WaitJobs();
7065 };
7166
7267 extern PrePostProcessor* g_PrePostProcessor;
0 /*
1 * This file is part of nzbget. See <http://nzbget.net>.
2 *
3 * Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19
20 #include "nzbget.h"
21
22 #include "RarReader.h"
23 #include "Log.h"
24 #include "Util.h"
25 #include "FileSystem.h"
26
27 // RAR3 constants
28
29 static const uint16 RAR3_MAIN_VOLUME = 0x0001;
30 static const uint16 RAR3_MAIN_NEWNUMBERING = 0x0010;
31 static const uint16 RAR3_MAIN_PASSWORD = 0x0080;
32
33 static const uint8 RAR3_BLOCK_MAIN = 0x73; // s
34 static const uint8 RAR3_BLOCK_FILE = 0x74; // t
35 static const uint8 RAR3_BLOCK_ENDARC = 0x7b; // {
36
37 static const uint16 RAR3_BLOCK_ADDSIZE = 0x8000;
38
39 static const uint16 RAR3_FILE_ADDSIZE = 0x0100;
40 static const uint16 RAR3_FILE_SPLITBEFORE = 0x0001;
41 static const uint16 RAR3_FILE_SPLITAFTER = 0x0002;
42
43 static const uint16 RAR3_ENDARC_NEXTVOL = 0x0001;
44 static const uint16 RAR3_ENDARC_DATACRC = 0x0002;
45 static const uint16 RAR3_ENDARC_VOLNUMBER = 0x0008;
46
47 // RAR5 constants
48
49 static const uint8 RAR5_BLOCK_MAIN = 1;
50 static const uint8 RAR5_BLOCK_FILE = 2;
51 static const uint8 RAR5_BLOCK_ENCRYPTION = 4;
52 static const uint8 RAR5_BLOCK_ENDARC = 5;
53
54 static const uint8 RAR5_BLOCK_EXTRADATA = 0x01;
55 static const uint8 RAR5_BLOCK_DATAAREA = 0x02;
56 static const uint8 RAR5_BLOCK_SPLITBEFORE = 0x08;
57 static const uint8 RAR5_BLOCK_SPLITAFTER = 0x10;
58
59 static const uint8 RAR5_MAIN_ISVOL = 0x01;
60 static const uint8 RAR5_MAIN_VOLNR = 0x02;
61
62 static const uint8 RAR5_FILE_TIME = 0x02;
63 static const uint8 RAR5_FILE_CRC = 0x04;
64 static const uint8 RAR5_FILE_EXTRATIME = 0x03;
65 static const uint8 RAR5_FILE_EXTRATIMEUNIXFORMAT = 0x01;
66
67 static const uint8 RAR5_ENDARC_NEXTVOL = 0x01;
68
69
70 bool RarVolume::Read()
71 {
72 debug("Checking file %s", *m_filename);
73
74 DiskFile file;
75 if (!file.Open(m_filename, DiskFile::omRead))
76 {
77 return false;
78 }
79
80 m_version = DetectRarVersion(file);
81 file.Seek(0);
82
83 bool ok = false;
84
85 switch (m_version)
86 {
87 case 3:
88 ok = ReadRar3Volume(file);
89 break;
90
91 case 5:
92 ok = ReadRar5Volume(file);
93 break;
94 }
95
96 file.Close();
97 DecryptFree();
98
99 LogDebugInfo();
100
101 return ok;
102 }
103
104 int RarVolume::DetectRarVersion(DiskFile& file)
105 {
106 static char RAR3_SIGNATURE[] = { 0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00 };
107 static char RAR5_SIGNATURE[] = { 0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x01, 0x00 };
108
109 char fileSignature[8];
110
111 int cnt = 0;
112 cnt = (int)file.Read(fileSignature, sizeof(fileSignature));
113
114 bool rar5 = cnt == sizeof(fileSignature) && !strcmp(RAR5_SIGNATURE, fileSignature);
115 bool rar3 = !rar5 && cnt == sizeof(fileSignature) && !strcmp(RAR3_SIGNATURE, fileSignature);
116
117 return rar3 ? 3 : rar5 ? 5 : 0;
118 }
119
120 bool RarVolume::Read(DiskFile& file, RarBlock* block, void* buffer, int64 size)
121 {
122 if (m_encrypted)
123 {
124 if (!DecryptRead(file, buffer, size)) return false;
125 }
126 else
127 {
128 if (file.Read(buffer, size) != size) return false;
129 }
130
131 if (block)
132 {
133 block->trailsize -= size;
134 }
135
136 return true;
137 }
138
139 bool RarVolume::Read16(DiskFile& file, RarBlock* block, uint16* result)
140 {
141 uint8 buf[2];
142 if (!Read(file, block, buf, sizeof(buf))) return false;
143 *result = ((uint16)buf[1] << 8) + buf[0];
144 return true;
145 }
146
147 bool RarVolume::Read32(DiskFile& file, RarBlock* block, uint32* result)
148 {
149 uint8 buf[4];
150 if (!Read(file, block, buf, sizeof(buf))) return false;
151 *result = ((uint32)buf[3] << 24) + ((uint32)buf[2] << 16) + ((uint32)buf[1] << 8) + buf[0];
152 return true;
153 }
154
155 bool RarVolume::ReadV(DiskFile& file, RarBlock* block, uint64* result)
156 {
157 *result = 0;
158 uint8 val;
159 uint8 bits = 0;
160 do
161 {
162 if (Read(file, block, &val, sizeof(val)) != sizeof(val)) return false;
163 *result += (uint64)(val & 0x7f) << bits;
164 bits += 7;
165 } while (val & 0x80);
166
167 return true;
168 }
169
170 bool RarVolume::Skip(DiskFile& file, RarBlock* block, int64 size)
171 {
172 uint8 buf[256];
173 while (size > 0)
174 {
175 int64 len = size <= sizeof(buf) ? size : sizeof(buf);
176 if (!Read(file, block, buf, len)) return false;
177 size -= len;
178 }
179 return true;
180 }
181
182 bool RarVolume::ReadRar3Volume(DiskFile& file)
183 {
184 debug("Reading rar3-file %s", *m_filename);
185
186 while (!file.Eof())
187 {
188 RarBlock block = ReadRar3Block(file);
189 if (!block.type)
190 {
191 return false;
192 }
193
194 if (block.type == RAR3_BLOCK_MAIN)
195 {
196 if (block.flags & RAR3_MAIN_PASSWORD)
197 {
198 m_encrypted = true;
199 if (m_password.Empty()) return false;
200 }
201 m_newNaming = block.flags & RAR3_MAIN_NEWNUMBERING;
202 m_multiVolume = block.flags & RAR3_MAIN_VOLUME;
203 }
204
205 else if (block.type == RAR3_BLOCK_FILE)
206 {
207 RarFile innerFile;
208 if (!ReadRar3File(file, block, innerFile)) return false;
209 m_files.push_back(std::move(innerFile));
210 }
211
212 else if (block.type == RAR3_BLOCK_ENDARC)
213 {
214 if (block.flags & RAR3_ENDARC_DATACRC)
215 {
216 if (!Skip(file, &block, 4)) return false;
217 }
218 if (block.flags & RAR3_ENDARC_VOLNUMBER)
219 {
220 if (!Read32(file, &block, &m_volumeNo)) return false;
221 m_hasNextVolume = (block.flags & RAR3_ENDARC_NEXTVOL) != 0;
222 }
223 break;
224 }
225
226 else if (block.type < 0x72 || block.type > 0x7b)
227 {
228 // inlvaid block type
229 return false;
230 }
231
232 uint64 skip = block.trailsize;
233 if (m_encrypted)
234 {
235 skip -= 16 - m_decryptPos;
236 m_decryptPos = 16;
237 DecryptFree();
238 }
239
240 if (!file.Seek(skip, DiskFile::soCur))
241 {
242 return false;
243 }
244 }
245
246 return true;
247 }
248
249 RarVolume::RarBlock RarVolume::ReadRar3Block(DiskFile& file)
250 {
251 RarBlock block {0};
252 uint8 salt[8];
253
254 if (m_encrypted &&
255 !(file.Read(salt, sizeof(salt)) == sizeof(salt) &&
256 DecryptRar3Prepare(salt) && DecryptInit(128)))
257 {
258 return {0};
259 }
260
261 uint8 buf[7];
262
263 if (!Read(file, nullptr, &buf, sizeof(buf))) return {0};
264 block.crc = ((uint16)buf[1] << 8) + buf[0];
265 block.type = buf[2];
266 block.flags = ((uint16)buf[4] << 8) + buf[3];
267 uint16 size = ((uint16)buf[6] << 8) + buf[5];
268
269 uint32 blocksize = size;
270 block.trailsize = blocksize - sizeof(buf);
271
272 uint8 addbuf[4];
273 if ((block.flags & RAR3_BLOCK_ADDSIZE) && !Read(file, nullptr, &addbuf, sizeof(addbuf)))
274 {
275 return {0};
276 }
277 block.addsize = ((uint32)addbuf[3] << 24) + ((uint32)addbuf[2] << 16) + ((uint32)addbuf[1] << 8) + addbuf[0];
278
279 if (block.flags & RAR3_BLOCK_ADDSIZE)
280 {
281 blocksize += (uint32)block.addsize;
282 block.trailsize = blocksize - sizeof(buf) - 4;
283 }
284
285 static int num = 0;
286 debug("%i) %llu, %i, %i, %i, %u, %llu", ++num, (long long)block.crc, (int)block.type, (int)block.flags, (int)size, (int)block.addsize, (long long)block.trailsize);
287
288 return block;
289 }
290
291 bool RarVolume::ReadRar3File(DiskFile& file, RarBlock& block, RarFile& innerFile)
292 {
293 innerFile.m_splitBefore = block.flags & RAR3_FILE_SPLITBEFORE;
294 innerFile.m_splitAfter = block.flags & RAR3_FILE_SPLITAFTER;
295
296 uint16 namelen;
297
298 uint32 size;
299 if (!Read32(file, &block, &size)) return false;
300 innerFile.m_size = size;
301
302 if (!Skip(file, &block, 1)) return false;
303 if (!Skip(file, &block, 4)) return false;
304 if (!Read32(file, &block, &innerFile.m_time)) return false;
305 if (!Skip(file, &block, 2)) return false;
306 if (!Read16(file, &block, &namelen)) return false;
307 if (!Read32(file, &block, &innerFile.m_attr)) return false;
308
309 if (block.flags & RAR3_FILE_ADDSIZE)
310 {
311 uint32 highsize;
312 if (!Read32(file, &block, &highsize)) return false;
313 block.trailsize += (uint64)highsize << 32;
314
315 if (!Read32(file, &block, &highsize)) return false;
316 innerFile.m_size += (uint64)highsize << 32;
317 }
318
319 if (namelen > 8192) return false; // an error
320 CharBuffer name;
321 name.Reserve(namelen + 1);
322 if (!Read(file, &block, (char*)name, namelen)) return false;
323 name[namelen] = '\0';
324 innerFile.m_filename = name;
325 debug("%i, %i, %s", (int)block.trailsize, (int)namelen, (const char*)name);
326
327 return true;
328 }
329
330 bool RarVolume::ReadRar5Volume(DiskFile& file)
331 {
332 debug("Reading rar5-file %s", *m_filename);
333
334 file.Seek(8);
335
336 while (!file.Eof())
337 {
338 RarBlock block = ReadRar5Block(file);
339 if (!block.type)
340 {
341 return false;
342 }
343
344 if (block.type == RAR5_BLOCK_MAIN)
345 {
346 uint64 arcflags;
347 if (!ReadV(file, &block, &arcflags)) return false;
348 if (arcflags & RAR5_MAIN_VOLNR)
349 {
350 uint64 volnr;
351 if (!ReadV(file, &block, &volnr)) return false;
352 m_volumeNo = (uint32)volnr;
353 }
354 m_newNaming = true;
355 m_multiVolume = (arcflags & RAR5_MAIN_ISVOL) != 0;
356 }
357
358 else if (block.type == RAR5_BLOCK_ENCRYPTION)
359 {
360 uint64 val;
361 if (!ReadV(file, &block, &val)) return false;
362 if (val != 0) return false; // supporting only AES
363 if (!ReadV(file, &block, &val)) return false;
364 uint8 kdfCount;
365 uint8 salt[16];
366 if (!Read(file, &block, &kdfCount, sizeof(kdfCount))) return false;
367 if (!Read(file, &block, &salt, sizeof(salt))) return false;
368 m_encrypted = true;
369 if (m_password.Empty()) return false;
370 if (!DecryptRar5Prepare(kdfCount, salt)) return false;
371 }
372
373 else if (block.type == RAR5_BLOCK_FILE)
374 {
375 RarFile innerFile;
376 if (!ReadRar5File(file, block, innerFile)) return false;
377 m_files.push_back(std::move(innerFile));
378 }
379
380 else if (block.type == RAR5_BLOCK_ENDARC)
381 {
382 uint64 endflags;
383 if (!ReadV(file, &block, &endflags)) return false;
384 m_hasNextVolume = (endflags & RAR5_ENDARC_NEXTVOL) != 0;
385 break;
386 }
387
388 else if (block.type < 1 || block.type > 5)
389 {
390 // inlvaid block type
391 return false;
392 }
393
394 uint64 skip = block.trailsize;
395 if (m_encrypted)
396 {
397 skip -= 16 - m_decryptPos;
398 if (m_decryptPos < 16)
399 {
400 skip += skip % 16 > 0 ? 16 - skip % 16 : 0;
401 m_decryptPos = 16;
402 }
403 DecryptFree();
404 }
405
406 if (!file.Seek(skip, DiskFile::soCur))
407 {
408 return false;
409 }
410 }
411
412 return true;
413 }
414
415 RarVolume::RarBlock RarVolume::ReadRar5Block(DiskFile& file)
416 {
417 RarBlock block {0};
418 uint64 buf = 0;
419
420 if (m_encrypted &&
421 !(file.Read(m_decryptIV, sizeof(m_decryptIV)) == sizeof(m_decryptIV) &&
422 DecryptInit(256)))
423 {
424 return {0};
425 }
426
427 if (!Read32(file, nullptr, &block.crc)) return {0};
428
429 if (!ReadV(file, nullptr, &buf)) return {0};
430 uint32 size = (uint32)buf;
431 block.trailsize = size;
432
433 if (!ReadV(file, &block, &buf)) return {0};
434 block.type = (uint8)buf;
435
436 if (!ReadV(file, &block, &buf)) return {0};
437 block.flags = (uint16)buf;
438
439 block.addsize = 0;
440 if ((block.flags & RAR5_BLOCK_EXTRADATA) && !ReadV(file, &block, &block.addsize)) return {0};
441
442 uint64 datasize = 0;
443 if ((block.flags & RAR5_BLOCK_DATAAREA) && !ReadV(file, &block, &datasize)) return {0};
444 block.trailsize += datasize;
445
446 static int num = 0;
447 debug("%i) %llu, %i, %i, %i, %u, %llu", ++num, (long long)block.crc, (int)block.type, (int)block.flags, (int)size, (int)block.addsize, (long long)block.trailsize);
448
449 return block;
450 }
451
452 bool RarVolume::ReadRar5File(DiskFile& file, RarBlock& block, RarFile& innerFile)
453 {
454 innerFile.m_splitBefore = block.flags & RAR5_BLOCK_SPLITBEFORE;
455 innerFile.m_splitAfter = block.flags & RAR5_BLOCK_SPLITAFTER;
456
457 uint64 val;
458
459 uint64 fileflags;
460 if (!ReadV(file, &block, &fileflags)) return false;
461
462 if (!ReadV(file, &block, &val)) return false; // skip
463 innerFile.m_size = (int64)val;
464
465 if (!ReadV(file, &block, &val)) return false;
466 innerFile.m_attr = (uint32)val;
467
468 if (fileflags & RAR5_FILE_TIME && !Read32(file, &block, &innerFile.m_time)) return false;
469 if (fileflags & RAR5_FILE_CRC && !Skip(file, &block, 4)) return false;
470
471 if (!ReadV(file, &block, &val)) return false; // skip
472 if (!ReadV(file, &block, &val)) return false; // skip
473
474 uint64 namelen;
475 if (!ReadV(file, &block, &namelen)) return false;
476 if (namelen > 8192) return false; // an error
477 CharBuffer name;
478 name.Reserve((uint32)namelen + 1);
479 if (!Read(file, &block, (char*)name, namelen)) return false;
480 name[namelen] = '\0';
481 innerFile.m_filename = name;
482
483 // reading extra headers to find file time
484 if (block.flags & RAR5_BLOCK_EXTRADATA)
485 {
486 uint64 remsize = block.addsize;
487 while (remsize > 0)
488 {
489 uint64 trailsize = block.trailsize;
490
491 uint64 len;
492 if (!ReadV(file, &block, &len)) return false;
493 remsize -= trailsize - block.trailsize + len;
494 trailsize = block.trailsize;
495
496 uint64 type;
497 if (!ReadV(file, &block, &type)) return false;
498
499 if (type == RAR5_FILE_EXTRATIME)
500 {
501 uint64 flags;
502 if (!ReadV(file, &block, &flags)) return false;
503 if (flags & RAR5_FILE_EXTRATIMEUNIXFORMAT)
504 {
505 if (!Read32(file, &block, &innerFile.m_time)) return false;
506 }
507 else
508 {
509 uint32 timelow, timehigh;
510 if (!Read32(file, &block, &timelow)) return false;
511 if (!Read32(file, &block, &timehigh)) return false;
512 uint64 wintime = ((uint64)timehigh << 32) + timelow;
513 innerFile.m_time = (uint32)(wintime / 10000000 - 11644473600LL);
514 }
515 }
516
517 len -= trailsize - block.trailsize;
518
519 if (!Skip(file, &block, len)) return false;
520 }
521 }
522
523 debug("%llu, %i, %s", (long long)block.trailsize, (int)namelen, (const char*)name);
524
525 return true;
526 }
527
528 void RarVolume::LogDebugInfo()
529 {
530 debug("Volume: version:%i, multi:%i, vol-no:%i, new-naming:%i, has-next:%i, encrypted:%i, file-count:%i, [%s]",
531 (int)m_version, (int)m_multiVolume, m_volumeNo, (int)m_newNaming, (int)m_hasNextVolume,
532 (int)m_encrypted, (int)m_files.size(), FileSystem::BaseFileName(m_filename));
533
534 for (RarFile& file : m_files)
535 {
536 debug(" time:%i, size:%lli, attr:%i, split-before:%i, split-after:%i, [%s]",
537 (int)file.m_time, (long long)file.m_size, (int)file.m_attr,
538 (int)file.m_splitBefore, (int)file.m_splitAfter, *file.m_filename);
539 }
540 }
541
542 bool RarVolume::DecryptRar3Prepare(const uint8 salt[8])
543 {
544 WString wstr(*m_password);
545 int len = wstr.Length();
546 if (len == 0) return false;
547
548 CharBuffer seed(len * 2 + 8);
549 for (int i = 0; i < len; i++)
550 {
551 wchar_t ch = wstr[i];
552 seed[i * 2] = ch & 0xFF;
553 seed[i * 2 + 1] = (ch & 0xFF00) >> 8;
554 }
555 memcpy(seed + len * 2, salt, 8);
556
557 debug("seed: %s", *Util::FormatBuffer((const char*)seed, seed.Size()));
558
559 #ifdef HAVE_OPENSSL
560 EVP_MD_CTX* context = EVP_MD_CTX_create();
561
562 if (!EVP_DigestInit(context, EVP_sha1()))
563 {
564 EVP_MD_CTX_destroy(context);
565 return false;
566 }
567 #elif defined(HAVE_NETTLE)
568 sha1_ctx context;
569 sha1_init(&context);
570 #else
571 return false;
572 #endif
573
574 uint8 digest[20];
575 const int rounds = 0x40000;
576
577 for (int i = 0; i < rounds; i++)
578 {
579 #ifdef HAVE_OPENSSL
580 EVP_DigestUpdate(context, *seed, seed.Size());
581 #elif defined(HAVE_NETTLE)
582 sha1_update(&context, seed.Size(), (const uint8_t*)*seed);
583 #endif
584
585 uint8 buf[3];
586 buf[0] = (uint8)i;
587 buf[1] = (uint8)(i >> 8);
588 buf[2] = (uint8)(i >> 16);
589
590 #ifdef HAVE_OPENSSL
591 EVP_DigestUpdate(context, buf, sizeof(buf));
592 #elif defined(HAVE_NETTLE)
593 sha1_update(&context, sizeof(buf), buf);
594 #endif
595
596 if (i % (rounds / 16) == 0)
597 {
598 #ifdef HAVE_OPENSSL
599 EVP_MD_CTX* ivContext = EVP_MD_CTX_create();
600 EVP_MD_CTX_copy(ivContext, context);
601 EVP_DigestFinal(ivContext, digest, nullptr);
602 EVP_MD_CTX_destroy(ivContext);
603 #elif defined(HAVE_NETTLE)
604 sha1_ctx ivContext = context;
605 sha1_digest(&ivContext, sizeof(digest), digest);
606 #endif
607 m_decryptIV[i / (rounds / 16)] = digest[sizeof(digest) - 1];
608 }
609 }
610
611 #ifdef HAVE_OPENSSL
612 EVP_DigestFinal(context, digest, nullptr);
613 EVP_MD_CTX_destroy(context);
614 #elif defined(HAVE_NETTLE)
615 sha1_digest(&context, sizeof(digest), digest);
616 #endif
617
618 debug("digest: %s", *Util::FormatBuffer((const char*)digest, sizeof(digest)));
619
620 for (int i = 0; i < 4; i++)
621 {
622 for (int j = 0; j < 4; j++)
623 {
624 m_decryptKey[i * 4 + j] = digest[i * 4 + 3 - j];
625 }
626 }
627
628 debug("key: %s", *Util::FormatBuffer((const char*)m_decryptKey, sizeof(m_decryptKey)));
629 debug("iv: %s", *Util::FormatBuffer((const char*)m_decryptIV, sizeof(m_decryptIV)));
630
631 return true;
632 }
633
634 bool RarVolume::DecryptRar5Prepare(uint8 kdfCount, const uint8 salt[16])
635 {
636 if (kdfCount > 24) return false;
637
638 int iterations = 1 << kdfCount;
639
640 #ifdef HAVE_OPENSSL
641 if (!PKCS5_PBKDF2_HMAC(m_password, m_password.Length(), salt, 16,
642 iterations, EVP_sha256(), sizeof(m_decryptKey), m_decryptKey)) return false;
643 return true;
644 #elif defined(HAVE_NETTLE)
645 pbkdf2_hmac_sha256(m_password.Length(), (const uint8_t*)*m_password,
646 iterations, 16, salt, sizeof(m_decryptKey), m_decryptKey);
647 return true;
648 #else
649 return false;
650 #endif
651 }
652
653 bool RarVolume::DecryptInit(int keyLength)
654 {
655 #ifdef HAVE_OPENSSL
656 if (!(m_context = EVP_CIPHER_CTX_new())) return false;
657 if (!EVP_DecryptInit((EVP_CIPHER_CTX*)m_context,
658 keyLength == 128 ? EVP_aes_128_cbc() : EVP_aes_256_cbc(),
659 m_decryptKey, m_decryptIV))
660 return false;
661 return true;
662 #elif defined(HAVE_NETTLE)
663 m_context = new aes_ctx;
664 aes_set_decrypt_key((aes_ctx*)m_context, keyLength == 128 ? 16 : 32, m_decryptKey);
665 return true;
666 #else
667 return false;
668 #endif
669 }
670
671 bool RarVolume::DecryptBuf(const uint8 in[16], uint8 out[16])
672 {
673 #ifdef HAVE_OPENSSL
674 uint8 outbuf[32];
675 int outlen = 0;
676 if (!EVP_DecryptUpdate((EVP_CIPHER_CTX*)m_context, outbuf, &outlen, in, 16)) return false;
677 memcpy(out, outbuf + outlen, 16);
678 debug("decrypted: %s", *Util::FormatBuffer((const char*)out, 16));
679 return true;
680 #elif defined(HAVE_NETTLE)
681 aes_decrypt((aes_ctx*)m_context, 16, out, in);
682 for (int i = 0; i < 16; i++)
683 {
684 out[i] ^= m_decryptIV[i];
685 }
686 memcpy(m_decryptIV, in, 16);
687 debug("decrypted: %s", *Util::FormatBuffer((const char*)out, 16));
688 return true;
689 #else
690 return false;
691 #endif
692 }
693
694 void RarVolume::DecryptFree()
695 {
696 if (m_context)
697 {
698 #ifdef HAVE_OPENSSL
699 EVP_CIPHER_CTX_free((EVP_CIPHER_CTX*)m_context);
700 #elif defined(HAVE_NETTLE)
701 delete (aes_ctx*)m_context;
702 #endif
703 m_context = nullptr;
704 }
705 }
706
707 bool RarVolume::DecryptRead(DiskFile& file, void* buffer, int64 size)
708 {
709 while (size > 0)
710 {
711 if (m_decryptPos >= 16)
712 {
713 uint8 buf[16];
714 if (file.Read(&buf, sizeof(buf)) != sizeof(buf)) return false;
715 m_decryptPos = 0;
716 if (!DecryptBuf(buf, m_decryptBuf)) return false;
717 }
718
719 uint8 remainingBuf = 16 - m_decryptPos;
720 uint8 len = size <= remainingBuf ? (uint8)size : remainingBuf;
721 memcpy(buffer, m_decryptBuf + m_decryptPos, len);
722 m_decryptPos += len;
723 size -= len;
724 buffer = (char*)buffer + len;
725 }
726
727 return true;
728 }
729
0 /*
1 * This file is part of nzbget. See <http://nzbget.net>.
2 *
3 * Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19
20 #ifndef RARREADER_H
21 #define RARREADER_H
22
23 #include "NString.h"
24 #include "Log.h"
25 #include "FileSystem.h"
26
27 class RarFile
28 {
29 public:
30 const char* GetFilename() { return m_filename; }
31 uint32 GetTime() { return m_time; }
32 uint32 GetAttr() { return m_attr; }
33 int64 GetSize() { return m_size; }
34 bool GetSplitBefore() { return m_splitBefore; }
35 bool GetSplitAfter() { return m_splitAfter; }
36 private:
37 CString m_filename;
38 uint32 m_time = 0;
39 uint32 m_attr = 0;
40 int64 m_size = 0;
41 bool m_splitBefore = false;
42 bool m_splitAfter = false;
43 friend class RarVolume;
44 };
45
46 class RarVolume
47 {
48 public:
49 typedef std::deque<RarFile> FileList;
50
51 RarVolume(const char* filename) : m_filename(filename) {}
52 bool Read();
53
54 const char* GetFilename() { return m_filename; }
55 int GetVersion() { return m_version; }
56 uint32 GetVolumeNo() { return m_volumeNo; }
57 bool GetNewNaming() { return m_newNaming; }
58 bool GetHasNextVolume() { return m_hasNextVolume; }
59 bool GetMultiVolume() { return m_multiVolume; }
60 bool GetEncrypted() { return m_encrypted; }
61 void SetPassword(const char* password) { m_password = password; }
62 FileList* GetFiles() { return &m_files; }
63
64 private:
65 struct RarBlock
66 {
67 uint32 crc;
68 uint8 type;
69 uint16 flags;
70 uint64 addsize;
71 uint64 trailsize;
72 };
73
74 CString m_filename;
75 int m_version = 0;
76 uint32 m_volumeNo = 0;
77 bool m_newNaming = false;
78 bool m_hasNextVolume = false;
79 bool m_multiVolume = false;
80 FileList m_files;
81 bool m_encrypted = false;
82 CString m_password;
83 uint8 m_decryptKey[32];
84 uint8 m_decryptIV[16];
85 uint8 m_decryptBuf[16];
86 uint8 m_decryptPos = 16;
87
88 // using "void*" to prevent the including of GnuTLS/OpenSSL header files into TlsSocket.h
89 void* m_context = nullptr;
90 void* m_session = nullptr;
91
92 int DetectRarVersion(DiskFile& file);
93 void LogDebugInfo();
94 bool Skip(DiskFile& file, RarBlock* block, int64 size);
95 bool Read(DiskFile& file, RarBlock* block, void* buffer, int64 size);
96 bool Read16(DiskFile& file, RarBlock* block, uint16* result);
97 bool Read32(DiskFile& file, RarBlock* block, uint32* result);
98 bool ReadV(DiskFile& file, RarBlock* block, uint64* result);
99 bool ReadRar3Volume(DiskFile& file);
100 bool ReadRar5Volume(DiskFile& file);
101 RarBlock ReadRar3Block(DiskFile& file);
102 RarBlock ReadRar5Block(DiskFile& file);
103 bool ReadRar3File(DiskFile& file, RarBlock& block, RarFile& innerFile);
104 bool ReadRar5File(DiskFile& file, RarBlock& block, RarFile& innerFile);
105 bool DecryptRar3Prepare(const uint8 salt[8]);
106 bool DecryptRar5Prepare(uint8 kdfCount, const uint8 salt[16]);
107 bool DecryptInit(int keyLength);
108 bool DecryptBuf(const uint8 in[16], uint8 out[16]);
109 void DecryptFree();
110 bool DecryptRead(DiskFile& file, void* buffer, int64 size);
111 };
112
113 #endif
0 /*
1 * This file is part of nzbget. See <http://nzbget.net>.
2 *
3 * Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19
20 #include "nzbget.h"
21
22 #include "RarRenamer.h"
23 #include "Log.h"
24 #include "Util.h"
25 #include "FileSystem.h"
26
27 void RarRenamer::Execute()
28 {
29 m_progressLabel.Format("Checking renamed rar-files for %s", *m_infoName);
30 m_stageProgress = 0;
31 UpdateProgress();
32
33 BuildDirList(m_destDir);
34
35 for (CString& destDir : m_dirList)
36 {
37 debug("Checking %s", *destDir);
38 CheckFiles(destDir);
39 }
40 }
41
42 void RarRenamer::BuildDirList(const char* destDir)
43 {
44 m_dirList.push_back(destDir);
45
46 DirBrowser dirBrowser(destDir);
47
48 while (const char* filename = dirBrowser.Next())
49 {
50 if (!IsStopped())
51 {
52 BString<1024> fullFilename("%s%c%s", destDir, PATH_SEPARATOR, filename);
53 if (FileSystem::DirectoryExists(fullFilename))
54 {
55 BuildDirList(fullFilename);
56 }
57 else
58 {
59 m_fileCount++;
60 }
61 }
62 }
63 }
64
65 void RarRenamer::CheckFiles(const char* destDir)
66 {
67 DirBrowser dir(destDir);
68 while (const char* filename = dir.Next())
69 {
70 if (!IsStopped())
71 {
72 BString<1024> fullFilename("%s%c%s", destDir, PATH_SEPARATOR, filename);
73
74 if (!FileSystem::DirectoryExists(fullFilename))
75 {
76 m_progressLabel.Format("Checking file %s", filename);
77 m_stageProgress = m_fileCount > 0 ? m_curFile * 1000 / m_fileCount : 1000;
78 UpdateProgress();
79 m_curFile++;
80
81 CheckOneFile(fullFilename);
82 }
83 }
84 }
85
86 if (!m_volumes.empty())
87 {
88 RenameFiles(destDir);
89 }
90 }
91
92 void RarRenamer::CheckOneFile(const char* filename)
93 {
94 if (m_ignoreExt && Util::MatchFileExt(FileSystem::BaseFileName(filename), m_ignoreExt, ",;"))
95 {
96 return;
97 }
98
99 RarVolume volume(filename);
100 volume.SetPassword(m_password);
101 if (volume.Read())
102 {
103 m_volumes.push_back(std::move(volume));
104 }
105 }
106
107 void RarRenamer::RenameFile(const char* srcFilename, const char* destFileName)
108 {
109 PrintMessage(Message::mkInfo, "Renaming %s to %s", FileSystem::BaseFileName(srcFilename), FileSystem::BaseFileName(destFileName));
110 if (!FileSystem::MoveFile(srcFilename, destFileName))
111 {
112 PrintMessage(Message::mkError, "Could not rename %s to %s: %s", srcFilename, destFileName,
113 *FileSystem::GetLastErrorMessage());
114 return;
115 }
116
117 m_renamedCount++;
118
119 // notify about new file name
120 RegisterRenamedFile(FileSystem::BaseFileName(srcFilename), FileSystem::BaseFileName(destFileName));
121 }
122
123 void RarRenamer::RenameFiles(const char* destDir)
124 {
125 MakeSets();
126
127 for (RarVolumeSet& set : m_sets)
128 {
129 if (!IsSetProperlyNamed(set))
130 {
131 RarFile* mainFile = FindMainFile(set);
132 BString<1024> mainBasename = FileSystem::BaseFileName(mainFile->GetFilename());
133 char* ext = strrchr(mainBasename, '.');
134 // strip extension if its length is 3 chars
135 if (ext && strlen(ext) == 4)
136 {
137 *ext = '\0';
138 }
139
140 BString<1024> newBasename = *mainBasename;
141 int num = 0;
142 bool willOverwrite = true;
143 while (willOverwrite)
144 {
145 if (num++)
146 {
147 newBasename.Format("%s-%i", *mainBasename, num);
148 }
149
150 for (RarVolume* volume : set)
151 {
152 CString destfilename = GenNewVolumeFilename(destDir, newBasename, volume);
153 willOverwrite = strcmp(volume->GetFilename(), destfilename) && FileSystem::FileExists(destfilename);
154 if (willOverwrite)
155 {
156 break;
157 }
158 }
159 }
160
161 for (RarVolume* volume : set)
162 {
163 CString destfilename = GenNewVolumeFilename(destDir, newBasename, volume);
164 if (strcmp(volume->GetFilename(), destfilename))
165 {
166 RenameFile(volume->GetFilename(), destfilename);
167 }
168 }
169 }
170 }
171 }
172
173 CString RarRenamer::GenNewVolumeFilename(const char* destDir, const char* newBasename, RarVolume* volume)
174 {
175 CString extension = volume->GetNewNaming() ? GenNewExtension(volume->GetVolumeNo()) : GenOldExtension(volume->GetVolumeNo());
176 return CString::FormatStr("%s%c%s.%s", destDir, PATH_SEPARATOR, newBasename, *extension);
177 }
178
179 CString RarRenamer::GenNewExtension(int volumeNo)
180 {
181 return CString::FormatStr("part%04i.rar", volumeNo + 1);
182 }
183
184 CString RarRenamer::GenOldExtension(int volumeNo)
185 {
186 if (volumeNo == 0)
187 {
188 return "rar";
189 }
190 else
191 {
192 unsigned char ch = 'r' + (volumeNo - 1) / 100;
193 return CString::FormatStr("%c%02d", ch, (volumeNo - 1) % 100);
194 }
195 }
196
197 void RarRenamer::MakeSets()
198 {
199 m_sets.clear();
200
201 // find first volumes and create initial incomplete sets
202 for (RarVolume& volume : m_volumes)
203 {
204 if (!volume.GetFiles()->empty() && volume.GetVolumeNo() == 0)
205 {
206 m_sets.push_back({&volume});
207 }
208 }
209
210 // complete sets, discard sets which cannot be completed
211 m_sets.erase(std::remove_if(m_sets.begin(), m_sets.end(),
212 [volumes = &m_volumes](RarVolumeSet& set)
213 {
214 debug("*** Building set %s", FileSystem::BaseFileName(set[0]->GetFilename()));
215 bool found = true;
216 while (found)
217 {
218 found = false;
219 RarVolume* lastVolume = set.back();
220 for (RarVolume& volume : *volumes)
221 {
222 if (!volume.GetFiles()->empty() && volume.GetMultiVolume() &&
223 volume.GetVolumeNo() == lastVolume->GetVolumeNo() + 1 &&
224 volume.GetVersion() == lastVolume->GetVersion() &&
225 lastVolume->GetHasNextVolume() &&
226 ((volume.GetFiles()->at(0).GetSplitBefore() &&
227 lastVolume->GetFiles()->at(0).GetSplitAfter() &&
228 !strcmp(volume.GetFiles()->at(0).GetFilename(), lastVolume->GetFiles()->at(0).GetFilename())) ||
229 (!volume.GetFiles()->at(0).GetSplitBefore() && !lastVolume->GetFiles()->at(0).GetSplitAfter())))
230 {
231 debug(" adding %s", FileSystem::BaseFileName(volume.GetFilename()));
232 set.push_back(&volume);
233 found = true;
234 break;
235 }
236 }
237 }
238
239 bool completed = !set.back()->GetHasNextVolume();
240
241 return !completed;
242 }),
243 m_sets.end());
244
245 // debug log
246 for (RarVolumeSet& set : m_sets)
247 {
248 debug("*** Set ***");
249 for (RarVolume* volume : set)
250 {
251 debug(" %s", FileSystem::BaseFileName(volume->GetFilename()));
252 }
253 }
254 }
255
256 bool RarRenamer::IsSetProperlyNamed(RarVolumeSet& set)
257 {
258 RegEx regExPart(".*.part([0-9]+)\\.rar$");
259
260 const char* setBasename = FileSystem::BaseFileName(set[0]->GetFilename());
261 int setPartLen = 0;
262 for (RarVolume* volume : set)
263 {
264 const char* filename = FileSystem::BaseFileName(volume->GetFilename());
265
266 if (strlen(setBasename) != strlen(filename))
267 {
268 return false;
269 }
270
271 if (volume->GetNewNaming())
272 {
273 if (!regExPart.Match(filename))
274 {
275 return false;
276 }
277 BString<1024> partNo(filename + regExPart.GetMatchStart(1), regExPart.GetMatchLen(1));
278 if (setPartLen == 0)
279 {
280 setPartLen = partNo.Length();
281 }
282 bool ok = atoi(partNo) == volume->GetVolumeNo() + 1 &&
283 partNo.Length() == setPartLen &&
284 !strncmp(setBasename, filename, regExPart.GetMatchStart(1));
285 if (!ok)
286 {
287 return false;
288 }
289 }
290 else
291 {
292 const char* ext = strrchr(filename, '.');
293 if (!ext || strcmp(ext + 1, GenOldExtension(volume->GetVolumeNo())) ||
294 strncmp(setBasename, filename, ext - filename))
295 {
296 return false;
297 }
298 }
299 }
300
301 return true;
302 }
303
304 RarFile* RarRenamer::FindMainFile(RarVolumeSet& set)
305 {
306 std::deque<RarFile*> allFiles;
307
308 for (RarVolume* volume : set)
309 {
310 for (RarFile& file : *volume->GetFiles())
311 {
312 allFiles.push_back(&file);
313 }
314
315 }
316
317 std::deque<RarFile*>::iterator it = std::max_element(allFiles.begin(), allFiles.end(),
318 [](RarFile* file1, RarFile* file2)
319 {
320 return file1->GetSize() < file2->GetSize();
321 });
322
323 return *it;
324 }
0 /*
1 * This file is part of nzbget. See <http://nzbget.net>.
2 *
3 * Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19
20 #ifndef RARRENAMER_H
21 #define RARRENAMER_H
22
23 #include "NString.h"
24 #include "Log.h"
25 #include "FileSystem.h"
26 #include "RarReader.h"
27
28 class RarRenamer
29 {
30 public:
31 void Execute();
32 void SetDestDir(const char* destDir) { m_destDir = destDir; }
33 const char* GetInfoName() { return m_infoName; }
34 void SetInfoName(const char* infoName) { m_infoName = infoName; }
35 void SetPassword(const char* password) { m_password = password; }
36 void SetIgnoreExt(const char* ignoreExt) { m_ignoreExt = ignoreExt; }
37 int GetRenamedCount() { return m_renamedCount; }
38
39 protected:
40 virtual void UpdateProgress() {}
41 virtual bool IsStopped() { return false; };
42 virtual void PrintMessage(Message::EKind kind, const char* format, ...) PRINTF_SYNTAX(3) {}
43 virtual void RegisterRenamedFile(const char* oldFilename, const char* newFileName) {}
44 const char* GetProgressLabel() { return m_progressLabel; }
45 int GetStageProgress() { return m_stageProgress; }
46
47 private:
48 typedef std::deque<CString> DirList;
49 typedef std::deque<RarVolume> RarVolumeList;
50 typedef std::deque<RarVolume*> RarVolumeSet;
51 typedef std::deque<RarVolumeSet> RarSets;
52
53 CString m_infoName;
54 CString m_destDir;
55 CString m_progressLabel;
56 int m_stageProgress = 0;
57 bool m_cancelled = false;
58 DirList m_dirList;
59 int m_fileCount = 0;
60 int m_curFile = 0;
61 int m_renamedCount = 0;
62 RarVolumeList m_volumes;
63 RarSets m_sets;
64 CString m_password;
65 CString m_ignoreExt;
66
67 void BuildDirList(const char* destDir);
68 void CheckFiles(const char* destDir);
69 void CheckOneFile(const char* filename);
70 void RenameFile(const char* srcFilename, const char* destFileName);
71 void RenameFiles(const char* destDir);
72 CString GenNewVolumeFilename(const char* destDir, const char* newBasename, RarVolume* volume);
73 CString GenNewExtension(int volumeNo);
74 CString GenOldExtension(int volumeNo);
75 void MakeSets();
76 bool IsSetProperlyNamed(RarVolumeSet& set);
77 RarFile* FindMainFile(RarVolumeSet& set);
78 };
79
80 #endif
0 /*
1 * This file is part of nzbget. See <http://nzbget.net>.
2 *
3 * Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19
20 #include "nzbget.h"
21 #include "Options.h"
22 #include "DiskState.h"
23 #include "Log.h"
24 #include "FileSystem.h"
25 #include "Rename.h"
26
27 #ifndef DISABLE_PARCHECK
28 void RenameController::PostParRenamer::PrintMessage(Message::EKind kind, const char* format, ...)
29 {
30 char text[1024];
31 va_list args;
32 va_start(args, format);
33 vsnprintf(text, 1024, format, args);
34 va_end(args);
35 text[1024-1] = '\0';
36
37 m_owner->m_postInfo->GetNzbInfo()->AddMessage(kind, text);
38 }
39 #endif
40
41
42 void RenameController::PostRarRenamer::PrintMessage(Message::EKind kind, const char* format, ...)
43 {
44 char text[1024];
45 va_list args;
46 va_start(args, format);
47 vsnprintf(text, 1024, format, args);
48 va_end(args);
49 text[1024 - 1] = '\0';
50
51 m_owner->m_postInfo->GetNzbInfo()->AddMessage(kind, text);
52 }
53
54
55 RenameController::RenameController()
56 {
57 debug("Creating RenameController");
58
59 #ifndef DISABLE_PARCHECK
60 m_parRenamer.m_owner = this;
61 #endif
62
63 m_rarRenamer.m_owner = this;
64 }
65
66 void RenameController::StartJob(PostInfo* postInfo, EJobKind kind)
67 {
68 RenameController* renameController = new RenameController();
69 renameController->m_postInfo = postInfo;
70 renameController->m_kind = kind;
71 renameController->SetAutoDestroy(false);
72
73 postInfo->SetPostThread(renameController);
74
75 renameController->Start();
76 }
77
78 void RenameController::Run()
79 {
80 BString<1024> nzbName;
81 CString destDir;
82 CString finalDir;
83 {
84 GuardedDownloadQueue guard = DownloadQueue::Guard();
85 nzbName = m_postInfo->GetNzbInfo()->GetName();
86 destDir = m_postInfo->GetNzbInfo()->GetDestDir();
87 finalDir = m_postInfo->GetNzbInfo()->GetFinalDir();
88 }
89
90 BString<1024> infoName("rename for %s", *nzbName);
91 SetInfoName(infoName);
92
93 PrintMessage(Message::mkInfo, "Checking renamed %sfiles for %s",
94 m_kind == jkRar ? "archive " : "", *nzbName);
95
96 ExecRename(destDir, finalDir, nzbName);
97
98 if (IsStopped())
99 {
100 PrintMessage(Message::mkWarning, "Renaming cancelled for %s", *nzbName);
101 }
102 else if (m_renamedCount > 0)
103 {
104 PrintMessage(Message::mkInfo, "Successfully renamed %i %sfile(s) for %s",
105 m_renamedCount, m_kind == jkRar ? "archive " : "", *nzbName);
106 }
107 else
108 {
109 PrintMessage(Message::mkInfo, "No renamed %sfiles found for %s",
110 m_kind == jkRar ? "archive " : "", *nzbName);
111 }
112
113 RenameCompleted();
114 }
115
116 void RenameController::AddMessage(Message::EKind kind, const char* text)
117 {
118 m_postInfo->GetNzbInfo()->AddMessage(kind, text);
119 }
120
121 void RenameController::ExecRename(const char* destDir, const char* finalDir, const char* nzbName)
122 {
123 if (m_kind == jkPar)
124 {
125 #ifndef DISABLE_PARCHECK
126 m_parRenamer.SetDestDir(m_postInfo->GetNzbInfo()->GetUnpackStatus() == NzbInfo::usSuccess &&
127 !Util::EmptyStr(finalDir) ? finalDir : destDir);
128 m_parRenamer.SetInfoName(nzbName);
129 m_parRenamer.SetDetectMissing(m_postInfo->GetNzbInfo()->GetUnpackStatus() == NzbInfo::usNone);
130 m_parRenamer.Execute();
131 #endif
132 }
133 else if (m_kind == jkRar)
134 {
135 m_rarRenamer.SetDestDir(destDir);
136 m_rarRenamer.SetInfoName(nzbName);
137 m_rarRenamer.SetIgnoreExt(g_Options->GetUnpackIgnoreExt());
138
139 NzbParameter* parameter = m_postInfo->GetNzbInfo()->GetParameters()->Find("*Unpack:Password", false);
140 if (parameter)
141 {
142 m_rarRenamer.SetPassword(parameter->GetValue());
143 }
144
145 m_rarRenamer.Execute();
146 }
147 }
148
149 void RenameController::RenameCompleted()
150 {
151 GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
152
153 if (m_kind == jkPar)
154 {
155 m_postInfo->GetNzbInfo()->SetParRenameStatus(m_renamedCount > 0 ? NzbInfo::rsSuccess : NzbInfo::rsNothing);
156 #ifndef DISABLE_PARCHECK
157 // request another par2-file if the renaming has failed due to damaged par2-files
158 if (m_renamedCount == 0 && m_parRenamer.HasDamagedParFiles() &&
159 m_postInfo->GetNzbInfo()->GetRemainingParCount() > 0)
160 {
161 m_parRenamer.PrintMessage(Message::mkInfo, "Requesting extra par2-files for %s to perform par-rename", m_parRenamer.GetInfoName());
162 downloadQueue->EditEntry(m_postInfo->GetNzbInfo()->GetId(), DownloadQueue::eaGroupResume, nullptr);
163 downloadQueue->EditEntry(m_postInfo->GetNzbInfo()->GetId(), DownloadQueue::eaGroupPauseExtraPars, nullptr);
164 if (m_postInfo->GetNzbInfo()->GetRemainingSize() > 0)
165 {
166 // reset rename status to execute renamer again, after the new par2-file is downloaded
167 m_postInfo->GetNzbInfo()->SetParRenameStatus(NzbInfo::rsNone);
168 }
169 }
170 #endif
171 }
172 else if (m_kind == jkRar)
173 {
174 m_postInfo->GetNzbInfo()->SetRarRenameStatus(m_renamedCount > 0 ? NzbInfo::rsSuccess : NzbInfo::rsNothing);
175 }
176
177 #ifndef DISABLE_PARCHECK
178 if (m_parRenamer.HasMissedFiles() && m_postInfo->GetNzbInfo()->GetParStatus() <= NzbInfo::psSkipped)
179 {
180 m_parRenamer.PrintMessage(Message::mkInfo, "Requesting par-check/repair for %s to restore missing files ", m_parRenamer.GetInfoName());
181 m_postInfo->SetRequestParCheck(true);
182 }
183 #endif
184
185 m_postInfo->SetWorking(false);
186 }
187
188 #ifndef DISABLE_PARCHECK
189 void RenameController::UpdateParRenameProgress()
190 {
191 GuardedDownloadQueue guard = DownloadQueue::Guard();
192
193 m_postInfo->SetProgressLabel(m_parRenamer.GetProgressLabel());
194 m_postInfo->SetStageProgress(m_parRenamer.GetStageProgress());
195 }
196 #endif
197
198 void RenameController::UpdateRarRenameProgress()
199 {
200 GuardedDownloadQueue guard = DownloadQueue::Guard();
201
202 m_postInfo->SetProgressLabel(m_rarRenamer.GetProgressLabel());
203 m_postInfo->SetStageProgress(m_rarRenamer.GetStageProgress());
204 }
205
206 /**
207 * Update file name in the CompletedFiles-list of NZBInfo
208 */
209 void RenameController::RegisterRenamedFile(const char* oldFilename, const char* newFileName)
210 {
211 for (CompletedFile& completedFile : m_postInfo->GetNzbInfo()->GetCompletedFiles())
212 {
213 if (!strcasecmp(completedFile.GetFileName(), oldFilename))
214 {
215 completedFile.SetFileName(newFileName);
216 break;
217 }
218 }
219 m_renamedCount++;
220 }
0 /*
1 * This file is part of nzbget. See <http://nzbget.net>.
2 *
3 * Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19
20 #ifndef RENAME_H
21 #define RENAME_H
22
23 #include "Thread.h"
24 #include "DownloadInfo.h"
25 #include "Script.h"
26 #include "RarRenamer.h"
27
28 #ifndef DISABLE_PARCHECK
29 #include "ParRenamer.h"
30 #endif
31
32 class RenameController : public Thread, public ScriptController
33 {
34 public:
35 enum EJobKind
36 {
37 jkPar,
38 jkRar
39 };
40
41 RenameController();
42 virtual void Run();
43 static void StartJob(PostInfo* postInfo, EJobKind kind);
44
45 protected:
46 virtual void AddMessage(Message::EKind kind, const char* text);
47
48 private:
49 PostInfo* m_postInfo;
50 CString m_destDir;
51 int m_renamedCount = 0;
52 EJobKind m_kind;
53
54 #ifndef DISABLE_PARCHECK
55 class PostParRenamer : public ParRenamer
56 {
57 protected:
58 virtual void UpdateProgress() { m_owner->UpdateParRenameProgress(); }
59 virtual void PrintMessage(Message::EKind kind, const char* format, ...) PRINTF_SYNTAX(3);
60 virtual void RegisterParredFile(const char* filename)
61 { m_owner->m_postInfo->GetParredFiles()->push_back(filename); }
62 virtual void RegisterRenamedFile(const char* oldFilename, const char* newFileName)
63 { m_owner->RegisterRenamedFile(oldFilename, newFileName); }
64 virtual bool IsStopped() { return m_owner->IsStopped(); };
65 private:
66 RenameController* m_owner;
67 friend class RenameController;
68 };
69
70 PostParRenamer m_parRenamer;
71
72 void UpdateParRenameProgress();
73 #endif
74
75 class PostRarRenamer : public RarRenamer
76 {
77 protected:
78 virtual void UpdateProgress() { m_owner->UpdateRarRenameProgress(); }
79 virtual void PrintMessage(Message::EKind kind, const char* format, ...) PRINTF_SYNTAX(3);
80 virtual void RegisterRenamedFile(const char* oldFilename, const char* newFileName)
81 { m_owner->RegisterRenamedFile(oldFilename, newFileName); }
82 virtual bool IsStopped() { return m_owner->IsStopped(); };
83 private:
84 RenameController* m_owner;
85 friend class RenameController;
86 };
87
88 PostRarRenamer m_rarRenamer;
89
90 void UpdateRarRenameProgress();
91
92 void ExecRename(const char* destDir, const char* finalDir, const char* nzbName);
93 void RenameCompleted();
94 void RegisterRenamedFile(const char* oldFilename, const char* newFileName);
95 };
96
97 #endif
0 /*
1 * This file is part of nzbget. See <http://nzbget.net>.
2 *
3 * Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19
20 #include "nzbget.h"
21 #include "Repair.h"
22 #include "DupeCoordinator.h"
23 #include "ParParser.h"
24 #include "Options.h"
25 #include "DiskState.h"
26 #include "Log.h"
27 #include "FileSystem.h"
28
29 #ifndef DISABLE_PARCHECK
30 bool RepairController::PostParChecker::RequestMorePars(int blockNeeded, int* blockFound)
31 {
32 return m_owner->RequestMorePars(m_postInfo->GetNzbInfo(), GetParFilename(), blockNeeded, blockFound);
33 }
34
35 void RepairController::PostParChecker::UpdateProgress()
36 {
37 m_owner->UpdateParCheckProgress();
38 }
39
40 void RepairController::PostParChecker::PrintMessage(Message::EKind kind, const char* format, ...)
41 {
42 char text[1024];
43 va_list args;
44 va_start(args, format);
45 vsnprintf(text, 1024, format, args);
46 va_end(args);
47 text[1024-1] = '\0';
48
49 m_postInfo->GetNzbInfo()->AddMessage(kind, text);
50 }
51
52 void RepairController::PostParChecker::RegisterParredFile(const char* filename)
53 {
54 m_postInfo->GetParredFiles()->push_back(filename);
55 }
56
57 bool RepairController::PostParChecker::IsParredFile(const char* filename)
58 {
59 for (CString& parredFile : m_postInfo->GetParredFiles())
60 {
61 if (!strcasecmp(parredFile, filename))
62 {
63 return true;
64 }
65 }
66 return false;
67 }
68
69 ParChecker::EFileStatus RepairController::PostParChecker::FindFileCrc(const char* filename,
70 uint32* crc, SegmentList* segments)
71 {
72 CompletedFile* completedFile = nullptr;
73
74 for (CompletedFile& completedFile2 : m_postInfo->GetNzbInfo()->GetCompletedFiles())
75 {
76 if (!strcasecmp(completedFile2.GetFileName(), filename))
77 {
78 completedFile = &completedFile2;
79 break;
80 }
81 }
82 if (!completedFile)
83 {
84 return ParChecker::fsUnknown;
85 }
86
87 debug("Found completed file: %s, CRC: %.8x, Status: %i", FileSystem::BaseFileName(completedFile->GetFileName()), completedFile->GetCrc(), (int)completedFile->GetStatus());
88
89 *crc = completedFile->GetCrc();
90
91 if (completedFile->GetStatus() == CompletedFile::cfPartial && completedFile->GetId() > 0 &&
92 !m_postInfo->GetNzbInfo()->GetReprocess())
93 {
94 FileInfo tmpFileInfo(completedFile->GetId());
95
96 if (!g_DiskState->LoadFileState(&tmpFileInfo, nullptr, true))
97 {
98 return ParChecker::fsUnknown;
99 }
100
101 for (ArticleInfo* pa : tmpFileInfo.GetArticles())
102 {
103 segments->emplace_back(pa->GetStatus() == ArticleInfo::aiFinished,
104 pa->GetSegmentOffset(), pa->GetSegmentSize(), pa->GetCrc());
105 }
106 }
107
108 return completedFile->GetStatus() == CompletedFile::cfSuccess ? ParChecker::fsSuccess :
109 completedFile->GetStatus() == CompletedFile::cfFailure &&
110 !m_postInfo->GetNzbInfo()->GetReprocess() ? ParChecker::fsFailure :
111 completedFile->GetStatus() == CompletedFile::cfPartial && segments->size() > 0 &&
112 !m_postInfo->GetNzbInfo()->GetReprocess()? ParChecker::fsPartial :
113 ParChecker::fsUnknown;
114 }
115
116 void RepairController::PostParChecker::RequestDupeSources(DupeSourceList* dupeSourceList)
117 {
118 GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
119
120 RawNzbList dupeList = g_DupeCoordinator->ListHistoryDupes(downloadQueue, m_postInfo->GetNzbInfo());
121
122 if (!dupeList.empty())
123 {
124 PostDupeMatcher dupeMatcher(m_postInfo);
125 PrintMessage(Message::mkInfo, "Checking %s for dupe scan usability", m_postInfo->GetNzbInfo()->GetName());
126 bool sizeComparisonPossible = dupeMatcher.Prepare();
127 for (NzbInfo* dupeNzbInfo : dupeList)
128 {
129 if (sizeComparisonPossible)
130 {
131 PrintMessage(Message::mkInfo, "Checking %s for dupe scan usability", FileSystem::BaseFileName(dupeNzbInfo->GetDestDir()));
132 }
133 bool useDupe = !sizeComparisonPossible || dupeMatcher.MatchDupeContent(dupeNzbInfo->GetDestDir());
134 if (useDupe)
135 {
136 PrintMessage(Message::mkInfo, "Adding %s to dupe scan sources", FileSystem::BaseFileName(dupeNzbInfo->GetDestDir()));
137 dupeSourceList->emplace_back(dupeNzbInfo->GetId(), dupeNzbInfo->GetDestDir());
138 }
139 }
140 if (dupeSourceList->empty())
141 {
142 PrintMessage(Message::mkInfo, "No usable dupe scan sources found");
143 }
144 }
145 }
146
147 void RepairController::PostParChecker::StatDupeSources(DupeSourceList* dupeSourceList)
148 {
149 GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
150
151 int totalExtraParBlocks = 0;
152 for (DupeSource& dupeSource : dupeSourceList)
153 {
154 if (dupeSource.GetUsedBlocks() > 0)
155 {
156 for (HistoryInfo* historyInfo : downloadQueue->GetHistory())
157 {
158 if (historyInfo->GetKind() == HistoryInfo::hkNzb &&
159 historyInfo->GetNzbInfo()->GetId() == dupeSource.GetId())
160 {
161 historyInfo->GetNzbInfo()->SetExtraParBlocks(historyInfo->GetNzbInfo()->GetExtraParBlocks() - dupeSource.GetUsedBlocks());
162 }
163 }
164 }
165 totalExtraParBlocks += dupeSource.GetUsedBlocks();
166 }
167
168 m_postInfo->GetNzbInfo()->SetExtraParBlocks(m_postInfo->GetNzbInfo()->GetExtraParBlocks() + totalExtraParBlocks);
169 }
170
171
172 void RepairController::PostDupeMatcher::PrintMessage(Message::EKind kind, const char* format, ...)
173 {
174 char text[1024];
175 va_list args;
176 va_start(args, format);
177 vsnprintf(text, 1024, format, args);
178 va_end(args);
179 text[1024-1] = '\0';
180
181 m_postInfo->GetNzbInfo()->AddMessage(kind, text);
182 }
183
184 #endif
185
186 RepairController::RepairController()
187 {
188 debug("Creating RepairController");
189
190 #ifndef DISABLE_PARCHECK
191 m_parChecker.m_owner = this;
192 #endif
193 }
194
195 void RepairController::Stop()
196 {
197 debug("Stopping RepairController");
198 Thread::Stop();
199 #ifndef DISABLE_PARCHECK
200 m_parChecker.Cancel();
201 #endif
202 }
203
204 #ifndef DISABLE_PARCHECK
205
206 void RepairController::StartJob(PostInfo* postInfo)
207 {
208 RepairController* repairController = new RepairController();
209 repairController->m_postInfo = postInfo;
210 repairController->SetAutoDestroy(false);
211
212 postInfo->SetPostThread(repairController);
213
214 repairController->Start();
215 }
216
217 void RepairController::Run()
218 {
219 BString<1024> nzbName;
220 CString destDir;
221 {
222 GuardedDownloadQueue guard = DownloadQueue::Guard();
223 nzbName = m_postInfo->GetNzbInfo()->GetName();
224 destDir = m_postInfo->GetNzbInfo()->GetDestDir();
225 }
226
227 m_parChecker.SetPostInfo(m_postInfo);
228 m_parChecker.SetDestDir(destDir);
229 m_parChecker.SetNzbName(nzbName);
230 m_parChecker.SetParTime(Util::CurrentTime());
231 m_parChecker.SetDownloadSec(m_postInfo->GetNzbInfo()->GetDownloadSec());
232 m_parChecker.SetParQuick(g_Options->GetParQuick() && !m_postInfo->GetForceParFull());
233 m_parChecker.SetForceRepair(m_postInfo->GetForceRepair());
234
235 m_parChecker.PrintMessage(Message::mkInfo, "Checking pars for %s", *nzbName);
236
237 m_parChecker.Execute();
238 }
239
240 /**
241 * DownloadQueue must be locked prior to call of this function.
242 */
243 bool RepairController::AddPar(FileInfo* fileInfo, bool deleted)
244 {
245 bool sameCollection = fileInfo->GetNzbInfo() == m_parChecker.GetPostInfo()->GetNzbInfo();
246 if (sameCollection && !deleted)
247 {
248 BString<1024> fullFilename("%s%c%s", fileInfo->GetNzbInfo()->GetDestDir(), (int)PATH_SEPARATOR, fileInfo->GetFilename());
249 m_parChecker.AddParFile(fullFilename);
250 }
251 else
252 {
253 m_parChecker.QueueChanged();
254 }
255 return sameCollection;
256 }
257
258 void RepairController::ParCheckCompleted()
259 {
260 GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
261
262 PostInfo* postInfo = m_parChecker.GetPostInfo();
263
264 // Update ParStatus (accumulate result)
265 if ((m_parChecker.GetStatus() == ParChecker::psRepaired ||
266 m_parChecker.GetStatus() == ParChecker::psRepairNotNeeded) &&
267 postInfo->GetNzbInfo()->GetParStatus() <= NzbInfo::psSkipped)
268 {
269 postInfo->GetNzbInfo()->SetParStatus(NzbInfo::psSuccess);
270 postInfo->SetParRepaired(m_parChecker.GetStatus() == ParChecker::psRepaired);
271 }
272 else if (m_parChecker.GetStatus() == ParChecker::psRepairPossible &&
273 postInfo->GetNzbInfo()->GetParStatus() != NzbInfo::psFailure)
274 {
275 postInfo->GetNzbInfo()->SetParStatus(NzbInfo::psRepairPossible);
276 }
277 else
278 {
279 postInfo->GetNzbInfo()->SetParStatus(NzbInfo::psFailure);
280 }
281
282 int waitTime = postInfo->GetNzbInfo()->GetDownloadSec() - m_parChecker.GetDownloadSec();
283 postInfo->SetStartTime(postInfo->GetStartTime() + (time_t)waitTime);
284 int parSec = (int)(Util::CurrentTime() - m_parChecker.GetParTime()) - waitTime;
285 postInfo->GetNzbInfo()->SetParSec(postInfo->GetNzbInfo()->GetParSec() + parSec);
286
287 postInfo->GetNzbInfo()->SetParFull(m_parChecker.GetParFull());
288
289 postInfo->SetWorking(false);
290 }
291
292 /**
293 * Unpause par2-files
294 * returns true, if the files with required number of blocks were unpaused,
295 * or false if there are no more files in queue for this collection or not enough blocks.
296 * special case: returns true if there are any unpaused par2-files in the queue regardless
297 * of the amount of blocks; this is to keep par-checker wait for download completion.
298 */
299 bool RepairController::RequestMorePars(NzbInfo* nzbInfo, const char* parFilename, int blockNeeded, int* blockFoundOut)
300 {
301 GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
302
303 Blocks availableBlocks;
304 Blocks selectedBlocks;
305 int blockFound = 0;
306 int curBlockFound = 0;
307
308 FindPars(downloadQueue, nzbInfo, parFilename, availableBlocks, true, true, &curBlockFound);
309 blockFound += curBlockFound;
310 if (blockFound < blockNeeded)
311 {
312 FindPars(downloadQueue, nzbInfo, parFilename, availableBlocks, true, false, &curBlockFound);
313 blockFound += curBlockFound;
314 }
315 if (blockFound < blockNeeded)
316 {
317 FindPars(downloadQueue, nzbInfo, parFilename, availableBlocks, false, false, &curBlockFound);
318 blockFound += curBlockFound;
319 }
320
321 std::sort(availableBlocks.begin(), availableBlocks.end(),
322 [](BlockInfo& block1, BlockInfo& block2)
323 {
324 return block1.m_blockCount < block2.m_blockCount;
325 });
326
327 if (blockFound >= blockNeeded)
328 {
329 // collect as much blocks as needed
330 for (Blocks::iterator it = availableBlocks.begin(); blockNeeded > 0 && it != availableBlocks.end(); it++)
331 {
332 BlockInfo& blockInfo = *it;
333 selectedBlocks.push_front(blockInfo);
334 blockNeeded -= blockInfo.m_blockCount;
335 }
336
337 // discarding superfluous blocks
338 for (Blocks::iterator it = selectedBlocks.begin(); it != selectedBlocks.end(); )
339 {
340 BlockInfo& blockInfo = *it;
341 if (blockNeeded + blockInfo.m_blockCount <= 0)
342 {
343 blockNeeded += blockInfo.m_blockCount;
344 it = selectedBlocks.erase(it);
345 }
346 else
347 {
348 it++;
349 }
350 }
351
352 // unpause files with blocks
353 for (BlockInfo& blockInfo : selectedBlocks)
354 {
355 if (blockInfo.m_fileInfo->GetPaused())
356 {
357 m_parChecker.PrintMessage(Message::mkInfo, "Unpausing %s%c%s for par-recovery", nzbInfo->GetName(), (int)PATH_SEPARATOR, blockInfo.m_fileInfo->GetFilename());
358 blockInfo.m_fileInfo->SetPaused(false);
359 blockInfo.m_fileInfo->SetExtraPriority(true);
360 }
361 }
362 }
363
364 bool hasUnpausedParFiles = false;
365 for (FileInfo* fileInfo : nzbInfo->GetFileList())
366 {
367 if (fileInfo->GetParFile() && !fileInfo->GetPaused())
368 {
369 hasUnpausedParFiles = true;
370 break;
371 }
372 }
373
374 if (blockFoundOut)
375 {
376 *blockFoundOut = blockFound;
377 }
378
379 bool ok = blockNeeded <= 0 || hasUnpausedParFiles;
380
381 return ok;
382 }
383
384 void RepairController::FindPars(DownloadQueue* downloadQueue, NzbInfo* nzbInfo, const char* parFilename,
385 Blocks& blocks, bool strictParName, bool exactParName, int* blockFound)
386 {
387 *blockFound = 0;
388
389 // extract base name from m_szParFilename (trim .par2-extension and possible .vol-part)
390 char* baseParFilename = FileSystem::BaseFileName(parFilename);
391 int mainBaseLen = 0;
392 if (!ParParser::ParseParFilename(baseParFilename, true, &mainBaseLen, nullptr))
393 {
394 // should not happen
395 nzbInfo->PrintMessage(Message::mkError, "Internal error: could not parse filename %s", baseParFilename);
396 return;
397 }
398 BString<1024> mainBaseFilename;
399 mainBaseFilename.Set(baseParFilename, mainBaseLen);
400 for (char* p = mainBaseFilename; *p; p++) *p = tolower(*p); // convert string to lowercase
401
402 for (FileInfo* fileInfo : nzbInfo->GetFileList())
403 {
404 int blockCount = 0;
405 if (ParParser::ParseParFilename(fileInfo->GetFilename(), fileInfo->GetFilenameConfirmed(), nullptr, &blockCount) &&
406 blockCount > 0)
407 {
408 bool useFile = true;
409
410 if (exactParName)
411 {
412 useFile = ParParser::SameParCollection(fileInfo->GetFilename(), FileSystem::BaseFileName(parFilename));
413 }
414 else if (strictParName)
415 {
416 // the pFileInfo->GetFilename() may be not confirmed and may contain
417 // additional texts if Subject could not be parsed correctly
418
419 BString<1024> loFileName = fileInfo->GetFilename();
420 for (char* p = loFileName; *p; p++) *p = tolower(*p); // convert string to lowercase
421
422 BString<1024> candidateFileName("%s.par2", *mainBaseFilename);
423 if (!strstr(loFileName, candidateFileName))
424 {
425 candidateFileName.Format("%s.vol", *mainBaseFilename);
426 useFile = strstr(loFileName, candidateFileName);
427 }
428 }
429
430 bool alreadyAdded = false;
431 // check if file is not in the list already
432 if (useFile)
433 {
434 for (BlockInfo& blockInfo : blocks)
435 {
436 if (blockInfo.m_fileInfo == fileInfo)
437 {
438 alreadyAdded = true;
439 break;
440 }
441 }
442 }
443
444 // if it is a par2-file with blocks and it was from the same NZB-request
445 // and it belongs to the same file collection (same base name),
446 // then OK, we can use it
447 if (useFile && !alreadyAdded)
448 {
449 blocks.emplace_back(fileInfo, blockCount);
450 *blockFound += blockCount;
451 }
452 }
453 }
454 }
455
456 void RepairController::UpdateParCheckProgress()
457 {
458 PostInfo* postInfo;
459
460 {
461 GuardedDownloadQueue guard = DownloadQueue::Guard();
462
463 postInfo = m_parChecker.GetPostInfo();
464 if (m_parChecker.GetFileProgress() == 0)
465 {
466 postInfo->SetProgressLabel(m_parChecker.GetProgressLabel());
467 }
468 postInfo->SetFileProgress(m_parChecker.GetFileProgress());
469 postInfo->SetStageProgress(m_parChecker.GetStageProgress());
470 PostInfo::EStage StageKind[] = {PostInfo::ptLoadingPars, PostInfo::ptVerifyingSources, PostInfo::ptRepairing, PostInfo::ptVerifyingRepaired};
471 PostInfo::EStage stage = StageKind[m_parChecker.GetStage()];
472 time_t current = Util::CurrentTime();
473
474 if (postInfo->GetStage() != stage)
475 {
476 postInfo->SetStage(stage);
477 postInfo->SetStageTime(current);
478 if (postInfo->GetStage() == PostInfo::ptRepairing)
479 {
480 m_parChecker.SetRepairTime(current);
481 }
482 else if (postInfo->GetStage() == PostInfo::ptVerifyingRepaired)
483 {
484 int repairSec = (int)(current - m_parChecker.GetRepairTime());
485 postInfo->GetNzbInfo()->SetRepairSec(postInfo->GetNzbInfo()->GetRepairSec() + repairSec);
486 }
487 }
488
489 bool parCancel = false;
490 if (!IsStopped())
491 {
492 if ((g_Options->GetParTimeLimit() > 0) &&
493 m_parChecker.GetStage() == PostParChecker::ptRepairing &&
494 ((g_Options->GetParTimeLimit() > 5 && current - postInfo->GetStageTime() > 5 * 60) ||
495 (g_Options->GetParTimeLimit() <= 5 && current - postInfo->GetStageTime() > 1 * 60)))
496 {
497 // first five (or one) minutes elapsed, now can check the estimated time
498 int estimatedRepairTime = (int)((current - postInfo->GetStartTime()) * 1000 /
499 (postInfo->GetStageProgress() > 0 ? postInfo->GetStageProgress() : 1));
500 if (estimatedRepairTime > g_Options->GetParTimeLimit() * 60)
501 {
502 debug("Estimated repair time %i seconds", estimatedRepairTime);
503 m_parChecker.PrintMessage(Message::mkWarning, "Cancelling par-repair for %s, estimated repair time (%i minutes) exceeds allowed repair time", m_parChecker.GetInfoName(), estimatedRepairTime / 60);
504 parCancel = true;
505 }
506 }
507 }
508
509 if (parCancel)
510 {
511 Stop();
512 }
513 }
514
515 CheckPauseState(postInfo);
516 }
517
518 void RepairController::CheckPauseState(PostInfo* postInfo)
519 {
520 if (g_Options->GetPausePostProcess() && !postInfo->GetNzbInfo()->GetForcePriority())
521 {
522 time_t stageTime = postInfo->GetStageTime();
523 time_t startTime = postInfo->GetStartTime();
524 time_t parTime = m_parChecker.GetParTime();
525 time_t repairTime = m_parChecker.GetRepairTime();
526 time_t waitTime = Util::CurrentTime();
527
528 // wait until Post-processor is unpaused
529 while (g_Options->GetPausePostProcess() && !postInfo->GetNzbInfo()->GetForcePriority() && !IsStopped())
530 {
531 usleep(50 * 1000);
532
533 // update time stamps
534
535 time_t delta = Util::CurrentTime() - waitTime;
536
537 if (stageTime > 0)
538 {
539 postInfo->SetStageTime(stageTime + delta);
540 }
541 if (startTime > 0)
542 {
543 postInfo->SetStartTime(startTime + delta);
544 }
545 if (parTime > 0)
546 {
547 m_parChecker.SetParTime(parTime + delta);
548 }
549 if (repairTime > 0)
550 {
551 m_parChecker.SetRepairTime(repairTime + delta);
552 }
553 }
554 }
555 }
556
557 #endif
0 /*
1 * This file is part of nzbget. See <http://nzbget.net>.
2 *
3 * Copyright (C) 2007-2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19
20 #ifndef REPAIR_H
21 #define REPAIR_H
22
23 #include "DownloadInfo.h"
24 #include "Thread.h"
25 #include "Script.h"
26
27 #ifndef DISABLE_PARCHECK
28 #include "ParChecker.h"
29 #include "DupeMatcher.h"
30 #endif
31
32 class RepairController : public Thread, public ScriptController
33 {
34 public:
35 RepairController();
36 virtual void Stop();
37
38 #ifndef DISABLE_PARCHECK
39 virtual void Run();
40 static void StartJob(PostInfo* postInfo);
41 bool AddPar(FileInfo* fileInfo, bool deleted);
42
43 protected:
44 void UpdateParCheckProgress();
45 void ParCheckCompleted();
46 void CheckPauseState(PostInfo* postInfo);
47 bool RequestMorePars(NzbInfo* nzbInfo, const char* parFilename, int blockNeeded, int* blockFound);
48
49 private:
50 class PostParChecker: public ParChecker
51 {
52 public:
53 PostInfo* GetPostInfo() { return m_postInfo; }
54 void SetPostInfo(PostInfo* postInfo) { m_postInfo = postInfo; }
55 time_t GetParTime() { return m_parTime; }
56 void SetParTime(time_t parTime) { m_parTime = parTime; }
57 time_t GetRepairTime() { return m_repairTime; }
58 void SetRepairTime(time_t repairTime) { m_repairTime = repairTime; }
59 int GetDownloadSec() { return m_downloadSec; }
60 void SetDownloadSec(int downloadSec) { m_downloadSec = downloadSec; }
61 protected:
62 virtual bool RequestMorePars(int blockNeeded, int* blockFound);
63 virtual void UpdateProgress();
64 virtual bool IsStopped() { return m_owner->IsStopped(); };
65 virtual void Completed() { m_owner->ParCheckCompleted(); }
66 virtual void PrintMessage(Message::EKind kind, const char* format, ...) PRINTF_SYNTAX(3);
67 virtual void RegisterParredFile(const char* filename);
68 virtual bool IsParredFile(const char* filename);
69 virtual EFileStatus FindFileCrc(const char* filename, uint32* crc, SegmentList* segments);
70 virtual void RequestDupeSources(DupeSourceList* dupeSourceList);
71 virtual void StatDupeSources(DupeSourceList* dupeSourceList);
72 private:
73 RepairController* m_owner;
74 PostInfo* m_postInfo;
75 time_t m_parTime;
76 time_t m_repairTime;
77 int m_downloadSec;
78
79 friend class RepairController;
80 };
81
82 class PostDupeMatcher: public DupeMatcher
83 {
84 public:
85 PostDupeMatcher(PostInfo* postInfo):
86 DupeMatcher(postInfo->GetNzbInfo()->GetDestDir(),
87 postInfo->GetNzbInfo()->GetSize() - postInfo->GetNzbInfo()->GetParSize()),
88 m_postInfo(postInfo) {}
89 protected:
90 virtual void PrintMessage(Message::EKind kind, const char* format, ...) PRINTF_SYNTAX(3);
91 private:
92 PostInfo* m_postInfo;
93 };
94
95 struct BlockInfo
96 {
97 FileInfo* m_fileInfo;
98 int m_blockCount;
99 BlockInfo(FileInfo* fileInfo, int blockCount) :
100 m_fileInfo(fileInfo), m_blockCount(blockCount) {}
101 };
102
103 typedef std::deque<BlockInfo> Blocks;
104
105 PostInfo* m_postInfo;
106 PostParChecker m_parChecker;
107
108 void FindPars(DownloadQueue* downloadQueue, NzbInfo* nzbInfo, const char* parFilename,
109 Blocks& blocks, bool strictParName, bool exactParName, int* blockFound);
110 #endif
111 };
112
113 #endif
8686
8787 if (unpack)
8888 {
89 bool scanNonStdFiles = m_postInfo->GetNzbInfo()->GetRenameStatus() > NzbInfo::rsSkipped ||
90 m_postInfo->GetNzbInfo()->GetParStatus() == NzbInfo::psSuccess ||
91 !m_hasParFiles;
92 CheckArchiveFiles(scanNonStdFiles);
89 CheckArchiveFiles();
9390 }
9491
9592 SetInfoName(m_infoName);
9693 SetWorkingDir(m_destDir);
9794
98 bool hasFiles = m_hasRarFiles || m_hasNonStdRarFiles || m_hasSevenZipFiles || m_hasSevenZipMultiFiles || m_hasSplittedFiles;
95 bool hasFiles = m_hasRarFiles || m_hasSevenZipFiles || m_hasSevenZipMultiFiles || m_hasSplittedFiles;
9996
10097 if (m_postInfo->GetUnpackTried() && !m_postInfo->GetParRepaired() &&
10198 (!m_password.Empty() || Util::EmptyStr(g_Options->GetUnpackPassFile()) || m_postInfo->GetPassListTried()))
106103 "%s failed: checksum error in the encrypted file. Corrupt file or wrong password." : "%s failed.",
107104 *m_infoNameUp);
108105 m_postInfo->GetNzbInfo()->SetUnpackStatus((NzbInfo::EUnpackStatus)m_postInfo->GetLastUnpackStatus());
109 m_postInfo->SetStage(PostInfo::ptQueued);
110106 }
111107 else if (unpack && hasFiles)
112108 {
114110
115111 CreateUnpackDir();
116112
117 if (m_hasRarFiles || m_hasNonStdRarFiles)
113 if (m_hasRarFiles)
118114 {
119115 UnpackArchives(upUnrar, false);
120116 }
138134
139135 m_joinedFiles.clear();
140136 }
141 else
142 {
143 PrintMessage(Message::mkInfo, (unpack ? "Nothing to unpack for %s" : "Unpack for %s skipped"), *m_name);
144
137 else if (unpack)
138 {
145139 #ifndef DISABLE_PARCHECK
146 if (unpack && m_postInfo->GetNzbInfo()->GetParStatus() <= NzbInfo::psSkipped &&
147 m_postInfo->GetNzbInfo()->GetRenameStatus() <= NzbInfo::rsSkipped && m_hasParFiles)
148 {
140 if (m_postInfo->GetNzbInfo()->GetParStatus() <= NzbInfo::psSkipped &&
141 m_postInfo->GetNzbInfo()->GetParRenameStatus() <= NzbInfo::rsSkipped &&
142 m_hasParFiles)
143 {
144 PrintMessage(Message::mkInfo, "Nothing to unpack for %s", *m_name);
149145 RequestParCheck(false);
150146 }
151147 else
152148 #endif
153 {
149 if (m_hasRenamedArchiveFiles)
150 {
151 PrintMessage(Message::mkError, "Could not unpack %s due to renamed archive files", *m_name);
152 m_postInfo->GetNzbInfo()->SetUnpackStatus(NzbInfo::usFailure);
153 }
154 else
155 {
156 PrintMessage(Message::mkInfo, "Nothing to unpack for %s", *m_name);
154157 m_postInfo->GetNzbInfo()->SetUnpackStatus(NzbInfo::usSkipped);
155 m_postInfo->SetStage(PostInfo::ptQueued);
156 }
157 }
158 }
159 }
160 else
161 {
162 PrintMessage(Message::mkInfo, "Unpack for %s skipped", *m_name);
163 m_postInfo->GetNzbInfo()->SetUnpackStatus(NzbInfo::usSkipped);
164 }
165
158166
159167 int unpackSec = (int)(Util::CurrentTime() - start);
160168 m_postInfo->GetNzbInfo()->SetUnpackSec(m_postInfo->GetNzbInfo()->GetUnpackSec() + unpackSec);
267275 params.emplace_back("-o+");
268276 }
269277
270 params.emplace_back(m_hasNonStdRarFiles ? "*.*" : "*.rar");
278 params.emplace_back("*.rar");
271279 params.push_back(FileSystem::MakeExtendedPath(BString<1024>("%s%c", *m_unpackDir, PATH_SEPARATOR), true));
272280 SetArgs(std::move(params));
273281 SetLogPrefix("Unrar");
531539 if (g_Options->GetParRename())
532540 {
533541 //request par-rename check for extracted files
534 m_postInfo->GetNzbInfo()->SetRenameStatus(NzbInfo::rsNone);
535 }
536 m_postInfo->SetStage(PostInfo::ptQueued);
542 m_postInfo->GetNzbInfo()->SetParRenameStatus(NzbInfo::rsNone);
543 }
537544 }
538545 else
539546 {
557564 m_unpackSpaceError ? NzbInfo::usSpace :
558565 m_unpackPasswordError || m_unpackDecryptError ? NzbInfo::usPassword :
559566 NzbInfo::usFailure);
560 m_postInfo->SetStage(PostInfo::ptQueued);
561567 }
562568 }
563569 }
568574 PrintMessage(Message::mkInfo, "%s requested %s", *m_infoNameUp, forceRepair ? "par-check with forced repair" : "par-check/repair");
569575 m_postInfo->SetRequestParCheck(true);
570576 m_postInfo->SetForceRepair(forceRepair);
571 m_postInfo->SetStage(PostInfo::ptFinished);
572577 m_postInfo->SetUnpackTried(true);
573578 m_postInfo->SetPassListTried(m_passListTried);
574579 m_postInfo->SetLastUnpackStatus((int)(m_unpackSpaceError ? NzbInfo::usSpace :
608613 }
609614 }
610615
611 void UnpackController::CheckArchiveFiles(bool scanNonStdFiles)
616 void UnpackController::CheckArchiveFiles()
612617 {
613618 m_hasRarFiles = false;
614 m_hasNonStdRarFiles = false;
619 m_hasRenamedArchiveFiles = false;
615620 m_hasSevenZipFiles = false;
616621 m_hasSevenZipMultiFiles = false;
617622 m_hasSplittedFiles = false;
618623
619624 RegEx regExRar(".*\\.rar$");
620 RegEx regExRarMultiSeq(".*\\.(r|s)[0-9][0-9]$");
625 RegEx regExRarMultiSeq(".*\\.[r-z][0-9][0-9]$");
621626 RegEx regExSevenZip(".*\\.7z$");
622627 RegEx regExSevenZipMulti(".*\\.7z\\.[0-9]+$");
623 RegEx regExNumExt(".*\\.[0-9]+$");
624628 RegEx regExSplitExt(".*\\.[a-z,0-9]{3}\\.[0-9]{3}$");
625629
626630 DirBrowser dir(m_destDir);
645649 {
646650 m_hasSevenZipMultiFiles = true;
647651 }
648 else if (scanNonStdFiles && !m_hasNonStdRarFiles && extNum > 1 &&
649 !regExRarMultiSeq.Match(filename) && regExNumExt.Match(filename) &&
652 else if (regExSplitExt.Match(filename) && (extNum == 0 || extNum == 1))
653 {
654 m_hasSplittedFiles = true;
655 }
656 else if (!m_hasRenamedArchiveFiles && !regExRarMultiSeq.Match(filename) &&
657 !Util::MatchFileExt(filename, g_Options->GetUnpackIgnoreExt(), ",;") &&
650658 FileHasRarSignature(fullFilename))
651659 {
652 m_hasNonStdRarFiles = true;
653 }
654 else if (regExSplitExt.Match(filename) && (extNum == 0 || extNum == 1))
655 {
656 m_hasSplittedFiles = true;
660 m_hasRenamedArchiveFiles = true;
657661 }
658662 }
659663 }
736740 RegEx regExRar(".*\\.rar$");
737741 RegEx regExRarMultiSeq(".*\\.[r-z][0-9][0-9]$");
738742 RegEx regExSevenZip(".*\\.7z$|.*\\.7z\\.[0-9]+$");
739 RegEx regExNumExt(".*\\.[0-9]+$");
740743 RegEx regExSplitExt(".*\\.[a-z,0-9]{3}\\.[0-9]{3}$");
741744
742745 DirBrowser dir(m_destDir);
748751 (m_interDir || !extractedFiles.Exists(filename)) &&
749752 (regExRar.Match(filename) || regExSevenZip.Match(filename) ||
750753 (regExRarMultiSeq.Match(filename) && FileHasRarSignature(fullFilename)) ||
751 (m_hasNonStdRarFiles && regExNumExt.Match(filename) && FileHasRarSignature(fullFilename)) ||
752754 (m_hasSplittedFiles && regExSplitExt.Match(filename) && m_joinedFiles.Exists(filename))))
753755 {
754756 PrintMessage(Message::mkInfo, "Deleting file %s", filename);
870872 m_unpackDecryptError = true;
871873 }
872874
873 if (m_unpacker == upUnrar && !strncmp(text, "Unrar: The specified password is incorrect.'", 43))
875 if (m_unpacker == upUnrar && !strncmp(text, "Unrar: The specified password is incorrect.", 43))
874876 {
875877 m_unpackPasswordError = true;
876878 }
7070 bool m_noFilesMessageReceived;
7171 bool m_hasParFiles;
7272 bool m_hasRarFiles;
73 bool m_hasNonStdRarFiles;
73 bool m_hasRenamedArchiveFiles;
7474 bool m_hasSevenZipFiles;
7575 bool m_hasSevenZipMultiFiles;
7676 bool m_hasSplittedFiles;
9595 void Completed();
9696 void CreateUnpackDir();
9797 bool Cleanup();
98 void CheckArchiveFiles(bool scanNonStdFiles);
98 void CheckArchiveFiles();
9999 void SetProgressLabel(const char* progressLabel);
100100 #ifndef DISABLE_PARCHECK
101101 void RequestParCheck(bool forceRepair);
264264 bool ok = true;
265265
266266 {
267 StateFile stateFile("queue", 57, true);
267 StateFile stateFile("queue", 59, true);
268268 if (!downloadQueue->GetQueue()->empty())
269269 {
270270 StateDiskFile* outfile = stateFile.BeginWrite();
287287
288288 if (saveHistory)
289289 {
290 StateFile stateFile("history", 57, true);
290 StateFile stateFile("history", 59, true);
291291 if (!downloadQueue->GetHistory()->empty())
292292 {
293293 StateDiskFile* outfile = stateFile.BeginWrite();
319319 int formatVersion = 0;
320320
321321 {
322 StateFile stateFile("queue", 57, true);
322 StateFile stateFile("queue", 59, true);
323323 if (stateFile.FileExists())
324324 {
325325 StateDiskFile* infile = stateFile.BeginRead();
348348
349349 if (formatVersion == 0 || formatVersion >= 57)
350350 {
351 StateFile stateFile("history", 57, true);
351 StateFile stateFile("history", 59, true);
352352 if (stateFile.FileExists())
353353 {
354354 StateDiskFile* infile = stateFile.BeginRead();
427427 outfile.PrintLine("%i,%i,%i,%i,%i", (int)nzbInfo->GetPriority(),
428428 nzbInfo->GetPostInfo() ? (int)nzbInfo->GetPostInfo()->GetStage() + 1 : 0,
429429 (int)nzbInfo->GetDeletePaused(), (int)nzbInfo->GetManyDupeFiles(), nzbInfo->GetFeedId());
430 outfile.PrintLine("%i,%i,%i,%i,%i,%i,%i", (int)nzbInfo->GetParStatus(), (int)nzbInfo->GetUnpackStatus(),
431 (int)nzbInfo->GetMoveStatus(), (int)nzbInfo->GetRenameStatus(), (int)nzbInfo->GetDeleteStatus(),
432 (int)nzbInfo->GetMarkStatus(), (int)nzbInfo->GetUrlStatus());
430 outfile.PrintLine("%i,%i,%i,%i,%i,%i,%i,%i", (int)nzbInfo->GetParStatus(), (int)nzbInfo->GetUnpackStatus(),
431 (int)nzbInfo->GetMoveStatus(), (int)nzbInfo->GetParRenameStatus(), (int)nzbInfo->GetRarRenameStatus(),
432 (int)nzbInfo->GetDeleteStatus(), (int)nzbInfo->GetMarkStatus(), (int)nzbInfo->GetUrlStatus());
433433 outfile.PrintLine("%i,%i,%i", (int)nzbInfo->GetUnpackCleanedUpDisk(), (int)nzbInfo->GetHealthPaused(),
434434 (int)nzbInfo->GetAddUrlPaused());
435435 outfile.PrintLine("%i,%i,%i", nzbInfo->GetFileCount(), nzbInfo->GetParkedFileCount(),
555555 if (postStage > 0)
556556 {
557557 nzbInfo->EnterPostProcess();
558 if (formatVersion < 59 && postStage == 6)
559 {
560 postStage++;
561 }
562 else if (formatVersion < 59 && postStage > 6)
563 {
564 postStage += 2;
565 }
558566 nzbInfo->GetPostInfo()->SetStage((PostInfo::EStage)postStage);
559567 }
560568 nzbInfo->SetFeedId(feedId);
561569
562 int parStatus, unpackStatus, moveStatus, renameStatus, deleteStatus, markStatus, urlStatus;
563 if (infile.ScanLine("%i,%i,%i,%i,%i,%i,%i", &parStatus, &unpackStatus, &moveStatus,
564 &renameStatus, &deleteStatus, &markStatus, &urlStatus) != 7) goto error;
570 int parStatus, unpackStatus, moveStatus, parRenameStatus, rarRenameStatus, deleteStatus, markStatus, urlStatus;
571 if (formatVersion < 58 && infile.ScanLine("%i,%i,%i,%i,%i,%i,%i", &parStatus, &unpackStatus, &moveStatus,
572 &parRenameStatus, &deleteStatus, &markStatus, &urlStatus) != 7) goto error;
573 rarRenameStatus = 0;
574 if (formatVersion >= 58 && infile.ScanLine("%i,%i,%i,%i,%i,%i,%i,%i", &parStatus, &unpackStatus, &moveStatus,
575 &parRenameStatus, &rarRenameStatus, &deleteStatus, &markStatus, &urlStatus) != 8) goto error;
565576 nzbInfo->SetParStatus((NzbInfo::EParStatus)parStatus);
566577 nzbInfo->SetUnpackStatus((NzbInfo::EUnpackStatus)unpackStatus);
567578 nzbInfo->SetMoveStatus((NzbInfo::EMoveStatus)moveStatus);
568 nzbInfo->SetRenameStatus((NzbInfo::ERenameStatus)renameStatus);
579 nzbInfo->SetParRenameStatus((NzbInfo::ERenameStatus)parRenameStatus);
580 nzbInfo->SetRarRenameStatus((NzbInfo::ERenameStatus)rarRenameStatus);
569581 nzbInfo->SetDeleteStatus((NzbInfo::EDeleteStatus)deleteStatus);
570582 nzbInfo->SetMarkStatus((NzbInfo::EMarkStatus)markStatus);
571583 if (nzbInfo->GetKind() == NzbInfo::nkNzb ||
335335 {
336336 rsNone,
337337 rsSkipped,
338 rsFailure,
338 rsNothing,
339339 rsSuccess
340340 };
341341
484484 void BuildDestDirName();
485485 CString BuildFinalDirName();
486486 CompletedFileList* GetCompletedFiles() { return &m_completedFiles; }
487 ERenameStatus GetRenameStatus() { return m_renameStatus; }
488 void SetRenameStatus(ERenameStatus renameStatus) { m_renameStatus = renameStatus; }
487 ERenameStatus GetParRenameStatus() { return m_parRenameStatus; }
488 void SetParRenameStatus(ERenameStatus renameStatus) { m_parRenameStatus = renameStatus; }
489 ERenameStatus GetRarRenameStatus() { return m_rarRenameStatus; }
490 void SetRarRenameStatus(ERenameStatus renameStatus) { m_rarRenameStatus = renameStatus; }
489491 EParStatus GetParStatus() { return m_parStatus; }
490492 void SetParStatus(EParStatus parStatus) { m_parStatus = parStatus; }
491493 EUnpackStatus GetUnpackStatus() { return m_unpackStatus; }
615617 time_t m_maxTime = 0;
616618 int m_priority = 0;
617619 CompletedFileList m_completedFiles;
618 ERenameStatus m_renameStatus = rsNone;
620 ERenameStatus m_parRenameStatus = rsNone;
621 ERenameStatus m_rarRenameStatus = rsNone;
619622 EParStatus m_parStatus = psNone;
620623 EUnpackStatus m_unpackStatus = usNone;
621624 ECleanupStatus m_cleanupStatus = csNone;
684687 ptVerifyingSources,
685688 ptRepairing,
686689 ptVerifyingRepaired,
687 ptRenaming,
690 ptParRenaming,
691 ptRarRenaming,
688692 ptUnpacking,
693 ptCleaningUp,
689694 ptMoving,
690695 ptExecutingScript,
691696 ptFinished
725730 void SetPassListTried(bool passListTried) { m_passListTried = passListTried; }
726731 int GetLastUnpackStatus() { return m_lastUnpackStatus; }
727732 void SetLastUnpackStatus(int unpackStatus) { m_lastUnpackStatus = unpackStatus; }
733 bool GetNeedParCheck() { return m_needParCheck; }
734 void SetNeedParCheck(bool needParCheck) { m_needParCheck = needParCheck; }
728735 Thread* GetPostThread() { return m_postThread; }
729736 void SetPostThread(Thread* postThread) { m_postThread = postThread; }
730737 ParredFiles* GetParredFiles() { return &m_parredFiles; }
740747 bool m_unpackTried = false;
741748 bool m_passListTried = false;
742749 int m_lastUnpackStatus = 0;
750 bool m_needParCheck = false;
743751 EStage m_stage = ptQueued;
744752 CString m_progressLabel = "";
745753 int m_fileProgress = 0;
866874 eaFilePauseExtraPars, // pause only (almost all) pars, except main par-file (does not affect other files)
867875 eaFileReorder, // set file order
868876 eaFileSplit, // split - create new group from selected files
869 eaGroupMoveOffset, // move group to m_iOffset relative to the current position in download-queue
877 eaGroupMoveOffset, // move group to offset relative to the current position in download-queue
870878 eaGroupMoveTop, // move group to the top of download-queue
871879 eaGroupMoveBottom, // move group to the bottom of download-queue
880 eaGroupMoveBefore, // move group to a certain position
881 eaGroupMoveAfter, // move group to a certain position
872882 eaGroupPause, // pause group
873883 eaGroupResume, // resume (unpause) group
874884 eaGroupDelete, // delete group and put to history, delete already downloaded files
917927 static GuardedDownloadQueue Guard() { return GuardedDownloadQueue(g_DownloadQueue, &g_DownloadQueue->m_lockMutex); }
918928 NzbList* GetQueue() { return &m_queue; }
919929 HistoryList* GetHistory() { return &m_history; }
920 virtual bool EditEntry(int ID, EEditAction action, int offset, const char* text) = 0;
921 virtual bool EditList(IdList* idList, NameList* nameList, EMatchMode matchMode, EEditAction action, int offset, const char* text) = 0;
930 virtual bool EditEntry(int ID, EEditAction action, const char* args) = 0;
931 virtual bool EditList(IdList* idList, NameList* nameList, EMatchMode matchMode, EEditAction action, const char* args) = 0;
922932 virtual void HistoryChanged() = 0;
923933 virtual void Save() = 0;
924934 void CalcRemainingSize(int64* remaining, int64* remainingForced);
286286 info("Moving collection %s with lower duplicate score to history", queuedNzbInfo->GetName());
287287 queuedNzbInfo->SetDeleteStatus(NzbInfo::dsDupe);
288288 downloadQueue->EditEntry(queuedNzbInfo->GetId(),
289 DownloadQueue::eaGroupDelete, 0, nullptr);
289 DownloadQueue::eaGroupDelete, nullptr);
290290 it = downloadQueue->GetQueue()->begin() + index;
291291 }
292292 }
231231 }
232232 }
233233
234 bool HistoryCoordinator::EditList(DownloadQueue* downloadQueue, IdList* idList, DownloadQueue::EEditAction action, int offset, const char* text)
234 bool HistoryCoordinator::EditList(DownloadQueue* downloadQueue, IdList* idList,
235 DownloadQueue::EEditAction action, const char* args)
235236 {
236237 bool ok = false;
237238 PrepareEdit(downloadQueue, idList, action);
269270 break;
270271
271272 case DownloadQueue::eaHistorySetParameter:
272 ok = HistorySetParameter(historyInfo, text);
273 ok = HistorySetParameter(historyInfo, args);
273274 break;
274275
275276 case DownloadQueue::eaHistorySetCategory:
276 ok = HistorySetCategory(historyInfo, text);
277 ok = HistorySetCategory(historyInfo, args);
277278 break;
278279
279280 case DownloadQueue::eaHistorySetName:
280 ok = HistorySetName(historyInfo, text);
281 ok = HistorySetName(historyInfo, args);
281282 break;
282283
283284 case DownloadQueue::eaHistorySetDupeKey:
284285 case DownloadQueue::eaHistorySetDupeScore:
285286 case DownloadQueue::eaHistorySetDupeMode:
286287 case DownloadQueue::eaHistorySetDupeBackup:
287 HistorySetDupeParam(historyInfo, action, text);
288 HistorySetDupeParam(historyInfo, action, args);
288289 break;
289290
290291 case DownloadQueue::eaHistoryMarkBad:
378379 {
379380 nzbInfo->SetUnpackStatus(NzbInfo::usNone);
380381 nzbInfo->SetCleanupStatus(NzbInfo::csNone);
381 nzbInfo->SetRenameStatus(NzbInfo::rsNone);
382 nzbInfo->SetParRenameStatus(NzbInfo::rsNone);
383 nzbInfo->SetRarRenameStatus(NzbInfo::rsNone);
382384 nzbInfo->SetPostTotalSec(nzbInfo->GetPostTotalSec() - nzbInfo->GetUnpackSec());
383385 nzbInfo->SetUnpackSec(0);
384386
488490 nzbInfo->SetMoveStatus(NzbInfo::msNone);
489491 nzbInfo->SetUnpackCleanedUpDisk(false);
490492 nzbInfo->SetParStatus(NzbInfo::psNone);
491 nzbInfo->SetRenameStatus(NzbInfo::rsNone);
493 nzbInfo->SetParRenameStatus(NzbInfo::rsNone);
494 nzbInfo->SetRarRenameStatus(NzbInfo::rsNone);
492495 nzbInfo->SetDownloadedSize(0);
493496 nzbInfo->SetDownloadSec(0);
494497 nzbInfo->SetPostTotalSec(0);
630633
631634 if (g_Options->GetParCheck() != Options::pcForce)
632635 {
633 downloadQueue->EditEntry(nzbInfo->GetId(), DownloadQueue::eaGroupPauseExtraPars, 0, nullptr);
636 downloadQueue->EditEntry(nzbInfo->GetId(), DownloadQueue::eaGroupPauseExtraPars, nullptr);
634637 }
635638 }
636639
2727 {
2828 public:
2929 void AddToHistory(DownloadQueue* downloadQueue, NzbInfo* nzbInfo);
30 bool EditList(DownloadQueue* downloadQueue, IdList* idList, DownloadQueue::EEditAction action, int offset, const char* text);
30 bool EditList(DownloadQueue* downloadQueue, IdList* idList, DownloadQueue::EEditAction action, const char* args);
3131 void DeleteDiskFiles(NzbInfo* nzbInfo);
3232 void HistoryHide(DownloadQueue* downloadQueue, HistoryInfo* historyInfo, int rindex);
3333 void Redownload(DownloadQueue* downloadQueue, HistoryInfo* historyInfo);
3131 #include "StatMeter.h"
3232
3333 bool QueueCoordinator::CoordinatorDownloadQueue::EditEntry(
34 int ID, EEditAction action, int offset, const char* text)
35 {
36 return m_owner->m_queueEditor.EditEntry(&m_owner->m_downloadQueue, ID, action, offset, text);
34 int ID, EEditAction action, const char* args)
35 {
36 return m_owner->m_queueEditor.EditEntry(&m_owner->m_downloadQueue, ID, action, args);
3737 }
3838
3939 bool QueueCoordinator::CoordinatorDownloadQueue::EditList(
40 IdList* idList, NameList* nameList, EMatchMode matchMode, EEditAction action, int offset, const char* text)
40 IdList* idList, NameList* nameList, EMatchMode matchMode, EEditAction action, const char* args)
4141 {
4242 m_massEdit = true;
43 bool ret = m_owner->m_queueEditor.EditList(&m_owner->m_downloadQueue, idList, nameList, matchMode, action, offset, text);
43 bool ret = m_owner->m_queueEditor.EditList(&m_owner->m_downloadQueue, idList, nameList, matchMode, action, args);
4444 m_massEdit = false;
4545 if (m_wantSave)
4646 {
255255 }
256256 }
257257
258 WaitJobs();
259 SavePartialState();
260
261 debug("Exiting QueueCoordinator-loop");
262 }
263
264 void QueueCoordinator::WaitJobs()
265 {
258266 // waiting for downloads
259267 debug("QueueCoordinator: waiting for Downloads to complete");
260 bool completed = false;
261 while (!completed)
268
269 while (true)
262270 {
263271 {
264272 GuardedDownloadQueue guard = DownloadQueue::Guard();
265 completed = m_activeDownloads.size() == 0;
273 if (m_activeDownloads.empty())
274 {
275 break;
276 }
266277 }
267278 usleep(100 * 1000);
268279 ResetHangingDownloads();
269280 }
281
270282 debug("QueueCoordinator: Downloads are completed");
271
272 SavePartialState();
273
274 debug("Exiting QueueCoordinator-loop");
275283 }
276284
277285 /*
370378 {
371379 // in a case if none of listeners did already delete the temporary object - we do it ourselves
372380 downloadQueue->GetQueue()->Remove(addedNzb);
373 addedNzb = nullptr;
381 if (!downloadQueue->GetHistory()->Find(addedNzb->GetId()))
382 {
383 addedNzb = nullptr;
384 }
374385 }
375386
376387 downloadQueue->Save();
556567 debug("Article downloaded");
557568
558569 FileInfo* fileInfo = articleDownloader->GetFileInfo();
559 NzbInfo* nzbInfo = fileInfo->GetNzbInfo();
560 ArticleInfo* articleInfo = articleDownloader->GetArticleInfo();
561 bool retry = false;
562 bool fileCompleted = false;
563
564 {
570 bool completeFileParts = false;
571
572 {
573 NzbInfo* nzbInfo = fileInfo->GetNzbInfo();
574 ArticleInfo* articleInfo = articleDownloader->GetArticleInfo();
575 bool retry = false;
576 bool fileCompleted = false;
577
565578 GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
566579
567580 if (articleDownloader->GetStatus() == ArticleDownloader::adFinished)
630643 {
631644 fileCompleted = true;
632645 }
633 }
634
635 bool deleteFileObj = false;
636
637 if (fileCompleted && (!fileInfo->GetDeleted() || nzbInfo->GetParking()))
646
647 completeFileParts = fileCompleted && (!fileInfo->GetDeleted() || nzbInfo->GetParking());
648
649 if (!completeFileParts)
650 {
651 DeleteDownloader(downloadQueue, articleDownloader, false);
652 }
653 }
654
655 if (completeFileParts)
638656 {
639657 // all jobs done
640658 articleDownloader->CompleteFileParts();
641659 fileInfo->SetPartialChanged(false);
642 deleteFileObj = true;
643 }
644
645 {
660
646661 GuardedDownloadQueue downloadQueue = DownloadQueue::Guard();
647
648 bool hasOtherDownloaders = fileInfo->GetActiveDownloads() > 1;
649 deleteFileObj |= fileInfo->GetDeleted() && !hasOtherDownloaders;
650
651 // remove downloader from downloader list
652 m_activeDownloads.erase(std::find(m_activeDownloads.begin(), m_activeDownloads.end(), articleDownloader));
653
654 fileInfo->SetActiveDownloads(fileInfo->GetActiveDownloads() - 1);
655 nzbInfo->SetActiveDownloads(nzbInfo->GetActiveDownloads() - 1);
656
657 if (deleteFileObj)
658 {
659 DeleteFileInfo(downloadQueue, fileInfo, fileCompleted);
660 downloadQueue->Save();
661 }
662 DeleteDownloader(downloadQueue, articleDownloader, true);
663 }
664 }
665
666 void QueueCoordinator::DeleteDownloader(DownloadQueue* downloadQueue,
667 ArticleDownloader* articleDownloader, bool fileCompleted)
668 {
669 FileInfo* fileInfo = articleDownloader->GetFileInfo();
670 NzbInfo* nzbInfo = fileInfo->GetNzbInfo();
671 bool hasOtherDownloaders = fileInfo->GetActiveDownloads() > 1;
672 bool deleteFileObj = fileCompleted || (fileInfo->GetDeleted() && !hasOtherDownloaders);
673
674 // remove downloader from downloader list
675 m_activeDownloads.erase(std::find(m_activeDownloads.begin(), m_activeDownloads.end(), articleDownloader));
676
677 fileInfo->SetActiveDownloads(fileInfo->GetActiveDownloads() - 1);
678 nzbInfo->SetActiveDownloads(nzbInfo->GetActiveDownloads() - 1);
679
680 if (deleteFileObj)
681 {
682 DeleteFileInfo(downloadQueue, fileInfo, fileCompleted);
683 downloadQueue->Save();
662684 }
663685 }
664686
807829 warn("Pausing %s due to health %.1f%% below critical %.1f%%", fileInfo->GetNzbInfo()->GetName(),
808830 fileInfo->GetNzbInfo()->CalcHealth() / 10.0, fileInfo->GetNzbInfo()->CalcCriticalHealth(true) / 10.0);
809831 fileInfo->GetNzbInfo()->SetHealthPaused(true);
810 downloadQueue->EditEntry(fileInfo->GetNzbInfo()->GetId(), DownloadQueue::eaGroupPause, 0, nullptr);
832 downloadQueue->EditEntry(fileInfo->GetNzbInfo()->GetId(), DownloadQueue::eaGroupPause, nullptr);
811833 }
812834 else if (g_Options->GetHealthCheck() == Options::hcDelete ||
813835 g_Options->GetHealthCheck() == Options::hcPark)
819841 fileInfo->GetNzbInfo()->SetDeleteStatus(NzbInfo::dsHealth);
820842 downloadQueue->EditEntry(fileInfo->GetNzbInfo()->GetId(),
821843 g_Options->GetHealthCheck() == Options::hcPark ? DownloadQueue::eaGroupParkDelete : DownloadQueue::eaGroupDelete,
822 0, nullptr);
844 nullptr);
823845 }
824846 }
825847
5959 class CoordinatorDownloadQueue : public DownloadQueue
6060 {
6161 public:
62 virtual bool EditEntry(int ID, EEditAction action, int offset, const char* text);
62 virtual bool EditEntry(int ID, EEditAction action, const char* args);
6363 virtual bool EditList(IdList* idList, NameList* nameList, EMatchMode matchMode,
64 EEditAction action, int offset, const char* text);
64 EEditAction action, const char* args);
6565 virtual void HistoryChanged() { m_historyChanged = true; }
6666 virtual void Save();
6767 private:
8282 bool GetNextArticle(DownloadQueue* downloadQueue, FileInfo* &fileInfo, ArticleInfo* &articleInfo);
8383 void StartArticleDownload(FileInfo* fileInfo, ArticleInfo* articleInfo, NntpConnection* connection);
8484 void ArticleCompleted(ArticleDownloader* articleDownloader);
85 void DeleteDownloader(DownloadQueue* downloadQueue, ArticleDownloader* articleDownloader, bool fileCompleted);
8586 void DeleteFileInfo(DownloadQueue* downloadQueue, FileInfo* fileInfo, bool completed);
8687 void CheckHealth(DownloadQueue* downloadQueue, FileInfo* fileInfo);
8788 void ResetHangingDownloads();
8990 void Load();
9091 void SavePartialState();
9192 void LoadPartialState(FileInfo* fileInfo);
93 void WaitJobs();
9294 };
9395
9496 extern QueueCoordinator* g_QueueCoordinator;
6161 QueueEditor::ItemList* m_sortItemList;
6262 ESortCriteria m_sortCriteria;
6363 ESortOrder m_sortOrder;
64
65 void AlignSelectedGroups();
6664 };
6765
6866 bool GroupSorter::Execute(const char* sort)
111109 m_sortOrder = soAuto;
112110 }
113111
114 AlignSelectedGroups();
115
116112 RawNzbList tempList;
117113 for (NzbInfo* nzbInfo : m_nzbList)
118114 {
125121 m_sortOrder = soDescending;
126122 }
127123
128 std::sort(m_nzbList->begin(), m_nzbList->end(), *this);
124 std::stable_sort(m_nzbList->begin(), m_nzbList->end(), *this);
129125
130126 if (origSortOrder == soAuto &&
131127 std::equal(tempList.begin(), tempList.end(), m_nzbList->begin(),
135131 }))
136132 {
137133 m_sortOrder = m_sortOrder == soDescending ? soAscending : soDescending;
138 std::sort(m_nzbList->begin(), m_nzbList->end(), *this);
134 std::stable_sort(m_nzbList->begin(), m_nzbList->end(), *this);
139135 }
140136
141137 return true;
199195 return ret;
200196 }
201197
202 void GroupSorter::AlignSelectedGroups()
203 {
204 NzbInfo* lastNzbInfo = nullptr;
205 uint32 lastNum = 0;
206 uint32 num = 0;
207 while (num < m_nzbList->size())
208 {
209 std::unique_ptr<NzbInfo>& nzbInfo = m_nzbList->at(num);
210
211 bool selected = false;
212 for (QueueEditor::EditItem& item : m_sortItemList)
213 {
214 if (item.m_nzbInfo == nzbInfo.get())
215 {
216 selected = true;
217 break;
218 }
219 }
220
221 if (selected)
222 {
223 if (lastNzbInfo && num - lastNum > 1)
224 {
225 std::unique_ptr<NzbInfo> movedNzbInfo = std::move(*(m_nzbList->begin() + num));
226 m_nzbList->erase(m_nzbList->begin() + num);
227 m_nzbList->insert(m_nzbList->begin() + lastNum + 1, std::move(movedNzbInfo));
228 lastNum++;
229 }
230 else
231 {
232 lastNum = num;
233 }
234 lastNzbInfo = nzbInfo.get();
235 }
236 num++;
237 }
238 }
239
240198
241199 FileInfo* QueueEditor::FindFileInfo(int id)
242200 {
251209 return nullptr;
252210 }
253211
254 /*
255 * Set the pause flag of the specific entry in the queue
256 */
257212 void QueueEditor::PauseUnpauseEntry(FileInfo* fileInfo, bool pause)
258213 {
259214 fileInfo->SetPaused(pause);
260215 }
261216
262 /*
263 * Removes entry
264 */
265217 void QueueEditor::DeleteEntry(FileInfo* fileInfo)
266218 {
267219 if (!fileInfo->GetDeleted())
273225 }
274226 }
275227
276 /*
277 * Moves entry in the queue
278 */
279228 void QueueEditor::MoveEntry(FileInfo* fileInfo, int offset)
280229 {
281230 int entry = 0;
308257 }
309258 }
310259
311 /*
312 * Moves group in the queue
313 */
314260 void QueueEditor::MoveGroup(NzbInfo* nzbInfo, int offset)
315261 {
316262 int entry = 0;
343289 }
344290 }
345291
346 bool QueueEditor::EditEntry(DownloadQueue* downloadQueue, int ID, DownloadQueue::EEditAction action, int offset, const char* text)
292 bool QueueEditor::EditEntry(DownloadQueue* downloadQueue, int ID, DownloadQueue::EEditAction action, const char* args)
347293 {
348294 m_downloadQueue = downloadQueue;
349295 IdList cIdList;
350296 cIdList.push_back(ID);
351 return InternEditList(nullptr, &cIdList, action, offset, text);
297 return InternEditList(nullptr, &cIdList, action, args);
352298 }
353299
354300 bool QueueEditor::EditList(DownloadQueue* downloadQueue, IdList* idList, NameList* nameList, DownloadQueue::EMatchMode matchMode,
355 DownloadQueue::EEditAction action, int offset, const char* text)
301 DownloadQueue::EEditAction action, const char* args)
356302 {
357303 if (action == DownloadQueue::eaPostDelete)
358304 {
359 return g_PrePostProcessor->EditList(downloadQueue, idList, action, offset, text);
305 return g_PrePostProcessor->EditList(downloadQueue, idList, action, args);
360306 }
361307 else if (DownloadQueue::eaHistoryDelete <= action && action <= DownloadQueue::eaHistorySetName)
362308 {
363 return g_HistoryCoordinator->EditList(downloadQueue, idList, action, offset, text);
309 return g_HistoryCoordinator->EditList(downloadQueue, idList, action, args);
364310 }
365311
366312 m_downloadQueue = downloadQueue;
375321 ok = BuildIdListFromNameList(idList, nameList, matchMode, action);
376322 }
377323
378 ok = ok && (InternEditList(nullptr, idList, action, offset, text) || matchMode == DownloadQueue::mmRegEx);
324 ok = ok && (InternEditList(nullptr, idList, action, args) || matchMode == DownloadQueue::mmRegEx);
379325
380326 m_downloadQueue->Save();
381327
383329 }
384330
385331 bool QueueEditor::InternEditList(ItemList* itemList,
386 IdList* idList, DownloadQueue::EEditAction action, int offset, const char* text)
332 IdList* idList, DownloadQueue::EEditAction action, const char* args)
387333 {
388334 ItemList workItems;
389335 if (!itemList)
390336 {
391337 itemList = &workItems;
338 int offset = args && (action == DownloadQueue::eaFileMoveOffset ||
339 action == DownloadQueue::eaGroupMoveOffset) ? atoi(args) : 0;
392340 PrepareList(itemList, idList, action, offset);
393341 }
394342
403351 return MergeGroups(itemList);
404352
405353 case DownloadQueue::eaGroupSort:
406 return SortGroups(itemList, text);
354 return SortGroups(itemList, args);
355
356 case DownloadQueue::eaGroupMoveAfter:
357 case DownloadQueue::eaGroupMoveBefore:
358 return MoveGroupsTo(itemList, idList, action == DownloadQueue::eaGroupMoveBefore, args);
407359
408360 case DownloadQueue::eaFileSplit:
409 return SplitGroup(itemList, text);
361 return SplitGroup(itemList, args);
410362
411363 case DownloadQueue::eaFileReorder:
412364 ReorderFiles(itemList);
436388 break;
437389
438390 case DownloadQueue::eaGroupSetPriority:
439 SetNzbPriority(item.m_nzbInfo, text);
391 SetNzbPriority(item.m_nzbInfo, args);
440392 break;
441393
442394 case DownloadQueue::eaGroupSetCategory:
443395 case DownloadQueue::eaGroupApplyCategory:
444 SetNzbCategory(item.m_nzbInfo, text, action == DownloadQueue::eaGroupApplyCategory);
396 SetNzbCategory(item.m_nzbInfo, args, action == DownloadQueue::eaGroupApplyCategory);
445397 break;
446398
447399 case DownloadQueue::eaGroupSetName:
448 SetNzbName(item.m_nzbInfo, text);
400 SetNzbName(item.m_nzbInfo, args);
449401 break;
450402
451403 case DownloadQueue::eaGroupSetDupeKey:
452404 case DownloadQueue::eaGroupSetDupeScore:
453405 case DownloadQueue::eaGroupSetDupeMode:
454 SetNzbDupeParam(item.m_nzbInfo, action, text);
406 SetNzbDupeParam(item.m_nzbInfo, action, args);
455407 break;
456408
457409 case DownloadQueue::eaGroupSetParameter:
458 SetNzbParameter(item.m_nzbInfo, text);
410 SetNzbParameter(item.m_nzbInfo, args);
459411 break;
460412
461413 case DownloadQueue::eaGroupMoveTop:
468420 case DownloadQueue::eaGroupResume:
469421 case DownloadQueue::eaGroupPauseAllPars:
470422 case DownloadQueue::eaGroupPauseExtraPars:
471 EditGroup(item.m_nzbInfo, action, offset, text);
423 EditGroup(item.m_nzbInfo, action, args);
472424 break;
473425
474426 case DownloadQueue::eaGroupDelete:
481433 }
482434 else
483435 {
484 EditGroup(item.m_nzbInfo, action, offset, text);
436 EditGroup(item.m_nzbInfo, action, args);
485437 }
486438
487439
565517 }
566518 }
567519 }
568 else if ((offset != 0) &&
569 (action == DownloadQueue::eaGroupMoveOffset || action == DownloadQueue::eaGroupMoveTop || action == DownloadQueue::eaGroupMoveBottom))
520 else if (((offset != 0) &&
521 (action == DownloadQueue::eaGroupMoveOffset || action == DownloadQueue::eaGroupMoveTop || action == DownloadQueue::eaGroupMoveBottom)) ||
522 action == DownloadQueue::eaGroupMoveBefore || action == DownloadQueue::eaGroupMoveAfter)
570523 {
571524 // add IDs to list in order they currently have in download queue
572 // per group only one FileInfo is added to the list
573525 int nrEntries = (int)m_downloadQueue->GetQueue()->size();
574526 int lastDestPos = -1;
575527 int start, end, step;
576 if (offset < 0)
528 if (offset <= 0)
577529 {
578530 start = 0;
579531 end = nrEntries;
755707 return true;
756708 }
757709
758 bool QueueEditor::EditGroup(NzbInfo* nzbInfo, DownloadQueue::EEditAction action, int offset, const char* text)
710 bool QueueEditor::EditGroup(NzbInfo* nzbInfo, DownloadQueue::EEditAction action, const char* args)
759711 {
760712 ItemList itemList;
761713 bool allPaused = true;
802754 DownloadQueue::eaFileMoveOffset,
803755 DownloadQueue::eaFileMoveTop,
804756 DownloadQueue::eaFileMoveBottom,
757 (DownloadQueue::EEditAction)0,
758 (DownloadQueue::EEditAction)0,
805759 DownloadQueue::eaFilePause,
806760 DownloadQueue::eaFileResume,
807761 DownloadQueue::eaFileDelete,
815769 (DownloadQueue::EEditAction)0,
816770 (DownloadQueue::EEditAction)0 };
817771
818 bool ok = InternEditList(&itemList, nullptr, GroupToFileMap[action], offset, text);
772 bool ok = InternEditList(&itemList, nullptr, GroupToFileMap[action], args);
819773
820774 if ((action == DownloadQueue::eaGroupDelete || action == DownloadQueue::eaGroupDupeDelete || action == DownloadQueue::eaGroupFinalDelete) &&
821775 // NZBInfo could have been destroyed already
949903 debug("QueueEditor: setting category '%s' for '%s'", category, nzbInfo->GetName());
950904
951905 bool oldUnpack = g_Options->GetUnpack();
952 const char* oldPostScript = g_Options->GetPostScript();
906 const char* oldExtensions = g_Options->GetExtensions();
953907 if (applyParams && !Util::EmptyStr(nzbInfo->GetCategory()))
954908 {
955909 Options::Category* categoryObj = g_Options->FindCategory(nzbInfo->GetCategory(), false);
956910 if (categoryObj)
957911 {
958912 oldUnpack = categoryObj->GetUnpack();
959 if (!Util::EmptyStr(categoryObj->GetPostScript()))
960 {
961 oldPostScript = categoryObj->GetPostScript();
913 if (!Util::EmptyStr(categoryObj->GetExtensions()))
914 {
915 oldExtensions = categoryObj->GetExtensions();
962916 }
963917 }
964918 }
971925 }
972926
973927 bool newUnpack = g_Options->GetUnpack();
974 const char* newPostScript = g_Options->GetPostScript();
928 const char* newExtensions = g_Options->GetExtensions();
975929 if (!Util::EmptyStr(nzbInfo->GetCategory()))
976930 {
977931 Options::Category* categoryObj = g_Options->FindCategory(nzbInfo->GetCategory(), false);
978932 if (categoryObj)
979933 {
980934 newUnpack = categoryObj->GetUnpack();
981 if (!Util::EmptyStr(categoryObj->GetPostScript()))
982 {
983 newPostScript = categoryObj->GetPostScript();
935 if (!Util::EmptyStr(categoryObj->GetExtensions()))
936 {
937 newExtensions = categoryObj->GetExtensions();
984938 }
985939 }
986940 }
990944 nzbInfo->GetParameters()->SetParameter("*Unpack:", newUnpack ? "yes" : "no");
991945 }
992946
993 if (strcasecmp(oldPostScript, newPostScript))
947 if (strcasecmp(oldExtensions, newExtensions))
994948 {
995949 // add new params not existed in old category
996 Tokenizer tokNew(newPostScript, ",;");
950 Tokenizer tokNew(newExtensions, ",;");
997951 while (const char* newScriptName = tokNew.Next())
998952 {
999953 bool found = false;
1000954 const char* oldScriptName;
1001 Tokenizer tokOld(oldPostScript, ",;");
955 Tokenizer tokOld(oldExtensions, ",;");
1002956 while ((oldScriptName = tokOld.Next()) && !found)
1003957 {
1004958 found = !strcasecmp(newScriptName, oldScriptName);
1010964 }
1011965
1012966 // remove old params not existed in new category
1013 Tokenizer tokOld(oldPostScript, ",;");
967 Tokenizer tokOld(oldExtensions, ",;");
1014968 while (const char* oldScriptName = tokOld.Next())
1015969 {
1016970 bool found = false;
1017971 const char* newScriptName;
1018 Tokenizer tokNew(newPostScript, ",;");
972 Tokenizer tokNew(newExtensions, ",;");
1019973 while ((newScriptName = tokNew.Next()) && !found)
1020974 {
1021975 found = !strcasecmp(newScriptName, oldScriptName);
10831037
10841038 bool QueueEditor::SortGroups(ItemList* itemList, const char* sort)
10851039 {
1040 AlignGroups(itemList);
10861041 GroupSorter sorter(m_downloadQueue->GetQueue(), itemList);
10871042 return sorter.Execute(sort);
1043 }
1044
1045 void QueueEditor::AlignGroups(ItemList* itemList)
1046 {
1047 NzbList* nzbList = m_downloadQueue->GetQueue();
1048 NzbInfo* lastNzbInfo = nullptr;
1049 uint32 lastNum = 0;
1050 uint32 num = 0;
1051 while (num < nzbList->size())
1052 {
1053 std::unique_ptr<NzbInfo>& nzbInfo = nzbList->at(num);
1054
1055 bool selected = false;
1056 for (QueueEditor::EditItem& item : itemList)
1057 {
1058 if (item.m_nzbInfo == nzbInfo.get())
1059 {
1060 selected = true;
1061 break;
1062 }
1063 }
1064
1065 if (selected)
1066 {
1067 if (lastNzbInfo && num - lastNum > 1)
1068 {
1069 std::unique_ptr<NzbInfo> movedNzbInfo = std::move(*(nzbList->begin() + num));
1070 nzbList->erase(nzbList->begin() + num);
1071 nzbList->insert(nzbList->begin() + lastNum + 1, std::move(movedNzbInfo));
1072 lastNum++;
1073 }
1074 else
1075 {
1076 lastNum = num;
1077 }
1078 lastNzbInfo = nzbInfo.get();
1079 }
1080 num++;
1081 }
1082 }
1083
1084 bool QueueEditor::ItemListContainsItem(ItemList* itemList, int id)
1085 {
1086 return std::find_if(itemList->begin(), itemList->end(),
1087 [id](const EditItem& item)
1088 {
1089 return item.m_nzbInfo->GetId() == id;
1090 }) != itemList->end();
1091 };
1092
1093 bool QueueEditor::MoveGroupsTo(ItemList* itemList, IdList* idList, bool before, const char* args)
1094 {
1095 if (itemList->size() == 0 || Util::EmptyStr(args))
1096 {
1097 return false;
1098 }
1099
1100 int targetId = atoi(args);
1101 int offset = 0;
1102
1103 // check if target is in list of moved items
1104 if (ItemListContainsItem(itemList, targetId))
1105 {
1106 // find the next item to use as target-before
1107 bool found = false;
1108 bool targetSet = false;
1109
1110 for (NzbInfo* nzbInfo : m_downloadQueue->GetQueue())
1111 {
1112 if (found)
1113 {
1114 if (!ItemListContainsItem(itemList, nzbInfo->GetId()))
1115 {
1116 targetId = nzbInfo->GetId();
1117 before = true;
1118 targetSet = true;
1119 break;
1120 }
1121 }
1122 else if (targetId == nzbInfo->GetId())
1123 {
1124 found = true;
1125 }
1126 }
1127
1128 if (!targetSet)
1129 {
1130 // there are no next item; move to the bottom then
1131 offset = MAX_ID;
1132 }
1133 }
1134
1135 AlignGroups(itemList);
1136
1137 if (offset == 0)
1138 {
1139 // calculate offset between first moving item and target
1140 int moveId = itemList->at(0).m_nzbInfo->GetId();
1141 bool progress = false;
1142 int step = 0;
1143 for (NzbInfo* nzbInfo : m_downloadQueue->GetQueue())
1144 {
1145 int id = nzbInfo->GetId();
1146 if (id == targetId || id == moveId)
1147 {
1148 if (!progress)
1149 {
1150 step = id == targetId ? -1 : 1;
1151 offset = (before ? 0 : 1) - (step > 0 ? itemList->size() : 0);
1152 progress = true;
1153 }
1154 else
1155 {
1156 break;
1157 }
1158 }
1159
1160 if (progress)
1161 {
1162 offset += step;
1163 }
1164 }
1165 }
1166
1167 return InternEditList(nullptr, idList, DownloadQueue::eaGroupMoveOffset,
1168 CString::FormatStr("%i", offset));
10881169 }
10891170
10901171 void QueueEditor::ReorderFiles(ItemList* itemList)
2525 class QueueEditor
2626 {
2727 public:
28 bool EditEntry(DownloadQueue* downloadQueue, int ID, DownloadQueue::EEditAction action, int offset, const char* text);
29 bool EditList(DownloadQueue* downloadQueue, IdList* idList, NameList* nameList, DownloadQueue::EMatchMode matchMode, DownloadQueue::EEditAction action, int offset, const char* text);
28 bool EditEntry(DownloadQueue* downloadQueue, int ID, DownloadQueue::EEditAction action, const char* args);
29 bool EditList(DownloadQueue* downloadQueue, IdList* idList, NameList* nameList, DownloadQueue::EMatchMode matchMode, DownloadQueue::EEditAction action, const char* args);
3030
3131 private:
3232 class EditItem
4545 DownloadQueue* m_downloadQueue;
4646
4747 FileInfo* FindFileInfo(int id);
48 bool InternEditList(ItemList* itemList, IdList* idList, DownloadQueue::EEditAction action, int offset, const char* text);
48 bool InternEditList(ItemList* itemList, IdList* idList, DownloadQueue::EEditAction action, const char* args);
4949 void PrepareList(ItemList* itemList, IdList* idList, DownloadQueue::EEditAction action, int offset);
5050 bool BuildIdListFromNameList(IdList* idList, NameList* nameList, DownloadQueue::EMatchMode matchMode, DownloadQueue::EEditAction action);
51 bool EditGroup(NzbInfo* nzbInfo, DownloadQueue::EEditAction action, int offset, const char* text);
51 bool EditGroup(NzbInfo* nzbInfo, DownloadQueue::EEditAction action, const char* args);
5252 void PauseParsInGroups(ItemList* itemList, bool extraParsOnly);
5353 void PausePars(RawFileList* fileList, bool extraParsOnly);
5454 void SetNzbPriority(NzbInfo* nzbInfo, const char* priority);
5656 void SetNzbName(NzbInfo* nzbInfo, const char* name);
5757 bool MergeGroups(ItemList* itemList);
5858 bool SortGroups(ItemList* itemList, const char* sort);
59 void AlignGroups(ItemList* itemList);
60 bool MoveGroupsTo(ItemList* itemList, IdList* idList, bool before, const char* args);
5961 bool SplitGroup(ItemList* itemList, const char* name);
6062 bool DeleteUrl(NzbInfo* nzbInfo, DownloadQueue::EEditAction action);
6163 void ReorderFiles(ItemList* itemList);
6264 void SetNzbParameter(NzbInfo* nzbInfo, const char* paramString);
63 void SetNzbDupeParam(NzbInfo* nzbInfo, DownloadQueue::EEditAction action, const char* text);
65 void SetNzbDupeParam(NzbInfo* nzbInfo, DownloadQueue::EEditAction action, const char* args);
6466 void PauseUnpauseEntry(FileInfo* fileInfo, bool pause);
6567 void DeleteEntry(FileInfo* fileInfo);
6668 void MoveEntry(FileInfo* fileInfo, int offset);
6769 void MoveGroup(NzbInfo* nzbInfo, int offset);
70 bool ItemListContainsItem(ItemList* itemList, int id);
6871
6972 friend class GroupSorter;
7073 };
7171 void Scanner::InitOptions()
7272 {
7373 m_nzbDirInterval = g_Options->GetNzbDirInterval() * 1000;
74 const char* scanScript = g_Options->GetScanScript();
75 m_scanScript = scanScript && strlen(scanScript) > 0;
74 m_scanScript = ScanScriptController::HasScripts();
7675 }
7776
7877 void Scanner::ServiceWork()
9594 CheckIncomingNzbs(g_Options->GetNzbDir(), "", checkStat);
9695 if (!checkStat && m_scanScript)
9796 {
98 // if immediate scan requested, we need second scan to process files extracted by NzbProcess-script
97 // if immediate scan requested, we need second scan to process files extracted by scan-scripts
9998 CheckIncomingNzbs(g_Options->GetNzbDir(), "", checkStat);
10099 }
101100 m_scanning = false;
104103 // if NzbDirFileAge is less than NzbDirInterval (that can happen if NzbDirInterval
105104 // is set for rare scans like once per hour) we make 4 scans:
106105 // - one additional scan is neccessary to check sizes of detected files;
107 // - another scan is required to check files which were extracted by NzbProcess-script;
106 // - another scan is required to check files which were extracted by scan-scripts;
108107 // - third scan is needed to check sizes of extracted files.
109108 if (g_Options->GetNzbDirInterval() > 0 && g_Options->GetNzbDirFileAge() < g_Options->GetNzbDirInterval())
110109 {
346345 void Scanner::InitPPParameters(const char* category, NzbParameterList* parameters, bool reset)
347346 {
348347 bool unpack = g_Options->GetUnpack();
349 const char* postScript = g_Options->GetPostScript();
348 const char* extensions = g_Options->GetExtensions();
350349
351350 if (!Util::EmptyStr(category))
352351 {
354353 if (categoryObj)
355354 {
356355 unpack = categoryObj->GetUnpack();
357 if (!Util::EmptyStr(categoryObj->GetPostScript()))
358 {
359 postScript = categoryObj->GetPostScript();
356 if (!Util::EmptyStr(categoryObj->GetExtensions()))
357 {
358 extensions = categoryObj->GetExtensions();
360359 }
361360 }
362361 }
371370
372371 parameters->SetParameter("*Unpack:", unpack ? "yes" : "no");
373372
374 if (!Util::EmptyStr(postScript))
375 {
376 // split szPostScript into tokens and create pp-parameter for each token
377 Tokenizer tok(postScript, ",;");
373 if (!Util::EmptyStr(extensions))
374 {
375 // create pp-parameter for each post-processing or queue- script
376 Tokenizer tok(extensions, ",;");
378377 while (const char* scriptName = tok.Next())
379378 {
380 parameters->SetParameter(BString<1024>("%s:", scriptName), "yes");
379 for (ScriptConfig::Script& script : g_ScriptConfig->GetScripts())
380 {
381 if ((script.GetPostScript() || script.GetQueueScript()) &&
382 FileSystem::SameFilename(scriptName, script.GetName()))
383 {
384 parameters->SetParameter(BString<1024>("%s:", scriptName), "yes");
385 }
386 }
381387 }
382388 }
383389 }
116116 }
117117 }
118118
119 WaitJobs();
120
121 debug("Exiting UrlCoordinator-loop");
122 }
123
124 void UrlCoordinator::WaitJobs()
125 {
119126 // waiting for downloads
120127 debug("UrlCoordinator: waiting for Downloads to complete");
121 bool completed = false;
122 while (!completed)
128
129 while (true)
123130 {
124131 {
125132 GuardedDownloadQueue guard = DownloadQueue::Guard();
126 completed = m_activeDownloads.size() == 0;
133 if (m_activeDownloads.empty())
134 {
135 break;
136 }
127137 }
128138 usleep(100 * 1000);
129139 ResetHangingDownloads();
130140 }
141
131142 debug("UrlCoordinator: Downloads are completed");
132
133 debug("Exiting UrlCoordinator-loop");
134143 }
135144
136145 void UrlCoordinator::Stop()
5555 void StartUrlDownload(NzbInfo* nzbInfo);
5656 void UrlCompleted(UrlDownloader* urlDownloader);
5757 void ResetHangingDownloads();
58 void WaitJobs();
5859 };
5960
6061 extern UrlCoordinator* g_UrlCoordinator;
859859 bool ok = DownloadQueue::Guard()->EditList(
860860 nrIdEntries > 0 ? &cIdList : nullptr,
861861 nrNameEntries > 0 ? &cNameList : nullptr,
862 (DownloadQueue::EMatchMode)matchMode, (DownloadQueue::EEditAction)action, offset, text);
862 (DownloadQueue::EMatchMode)matchMode, (DownloadQueue::EEditAction)action,
863 action == DownloadQueue::eaFileMoveOffset || action == DownloadQueue::eaGroupMoveOffset ?
864 *CString::FormatStr("%i", offset) : text);
863865
864866 if (ok)
865867 {
2121 #ifndef MESSAGEBASE_H
2222 #define MESSAGEBASE_H
2323
24 static const int32 NZBMESSAGE_SIGNATURE = 0x6E7A6228; // = "nzb-XX" (protocol version)
24 static const int32 NZBMESSAGE_SIGNATURE = 0x6E7A6230; // = "nzb-XX" (protocol version)
2525 static const int NZBREQUESTFILENAMESIZE = 512;
2626 static const int NZBREQUESTPASSWORDSIZE = 32;
2727
956956 completed.Format(", %i%s", (int)(stageProgress / 10), "%");
957957 }
958958
959 const char* postStageName[] = { "", ", Loading Pars", ", Verifying source files", ", Repairing", ", Verifying repaired files", ", Unpacking", ", Executing postprocess-script", "" };
959 const char* postStageName[] = { "", ", Loading Pars", ", Verifying source files", ", Repairing",
960 ", Verifying repaired files", ", Par-Renaming", ", Rar-Renaming", ", Unpacking", ", Cleaning up",
961 ", Moving", ", Executing postprocess-script", "" };
960962 char* infoName = bufPtr + sizeof(SNzbPostQueueResponseEntry) + ntohl(postQueueAnswer->m_nzbFilenameLen);
961963
962964 printf("[%i] %s%s%s\n", ntohl(postQueueAnswer->m_id), infoName, postStageName[ntohl(postQueueAnswer->m_stage)], *completed);
17451745 *EncodeStr(nzbInfo->GetDupeKey()), nzbInfo->GetDupeScore(), dupeModeName[nzbInfo->GetDupeMode()],
17461746 BoolToStr(nzbInfo->GetDeleteStatus() != NzbInfo::dsNone),
17471747 downloadedSizeLo, downloadedSizeHi, downloadedSizeMB, nzbInfo->GetDownloadSec(),
1748 nzbInfo->GetPostInfo() && nzbInfo->GetPostInfo()->GetStartTime() ?
1749 Util::CurrentTime() - nzbInfo->GetPostInfo()->GetStartTime() : nzbInfo->GetPostTotalSec(),
1748 nzbInfo->GetPostTotalSec() + (nzbInfo->GetPostInfo() && nzbInfo->GetPostInfo()->GetStartTime() ?
1749 Util::CurrentTime() - nzbInfo->GetPostInfo()->GetStartTime() : 0),
17501750 nzbInfo->GetParSec(), nzbInfo->GetRepairSec(), nzbInfo->GetUnpackSec(), messageCount, nzbInfo->GetExtraParBlocks());
17511751
17521752 // Post-processing parameters
19651965 const char* ListGroupsXmlCommand::DetectStatus(NzbInfo* nzbInfo)
19661966 {
19671967 const char* postStageName[] = { "PP_QUEUED", "LOADING_PARS", "VERIFYING_SOURCES", "REPAIRING",
1968 "VERIFYING_REPAIRED", "RENAMING", "UNPACKING", "MOVING", "EXECUTING_SCRIPT", "PP_FINISHED" };
1968 "VERIFYING_REPAIRED", "RENAMING", "RENAMING", "UNPACKING", "MOVING", "MOVING", "EXECUTING_SCRIPT", "PP_FINISHED" };
19691969
19701970 const char* status = nullptr;
19711971
20182018 { DownloadQueue::eaGroupMoveOffset, "GroupMoveOffset" },
20192019 { DownloadQueue::eaGroupMoveTop, "GroupMoveTop" },
20202020 { DownloadQueue::eaGroupMoveBottom, "GroupMoveBottom" },
2021 { DownloadQueue::eaGroupMoveBefore, "GroupMoveBefore" },
2022 { DownloadQueue::eaGroupMoveAfter, "GroupMoveAfter" },
20212023 { DownloadQueue::eaGroupPause, "GroupPause" },
20222024 { DownloadQueue::eaGroupResume, "GroupResume" },
20232025 { DownloadQueue::eaGroupDelete, "GroupDelete" },
20562058 { 0, nullptr }
20572059 };
20582060
2061 // v18:
2062 // bool editqueue(string Command, string Args, int[] IDs)
2063 // v17:
2064 // bool editqueue(string Command, int Offset, string Args, int[] IDs)
20592065 void EditQueueXmlCommand::Execute()
20602066 {
20612067 if (!CheckSafeMethod())
20882094 }
20892095
20902096 int offset = 0;
2091 if (!NextParamAsInt(&offset))
2097 bool hasOffset = NextParamAsInt(&offset);
2098
2099 char* args;
2100 if (!NextParamAsStr(&args))
20922101 {
20932102 BuildErrorResponse(2, "Invalid parameter");
20942103 return;
20952104 }
2096
2097 char* editText;
2098 if (!NextParamAsStr(&editText))
2099 {
2100 BuildErrorResponse(2, "Invalid parameter");
2101 return;
2102 }
2103 debug("EditText=%s", editText);
2104
2105 DecodeStr(editText);
2106
2107 IdList cIdList;
2105 debug("Args=%s", args);
2106
2107 DecodeStr(args);
2108
2109 BString<100> offsetStr("%i", offset);
2110 if (hasOffset && (action == DownloadQueue::eaFileMoveOffset ||
2111 action == DownloadQueue::eaGroupMoveOffset))
2112 {
2113 args = *offsetStr;
2114 }
2115
2116 IdList idList;
21082117 int id = 0;
21092118 while (NextParamAsInt(&id))
21102119 {
2111 cIdList.push_back(id);
2112 }
2113
2114 bool ok = DownloadQueue::Guard()->EditList(&cIdList, nullptr, DownloadQueue::mmId, (DownloadQueue::EEditAction)action, offset, editText);
2120 idList.push_back(id);
2121 }
2122
2123 bool ok = DownloadQueue::Guard()->EditList(&idList, nullptr, DownloadQueue::mmId,
2124 (DownloadQueue::EEditAction)action, args);
21152125
21162126 BuildBoolResponse(ok);
21172127 }
23112321 "}";
23122322
23132323 const char* postStageName[] = { "QUEUED", "LOADING_PARS", "VERIFYING_SOURCES", "REPAIRING",
2314 "VERIFYING_REPAIRED", "RENAMING", "UNPACKING", "MOVING", "EXECUTING_SCRIPT", "FINISHED" };
2324 "VERIFYING_REPAIRED", "RENAMING", "RENAMING", "UNPACKING", "MOVING", "MOVING", "EXECUTING_SCRIPT", "FINISHED" };
23152325
23162326 int index = 0;
23172327
27052715 "<member><name>QueueScript</name><value><boolean>%s</boolean></value></member>\n"
27062716 "<member><name>SchedulerScript</name><value><boolean>%s</boolean></value></member>\n"
27072717 "<member><name>FeedScript</name><value><boolean>%s</boolean></value></member>\n"
2718 "<member><name>QueueEvents</name><value><string>%s</string></value></member>\n"
2719 "<member><name>TaskTime</name><value><string>%s</string></value></member>\n"
27082720 "<member><name>Template</name><value><string>%s</string></value></member>\n"
27092721 "</struct></value>\n";
27102722
27172729 "\"QueueScript\" : %s,\n"
27182730 "\"SchedulerScript\" : %s,\n"
27192731 "\"FeedScript\" : %s,\n"
2732 "\"QueueEvents\" : \"%s\",\n"
2733 "\"TaskTime\" : \"%s\",\n"
27202734 "\"Template\" : \"%s\"\n"
27212735 "}";
27222736
27512765 BoolToStr(configTemplate.GetScript()->GetQueueScript()),
27522766 BoolToStr(configTemplate.GetScript()->GetSchedulerScript()),
27532767 BoolToStr(configTemplate.GetScript()->GetFeedScript()),
2768 *EncodeStr(configTemplate.GetScript()->GetQueueEvents()),
2769 *EncodeStr(configTemplate.GetScript()->GetTaskTime()),
27542770 *EncodeStr(configTemplate.GetTemplate()));
27552771 }
27562772
11671167 return ftell(m_file);
11681168 }
11691169
1170 int64 DiskFile::Seek(int64 position, ESeekOrigin origin)
1170 bool DiskFile::Seek(int64 position, ESeekOrigin origin)
11711171 {
11721172 return fseek(m_file, position,
11731173 origin == soCur ? SEEK_CUR :
2121 #define FILESYSTEM_H
2222
2323 #include "NString.h"
24
25 #ifdef WIN32
26 class WString;
27 #endif
2824
2925 class FileSystem
3026 {
138134 int64 Read(void* buffer, int64 size);
139135 int64 Write(const void* buffer, int64 size);
140136 int64 Position();
141 int64 Seek(int64 position, ESeekOrigin origin = soSet);
137 bool Seek(int64 position, ESeekOrigin origin = soSet);
142138 bool Eof();
143139 bool Error();
144140 int64 Print(const char* format, ...) PRINTF_SYNTAX(2);
243243 if (capacity > curLen || curLen == 0)
244244 {
245245 m_data = (char*)realloc(m_data, capacity + 1);
246 m_data[curLen] = '\0';
246247 }
247248 }
248249
274275 }
275276
276277
277 #ifdef WIN32
278278 WString::WString(const char* utfstr)
279279 {
280 int len = MultiByteToWideChar(CP_UTF8, 0, utfstr, -1, nullptr, 0);
281 m_data = (wchar_t*)malloc((len + 1) * sizeof(wchar_t));
282 MultiByteToWideChar(CP_UTF8, 0, utfstr, -1, m_data, len);
283 }
284 #endif
280 m_data = (wchar_t*)malloc((strlen(utfstr) * 2 + 1) * sizeof(wchar_t));
281
282 wchar_t* out = m_data;
283 unsigned int codepoint;
284 while (*utfstr != 0)
285 {
286 unsigned char ch = (unsigned char)*utfstr;
287 if (ch <= 0x7f)
288 codepoint = ch;
289 else if (ch <= 0xbf)
290 codepoint = (codepoint << 6) | (ch & 0x3f);
291 else if (ch <= 0xdf)
292 codepoint = ch & 0x1f;
293 else if (ch <= 0xef)
294 codepoint = ch & 0x0f;
295 else
296 codepoint = ch & 0x07;
297 ++utfstr;
298 if (((*utfstr & 0xc0) != 0x80) && (codepoint <= 0x10ffff))
299 {
300 if (codepoint > 0xffff)
301 {
302 *out++ = (wchar_t)(0xd800 + (codepoint >> 10));
303 *out++ = (wchar_t)(0xdc00 + (codepoint & 0x03ff));
304 }
305 else if (codepoint < 0xd800 || codepoint >= 0xe000)
306 *out++ = (wchar_t)(codepoint);
307 }
308 }
309 *out = '\0';
310 }
285311
286312
287313 void StringBuilder::Clear()
9393 char* m_data = nullptr;
9494 };
9595
96 #ifdef WIN32
9796 /*
98 Wide-character string, Windows specific.
97 Wide-character string.
9998 */
10099 class WString
101100 {
102101 public:
103 WString(wchar_t* wstr) : m_data(_wcsdup(wstr)) {}
102 WString(wchar_t* wstr) : m_data(wcsdup(wstr)) {}
104103 WString(const char* utfstr);
105104 ~WString() { free(m_data); }
106105 WString(WString&& other) noexcept { m_data = other.m_data; other.m_data = nullptr; }
112111 protected:
113112 wchar_t* m_data = nullptr;
114113 };
115 #endif
116114
117115 /*
118116 StringBuilder preallocates storage space and is best suitable for often "Append"s.
125123 explicit operator char*() { return m_data; }
126124 const char* operator*() const { return m_data; }
127125 int Length() const { return m_length; }
126 void SetLength(int length) { m_length = length; }
128127 int Capacity() const { return m_capacity; }
129128 void Reserve(int capacity, bool exact = false);
130129 bool Empty() const { return m_length == 0; }
303303 PrepareEnvOptions(nullptr);
304304 PrepareArgs();
305305
306 m_completed = false;
306307 int exitCode = 0;
307308
308309 #ifdef CHILD_WATCHDOG
314315 int pipein = StartProcess();
315316 if (pipein == -1)
316317 {
318 m_completed = true;
317319 return -1;
318320 }
319321
321323 m_readpipe = fdopen(pipein, "r");
322324 if (!m_readpipe)
323325 {
324 PrintMessage(Message::mkError, "Could not open pipe to %s", m_infoName);
326 PrintMessage(Message::mkError, "Could not open pipe to %s", *m_infoName);
325327 close(pipein);
328 m_completed = true;
326329 return -1;
327330 }
328331
382385
383386 if (m_terminated && m_infoName)
384387 {
385 warn("Interrupted %s", m_infoName);
388 warn("Interrupted %s", *m_infoName);
386389 }
387390
388391 exitCode = 0;
403406 #endif
404407
405408 debug("Exit code %i", exitCode);
406
409 m_completed = true;
407410 return exitCode;
408411 }
409412
473476 std::unique_ptr<wchar_t[]> environmentStrings = m_environmentStrings.GetStrings();
474477
475478 BOOL ok = CreateProcessW(nullptr, WString(cmdLine), nullptr, nullptr, TRUE,
476 NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT,
479 NORMAL_PRIORITY_CLASS | CREATE_NEW_PROCESS_GROUP | CREATE_UNICODE_ENVIRONMENT,
477480 environmentStrings.get(), wideWorkingDir, &startupInfo, &processInfo);
478481 if (!ok)
479482 {
482485 errMsg[255 - 1] = '\0';
483486 if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, errCode, 0, errMsg, 255, nullptr))
484487 {
485 PrintMessage(Message::mkError, "Could not start %s: %s", m_infoName, errMsg);
488 PrintMessage(Message::mkError, "Could not start %s: %s", *m_infoName, errMsg);
486489 }
487490 else
488491 {
489 PrintMessage(Message::mkError, "Could not start %s: error %i", m_infoName, errCode);
492 PrintMessage(Message::mkError, "Could not start %s: error %i", *m_infoName, errCode);
490493 }
491494 if (!FileSystem::FileExists(script))
492495 {
504507 debug("Child Process-ID: %i", (int)processInfo.dwProcessId);
505508
506509 m_processId = processInfo.hProcess;
510 m_dwProcessId = processInfo.dwProcessId;
507511
508512 // close unused "write" end
509513 CloseHandle(writePipe);
540544
541545 if (pid == -1)
542546 {
543 PrintMessage(Message::mkError, "Could not start %s: errno %i", m_infoName, errno);
547 PrintMessage(Message::mkError, "Could not start %s: errno %i", *m_infoName, errno);
544548 close(pipein);
545549 close(pipeout);
546550 return -1;
631635
632636 void ScriptController::Terminate()
633637 {
634 debug("Stopping %s", m_infoName);
638 debug("Stopping %s", *m_infoName);
635639 m_terminated = true;
636640
637641 #ifdef WIN32
638 BOOL ok = TerminateProcess(m_processId, -1);
642 BOOL ok = TerminateProcess(m_processId, -1) || m_completed;
639643 if (ok)
640644 {
641645 // wait 60 seconds for process to terminate
654658 // if the child process has its own group (setsid() was successful), kill the whole group
655659 killId = -killId;
656660 }
657 bool ok = killId && kill(killId, SIGKILL) == 0;
661 bool ok = (killId && kill(killId, SIGKILL) == 0) || m_completed;
658662 #endif
659663
660664 if (ok)
661665 {
662 debug("Terminated %s", m_infoName);
666 debug("Terminated %s", *m_infoName);
663667 }
664668 else
665669 {
666 error("Could not terminate %s", m_infoName);
667 }
668
669 debug("Stopped %s", m_infoName);
670 error("Could not terminate %s", *m_infoName);
671 }
672
673 debug("Stopped %s", *m_infoName);
670674 }
671675
672676 void ScriptController::TerminateAll()
676680 {
677681 if (script->m_processId && !script->m_detached)
678682 {
683 // send break signal and wait up to 5 seconds for graceful termination
684 if (script->Break())
685 {
686 time_t curtime = Util::CurrentTime();
687 while (!script->m_completed && std::abs(curtime - Util::CurrentTime()) <= 10)
688 {
689 usleep(100 * 1000);
690 }
691 }
679692 script->Terminate();
680693 }
681694 }
682695 }
683696
697 bool ScriptController::Break()
698 {
699 debug("Sending break signal to %s", *m_infoName);
700
701 #ifdef WIN32
702 BOOL ok = GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, m_dwProcessId);
703 #else
704 bool ok = kill(m_processId, SIGINT) == 0;
705 #endif
706
707 if (ok)
708 {
709 debug("Sent break signal to %s", *m_infoName);
710 }
711 else
712 {
713 warn("Could not send break signal to %s", *m_infoName);
714 }
715
716 return ok;
717 }
718
684719 void ScriptController::Detach()
685720 {
686 debug("Detaching %s", m_infoName);
721 debug("Detaching %s", *m_infoName);
687722 m_detached = true;
688723 FILE* readpipe = m_readpipe;
689724 m_readpipe = nullptr;
5353 virtual ~ScriptController();
5454 int Execute();
5555 void Terminate();
56 bool Break();
5657 void Resume();
5758 void Detach();
5859 static void TerminateAll();
8687 private:
8788 ArgList m_args;
8889 const char* m_workingDir = nullptr;
89 const char* m_infoName = nullptr;
90 CString m_infoName;
9091 const char* m_logPrefix = nullptr;
9192 EnvironmentStrings m_environmentStrings;
9293 bool m_terminated = false;
94 bool m_completed = false;
9395 bool m_detached = false;
9496 FILE* m_readpipe;
9597 #ifdef WIN32
9698 HANDLE m_processId = 0;
99 DWORD m_dwProcessId = 0;
97100 char m_cmdLine[2048];
98101 #else
99102 pid_t m_processId = 0;
272272 CString result;
273273 result.Reserve(50);
274274 FormatTime(timeSec, result, 50);
275 return result;
276 }
277
278 CString Util::FormatBuffer(const char* buf, int len)
279 {
280 CString result;
281 result.Reserve(len * 3 + 1);
282 while (len--)
283 {
284 result.AppendFmt("%02x ", (int)(uchar)*buf++);
285 }
275286 return result;
276287 }
277288
7171
7272 static CString FormatSpeed(int bytesPerSecond);
7373 static CString FormatSize(int64 fileSize);
74 static CString FormatBuffer(const char* buf, int len);
7475
7576 /*
7677 * Returns program version and revision number as string formatted like "0.7.0-r295".
3030 namespace Par2
3131 {
3232
33 NullStreamBuf nullStreamBuf;
34 std::ostream cout(&nullStreamBuf);
35 std::ostream cerr(&nullStreamBuf);
36
3733 CommandLine::ExtraFile::ExtraFile(void)
3834 : filename()
3935 , filesize(0)
6157 }
6258
6359
64 CommandLine::CommandLine(void)
60 CommandLine::CommandLine(std::ostream& cout, std::ostream& cerr)
6561 : operation(opNone)
6662 , version(verUnknown)
6763 , noiselevel(nlUnknown)
7975 , totalsourcesize(0)
8076 , largestsourcesize(0)
8177 , memorylimit(0)
78 , cout(cout)
79 , cerr(cerr)
8280 {
8381 }
8482
8583 void CommandLine::usage(void)
8684 {
87 cout <<
85 std::cout <<
8886 "\n"
8987 "Usage:\n"
9088 "\n"
2929 class CommandLine
3030 {
3131 public:
32 CommandLine(void);
32 CommandLine(std::ostream& cout, std::ostream& cerr);
3333
3434 // Parse the supplied command line arguments.
3535 bool Parse(int argc, char *argv[]);
152152 size_t memorylimit; // How much memory is permitted to be used
153153 // for the output buffer when creating
154154 // or repairing.
155
156 std::ostream& cout;
157 std::ostream& cerr;
155158 };
156159
157160 typedef list<CommandLine::ExtraFile>::const_iterator ExtraFileIterator;
3939 #define LengthType unsigned int
4040 #define MaxLength 0xffffffffUL
4141
42 DiskFile::DiskFile(void)
42 DiskFile::DiskFile(std::ostream& cerr) :
43 cerr(cerr)
4344 {
4445 filename;
4546 filesize = 0;
364365 #define LengthType unsigned int
365366 #define MaxLength 0xffffffffUL
366367
367 DiskFile::DiskFile(void)
368 DiskFile::DiskFile(std::ostream& cerr) :
369 cerr(cerr)
368370 {
369371 //filename;
370372 filesize = 0;
2828 class DiskFile
2929 {
3030 public:
31 DiskFile(void);
31 DiskFile(std::ostream& cerr);
3232 ~DiskFile(void);
3333
3434 // Create a file and set its length
105105 #ifdef WIN32
106106 static string ErrorMessage(DWORD error);
107107 #endif
108
109 std::ostream& cerr;
108110 };
109111
110112 // This class keeps track of which DiskFile objects exist
3131 {
3232
3333 // Construct the main packet from the source files and the block size
34
34 /*
3535 bool MainPacket::Create(vector<Par2CreatorSourceFile*> &sourcefiles, u64 _blocksize)
3636 {
3737 recoverablefilecount = totalfilecount =(u32)sourcefiles.size();
7979
8080 return true;
8181 }
82 */
8283
8384 // Load a main packet from a specified file
8485
3838 public:
3939 // Construct the main packet from the source file list and block size.
4040 // "sourcefiles" will be sorted base on their FileId value.
41 bool Create(vector<Par2CreatorSourceFile*> &sourcefiles,
42 u64 _blocksize);
41 /*bool Create(vector<Par2CreatorSourceFile*> &sourcefiles,
42 u64 _blocksize);*/
4343
4444 // Load a main packet from a specified file
4545 bool Load(DiskFile *diskfile, u64 offset, PACKET_HEADER &header);
140140
141141 } Result;
142142
143 class NullStreamBuf : public std::streambuf {};
144 extern NullStreamBuf nullStreamBuf;
145 extern std::ostream cout;
146 extern std::ostream cerr;
147
148143 } // end namespace Par2
149144
150145 #define LONGMULTIPLY
170165 #include "datablock.h"
171166
172167 #include "criticalpacket.h"
173 #include "par2creatorsourcefile.h"
168 //#include "par2creatorsourcefile.h"
174169
175170 #include "mainpacket.h"
176171 #include "creatorpacket.h"
+0
-348
lib/par2/par2creatorsourcefile.cpp less more
0 // This file is part of par2cmdline (a PAR 2.0 compatible file verification and
1 // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0.
2 //
3 // Copyright (c) 2003 Peter Brian Clements
4 //
5 // par2cmdline is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // (at your option) any later version.
9 //
10 // par2cmdline is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18
19 #include "nzbget.h"
20 #include "par2cmdline.h"
21
22 #ifdef _MSC_VER
23 #ifdef _DEBUG
24 #undef THIS_FILE
25 static char THIS_FILE[]=__FILE__;
26 #define new DEBUG_NEW
27 #endif
28 #endif
29
30 namespace Par2
31 {
32
33 Par2CreatorSourceFile::Par2CreatorSourceFile(void)
34 {
35 descriptionpacket = 0;
36 verificationpacket = 0;
37 diskfile = 0;
38 blockcount = 0;
39 //diskfilename;
40 //parfilename;
41 contextfull = 0;
42 }
43
44 Par2CreatorSourceFile::~Par2CreatorSourceFile(void)
45 {
46 delete descriptionpacket;
47 delete verificationpacket;
48 delete diskfile;
49 delete contextfull;
50 }
51
52 // Open the source file, compute the MD5 Hash of the whole file and the first
53 // 16k of the file, and then compute the FileId and store the results
54 // in a file description packet and a file verification packet.
55
56 bool Par2CreatorSourceFile::Open(CommandLine::NoiseLevel noiselevel, const CommandLine::ExtraFile &extrafile, u64 blocksize, bool deferhashcomputation)
57 {
58 // Get the filename and filesize
59 diskfilename = extrafile.FileName();
60 filesize = extrafile.FileSize();
61
62 // Work out how many blocks the file will be sliced into
63 blockcount = (u32)((filesize + blocksize-1) / blocksize);
64
65 // Determine what filename to record in the PAR2 files
66 string::size_type where;
67 if (string::npos != (where = diskfilename.find_last_of('\\')) ||
68 string::npos != (where = diskfilename.find_last_of('/')))
69 {
70 parfilename = diskfilename.substr(where+1);
71 }
72 else
73 {
74 parfilename = diskfilename;
75 }
76
77 // Create the Description and Verification packets
78 descriptionpacket = new DescriptionPacket;
79 descriptionpacket->Create(parfilename, filesize);
80
81 verificationpacket = new VerificationPacket;
82 verificationpacket->Create(blockcount);
83
84 // Create the diskfile object
85 diskfile = new DiskFile;
86
87 // Open the source file
88 if (!diskfile->Open(diskfilename, filesize))
89 return false;
90
91 // Do we want to defer the computation of the full file hash, and
92 // the block crc and hashes. This is only permitted if there
93 // is sufficient memory available to create all recovery blocks
94 // in one pass of the source files (i.e. chunksize == blocksize)
95 if (deferhashcomputation)
96 {
97 // Initialise a buffer to read the first 16k of the source file
98 size_t buffersize = 16 * 1024;
99 if (buffersize > filesize)
100 buffersize = (size_t)filesize;
101 char *buffer = new char[buffersize];
102
103 // Read the data from the file
104 if (!diskfile->Read(0, buffer, buffersize))
105 {
106 diskfile->Close();
107 delete [] buffer;
108 return false;
109 }
110
111 // Compute the hash of the data read from the file
112 MD5Context context;
113 context.Update(buffer, buffersize);
114 delete [] buffer;
115 MD5Hash hash;
116 context.Final(hash);
117
118 // Store the hash in the descriptionpacket and compute the file id
119 descriptionpacket->Hash16k(hash);
120
121 // Compute the fileid and store it in the verification packet.
122 descriptionpacket->ComputeFileId();
123 verificationpacket->FileId(descriptionpacket->FileId());
124
125 // Allocate an MD5 context for computing the file hash
126 // during the recovery data generation phase
127 contextfull = new MD5Context;
128 }
129 else
130 {
131 // Initialise a buffer to read the source file
132 size_t buffersize = 1024*1024;
133 if (buffersize > min(blocksize,filesize))
134 buffersize = (size_t)min(blocksize,filesize);
135 char *buffer = new char[buffersize];
136
137 // Get ready to start reading source file to compute the hashes and crcs
138 u64 offset = 0;
139 u32 blocknumber = 0;
140 u64 need = blocksize;
141
142 MD5Context filecontext;
143 MD5Context blockcontext;
144 u32 blockcrc = 0;
145
146 // Whilst we have not reached the end of the file
147 while (offset < filesize)
148 {
149 // Work out how much we can read
150 size_t want = (size_t)min(filesize-offset, (u64)buffersize);
151
152 // Read some data from the file into the buffer
153 if (!diskfile->Read(offset, buffer, want))
154 {
155 diskfile->Close();
156 delete [] buffer;
157 return false;
158 }
159
160 // If the new data passes the 16k boundary, compute the 16k hash for the file
161 if (offset < 16384 && offset + want >= 16384)
162 {
163 filecontext.Update(buffer, (size_t)(16384-offset));
164
165 MD5Context temp = filecontext;
166 MD5Hash hash;
167 temp.Final(hash);
168
169 // Store the 16k hash in the file description packet
170 descriptionpacket->Hash16k(hash);
171
172 if (offset + want > 16384)
173 {
174 filecontext.Update(&buffer[16384-offset], (size_t)(offset+want)-16384);
175 }
176 }
177 else
178 {
179 filecontext.Update(buffer, want);
180 }
181
182 // Get ready to update block hashes and crcs
183 u32 used = 0;
184
185 // Whilst we have not used all of the data we just read
186 while (used < want)
187 {
188 // How much of it can we use for the current block
189 u32 use = (u32)min(need, (u64)(want-used));
190
191 blockcrc = ~0 ^ CRCUpdateBlock(~0 ^ blockcrc, use, &buffer[used]);
192 blockcontext.Update(&buffer[used], use);
193
194 used += use;
195 need -= use;
196
197 // Have we finished the current block
198 if (need == 0)
199 {
200 MD5Hash blockhash;
201 blockcontext.Final(blockhash);
202
203 // Store the block hash and block crc in the file verification packet.
204 verificationpacket->SetBlockHashAndCRC(blocknumber, blockhash, blockcrc);
205
206 blocknumber++;
207
208 // More blocks
209 if (blocknumber < blockcount)
210 {
211 need = blocksize;
212
213 blockcontext.Reset();
214 blockcrc = 0;
215 }
216 }
217 }
218
219 if (noiselevel > CommandLine::nlQuiet)
220 {
221 // Display progress
222 u32 oldfraction = (u32)(1000 * offset / filesize);
223 offset += want;
224 u32 newfraction = (u32)(1000 * offset / filesize);
225 if (oldfraction != newfraction)
226 {
227 cout << newfraction/10 << '.' << newfraction%10 << "%\r" << flush;
228 }
229 }
230 }
231
232 // Did we finish the last block
233 if (need > 0)
234 {
235 blockcrc = ~0 ^ CRCUpdateBlock(~0 ^ blockcrc, (size_t)need);
236 blockcontext.Update((size_t)need);
237
238 MD5Hash blockhash;
239 blockcontext.Final(blockhash);
240
241 // Store the block hash and block crc in the file verification packet.
242 verificationpacket->SetBlockHashAndCRC(blocknumber, blockhash, blockcrc);
243
244 blocknumber++;
245
246 need = 0;
247 }
248
249 // Finish computing the file hash.
250 MD5Hash filehash;
251 filecontext.Final(filehash);
252
253 // Store the file hash in the file description packet.
254 descriptionpacket->HashFull(filehash);
255
256 // Did we compute the 16k hash.
257 if (offset < 16384)
258 {
259 // Store the 16k hash in the file description packet.
260 descriptionpacket->Hash16k(filehash);
261 }
262
263 delete [] buffer;
264
265 // Compute the fileid and store it in the verification packet.
266 descriptionpacket->ComputeFileId();
267 verificationpacket->FileId(descriptionpacket->FileId());
268 }
269
270 return true;
271 }
272
273 void Par2CreatorSourceFile::Close(void)
274 {
275 diskfile->Close();
276 }
277
278
279 void Par2CreatorSourceFile::RecordCriticalPackets(list<CriticalPacket*> &criticalpackets)
280 {
281 // Add the file description packet and file verification packet to
282 // the critical packet list.
283 criticalpackets.push_back(descriptionpacket);
284 criticalpackets.push_back(verificationpacket);
285 }
286
287 bool Par2CreatorSourceFile::CompareLess(const Par2CreatorSourceFile* const &left, const Par2CreatorSourceFile* const &right)
288 {
289 // Sort source files based on fileid
290 return left->descriptionpacket->FileId() < right->descriptionpacket->FileId();
291 }
292
293 const MD5Hash& Par2CreatorSourceFile::FileId(void) const
294 {
295 // Get the file id hash
296 return descriptionpacket->FileId();
297 }
298
299 void Par2CreatorSourceFile::InitialiseSourceBlocks(vector<DataBlock>::iterator &sourceblock, u64 blocksize)
300 {
301 for (u32 blocknum=0; blocknum<blockcount; blocknum++)
302 {
303 // Configure each source block to an appropriate offset and length within the source file.
304 sourceblock->SetLocation(diskfile, // file
305 blocknum * blocksize); // offset
306 sourceblock->SetLength(min(blocksize, filesize - (u64)blocknum * blocksize)); // length
307 sourceblock++;
308 }
309 }
310
311 void Par2CreatorSourceFile::UpdateHashes(u32 blocknumber, const void *buffer, size_t length)
312 {
313 // Compute the crc and hash of the data
314 u32 blockcrc = ~0 ^ CRCUpdateBlock(~0, length, buffer);
315 MD5Context blockcontext;
316 blockcontext.Update(buffer, length);
317 MD5Hash blockhash;
318 blockcontext.Final(blockhash);
319
320 // Store the results in the verification packet
321 verificationpacket->SetBlockHashAndCRC(blocknumber, blockhash, blockcrc);
322
323
324 // Update the full file hash, but don't go beyond the end of the file
325 if (length > filesize - blocknumber * length)
326 {
327 length = (size_t)(filesize - blocknumber * (u64)length);
328 }
329
330 assert(contextfull != 0);
331
332 contextfull->Update(buffer, length);
333 }
334
335 void Par2CreatorSourceFile::FinishHashes(void)
336 {
337 assert(contextfull != 0);
338
339 // Finish computation of the full file hash
340 MD5Hash hash;
341 contextfull->Final(hash);
342
343 // Store it in the description packet
344 descriptionpacket->HashFull(hash);
345 }
346
347 } // end namespace Par2
+0
-86
lib/par2/par2creatorsourcefile.h less more
0 // This file is part of par2cmdline (a PAR 2.0 compatible file verification and
1 // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0.
2 //
3 // Copyright (c) 2003 Peter Brian Clements
4 //
5 // par2cmdline is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // (at your option) any later version.
9 //
10 // par2cmdline is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18
19 #ifndef __PAR2CREATORSOURCEFILE_H__
20 #define __PAR2CREATORSOURCEFILE_H__
21
22 namespace Par2
23 {
24
25 class DescriptionPacket;
26 class VerificationPacket;
27 class DiskFile;
28
29 // The Par2CreatorSourceFile contains the file verification and file description
30 // packet for one source file.
31
32 class Par2CreatorSourceFile
33 {
34 private:
35 // Don't permit copying or assignment
36 Par2CreatorSourceFile(const Par2CreatorSourceFile &other);
37 Par2CreatorSourceFile& operator=(const Par2CreatorSourceFile &other);
38
39 public:
40 Par2CreatorSourceFile(void);
41 ~Par2CreatorSourceFile(void);
42
43 // Open the source file and compute the Hashes and CRCs.
44 bool Open(CommandLine::NoiseLevel noiselevel, const CommandLine::ExtraFile &extrafile, u64 blocksize, bool deferhashcomputation);
45 void Close(void);
46
47 // Recover the file description and file verification packets
48 // in the critical packet list.
49 void RecordCriticalPackets(list<CriticalPacket*> &criticalpackets);
50
51 // Get the file id
52 const MD5Hash& FileId(void) const;
53
54 // Sort source files based on the file id hash
55 static bool CompareLess(const Par2CreatorSourceFile* const &left, const Par2CreatorSourceFile* const &right);
56
57 // Allocate the appropriate number of source blocks to the source file
58 void InitialiseSourceBlocks(vector<DataBlock>::iterator &sourceblock, u64 blocksize);
59
60 // Update the file hash and the block crc and hashes
61 void UpdateHashes(u32 blocknumber, const void *buffer, size_t length);
62
63 // Finish computation of the file hash
64 void FinishHashes(void);
65
66 // How many blocks does this source file use
67 u32 BlockCount(void) const {return blockcount;}
68
69 protected:
70 DescriptionPacket *descriptionpacket; // The file description packet.
71 VerificationPacket *verificationpacket; // The file verification packet.
72 DiskFile *diskfile; // The source file
73
74 u64 filesize; // The size of the source file.
75 string diskfilename; // The filename of the source file on disk.
76 string parfilename; // The filename that will be recorded in the file description packet.
77
78 u32 blockcount; // How many blocks the file will be divided into.
79
80 MD5Context *contextfull; // MD5 context used to calculate the hash of the whole file
81 };
82
83 } // end namespace Par2
84
85 #endif // __PAR2CREATORSOURCEFILE_H__
3030 namespace Par2
3131 {
3232
33 Par2Repairer::Par2Repairer(void)
33 Par2Repairer::Par2Repairer(std::ostream& cout, std::ostream& cerr):
34 cout(cout), cerr(cerr), rs(cout, cerr)
3435 {
3536 firstpacket = true;
3637 mainpacket = 0;
359360 return true;
360361 }
361362
362 DiskFile *diskfile = new DiskFile;
363 DiskFile *diskfile = new DiskFile(cerr);
363364
364365 // Open the file
365366 if (!diskfile->Open(filename))
12751276 return false;
12761277 }
12771278
1278 DiskFile *diskfile = new DiskFile;
1279 DiskFile *diskfile = new DiskFile(cerr);
12791280
12801281 // Does the target file exist
12811282 if (diskfile->Open(filename))
13391340 // Has this file already been dealt with
13401341 if (diskFileMap.Find(filename) == 0)
13411342 {
1342 DiskFile *diskfile = new DiskFile;
1343 DiskFile *diskfile = new DiskFile(cerr);
13431344
13441345 // Does the file exist
13451346 if (!diskfile->Open(filename))
20982099 // If the file does not exist
20992100 if (!sourcefile->GetTargetExists())
21002101 {
2101 DiskFile *targetfile = new DiskFile;
2102 DiskFile *targetfile = new DiskFile(cerr);
21022103 string filename = sourcefile->TargetFileName();
21032104 u64 filesize = sourcefile->GetDescriptionPacket()->FileSize();
21042105
2626 class Par2Repairer
2727 {
2828 public:
29 Par2Repairer(void);
29 Par2Repairer(std::ostream& cout, std::ostream& cerr);
3030 ~Par2Repairer(void);
3131
3232 Result PreProcess(const CommandLine &commandline);
195195 u64 totalsize; // Total data size
196196
197197 bool cancelled; // repair cancelled
198
199 std::ostream& cout;
200 std::ostream& cerr;
198201 };
199202
200203 } // end namespace Par2
4646 public:
4747 typedef g G;
4848
49 ReedSolomon(void);
49 ReedSolomon(std::ostream& cout, std::ostream& cerr);
5050 ~ReedSolomon(void);
5151
5252 // Set which input blocks are present or missing
104104 #ifdef LONGMULTIPLY
105105 GaloisLongMultiplyTable<g> *glmt; // A multiplication table used by Process()
106106 #endif
107
108 std::ostream& cout;
109 std::ostream& cerr;
107110 };
108111
109112 template<class g>
110 inline ReedSolomon<g>::ReedSolomon(void)
113 inline ReedSolomon<g>::ReedSolomon(std::ostream& cout, std::ostream& cerr) :
114 cout(cout), cerr(cerr)
111115 {
112116 inputcount = 0;
113117
3535 # Directory for incoming nzb-files.
3636 #
3737 # If a new nzb-file is added to queue via web-interface or RPC-API, it
38 # is saved into this directory and then processed by preprocessing
39 # script (option <ScanScript>).
38 # is saved into this directory and then processed by extension
39 # scripts (option <Extensions>).
4040 #
4141 # This directory is also monitored for new nzb-files. If a new file
4242 # is found it is added to download queue. The directory can have
343343
344344 # Secure control of NZBGet server (yes, no).
345345 #
346 # Activate the option if you want to access NZBGet built-in web-server
346 # Activate the option if you want to access NZBGet built-in web-server
347347 # via HTTPS (web-interface and RPC). You should also provide certificate
348348 # and key files, see option <SecureCert> and option <SecureKey>.
349349 SecureControl=no
417417 # For more information see global option <Unpack>.
418418 Category1.Unpack=yes
419419
420 # Default list of post-processing scripts.
421 #
422 # For more information see global option <PostScript>.
423 Category1.PostScript=
420 # List of extension scripts for this category.
421 #
422 # For more information see global option <Extensions>.
423 Category1.Extensions=
424424
425425 # List of aliases.
426426 #
621621 # post-processed even if the program is in paused state (force mode).
622622 #Feed1.Priority=0
623623
624 # List of rss feed scripts to execute before rss feed content is processed.
625 #
626 # For more information see global option <FeedScript>.
627 #Feed1.FeedScript=
624 # List of rss feed extension scripts to execute for rss content.
625 #
626 # The scripts in the list must be separated with commas or semicolons. All
627 # scripts must be stored in directory set by option <ScriptDir> and
628 # paths relative to <ScriptDir> must be entered here.
629 #
630 # NOTE: For developer documentation visit http://nzbget.net/Extension_scripts.
631 #Feed1.Extensions=
628632
629633
630634 ##############################################################################
638642 #
639643 # Value "0" disables the check.
640644 #
641 # NOTE: nzb-files are processed by scan and queue scripts. See
642 # options <ScanScript> and <QueueScript>.
645 # NOTE: nzb-files are processed by extension scripts. See option <Extensions>.
643646 NzbDirInterval=5
644647
645648 # How old nzb-file should at least be for it to be loaded to queue (seconds).
713716 # Propagation delay to your news servers (minutes).
714717 #
715718 # The option sets minimum post age for nzb-files. Very recent files
716 # are not downloaded to avoid download failures. The files remain
719 # are not downloaded to avoid download failures. The files remain
717720 # on hold in the download queue until the propagation delay expires,
718721 # after that they are downloaded.
719722 PropagationDelay=0
767770 # all articles of the file are downloaded or when the cache becomes
768771 # full to 90%.
769772 #
770 # The direct write relies on the ability of file system to create
773 # The direct write relies on the ability of file system to create
771774 # empty files without allocating the space on the drive (sparse files),
772775 # which most modern file systems support including EXT3, EXT4
773776 # and NTFS. The notable exception is HFS+ (default file system on OSX).
811814 # slow CPU disabling of CRC-Check may improve performance.
812815 CrcCheck=yes
813816
817 # Post-processing strategy (sequential, balanced, aggressive, rocket).
818 #
819 # Sequential - downloaded items are post processed from a queue, one item at a
820 # time, to dedicate the most computer resources to each
821 # item. Therefore, a post process par repair will prevent another
822 # task from running even if the item does not require a par repair;
823 # Balanced - items that do not need par repair are post processed one at a
824 # time while par repair tasks may also run simultaneously one after
825 # another at the same time. This means that a post process par
826 # repair will not prevent another task from running, but at a cost
827 # of using more computer resource;
828 # Aggressive - will simultaneously post process up to three items including
829 # one par repair task;
830 # Rocket - will simultaneously post process up to six items including one
831 # or two par repair tasks.
832 #
833 # NOTE: Computer resources are in heavy demand when post-processing with
834 # simultaneous tasks - make sure the hardware is capable.
835 PostStrategy=balanced
836
837 # Pause if disk space gets below this value (megabytes).
838 #
839 # Disk space is checked for directories pointed by option <DestDir> and
840 # option <InterDir>.
841 #
842 # Value "0" disables the check.
843 DiskSpace=250
844
845 # Delete source nzb-file when it is not needed anymore (yes, no).
846 #
847 # Enable this option for automatic deletion of source nzb-file from
848 # incoming directory when the program doesn't require it anymore (the
849 # nzb-file has been deleted from queue and history).
850 NzbCleanupDisk=yes
851
852 # Keep the history of downloaded nzb-files (days).
853 #
854 # After download and post-processing the items are added to history where
855 # their status can be checked and they can be post-processed again if
856 # necessary.
857 #
858 # After expiring of defined period:
859 #
860 # If option <DupeCheck> is active the items become hidden and the amount
861 # of data kept is significantly reduced (for better performance), only
862 # fields necessary for duplicate check are kept. The item remains in the
863 # hidden history (forever);
864 #
865 # If option <DupeCheck> is NOT active the items are removed from history.
866 #
867 # When a failed item is removed from history or become hidden all downloaded
868 # files of that item are deleted from disk.
869 #
870 # Value "0" disables history. Duplicate check will not work.
871 KeepHistory=30
872
873 # Keep the history of outdated feed items (days).
874 #
875 # After fetching of an RSS feed the information about included items (nzb-files)
876 # is saved to disk. This allows to detect new items on next fetch. Feed
877 # providers update RSS feeds constantly. Since the feed length is limited
878 # (usually 100 items or less) the old items get pushed away by new
879 # ones. When an item is not present in the feed anymore it's not necessary
880 # to keep the information about this item on the disk.
881 #
882 # If option is set to "0", the outdated items are deleted from history
883 # immediately.
884 #
885 # Otherwise the items are held in the history for defined number of
886 # days. Keeping of items for few days helps in situations when feed provider
887 # has technical issues and may response with empty feeds (or with missing
888 # items). When the technical issue is fixed the items may reappear in the
889 # feed causing the program to re-download items if they were not found in
890 # the feed history.
891 FeedHistory=7
892
893 ##############################################################################
894 ### CONNECTION ###
895
814896 # How many retries should be attempted if a download error occurs (0-99).
815897 #
816898 # If download fails because of incomplete or damaged article or due to
817899 # CRC-error the program tries to re-download the article from the same
818 # news server as many times as defined in option <Retries>. If all
819 # attempts fail the program tries another news server.
900 # news server as many times as defined in this option. If all attempts fail
901 # the program tries another news server.
820902 #
821903 # If download fails because of "article or group not found error" the
822904 # program tries another news server without retrying on the failed server.
823 #
824 # If download fails because of interrupted connection the program
825 # tries another news server or the same server after the block interval
826 # expires.
827 Retries=3
828
829 # Wait interval between retries (seconds).
830 #
831 # If download of an article fails because of interrupted connection
905 ArticleRetries=3
906
907 # Article retry interval (seconds).
908 #
909 # If download of article fails because of interrupted connection
832910 # the server is temporary blocked until the retry interval expires.
833 RetryInterval=10
911 ArticleInterval=10
834912
835913 # Connection timeout for article downloading (seconds).
836914 ArticleTimeout=60
837915
916 # Number of download attempts for URL fetching (0-99).
917 #
918 # If fetching of nzb-file via URL or fetching of RSS feed fails another
919 # attempt is made after the retry interval (option <UrlInterval>).
920 UrlRetries=3
921
922 # URL fetching retry interval (seconds).
923 #
924 # If fetching of nzb-file via URL or fetching of RSS feed fails another
925 # attempt is made after the retry interval.
926 UrlInterval=10
927
838928 # Connection timeout for URL fetching (seconds).
839929 #
840 # This includes fetching of nzb-files via URLs and fetching of RSS feeds.
930 # Connection timeout when fetching nzb-files via URLs and fetching RSS feeds.
841931 UrlTimeout=60
842932
843933 # Timeout until a download-thread should be killed (seconds).
848938
849939 # Set the maximum download rate on program start (kilobytes/sec).
850940 #
851 # The download rate can be changed later via remote calls.
941 # The download rate can be changed later in web-interface or via remote calls.
852942 #
853943 # Value "0" means no speed control.
854944 DownloadRate=0
871961 # download speed on a particular system.
872962 AccurateRate=no
873963
874 # Pause if disk space gets below this value (megabytes).
875 #
876 # Disk space is checked for directories pointed by option <DestDir> and
877 # option <InterDir>.
878 #
879 # Value "0" disables the check.
880 DiskSpace=250
881
882 # Delete source nzb-file when it is not needed anymore (yes, no).
883 #
884 # Enable this option for automatic deletion of source nzb-file from
885 # incoming directory when the program doesn't require it anymore (the
886 # nzb-file has been deleted from queue and history).
887 NzbCleanupDisk=yes
888
889 # Keep the history of downloaded nzb-files (days).
890 #
891 # After download and post-processing the items are added to history where
892 # their status can be checked and they can be post-processed again if
893 # necessary.
894 #
895 # After expiring of defined period:
896 #
897 # If option <DupeCheck> is active the items become hidden and the amount
898 # of data kept is significantly reduced (for better performance), only
899 # fields necessary for duplicate check are kept. The item remains in the
900 # hidden history (forever);
901 #
902 # If option <DupeCheck> is NOT active the items are removed from history.
903 #
904 # Value "0" disables history. Duplicate check will not work.
905 KeepHistory=30
906
907 # Keep the history of outdated feed items (days).
908 #
909 # After fetching of an RSS feed the information about included items (nzb-files)
910 # is saved to disk. This allows to detect new items on next fetch. Feed
911 # providers update RSS feeds constantly. Since the feed length is limited
912 # (usually 100 items or less) the old items get pushed away by new
913 # ones. When an item is not present in the feed anymore it's not necessary
914 # to keep the information about this item on the disk.
915 #
916 # If option is set to "0", the outdated items are deleted from history
917 # immediately.
918 #
919 # Otherwise the items are held in the history for defined number of
920 # days. Keeping of items for few days helps in situations when feed provider
921 # has technical issues and may response with empty feeds (or with missing
922 # items). When the technical issue is fixed the items may reappear in the
923 # feed causing the program to re-download items if they were not found in
924 # the feed history.
925 FeedHistory=7
926
927964 # Maximum number of simultaneous connections for nzb URL downloads (0-999).
928965 #
929966 # When NZB-files are added to queue via URL, the program downloads them
10821119 # Time to execute the command (HH:MM).
10831120 #
10841121 # Multiple comma-separated values are accepted.
1085 # An asterisk placed in the hours location will run every hour.
1086 #
1087 # Examples: "08:00", "00:00,06:00,12:00,18:00", "*:00", "*:00,*:30".
1122 # An asterisk placed in the hours location will run task every hour (e. g. "*:00").
1123 # An asterisk without minutes will run task at program startup (e. g. "*").
1124 #
1125 # Examples: "08:00", "00:00,06:00,12:00,18:00", "*:00", "*,*:00,*:30".
10881126 #
10891127 # NOTE: Also see option <TimeCorrection>.
10901128 #Task1.Time=08:00
11301168 # list must be separated with commas or semicolons. All
11311169 # scripts must be stored in directory set by option
11321170 # <ScriptDir> and paths relative to <ScriptDir> must be
1133 # entered here. For more info see below;
1171 # entered here. For developer documentation visit
1172 # http://nzbget.net/Extension_scripts;
11341173 # Process - path to the program to execute and its parameters.
11351174 # Example: /home/user/fetch.sh.
11361175 # If filename or any parameter contains spaces it
11371176 # must be surrounded with single quotation
11381177 # marks. If filename/parameter contains single quotation marks,
1139 # each of them must be replaced (escaped) with two single quotation
1140 # marks and the resulting filename/parameter must be
1178 # each of them must be replaced (escaped) with two single quotation
1179 # marks and the resulting filename/parameter must be
11411180 # surrounded with single quotation marks.
11421181 # Example: '/home/user/download/my scripts/task process.sh' 'world''s fun'.
11431182 # In this example one parameter (world's fun) is passed
11521191 # Example: bookmarks feed, another feed.
11531192 # NOTE: feed names should not have commas.
11541193 # NOTE: use feed id "0" to fetch all feeds.
1155 #
1156 # INFO FOR DEVELOPERS:
1157 # The rest of the description is for command "Script".
1158 #
1159 # NOTE: This is a short documentation, for more information visit
1160 # http://nzbget.net/Extension_scripts.
1161 #
1162 # NZBGet passes following arguments to scheduler script as environment
1163 # variables:
1164 # NZBSP_TASKID - id number of scheduler Task.
1165 #
1166 # In addition to these arguments NZBGet passes all nzbget.conf-options
1167 # as environment variables. These variables have prefix "NZBOP_" and
1168 # are written in UPPER CASE. For Example option "ParRepair" is passed as
1169 # environment variable "NZBOP_PARREPAIR". The dots in option names are
1170 # replaced with underscores, for example "SERVER1_HOST". For options
1171 # with predefined possible values (yes/no, etc.) the values are passed
1172 # always in lower case.
1173 #
1174 # NOTE: This is a short documentation, for more information visit
1175 # http://nzbget.net/Extension_scripts.
11761194 #Task1.Param=
11771195
11781196 #Task2.Time=20:00
11821200
11831201
11841202 ##############################################################################
1185 ### PAR CHECK/REPAIR ###
1203 ### CHECK AND REPAIR ###
11861204
11871205 # Whether and how par-verification must be performed (auto, always, force, manual).
11881206 #
11921210 # is enabled;
11931211 # Always - check every download (even undamaged). One par2-file is
11941212 # always downloaded. Additional par2-files are downloaded
1195 # if needed for repair. Repair is performed if the option
1213 # if needed for repair. Repair is performed if the option
11961214 # <ParRepair> is enabled;
11971215 # Force - force par-check for every download (even undamaged). All
11981216 # par2-files are always downloaded. Repair is performed if
12041222 # eventually on another faster computer.
12051223 ParCheck=auto
12061224
1207 # Check for renamed and missing files (yes, no).
1208 #
1209 # Par-rename restores original file names using information stored
1210 # in par2-files. It also detects missing files (files listed in
1211 # par2-files but not present on disk). When enabled the par-rename is
1212 # performed as the first step of post-processing for every nzb-file.
1213 #
1214 # Par-rename is very fast and is highly recommended, especially if
1215 # unpack is disabled.
1216 ParRename=yes
1217
12181225 # Automatic par-repair after par-verification (yes, no).
12191226 #
12201227 # If option <ParCheck> is set to "Auto" or "Force" this option defines
12391246 # Quick file verification during par-check (yes, no).
12401247 #
12411248 # If the option is active the files are quickly verified using
1242 # checksums calculated during download; quick verification is very fast
1243 # because it doesn't require the reading of files from disk, NZBGet
1244 # knows checksums of downloaded files and quickly compares them with
1249 # checksums calculated during download; quick verification is very fast
1250 # because it doesn't require the reading of files from disk, NZBGet
1251 # knows checksums of downloaded files and quickly compares them with
12451252 # checksums stored in the par-file.
12461253 #
12471254 # If the option is disabled the files are verified as usual. That's
12881295 #
12891296 # Example: .sfv, .nzb, .nfo
12901297 ParIgnoreExt=.sfv, .nzb, .nfo
1298
1299 # Check for renamed and missing files using par-files (yes, no).
1300 #
1301 # Par-rename restores original file names using information stored
1302 # in par2-files. It also detects missing files (files listed in
1303 # par2-files but not present on disk). When enabled the par-rename is
1304 # performed as the first step of post-processing for every nzb-file.
1305 #
1306 # Par-rename is very fast and is highly recommended, especially if
1307 # unpack is disabled.
1308 ParRename=yes
1309
1310 # Check for renamed rar-files (yes, no).
1311 #
1312 # Rar-rename restores original file names using information stored
1313 # in rar-files. When enabled the rar-rename is performed as one of the
1314 # first steps of post-processing for every nzb-file.
1315 #
1316 # Rar-rename is useful for downloads not having par2-files or for
1317 # downloads those files were renamed before creating par2-files. In
1318 # both cases par-rename (option <ParRename>) can't rename files
1319 # and the rar-rename makes it possible to unpack downloads which
1320 # would fail otherwise.
1321 RarRename=yes
12911322
12921323 # What to do if download health drops below critical health (delete, park,
12931324 # pause, none).
13761407 #
13771408 # Example: /usr/bin/unrar.
13781409 #
1379 # The option can also contain extra switches to pass to unrar. To the
1410 # The option can also contain extra switches to pass to unrar. To the
13801411 # here defined command line NZBGet adds the following switches:
13811412 # x -y -p- -o+ *.rar ./_unpack/
13821413 #
14121443 #
14131444 # List of file extensions, file names or file masks to delete after
14141445 # successful download. If either unpack or par-check fail the cleanup is
1415 # not performed. If neither unpack nor par-check were made (because they
1416 # were disabled or the download doesn't contain archives and/or par-files
1417 # the cleanup is performed if the health is 100%.
1446 # not performed. If download doesn't contain archives nor par-files
1447 # the cleanup is performed if the health is 100%. If parameter "unpack"
1448 # is disabled for that nzb-file the cleanup isn't performed.
14181449 #
14191450 # The entries must be separated with commas. The entries can be file
14201451 # extensions, file names or file masks containing wildcard
14231454 # Example: .par2, .sfv
14241455 ExtCleanupDisk=.par2, .sfv, _brokenlog.txt
14251456
1457 # Files to ignore during unpack.
1458 #
1459 # List of file extensions to ignore when unpacking archives or renaming
1460 # obfuscated archive files. The entries must be separated with commas.
1461 #
1462 # Archive files with non standard extensions belong to one of two categories: they
1463 # are either obfuscated files or files with special purposes which should not be
1464 # unpacked. List the files of second type here to avoid attempts to unpack them.
1465 #
1466 # This option has effect on two post-processing stages.
1467 #
1468 # First, during rar-rename (option <RarRename>) rar-files with non-standard
1469 # extensions are renamed back to rar-extension, which is required for successful
1470 # unpacking. Files with extensions listed here will not be renamed.
1471 #
1472 # Second, if during unpack no rar-files are found but instead rar-archives
1473 # with non-rar extensions are found the unpack fails. For files listed here
1474 # no unpack failure occurs and download is considered not having archive
1475 # files and be successful.
1476 #
1477 # Example: .cbr
1478 UnpackIgnoreExt=.cbr
1479
14261480 # Path to file containing unpack passwords.
14271481 #
14281482 # If the option is set the program will try all passwords from the file
14331487 # then the password-file is not used for that nzb-file.
14341488 #
14351489 # NOTE: Trying multiple passwords is a time consuming task. Whenever possible
1436 # passwords should be set per nzb-file in their post-processing settings.
1490 # passwords should be set per nzb-file in their post-processing settings.
14371491 UnpackPassFile=
14381492
14391493
14401494 ##############################################################################
14411495 ### EXTENSION SCRIPTS ###
14421496
1443 # Default list of post-processing scripts to execute after the download
1444 # of nzb-file is completed and possibly par-checked/repaired and unpacked.
1497 # List of active extension scripts for new downloads.
1498 #
1499 # Extension scripts associated with nzb-files are executed before, during
1500 # or after download as defined by script developer.
1501 #
1502 # Each download (nzb-file) has its own list of extension scripts; the list
1503 # can be viewed and changed in web-interface in download details dialog or
1504 # via API. Option <Extensions> sets defaults for new downloads; changes
1505 # to option <Extensions> do not affect downloads which are already in queue.
1506 #
1507 # When nzb-file is added to queue it can have a category assigned to it. In this
1508 # case option <CategoryX.Extensions> (if not empty) have precedence and
1509 # defines the scripts for that nzb-file; consequently global option <Extensions>
1510 # has no effect for that nzb-file.
1511 #
1512 # Certain extensions work globally for the whole program instead of
1513 # per-nzb basis. Such extensions are activated once and cannot be overriden
1514 # per category or per nzb.
14451515 #
14461516 # The scripts in the list must be separated with commas or semicolons. All
14471517 # scripts must be stored in directory set by option <ScriptDir> and
14491519 #
14501520 # Example: Cleanup.sh, Move.sh, EMail.py.
14511521 #
1452 # Each download (nzb-file) has its own list of post-processing scripts. The option
1453 # <PostScript> is the default value assigned to download when it is added to
1454 # queue. The list of post-processing scripts for a particular download can be
1455 # changed in the edit dialog in web-interface or using remote command "--edit/-E".
1456 #
1457 # When nzb-file is added to queue it can have a category assigned to it. In this
1458 # case the option <CategoryX.PostScript> (if not empty) overrides the
1459 # global option <PostScript>.
1460 #
14611522 # NOTE: The script execution order is controlled by option <ScriptOrder>, not
1462 # by their order in option <PostScript>.
1463 #
1464 # NOTE: Changing options <PostScript> and <CategoryX.PostScript> doesn't affect
1465 # already queued downloads.
1466 #
1467 # NOTE: For the list of interesting post-processing scripts see
1523 # by their order in option <Extensions>.
1524 #
1525 # NOTE: For the list of interesting extension scripts see
14681526 # http://nzbget.net/Catalog_of_post-processing_scripts.
14691527 #
1470 # INFO FOR DEVELOPERS:
1471 # NOTE: This is a short documentation, for more information visit
1472 # http://nzbget.net/Extension_scripts.
1473 #
1474 # NZBGet passes following arguments to post-processing script as environment
1475 # variables:
1476 # NZBPP_DIRECTORY - path to destination directory for downloaded files;
1477 # NZBPP_NZBNAME - user-friendly name of processed nzb-file as it is displayed
1478 # by the program. The file path and extension are removed.
1479 # If download was renamed, this parameter reflects the new name;
1480 # NZBPP_NZBFILENAME - original name of processed nzb-file. It includes file extension
1481 # and may include full path;
1482 # NZBPP_QUEUEDFILE - full filename of the queued (renamed) nzb-file;
1483 # NZBPP_FINALDIR - final destination path if set by one of previous pp-scripts;
1484 # NZBPP_CATEGORY - category assigned to nzb-file (can be empty string);
1485 # NZBPP_DUPEKEY - duplicate key of nzb-file;
1486 # NZBPP_DUPESCORE - duplicate score of nzb-file;
1487 # NZBPP_DUPEMODE - duplicate mode of nzb-file: SCORE, ALL, FORCE;
1488 # NZBPP_TOTALSTATUS - total status of nzb-file:
1489 # SUCCESS - everything OK;
1490 # WARNING - download is damaged but probably can
1491 # be repaired; user intervention is
1492 # required;
1493 # FAILURE - download has failed or a serious error
1494 # occurred during post-processing (unpack, par);
1495 # DELETED - download was deleted; post-processing
1496 # scripts are usually not called in this case;
1497 # however it's possible to force calling
1498 # scripts with command "post-process again";
1499 # NZBPP_STATUS - complete status info for nzb-file: it consists
1500 # of total status and status detail separated with
1501 # slash, for example: "FAILURE/UNPACK"; for possible
1502 # status details see documentation on web site;
1503 # NZBPP_SCRIPTSTATUS - summary status of the scripts executed before the
1504 # current one:
1505 # NONE - no other scripts were executed yet or all
1506 # of them have ended with exit code "NONE";
1507 # SUCCESS - all other scripts have ended with exit
1508 # code "SUCCESS" ;
1509 # FAILURE - at least one of the script has failed;
1510 # NZBPP_HEALTH - download health: an integer value in the range
1511 # from 0 (all articles failed) to 1000 (all articles
1512 # successfully downloaded);
1513 # NZBPP_CRITICALHEALTH - critical health for this nzb-file: an integer
1514 # value in the range 0-1000. The critical health
1515 # is calculated based on number and size of
1516 # par-files. If nzb-file doesn't have any par-files
1517 # the critical health is 1000 (100.0%). If a half
1518 # of nzb-file were par-files its critical health
1519 # would be 0. If NZBPP_HEALTH goes down below
1520 # NZBPP_CRITICALHEALTH the download becomes unrepairable;
1521 # NZBPP_TOTALARTICLES - number of articles in nzb-file;
1522 # NZBPP_SUCCESSARTICLES - number of successfully downloaded articles;
1523 # NZBPP_FAILEDARTICLES - number of failed articles;
1524 # NZBPP_SERVERX_SUCCESSARTICLES - number of successfully downloaded
1525 # articles from ServerX (X is replaced with server
1526 # number, for example NZBPP_SERVER1_SUCCESSARTICLES);
1527 # NZBPP_SERVERX_FAILEDARTICLES - number of failed articles from ServerX.
1528 #
1529 # If the script defines own options they are also passed as environment
1530 # variables. These variables have prefix "NZBPO_" in their names. For
1531 # example, option "myoption" will be passed as environment variable
1532 # "NZBPO_myoption" and in addition in uppercase as "NZBPO_MYOPTION".
1533 #
1534 # If the script defines own post-processing parameters, they are also passed as
1535 # environment variables. These variables have prefix "NZBPR_" in their
1536 # names. For example, pp-parameter "myparam" will be passed as environment
1537 # variable "NZBPR_myparam" and in addition in uppercase as "NZBPR_MYPARAM".
1538 #
1539 # In addition to arguments, pp-options and pp-parameters NZBGet passes all
1540 # nzbget.conf-options to pp-script as environment variables. These
1541 # variables have prefix "NZBOP_" and are written in UPPER CASE. For Example
1542 # option "ParRepair" is passed as environment variable "NZBOP_PARREPAIR". The
1543 # dots in option names are replaced with underscores, for example
1544 # "SERVER1_HOST". For options with predefined possible values (yes/no, etc.)
1545 # the values are passed always in lower case.
1546 #
1547 # If the script moves files it can inform the program about new location
1548 # by printing special message into standard output (which is processed
1549 # by NZBGet):
1550 # echo "[NZB] DIRECTORY=/path/to/moved/files";
1551 # or:
1552 # echo "[NZB] FINALDIR=/path/to/moved/files";
1553 #
1554 # Command "DIRECTORY" changes the destination path of the download and
1555 # affects the scripts executed after the current script as well as the
1556 # program code itself, for example the command "Post-process again"
1557 # will work on new location. Command "FINALDIR" just sets a separate
1558 # property of the download and should be used when the files are moved
1559 # into an existing directory containing other files to avoid the processing
1560 # of those files by other scripts.
1561 #
1562 # To assign post-processing parameters:
1563 # echo "[NZB] NZBPR_myvar=my value";
1564 #
1565 # The prefix "NZBPR_" will be removed. In this example a post-processing
1566 # parameter with name "myvar" and value "my value" will be associated
1567 # with nzb-file.
1568 #
1569 # To inform NZBGet about bad download:
1570 # echo "[NZB] MARK=BAD";
1571 #
1572 # Return value: NZBGet processes the exit code returned by the script:
1573 # 93 - post-process successful (status = SUCCESS);
1574 # 94 - post-process failed (status = FAILURE);
1575 # 95 - post-process skipped (status = NONE). Use this code when you script
1576 # terminates immediately without doing any job and when this is not
1577 # a failure termination;
1578 # 92 - request NZBGet to do par-check/repair for current nzb-file.
1579 #
1580 # All other return codes are interpreted as failure (status = FAILURE).
1581 #
1582 # NOTE: This is a short documentation, for more information visit
1583 # http://nzbget.net/Extension_scripts.
1584 PostScript=
1585
1586 # List of scan scripts to execute before a nzb-file is added to queue.
1528 # NOTE: For developer documentation visit http://nzbget.net/Extension_scripts.
1529 Extensions=
1530
1531 # Execution order for extension scripts.
1532 #
1533 # If you assign multiple scripts to one nzb-file, they are executed in the
1534 # order defined by this option.
15871535 #
15881536 # The scripts in the list must be separated with commas or semicolons. All
15891537 # scripts must be stored in directory set by option <ScriptDir> and
15901538 # paths relative to <ScriptDir> must be entered here.
15911539 #
1592 # The scripts are executed each time a new file is found in incoming
1593 # directory (option <NzbDir>) or a file is received via RPC (web-interface,
1594 # command "nzbget --append", etc.).
1595 #
1596 # Example: UnzipNzb.sh, ScanNotify.py.
1597 #
1598 # The scripts can unpack archives which were put in incoming directory, make
1599 # filename cleanup, change nzb-name, category, priority and post-processing
1600 # parameters of the nzb-file or do other things.
1601 #
1602 # INFO FOR DEVELOPERS:
1603 # NOTE: This is a short documentation, for more information visit
1604 # http://nzbget.net/Extension_scripts.
1605 #
1606 # NZBGet passes following arguments to the script as environment
1607 # variables:
1608 # NZBNP_DIRECTORY - path to directory, where file is located. It is a directory
1609 # specified by the option <NzbDir> or a subdirectory;
1610 # NZBNP_FILENAME - name of file to be processed;
1611 # NZBNP_NZBNAME - nzb-name (without path but with extension);
1612 # NZBNP_CATEGORY - category of nzb-file;
1613 # NZBNP_PRIORITY - priority of nzb-file;
1614 # NZBNP_TOP - flag indicating that the file will be added to the top
1615 # of queue: 0 or 1;
1616 # NZBNP_PAUSED - flag indicating that the file will be added as
1617 # paused: 0 or 1;
1618 # NZBNP_DUPEKEY - duplicate key of nzb-file;
1619 # NZBNP_DUPESCORE - duplicate score of nzb-file;
1620 # NZBNP_DUPEMODE - duplicate mode of nzb-file: SCORE, ALL, FORCE.
1621 #
1622 # In addition to these arguments NZBGet passes all nzbget.conf-options
1623 # as environment variables. These variables have prefix "NZBOP_" and
1624 # are written in UPPER CASE. For , the option "ParRepair" is passed as
1625 # environment variable "NZBOP_PARREPAIR". The dots in option names are
1626 # replaced with underscores, for example "SERVER1_HOST". For options
1627 # with predefined possible values (yes/no, etc.) the values are passed
1628 # always in lower case.
1629 #
1630 # The script can change nzb-name, category, priority,
1631 # post-processing parameters and top-/paused-flags of the nzb-file
1632 # by printing special messages into standard output (which is processed
1633 # by NZBGet).
1634 #
1635 # To change nzb-name use following syntax:
1636 # echo "[NZB] NZBNAME=my download";
1637 #
1638 # To change category:
1639 # echo "[NZB] CATEGORY=my category";
1640 #
1641 # To change priority:
1642 # echo "[NZB] PRIORITY=signed_integer_value";
1643 #
1644 # for example: to set priority higher than normal:
1645 # echo "[NZB] PRIORITY=50";
1646 #
1647 # another example: use a negative value for "lower than normal" priority:
1648 # echo "[NZB] PRIORITY=-100";
1649 #
1650 # Although priority can be any integer value, the web-interface operates
1651 # with six predefined priorities:
1652 # -100 - very low priority;
1653 # -50 - low priority;
1654 # 0 - normal priority (default);
1655 # 50 - high priority;
1656 # 100 - very high priority;
1657 # 900 - force priority.
1658 #
1659 # Downloads with priorities equal to or greater than 900 are downloaded and
1660 # post-processed even if the program is in paused state (force mode).
1661 #
1662 # To assign post-processing parameters:
1663 # echo "[NZB] NZBPR_myvar=my value";
1664 #
1665 # The prefix "NZBPR_" will be removed. In this example a post-processing
1666 # parameter with name "myvar" and value "my value" will be associated
1667 # with nzb-file.
1668 #
1669 # To change top-flag (nzb-file will be added to the top of queue):
1670 # echo "[NZB] TOP=1";
1671 #
1672 # To change paused-flag (nzb-file will be added in paused state):
1673 # echo "[NZB] PAUSED=1";
1674 #
1675 # To change duplicate key:
1676 # echo "[NZB] DUPEKEY=tv show s01e02";
1677 #
1678 # To change duplicate score:
1679 # echo "[NZB] DUPESCORE=integer_value";
1680 #
1681 # To change duplicate mode:
1682 # echo "[NZB] DUPEMODE=(SCORE|ALL|FORCE)";
1683 #
1684 # The script can delete processed file, rename it or move somewhere.
1685 # After the calling of the script the file will be either added to queue
1686 # (if it was an nzb-file) or renamed by adding the extension ".processed".
1687 #
1688 # NOTE: Files with extensions ".processed", ".queued" and ".error" are skipped
1689 # during the directory scanning.
1690 #
1691 # NOTE: Files with extension ".nzb_processed" are not passed to
1692 # scan-script before adding to queue. This feature allows scan-script
1693 # to prevent the scanning of nzb-files extracted from archives, if
1694 # they were already processed by the script.
1695 #
1696 # NOTE: Files added via RPC calls in particular from web-interface are
1697 # saved into incoming nzb-directory and then processed by the script.
1698 #
1699 # NOTE: This is a short documentation, for more information visit
1700 # http://nzbget.net/Extension_scripts.
1701 ScanScript=
1702
1703 # List of queue scripts to execute on queue events.
1704 #
1705 # The scripts in the list must be separated with commas or semicolons. All
1706 # scripts must be stored in directory set by option <ScriptDir> and
1707 # paths relative to <ScriptDir> must be entered here.
1708 #
1709 # The scripts are executed on certain queue events such as adding
1710 # a new nzb-file to queue, etc.
1711 #
1712 # Example: DeleteQueueSamples.sh, NzbAddedNotify.py.
1713 #
1714 # The script can modify the files in download queue (for example
1715 # delete or pause all .nfo, .sfv, sample files) or do something else.
1716 #
1717 # INFO FOR DEVELOPERS:
1718 # NOTE: This is a short documentation, for more information visit
1719 # http://nzbget.net/Extension_scripts.
1720 #
1721 # NZBGet passes following arguments to the queue script as environment
1722 # variables:
1723 # NZBNA_NZBNAME - name of nzb-group. This name can be used in calls
1724 # to nzbget edit-command using the subswitch "-GN name";
1725 # NZBNA_FILENAME - filename of the nzb-file. If the file was added
1726 # from nzb-directory this is the full name with path.
1727 # If the file was added via web-interface it contains
1728 # only filename without path;
1729 # NZBNA_EVENT - describes why the script was called:
1730 # NZB_ADDED - after adding of nzb-file to queue;
1731 # FILE_DOWNLOADED - after a file included in nzb is
1732 # downloaded;
1733 # NZB_DOWNLOADED - after all files in nzb are downloaded
1734 # (before post-processing);
1735 # NZB_DELETED - when nzb is deleted from queue (moved
1736 # to history). See NZBNA_DELETESTATUS for details;
1737 # NZB_MARKED - when a history item is marked as good, bad or
1738 # success. See NZBNA_MARKSTATUS for details;
1739 # URL_COMPLETED - after an URL download is completed
1740 # and the downloaded file was not added to queue
1741 # (not nzb-extension, download error, parse
1742 # error). See NZBNA_URLSTATUS for details;
1743 # In the future the list of supported events may be
1744 # extended. To avoid conflicts with future NZBGet
1745 # versions the script must exit if the parameter
1746 # has a value unknown to the script;
1747 # NZBNA_QUEUEDFILE - full filename of the queued (renamed) nzb-file;
1748 # NZBNA_DELETESTATUS - delete status info, NZBNA_EVENT=NZB_DELETED:
1749 # MANUAL - deleted by user or via API call;
1750 # HEALTH - deleted by health check;
1751 # DUPE - moved to history by duplicate check, can be
1752 # reused later if necessary;
1753 # GOOD - moved to history by duplicate check because
1754 # there is already a duplicate marked as good;
1755 # BAD - marked as bad by user or by queue-script;
1756 # COPY - already in queue or in history;
1757 # SCAN - malformed nzb-file, cannot be parsed;
1758 # NZBNA_URLSTATUS - URL status info, when NZBNA_EVENT=URL_COMPLETED:
1759 # FAILURE - fetch error (could not be downloaded);
1760 # SCAN_SKIPPED - downloaded file doesn't have
1761 # nzb-extension and was skipped;
1762 # SCAN_FAILED - file format error;
1763 # NZBNA_MARKSTATUS - mark status info, NZBNA_EVENT=NZB_MARKED:
1764 # GOOD - marked as good by user or by a script;
1765 # BAD - marked as bad;
1766 # SUCCESS - marked as success;
1767 # NZBNA_CATEGORY - category of nzb-file (if assigned);
1768 # NZBNA_NZBID - id of the nzb-file. This ID can be used with
1769 # calls to nzbget edit-command;
1770 # NZBNA_PRIORITY - priority (default is 0);
1771 # NZBNA_DUPEKEY - duplicate key of nzb-file;
1772 # NZBNA_DUPESCORE - duplicate score of nzb-file;
1773 # NZBNA_DUPEMODE - duplicate mode of nzb-file: SCORE, ALL, FORCE.
1774 #
1775 # In addition to these arguments NZBGet passes all nzbget.conf-options
1776 # to the script as environment variables. These variables have prefix
1777 # "NZBOP_" and are written in UPPER CASE. For Example, the option "ParRepair"
1778 # is passed as environment variable "NZBOP_PARREPAIR". The dots in option
1779 # names are replaced with underscores, for example "SERVER1_HOST". For
1780 # options with predefined possible values (yes/no, etc.) the values are
1781 # passed always in lower case.
1782 #
1783 # The script can printing special messages into standard output (which
1784 # is processed by NZBGet).
1785 #
1786 # To assign post-processing parameters:
1787 # echo "[NZB] NZBPR_myvar=my value";
1788 #
1789 # The prefix "NZBPR_" will be removed. In this example a post-processing
1790 # parameter with name "myvar" and value "my value" will be associated
1791 # with nzb-file.
1792 #
1793 # To inform NZBGet about bad download:
1794 # echo "[NZB] MARK=BAD";
1795 #
1796 # To set destination directory (only from event "NZB_DOWNLOADED"):
1797 # echo "[NZB] DIRECTORY=/destination/path/for/this/nzb";
1798 #
1799 # Examples of what the script can do:
1800 # 1) pausing nzb-file using file-id:
1801 # "$NZBOP_APPBIN" -c "$NZBOP_CONFIGFILE" -E G P $NZBNA_NZBID;
1802 # 2) setting category using nzb-name:
1803 # "$NZBOP_APPBIN" -c "$NZBOP_CONFIGFILE" -E GN K "my cat" "$NZBNA_NZBNAME";
1804 # 3) pausing files with extension "nzb":
1805 # "$NZBOP_APPBIN" -c "$NZBOP_CONFIGFILE" -E FR P "$NZBNA_NZBNAME/.*\.nzb";
1806 #
1807 # NOTE: This is a short documentation, for more information visit
1808 # http://nzbget.net/Extension_scripts.
1809 QueueScript=
1810
1811 # List of rss feed scripts to execute before a rss feed content is processed.
1812 #
1813 # The scripts in the list must be separated with commas or semicolons. All
1814 # scripts must be stored in directory set by option <ScriptDir> and
1815 # paths relative to <ScriptDir> must be entered here.
1816 #
1817 # If rss feed has option <FeedX.FeedScript> defined (if not empty)
1818 # the scripts defined there override the global option <FeedScript>.
1819 #
1820 # The scripts are executed after rss feed is read from server and before it
1821 # is processed by the feed parser. Once the feed is fetched it is saved
1822 # to a temporary file and the feed scripts are executed. The scripts
1823 # can modify the content of the temporary feed file. Then the file is
1824 # read by the feed parser and processed.
1825 #
1826 # Example: Rss.sh, Filter.py.
1827 #
1828 # The feed content is usually filtered using option <FeedX.Filter>. If a
1829 # required filtering cannot be achieved via built-in filter commands the
1830 # more advanced processing of the feed can be made using feed scripts.
1831 #
1832 # INFO FOR DEVELOPERS:
1833 # NOTE: This is a short documentation, for more information visit
1834 # http://nzbget.net/Extension_scripts.
1835 #
1836 # NZBGet passes following arguments to the script as environment
1837 # variables:
1838 # NZBFP_FILENAME - name of feed file to be processed;
1839 # NZBFP_FEEDID - ID of the feed.
1840 #
1841 # In addition to these arguments NZBGet passes all nzbget.conf-options
1842 # as environment variables. These variables have prefix "NZBOP_" and
1843 # are written in UPPER CASE. For Example option "ParRepair" is passed as
1844 # environment variable "NZBOP_PARREPAIR". The dots in option names are
1845 # replaced with underscores, for example "SERVER1_HOST". For options
1846 # with predefined possible values (yes/no, etc.) the values are passed
1847 # always in lower case.
1848 #
1849 # Return value: NZBGet processes the exit code returned by the script:
1850 # 93 - script successful (status = SUCCESS).
1851 # All other return codes are interpreted as failure (status = FAILURE).
1852 #
1853 # If the script doesn't end with SUCCESS-status the whole content of RSS
1854 # feed is ignored. This is to prevent accidental enqueuing of many
1855 # nzb-files if a feed script unexpectedly terminates before processing
1856 # of the feed.
1857 #
1858 # NOTE: This is a short documentation, for more information visit
1859 # http://nzbget.net/Extension_scripts.
1860 FeedScript=
1861
1862 # Execution order for scripts.
1863 #
1864 # If you assign multiple scripts to one nzb-file, they are executed in the
1865 # order defined by this option. Scripts not listed here are executed at
1866 # the end in their alphabetical order.
1867 #
1868 # The scripts in the list must be separated with commas or semicolons. All
1869 # scripts must be stored in directory set by option <ScriptDir> and
1870 # paths relative to <ScriptDir> must be entered here.
1871 #
18721540 # Example: Cleanup.sh, Move.sh.
18731541 ScriptOrder=
18741542
18931561 # Example: .py=/usr/bin/python2;.py3=/usr/bin/python3;.sh=/usr/bin/bash.
18941562 ShellOverride=
18951563
1896 # Minimum interval between calls of queue-scripts (seconds).
1897 #
1898 # Queue-scripts are executed during download, after every file included in
1899 # nzb-file is downloaded. If the files are small they may be downloaded
1900 # very fast causing queue-scripts to be working all the time. Sometimes
1901 # this may lead to a performance decrease on systems with slow CPUs.
1902 #
1903 # This option allows to reduce the number of calls of queue-scripts by
1904 # skipping "file-downloaded"-events if the previous call of queue-scripts
1905 # for the same download (nzb-file) were performed a short time ago
1906 # (as defined by the option).
1907 #
1908 # Value "-1" disables executing of queue-scripts on
1909 # "file-downloaded"-events. Scripts are still executed on events
1910 # "nzb-added" and "nzb-downloaded".
1911 #
1912 # NOTE: This options affects only queue-scripts and only
1913 # "file-downloaded"-events. Queue-scripts can be activated using
1914 # option <QueueScript> (for pure queue-scripts) or option <PostScript>
1915 # (for dual-mode scripts which act as queue- and post-processing-scripts
1916 # at the same time).
1564 # Minimum interval between queue events (seconds).
1565 #
1566 # Extension scripts can opt-in for progress notifcations during
1567 # download. For downloads containing many small files the events can
1568 # be fired way too often increasing load on the system due to script
1569 # execution.
1570 #
1571 # This option allows to reduce the number of calls of scripts by
1572 # skipping "file-downloaded"-events if the previous call for the same
1573 # download (nzb-file) were performed a short time ago (as defined by
1574 # the option).
1575 #
1576 # Value "-1" disables "file-downloaded"-events. Scripts are still
1577 # notified on other events (such as "nzb-added" or "nzb-downloaded").
19171578 EventInterval=0
5050 <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
5151 <ClCompile>
5252 <Optimization>Disabled</Optimization>
53 <AdditionalIncludeDirectories>.\daemon\connect;.\daemon\extension;.\daemon\feed;.\daemon\frontend;.\daemon\main;.\daemon\nntp;.\daemon\postprocess;.\daemon\queue;.\daemon\remote;.\daemon\util;.\daemon\windows;.\lib\par2;.\windows\resources;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
54 <PreprocessorDefinitions>WIN32;PACKAGE="nzbget";VERSION="17.1";_DEBUG;_CONSOLE;DEBUG;_WIN32_WINNT=0x0403;%(PreprocessorDefinitions)</PreprocessorDefinitions>
53 <AdditionalIncludeDirectories>.\daemon\connect;.\daemon\extension;.\daemon\feed;.\daemon\frontend;.\daemon\main;.\daemon\nserv;.\daemon\nntp;.\daemon\postprocess;.\daemon\queue;.\daemon\remote;.\daemon\util;.\daemon\windows;.\lib\par2;.\windows\resources;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
54 <PreprocessorDefinitions>WIN32;PACKAGE="nzbget";VERSION="18.1";_DEBUG;_CONSOLE;DEBUG;_WIN32_WINNT=0x0403;%(PreprocessorDefinitions)</PreprocessorDefinitions>
5555 <MinimalRebuild>false</MinimalRebuild>
5656 <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
5757 <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
7070 </ItemDefinitionGroup>
7171 <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
7272 <ClCompile>
73 <AdditionalIncludeDirectories>.\daemon\connect;.\daemon\extension;.\daemon\feed;.\daemon\frontend;.\daemon\main;.\daemon\nntp;.\daemon\postprocess;.\daemon\queue;.\daemon\remote;.\daemon\util;.\daemon\windows;.\lib\par2;.\windows\resources;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
74 <PreprocessorDefinitions>WIN32;PACKAGE="nzbget";VERSION="17.1";NDEBUG;_CONSOLE;_WIN32_WINNT=0x0403;%(PreprocessorDefinitions)</PreprocessorDefinitions>
73 <AdditionalIncludeDirectories>.\daemon\connect;.\daemon\extension;.\daemon\feed;.\daemon\frontend;.\daemon\main;.\daemon\nserv;.\daemon\nntp;.\daemon\postprocess;.\daemon\queue;.\daemon\remote;.\daemon\util;.\daemon\windows;.\lib\par2;.\windows\resources;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
74 <PreprocessorDefinitions>WIN32;PACKAGE="nzbget";VERSION="18.1";NDEBUG;_CONSOLE;_WIN32_WINNT=0x0403;%(PreprocessorDefinitions)</PreprocessorDefinitions>
7575 <ExceptionHandling>Sync</ExceptionHandling>
7676 <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
7777 <PrecompiledHeader>Use</PrecompiledHeader>
123123 <ClCompile Include="daemon\nntp\NntpConnection.cpp" />
124124 <ClCompile Include="daemon\nntp\ServerPool.cpp" />
125125 <ClCompile Include="daemon\nntp\StatMeter.cpp" />
126 <ClCompile Include="daemon\nserv\NntpServer.cpp" />
127 <ClCompile Include="daemon\nserv\NServFrontend.cpp" />
128 <ClCompile Include="daemon\nserv\NServMain.cpp" />
129 <ClCompile Include="daemon\nserv\NzbGenerator.cpp" />
130 <ClCompile Include="daemon\nserv\YEncoder.cpp" />
126131 <ClCompile Include="daemon\postprocess\Cleanup.cpp" />
127132 <ClCompile Include="daemon\postprocess\DupeMatcher.cpp" />
128133 <ClCompile Include="daemon\postprocess\ParChecker.cpp" />
129 <ClCompile Include="daemon\postprocess\ParCoordinator.cpp" />
134 <ClCompile Include="daemon\postprocess\Repair.cpp" />
130135 <ClCompile Include="daemon\postprocess\ParParser.cpp" />
131136 <ClCompile Include="daemon\postprocess\ParRenamer.cpp" />
132137 <ClCompile Include="daemon\postprocess\PrePostProcessor.cpp" />
138 <ClCompile Include="daemon\postprocess\RarReader.cpp" />
139 <ClCompile Include="daemon\postprocess\RarRenamer.cpp" />
140 <ClCompile Include="daemon\postprocess\Rename.cpp" />
133141 <ClCompile Include="daemon\postprocess\Unpack.cpp" />
134142 <ClCompile Include="daemon\queue\DiskState.cpp" />
135143 <ClCompile Include="daemon\queue\DownloadInfo.cpp" />
170178 <ClCompile Include="lib\par2\galois.cpp" />
171179 <ClCompile Include="lib\par2\mainpacket.cpp" />
172180 <ClCompile Include="lib\par2\md5.cpp" />
173 <ClCompile Include="lib\par2\par2creatorsourcefile.cpp" />
174181 <ClCompile Include="lib\par2\par2fileformat.cpp" />
175182 <ClCompile Include="lib\par2\par2repairer.cpp" />
176183 <ClCompile Include="lib\par2\par2repairersourcefile.cpp" />
213220 <ClInclude Include="daemon\nntp\NntpConnection.h" />
214221 <ClInclude Include="daemon\nntp\ServerPool.h" />
215222 <ClInclude Include="daemon\nntp\StatMeter.h" />
223 <ClInclude Include="daemon\nserv\NntpServer.h" />
224 <ClInclude Include="daemon\nserv\NServFrontend.h" />
225 <ClInclude Include="daemon\nserv\NServMain.h" />
226 <ClInclude Include="daemon\nserv\NzbGenerator.h" />
227 <ClInclude Include="daemon\nserv\YEncoder.h" />
216228 <ClInclude Include="daemon\postprocess\Cleanup.h" />
217229 <ClInclude Include="daemon\postprocess\DupeMatcher.h" />
218230 <ClInclude Include="daemon\postprocess\ParChecker.h" />
219 <ClInclude Include="daemon\postprocess\ParCoordinator.h" />
231 <ClInclude Include="daemon\postprocess\Repair.h" />
220232 <ClInclude Include="daemon\postprocess\ParParser.h" />
221233 <ClInclude Include="daemon\postprocess\ParRenamer.h" />
222234 <ClInclude Include="daemon\postprocess\PrePostProcessor.h" />
235 <ClInclude Include="daemon\postprocess\RarReader.h" />
236 <ClInclude Include="daemon\postprocess\RarRenamer.h" />
237 <ClInclude Include="daemon\postprocess\Rename.h" />
223238 <ClInclude Include="daemon\postprocess\Unpack.h" />
224239 <ClInclude Include="daemon\queue\DiskState.h" />
225240 <ClInclude Include="daemon\queue\DownloadInfo.h" />
260275 <ClInclude Include="lib\par2\mainpacket.h" />
261276 <ClInclude Include="lib\par2\md5.h" />
262277 <ClInclude Include="lib\par2\par2cmdline.h" />
263 <ClInclude Include="lib\par2\par2creatorsourcefile.h" />
264278 <ClInclude Include="lib\par2\par2fileformat.h" />
265279 <ClInclude Include="lib\par2\par2repairer.h" />
266280 <ClInclude Include="lib\par2\par2repairersourcefile.h" />
283297 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
284298 <ImportGroup Label="ExtensionTargets">
285299 </ImportGroup>
286 </Project>
300 </Project>
2424 <key>LSUIElement</key>
2525 <true/>
2626 <key>NSHumanReadableCopyright</key>
27 <string>Copyright © 2007-2016 Andrey Prygunkov</string>
27 <string>Copyright © 2007-2017 Andrey Prygunkov</string>
2828 <key>NSMainNibFile</key>
2929 <string>MainApp</string>
30 <key>NSAppTransportSecurity</key>
31 <dict>
32 <key>NSAllowsArbitraryLoads</key>
33 <true/>
34 </dict>
3035 <key>NSPrincipalClass</key>
3136 <string>NSApplication</string>
3237 </dict>
322322 MACOSX_DEPLOYMENT_TARGET = 10.7;
323323 OTHER_LDFLAGS = "";
324324 PRODUCT_NAME = NZBGet;
325 SDKROOT = macosx10.7;
325 SDKROOT = macosx;
326326 VALID_ARCHS = x86_64;
327327 };
328328 name = Debug;
345345 INSTALL_PATH = "$(HOME)/Applications";
346346 MACOSX_DEPLOYMENT_TARGET = 10.7;
347347 PRODUCT_NAME = NZBGet;
348 SDKROOT = macosx10.7;
348 SDKROOT = macosx;
349349 VALID_ARCHS = x86_64;
350350 };
351351 name = Release;
77 # it under the terms of the GNU General Public License as published by
88 # the Free Software Foundation; either version 2 of the License, or
99 # (at your option) any later version.
10 #
10 #
1111 # This program is distributed in the hope that it will be useful,
1212 # but WITHOUT ANY WARRANTY; without even the implied warranty of
1313 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1414 # GNU General Public License for more details.
15 #
15 #
1616 # You should have received a copy of the GNU General Public License
1717 # along with this program. If not, see <http://www.gnu.org/licenses/>.
1818 #
3333 # When to send the message (Always, OnFailure).
3434 #SendMail=Always
3535
36 # Email address you want this email to be sent from.
36 # Email address you want this email to be sent from.
3737 #From="NZBGet" <myaccount@gmail.com>
3838
39 # Email address you want this email to be sent to.
39 # Email address you want this email to be sent to.
40 #
41 # Multiple addresses can be separated with comma.
4042 #To=myaccount@gmail.com
4143
4244 # SMTP server host.
122124 if total_status == 'SUCCESS' and os.environ['NZBPP_SCRIPTSTATUS'] == 'FAILURE':
123125 total_status = 'WARNING'
124126 status = 'WARNING/SCRIPT'
125
127
126128 success = total_status == 'SUCCESS'
127129
128130 if success and os.environ.get('NZBPO_SENDMAIL') == 'OnFailure':
150152 port = os.environ['NZBOP_CONTROLPORT'];
151153 username = os.environ['NZBOP_CONTROLUSERNAME'];
152154 password = os.environ['NZBOP_CONTROLPASSWORD'];
153
155
154156 if host == '0.0.0.0': host = '127.0.0.1'
155
157
156158 # Build an URL for XML-RPC requests
157159 rpcUrl = 'http://%s:%s@%s:%s/xmlrpc' % (username, password, host, port);
158
160
159161 # Create remote server object
160162 server = ServerProxy(rpcUrl)
161163
162164 if os.environ.get('NZBPO_STATISTICS') == 'yes':
163 # Find correct nzb in method listgroups
165 # Find correct nzb in method listgroups
164166 groups = server.listgroups(0)
165167 nzbID = int(os.environ['NZBPP_NZBID'])
166168 for nzbGroup in groups:
225227 # To get the item log we connect to NZBGet via XML-RPC and call
226228 # method "loadlog", which returns the log for a given nzb item.
227229 # For more info visit http://nzbget.net/RPC_API_reference
228
230
229231 # Call remote method 'loadlog'
230232 nzbid = int(os.environ['NZBPP_NZBID'])
231233 log = server.loadlog(nzbid, 0, 10000)
232
234
233235 # Now iterate through entries and save them to message text
234236 if len(log) > 0:
235237 text += '\n\nNzb-log:';
255257
256258 if os.environ['NZBPO_ENCRYPTION'] == 'yes':
257259 smtp.starttls()
258
260
259261 if os.environ['NZBPO_USERNAME'] != '' and os.environ['NZBPO_PASSWORD'] != '':
260262 smtp.login(os.environ['NZBPO_USERNAME'], os.environ['NZBPO_PASSWORD'])
261
262 smtp.sendmail(os.environ['NZBPO_FROM'], os.environ['NZBPO_TO'], msg.as_string())
263
263
264 smtp.sendmail(os.environ['NZBPO_FROM'], os.environ['NZBPO_TO'].split(','), msg.as_string())
265
264266 smtp.quit()
265267 except Exception as err:
266268 print('[ERROR] %s' % err)
0 import pytest
1 import subprocess
2 import os
3 import sys
4 import time
5 import shutil
6 import base64
7 try:
8 from xmlrpclib import ServerProxy # python 2
9 except ImportError:
10 from xmlrpc.client import ServerProxy # python 3
11
12 nzbget_srcdir = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
13 nzbget_maindir = nzbget_srcdir + '/tests/testdata/nzbget.temp'
14 nzbget_configfile = nzbget_maindir + '/nzbget.conf'
15 nzbget_rpcurl = 'http://127.0.0.1:6789/xmlrpc';
16
17 nzbget_bin = nzbget_srcdir + '/nzbget'
18 if os.name == 'nt':
19 nzbget_bin += '.exe'
20 nserv_datadir = nzbget_srcdir + '/tests/testdata/nserv.temp'
21
22 if os.name == 'nt':
23 sevenzip_bin = nzbget_srcdir + '/7z.exe'
24 else:
25 sevenzip_bin = nzbget_srcdir + '/p7zip'
26
27 has_failures = False
28
29 def pytest_addoption(parser):
30 parser.addini('nzbget_bin', 'path to nzbget binary', default=nzbget_bin)
31 parser.addini('nserv_datadir', 'path to nserv datadir', default=nserv_datadir)
32 parser.addini('nzbget_maindir', 'path to nzbget maindir', default=nzbget_maindir)
33 parser.addini('sevenzip_bin', 'path to 7-zip', default=sevenzip_bin)
34 parser.addoption("--hold", action="store_true", help="Hold at the end of test (keep NZBGet running)")
35
36 def check_config():
37 global nzbget_bin
38 nzbget_bin = pytest.config.getini('nzbget_bin')
39 if not os.path.exists(nzbget_bin):
40 pytest.exit('Could not find nzbget binary at ' + nzbget_bin + '. Alternative path can be set via pytest ini option "nzbget_bin".')
41
42 global sevenzip_bin
43 sevenzip_bin = pytest.config.getini('sevenzip_bin')
44 if not os.path.exists(sevenzip_bin):
45 pytest.exit('Could not find 7-zip binary at ' + sevenzip_bin + '. Alternative path can be set via pytest ini option "sevenzip_bin".')
46
47 global nserv_datadir
48 nserv_datadir = pytest.config.getini('nserv_datadir')
49
50 global nzbget_maindir
51 nzbget_maindir = pytest.config.getini('nzbget_maindir')
52 global nzbget_configfile
53 nzbget_configfile = nzbget_maindir + '/nzbget.conf'
54
55 class NServ:
56
57 def __init__(self):
58 self.process = subprocess.Popen([nzbget_bin, '--nserv', '-d', nserv_datadir, '-v', '0', '-i', '2'])
59
60 def finalize(self):
61 self.process.kill()
62
63 @pytest.fixture(scope='session')
64
65 def nserv(request):
66 check_config()
67
68 instance = NServ()
69 request.addfinalizer(instance.finalize)
70 return instance
71
72
73 class Nzbget:
74
75 def __init__(self, options, session):
76 self.options = options
77 self.session = session
78 self.api = ServerProxy(nzbget_rpcurl)
79 self.prepare_session()
80 self.process = subprocess.Popen([nzbget_bin, '-c', nzbget_configfile, '-s', '-o', 'outputmode=log'])
81 self.wait_until_started()
82
83 def finalize(self):
84 if pytest.config.getoption("--hold"):
85 print('\nNZBGet is still running, press Ctrl+C to quit')
86 time.sleep(100000)
87 self.process.kill()
88 if not has_failures:
89 self.remove_tempdir()
90
91 def remove_tempdir(self):
92 if os.path.exists(nzbget_maindir + '.old'):
93 shutil.rmtree(nzbget_maindir + '.old')
94 if os.path.exists(nzbget_maindir):
95 os.rename(nzbget_maindir, nzbget_maindir + '.old')
96 shutil.rmtree(nzbget_maindir + '.old')
97
98 def prepare_session(self):
99 self.remove_tempdir()
100 os.makedirs(nzbget_maindir)
101 config = open(nzbget_configfile, 'w')
102 config.write('MainDir=' + nzbget_maindir + '\n')
103 config.write('DestDir=${MainDir}/complete\n')
104 config.write('InterDir=${MainDir}/intermediate\n')
105 config.write('TempDir=${MainDir}/temp\n')
106 config.write('QueueDir=${MainDir}/queue\n')
107 config.write('NzbDir=${MainDir}/nzb\n')
108 config.write('LogFile=${MainDir}/nzbget.log\n')
109 config.write('SevenZipCmd=' + sevenzip_bin + '\n')
110 config.write('WriteLog=append\n')
111 config.write('DetailTarget=log\n')
112 config.write('InfoTarget=log\n')
113 config.write('WarningTarget=log\n')
114 config.write('ErrorTarget=log\n')
115 config.write('DebugTarget=none\n')
116 config.write('ContinuePartial=no\n')
117 config.write('DirectWrite=yes\n')
118 config.write('ArticleCache=500\n')
119 config.write('WriteBuffer=1024\n')
120 config.write('NzbDirInterval=0\n')
121 config.write('FlushQueue=no\n')
122 config.write('WebDir=' + nzbget_srcdir + '/webui\n')
123 config.write('ConfigTemplate=' + nzbget_srcdir + '/nzbget.conf\n')
124 config.write('ControlUsername=\n')
125 config.write('ControlPassword=\n')
126 config.write('ControlIP=127.0.0.1\n')
127 config.write('Server1.host=127.0.0.1\n')
128 config.write('Server1.port=6791\n')
129 config.write('Server1.connections=10\n')
130 config.write('Server1.level=0\n')
131 config.write('Server2.host=127.0.0.1\n')
132 config.write('Server2.port=6792\n')
133 config.write('Server2.connections=10\n')
134 config.write('Server2.level=1\n')
135 config.write('Server2.active=no\n')
136 for opt in self.options:
137 config.write(opt + '\n')
138
139 def wait_until_started(self):
140 print('Waiting for nzbget to start')
141 stat = None
142 for x in range(0, 3):
143 try:
144 stat = self.api.status()
145 except:
146 time.sleep(0.5)
147
148 if stat == None:
149 raise Exception('Could not start nzbget')
150 print('Started')
151
152 def append_nzb(self, nzb_name, nzb_content, unpack = None, dupekey = '', dupescore = 0, dupemode = 'FORCE', params = None):
153 nzbcontent64 = base64.standard_b64encode(nzb_content)
154 if params == None:
155 params = []
156 if unpack == True:
157 params.append(('*unpack:', 'yes'))
158 elif unpack == False:
159 params.append(('*unpack:', 'no'))
160 return self.api.append(nzb_name, nzbcontent64, 'test', 0, False, False, dupekey, dupescore, dupemode, params)
161
162 def load_nzb(self, nzb_name):
163 fullfilename = nserv_datadir + '/' + nzb_name
164 in_file = open(fullfilename, 'r')
165
166 nzbcontent = in_file.read()
167
168 in_file.close()
169 return nzbcontent
170
171
172 def download_nzb(self, nzb_name, nzb_content = None, unpack = None, dupekey = '', dupescore = 0, dupemode = 'FORCE', params = None):
173 if not nzb_content:
174 nzb_content = self.load_nzb(nzb_name)
175 self.append_nzb(nzb_name, nzb_content, unpack, dupekey, dupescore, dupemode, params)
176 hist = self.wait_nzb(nzb_name)
177 return hist
178
179 def wait_nzb(self, nzb_name):
180 print('Waiting for download completion')
181 hist = None
182 while not hist:
183 history = self.api.history()
184 for hist1 in history:
185 if hist1['NZBFilename'] == nzb_name:
186 hist = hist1
187 break
188 time.sleep(0.1)
189 return hist
190
191 def clear(self):
192 self.api.editqueue('HistoryFinalDelete', 0, '', range(1, 1000));
193
194 @pytest.fixture(scope='module')
195
196 def nzbget(request):
197
198 check_config()
199
200 instance = Nzbget(getattr(request.module, 'nzbget_options', []), request.session)
201 request.addfinalizer(instance.finalize)
202 return instance
203
204
205 @pytest.hookimpl(tryfirst=True, hookwrapper=True)
206
207 def pytest_runtest_makereport(item, call):
208
209 # execute all other hooks to obtain the report object
210
211 outcome = yield
212
213 rep = outcome.get_result()
214 global has_failures
215
216 has_failures = has_failures or rep.failed
0 import os
1 import shutil
2 import subprocess
3 import random
4 import array
5 import pytest
6
7
8 def pytest_addoption(parser):
9 parser.addini('sample_medium', 'size of meidum nzb (megabytes)', default=192)
10 parser.addini('sample_large', 'size of large nzb (megabytes)', default=1024)
11
12
13 @pytest.fixture(scope='session', autouse=True)
14 def prepare_testdata(request):
15 print('Preparing test data for "download"')
16
17 nserv_datadir = pytest.config.getini('nserv_datadir')
18 nzbget_bin = pytest.config.getini('nzbget_bin')
19 sevenzip_bin = pytest.config.getini('sevenzip_bin')
20
21 if not os.path.exists(nserv_datadir):
22 print('Creating nserv datadir')
23 os.makedirs(nserv_datadir)
24
25 if not os.path.exists(nserv_datadir + '/medium.nzb'):
26 sizemb = int(pytest.config.getini('sample_medium'))
27 create_test_file(nserv_datadir + '/medium', sevenzip_bin, sizemb)
28
29 if not os.path.exists(nserv_datadir + '/large.nzb'):
30 sizemb = int(pytest.config.getini('sample_large'))
31 create_test_file(nserv_datadir + '/large', sevenzip_bin, sizemb)
32
33 if not os.path.exists(nserv_datadir + '/medium.nzb') or not os.path.exists(nserv_datadir + '/large.nzb'):
34 if 0 != subprocess.call([nzbget_bin, '--nserv', '-d', nserv_datadir, '-v', '2', '-z', '500000', '-q']):
35 pytest.exit('Test file generation failed')
36
37 nzbget_srcdir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
38 if not os.path.exists(nserv_datadir + '/small.nzb'):
39 if not os.path.exists(nserv_datadir + '/small'):
40 os.makedirs(nserv_datadir + '/small')
41 shutil.copyfile(nzbget_srcdir +'/COPYING', nserv_datadir + '/small/small.dat')
42
43 if 0 != subprocess.call([nzbget_bin, '--nserv', '-d', nserv_datadir, '-v', '2', '-z', '3000', '-q']):
44 pytest.exit('Test file generation failed')
45
46
47 def create_test_file(bigdir, sevenzip_bin, sizemb):
48 print('Preparing test file (' + str(sizemb) + 'MB)')
49
50 if not os.path.exists(bigdir):
51 os.makedirs(bigdir)
52
53 f = open(bigdir + '/' + str(sizemb) + 'mb.dat', 'wb')
54 for n in xrange(64 * sizemb / 1024):
55 if n % 8 == 0:
56 print('Writing block %i from %i' % (n, 64 * sizemb / 1024))
57 f.write(os.urandom(1024 * 1024 * 16))
58 f.close()
59
60 if 0 != subprocess.call([sevenzip_bin, 'a', bigdir + '/' + str(sizemb) + 'mb.7z', '-mx=0', '-v50m', bigdir + '/' + str(sizemb) + 'mb.dat']):
61 pytest.exit('Test file generation failed')
62
63 os.remove(bigdir + '/' + str(sizemb) + 'mb.dat')
0 nzbget_options = ['HealthCheck=none', 'ArticleCache=500', 'DirectWrite=yes']
1
2 def test_small(nserv, nzbget):
3 hist = nzbget.download_nzb('small.nzb')
4 assert hist['Status'] == 'SUCCESS/HEALTH'
5
6 def test_small_bad(nserv, nzbget):
7 nzb_content = nzbget.load_nzb('small.nzb')
8 nzb_content = nzb_content.replace('?3=6000:3000', '?3=6000:3000!0')
9 hist = nzbget.download_nzb('small.bad.nzb', nzb_content)
10 assert hist['Status'] == 'FAILURE/HEALTH'
11
12 def test_medium(nserv, nzbget):
13 hist = nzbget.download_nzb('medium.nzb')
14 assert hist['Status'] == 'SUCCESS/HEALTH'
15
16 def test_medium_unpack(nserv, nzbget):
17 nzb_content = nzbget.load_nzb('medium.nzb')
18 hist = nzbget.download_nzb('medium_unpack.nzb', nzb_content, unpack=True)
19 assert hist['Status'] == 'SUCCESS/UNPACK'
20
21 def test_large(nserv, nzbget):
22 hist = nzbget.download_nzb('large.nzb')
23 assert hist['Status'] == 'SUCCESS/HEALTH'
24
25 def test_oneserver_small_success(nserv, nzbget):
26 nzb_content = nzbget.load_nzb('small.nzb')
27 nzb_content = nzb_content.replace('?3=6000:3000', '?3=6000:3000!1')
28 hist = nzbget.download_nzb('small.success.nzb', nzb_content)
29 assert hist['Status'] == 'SUCCESS/HEALTH'
30
31 def test_oneserver_small_bad(nserv, nzbget):
32 nzb_content = nzbget.load_nzb('small.nzb')
33 nzb_content = nzb_content.replace('?3=6000:3000', '?3=6000:3000!2')
34 hist = nzbget.download_nzb('small.bad2.nzb', nzb_content)
35 assert hist['Status'] == 'FAILURE/HEALTH'
36
37 def test_twoservers_small_success(nserv, nzbget):
38 nzbget.api.editserver(2, True)
39 nzb_content = nzbget.load_nzb('small.nzb')
40 nzb_content = nzb_content.replace('?3=6000:3000', '?3=6000:3000!2')
41 hist = nzbget.download_nzb('small.twoservers.nzb', nzb_content)
42 nzbget.api.editserver(2, False)
43 assert hist['Status'] == 'SUCCESS/HEALTH'
44
45 def test_oneserver_medium_failed(nserv, nzbget):
46 nzb_content = nzbget.load_nzb('medium.nzb')
47 nzb_content = nzb_content.replace(':500000', ':500000!2')
48 hist = nzbget.download_nzb('medium-health-failed.nzb', nzb_content)
49 assert hist['Status'] == 'FAILURE/HEALTH'
50
51 def test_twoservers_medium_success(nserv, nzbget):
52 nzbget.api.editserver(2, True)
53 nzb_content = nzbget.load_nzb('medium.nzb')
54 nzb_content = nzb_content.replace(':500000', ':500000!2')
55 hist = nzbget.download_nzb('medium-health-two.nzb', nzb_content)
56 nzbget.api.editserver(2, False)
57 assert hist['Status'] == 'SUCCESS/HEALTH'
0 nzbget_options = ['ArticleCache=0', 'DirectWrite=no']
1
2 def test_small(nserv, nzbget):
3 hist = nzbget.download_nzb('small.nzb')
4 assert hist['Status'] == 'SUCCESS/HEALTH'
5
6 def test_medium_unpack(nserv, nzbget):
7 nzb_content = nzbget.load_nzb('medium.nzb')
8 hist = nzbget.download_nzb('medium_unpack.nzb', nzb_content, unpack=True)
9 assert hist['Status'] == 'SUCCESS/UNPACK'
10
0 nzbget_options = ['ArticleCache=0', 'DirectWrite=yes']
1
2 def test_small(nserv, nzbget):
3 hist = nzbget.download_nzb('small.nzb')
4 assert hist['Status'] == 'SUCCESS/HEALTH'
5
6 def test_medium_unpack(nserv, nzbget):
7 nzb_content = nzbget.load_nzb('medium.nzb')
8 hist = nzbget.download_nzb('medium_unpack.nzb', nzb_content, unpack=True)
9 assert hist['Status'] == 'SUCCESS/UNPACK'
10
0 nzbget_options = ['ArticleCache=500', 'DirectWrite=no']
1
2 def test_small(nserv, nzbget):
3 hist = nzbget.download_nzb('small.nzb')
4 assert hist['Status'] == 'SUCCESS/HEALTH'
5
6 def test_medium_unpack(nserv, nzbget):
7 nzb_content = nzbget.load_nzb('medium.nzb')
8 hist = nzbget.download_nzb('medium_unpack.nzb', nzb_content, unpack=True)
9 assert hist['Status'] == 'SUCCESS/UNPACK'
10
0 import base64
1
2 nzbget_options = ['HealthCheck=none', 'DupeCheck=yes']
3
4 def test_dupecheck_small_copy(nserv, nzbget):
5 hist = nzbget.download_nzb('small.nzb', dupemode = 'SCORE')
6 assert hist['Status'] == 'SUCCESS/HEALTH'
7 nzb_content = nzbget.load_nzb('small.nzb')
8 hist = nzbget.download_nzb('small.copy.nzb', nzb_content, dupemode = 'SCORE')
9 assert hist['Status'] == 'DELETED/COPY'
10
11 def test_dupecheck_small_id(nserv, nzbget):
12 nzbget.clear()
13 hist = nzbget.download_nzb('small.nzb', dupemode = 'SCORE')
14 assert hist['Status'] == 'SUCCESS/HEALTH'
15 nzb_content = nzbget.load_nzb('small.nzb')
16 nzbcontent64 = base64.standard_b64encode(nzb_content)
17 id = nzbget.api.append('small.copy2.nzb', nzbcontent64, 'test', 0, False, False, '', 0, 'SCORE', [])
18 assert id > 0
0 nzbget_options = ['HealthCheck=delete']
1
2 def test_medium_health(nserv, nzbget):
3 nzb_content = nzbget.load_nzb('medium.nzb')
4 nzb_content = nzb_content.replace('.7z', '-does-not-exist.7z')
5 hist = nzbget.download_nzb('medium-health.nzb', nzb_content)
6 assert hist['Status'] == 'FAILURE/HEALTH'
7 assert hist['DeleteStatus'] == 'HEALTH'
0 nzbget_options = ['HealthCheck=park', 'ArticleCache=500', 'DirectWrite=yes']
1
2 def test_retry_small_redownload(nserv, nzbget):
3 nzb_content = nzbget.load_nzb('small.nzb')
4 nzb_content = nzb_content.replace('?3=6000:3000', '?3=6000:3000!2')
5 hist = nzbget.download_nzb('small.bad2.nzb', nzb_content)
6 assert hist['Status'] == 'FAILURE/HEALTH'
7 nzbget.api.editserver(2, True)
8 nzbget.api.editqueue('HistoryRedownload', 0, '', [hist['NZBID']])
9 hist = nzbget.wait_nzb('small.bad2.nzb')
10 nzbget.api.editserver(2, False)
11 assert hist['Status'] == 'SUCCESS/HEALTH'
12
13 def test_retry_small_retryfailed(nserv, nzbget):
14 nzb_content = nzbget.load_nzb('small.nzb')
15 nzb_content = nzb_content.replace('?3=6000:3000', '?3=6000:3000!2')
16 hist = nzbget.download_nzb('small.bad3.nzb', nzb_content)
17 assert hist['Status'] == 'FAILURE/HEALTH'
18 nzbget.api.editqueue('HistoryRetryFailed', 0, '', [hist['NZBID']])
19 hist = nzbget.wait_nzb('small.bad3.nzb')
20 assert hist['Status'] == 'FAILURE/HEALTH'
21 nzbget.api.editserver(2, True)
22 nzbget.api.editqueue('HistoryRetryFailed', 0, '', [hist['NZBID']])
23 hist = nzbget.wait_nzb('small.bad3.nzb')
24 nzbget.api.editserver(2, False)
25 assert hist['Status'] == 'SUCCESS/HEALTH'
26
27 def test_retry_medium_failed(nserv, nzbget):
28 nzb_content = nzbget.load_nzb('medium.nzb')
29 nzb_content = nzb_content.replace('000000:500000', '000000:500000!2')
30 hist = nzbget.download_nzb('medium.nzb', nzb_content)
31 assert hist['Status'] == 'FAILURE/HEALTH'
32 assert hist['DeleteStatus'] == 'HEALTH'
33
34 def test_retry_medium_redownload(nserv, nzbget):
35 nzb_content = nzbget.load_nzb('medium.nzb')
36 nzb_content = nzb_content.replace('0000000:500000', '0000000:500000!2')
37 hist = nzbget.download_nzb('medium.bad.nzb', nzb_content)
38 assert hist['Status'] == 'FAILURE/HEALTH'
39 nzbget.api.editserver(2, True)
40 nzbget.api.editqueue('HistoryRedownload', 0, '', [hist['NZBID']])
41 hist = nzbget.wait_nzb('medium.bad.nzb')
42 nzbget.api.editserver(2, False)
43 assert hist['Status'] == 'SUCCESS/HEALTH'
44
45 def test_retry_medium_retryfailed(nserv, nzbget):
46 nzb_content = nzbget.load_nzb('medium.nzb')
47 nzb_content = nzb_content.replace('0000000:500000', '0000000:500000!2')
48 hist = nzbget.download_nzb('medium.bad3.nzb', nzb_content)
49 assert hist['Status'] == 'FAILURE/HEALTH'
50 nzbget.api.editqueue('HistoryRetryFailed', 0, '', [hist['NZBID']])
51 hist = nzbget.wait_nzb('medium.bad3.nzb')
52 assert hist['Status'] == 'FAILURE/HEALTH'
53 nzbget.api.editserver(2, True)
54 nzbget.api.editqueue('HistoryRetryFailed', 0, '', [hist['NZBID']])
55 hist = nzbget.wait_nzb('medium.bad3.nzb')
56 nzbget.api.editserver(2, False)
57 assert hist['Status'] == 'SUCCESS/HEALTH'
0 nzbget_options = ['HealthCheck=park', 'ArticleCache=500', 'DirectWrite=no']
1
2 def test_retry_small_retryfailed(nserv, nzbget):
3 nzb_content = nzbget.load_nzb('small.nzb')
4 nzb_content = nzb_content.replace('?3=6000:3000', '?3=6000:3000!2')
5 hist = nzbget.download_nzb('small.bad3.nzb', nzb_content)
6 assert hist['Status'] == 'FAILURE/HEALTH'
7 nzbget.api.editqueue('HistoryRetryFailed', 0, '', [hist['NZBID']])
8 hist = nzbget.wait_nzb('small.bad3.nzb')
9 assert hist['Status'] == 'FAILURE/HEALTH'
10 nzbget.api.editserver(2, True)
11 nzbget.api.editqueue('HistoryRetryFailed', 0, '', [hist['NZBID']])
12 hist = nzbget.wait_nzb('small.bad3.nzb')
13 nzbget.api.editserver(2, False)
14 assert hist['Status'] == 'SUCCESS/HEALTH'
15
16 def test_retry_medium_retryfailed(nserv, nzbget):
17 nzb_content = nzbget.load_nzb('medium.nzb')
18 nzb_content = nzb_content.replace('0000000:500000', '0000000:500000!2')
19 hist = nzbget.download_nzb('medium.bad3.nzb', nzb_content)
20 assert hist['Status'] == 'FAILURE/HEALTH'
21 nzbget.api.editqueue('HistoryRetryFailed', 0, '', [hist['NZBID']])
22 hist = nzbget.wait_nzb('medium.bad3.nzb')
23 assert hist['Status'] == 'FAILURE/HEALTH'
24 nzbget.api.editserver(2, True)
25 nzbget.api.editqueue('HistoryRetryFailed', 0, '', [hist['NZBID']])
26 hist = nzbget.wait_nzb('medium.bad3.nzb')
27 nzbget.api.editserver(2, False)
28 assert hist['Status'] == 'SUCCESS/HEALTH'
0 nzbget_options = ['HealthCheck=park', 'ArticleCache=0', 'DirectWrite=no']
1
2 def test_retry_small_retryfailed(nserv, nzbget):
3 nzb_content = nzbget.load_nzb('small.nzb')
4 nzb_content = nzb_content.replace('?3=6000:3000', '?3=6000:3000!2')
5 hist = nzbget.download_nzb('small.bad3.nzb', nzb_content)
6 assert hist['Status'] == 'FAILURE/HEALTH'
7 nzbget.api.editqueue('HistoryRetryFailed', 0, '', [hist['NZBID']])
8 hist = nzbget.wait_nzb('small.bad3.nzb')
9 assert hist['Status'] == 'FAILURE/HEALTH'
10 nzbget.api.editserver(2, True)
11 nzbget.api.editqueue('HistoryRetryFailed', 0, '', [hist['NZBID']])
12 hist = nzbget.wait_nzb('small.bad3.nzb')
13 nzbget.api.editserver(2, False)
14 assert hist['Status'] == 'SUCCESS/HEALTH'
15
16 def test_retry_medium_retryfailed(nserv, nzbget):
17 nzb_content = nzbget.load_nzb('medium.nzb')
18 nzb_content = nzb_content.replace('0000000:500000', '0000000:500000!2')
19 hist = nzbget.download_nzb('medium.bad3.nzb', nzb_content)
20 assert hist['Status'] == 'FAILURE/HEALTH'
21 nzbget.api.editqueue('HistoryRetryFailed', 0, '', [hist['NZBID']])
22 hist = nzbget.wait_nzb('medium.bad3.nzb')
23 assert hist['Status'] == 'FAILURE/HEALTH'
24 nzbget.api.editserver(2, True)
25 nzbget.api.editqueue('HistoryRetryFailed', 0, '', [hist['NZBID']])
26 hist = nzbget.wait_nzb('medium.bad3.nzb')
27 nzbget.api.editserver(2, False)
28 assert hist['Status'] == 'SUCCESS/HEALTH'
0 nzbget_options = ['SaveQueue=no']
1
2 import os
3 import time
4
5 nzbget = None
6
7 def prepare_queue(nzbget_instance):
8 global nzbget
9
10 if nzbget != None:
11 nzbget.api.editqueue('GroupSort', 'Name', [])
12 check_list([1,2,3,4,5,6,7,8,9])
13 return
14
15 print('prepare_queue')
16 nzbget = nzbget_instance
17
18 nzbget.api.pausedownload()
19
20 nzbget_srcdir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
21 testdata_dir = nzbget_srcdir + '/tests/testdata'
22 fullfilename = nzbget_srcdir + '/tests/testdata/nzbfile/plain.nzb'
23 in_file = open(fullfilename, 'r')
24 nzb_content = in_file.read()
25 in_file.close()
26
27 nzbget.append_nzb('1', nzb_content)
28 nzbget.append_nzb('2', nzb_content)
29 nzbget.append_nzb('3', nzb_content)
30 nzbget.append_nzb('4', nzb_content)
31 nzbget.append_nzb('5', nzb_content)
32 nzbget.append_nzb('6', nzb_content)
33 nzbget.append_nzb('7', nzb_content)
34 nzbget.append_nzb('8', nzb_content)
35 nzbget.append_nzb('9', nzb_content)
36
37 def check_list(req):
38 queue = nzbget.api.listgroups()
39 assert len(req) == len(queue)
40 for i in range(len(req)):
41 assert req[i] == queue[i]['NZBID']
42
43 def test_editqueue_move_offset(nzbget):
44 prepare_queue(nzbget)
45
46 check_list([1,2,3,4,5,6,7,8,9])
47
48 nzbget.api.editqueue('GroupMoveOffset', '1', [2])
49 check_list([1,3,2,4,5,6,7,8,9])
50
51 nzbget.api.editqueue('GroupMoveOffset', '-1', [2])
52 check_list([1,2,3,4,5,6,7,8,9])
53
54 nzbget.api.editqueue('GroupMoveOffset', '2', [4,5,6])
55 check_list([1,2,3,7,8,4,5,6,9])
56
57 nzbget.api.editqueue('GroupMoveOffset', '-2', [4,5,6])
58 check_list([1,2,3,4,5,6,7,8,9])
59
60 nzbget.api.editqueue('GroupMoveOffset', '5', [4,6])
61 check_list([1,2,3,5,7,8,9,4,6])
62
63 nzbget.api.editqueue('GroupMoveOffset', '-5', [2,5,7])
64 check_list([2,5,7,1,3,8,9,4,6])
65
66 def test_editqueue_move_before_after(nzbget):
67 prepare_queue(nzbget)
68
69 check_list([1,2,3,4,5,6,7,8,9])
70
71 nzbget.api.editqueue('GroupMoveBefore', '2', [8,9])
72 check_list([1,8,9,2,3,4,5,6,7])
73
74 nzbget.api.editqueue('GroupMoveBefore', '2', [8,9])
75 check_list([1,8,9,2,3,4,5,6,7])
76
77 nzbget.api.editqueue('GroupMoveAfter', '2', [8,9])
78 check_list([1,2,8,9,3,4,5,6,7])
79
80 nzbget.api.editqueue('GroupMoveAfter', '7', [1,8,9])
81 check_list([2,3,4,5,6,7,1,8,9])
82
83 nzbget.api.editqueue('GroupMoveBefore', '2', [5,1,9])
84 check_list([5,1,9,2,3,4,6,7,8])
85
86 nzbget.api.editqueue('GroupMoveBefore', '2', [5,1,9])
87 check_list([5,1,9,2,3,4,6,7,8])
88
89 nzbget.api.editqueue('GroupMoveAfter', '2', [5,1,9])
90 check_list([2,5,1,9,3,4,6,7,8])
91
92 nzbget.api.editqueue('GroupMoveAfter', '8', [5,2])
93 check_list([1,9,3,4,6,7,8,2,5])
0 import os
1 import shutil
2 import subprocess
3 import pytest
4
5
6 @pytest.fixture(scope='session', autouse=True)
7 def prepare_testdata(request):
8 print('Preparing test data for "parcheck"')
9
10 nserv_datadir = pytest.config.getini('nserv_datadir')
11 nzbget_bin = pytest.config.getini('nzbget_bin')
12 sevenzip_bin = pytest.config.getini('sevenzip_bin')
13
14 if not os.path.exists(nserv_datadir):
15 print('Creating nserv datadir')
16 os.makedirs(nserv_datadir)
17
18 nzbget_srcdir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
19
20 testdata_dir = nzbget_srcdir + '/tests/testdata'
21 if not os.path.exists(nserv_datadir + '/parchecker'):
22 shutil.copytree(testdata_dir +'/parchecker', nserv_datadir + '/parchecker')
23
24 if 0 != subprocess.call([nzbget_bin, '--nserv', '-d', nserv_datadir, '-v', '2', '-z', '3000', '-q']):
25 pytest.exit('Test file generation failed')
0 nzbget_options = ['ParCheck=auto', 'ParQuick=yes', 'PostStrategy=sequential']
1
2 def test_parchecker_healthy(nserv, nzbget):
3 hist = nzbget.download_nzb('parchecker.nzb')
4 assert hist['Status'] == 'SUCCESS/HEALTH'
5
6 def test_parchecker_repair(nserv, nzbget):
7 nzb_content = nzbget.load_nzb('parchecker.nzb')
8 nzb_content = nzb_content.replace('parchecker/testfile.dat?1=0:3000', 'parchecker/testfile.dat?1=0:3000!0')
9 hist = nzbget.download_nzb('parchecker.repair.nzb', nzb_content)
10 assert hist['Status'] == 'SUCCESS/PAR'
11
12 def test_parchecker_subject(nserv, nzbget):
13 nzb_content = nzbget.load_nzb('parchecker.nzb')
14 nzb_content = nzb_content.replace('parchecker/testfile.dat?1=0:3000', 'parchecker/testfile.dat?1=0:3000!0')
15 nzb_content = nzb_content.replace('subject="&quot;', 'subject="')
16 nzb_content = nzb_content.replace('&quot; yEnc', '.dat yEnc')
17 hist = nzbget.download_nzb('parchecker.subject.nzb', nzb_content)
18 assert hist['Status'] == 'SUCCESS/PAR'
0 nzbget_options = ['ParCheck=force', 'ParQuick=yes', 'PostStrategy=sequential']
1
2 def test_parchecker_healthy(nserv, nzbget):
3 hist = nzbget.download_nzb('parchecker.nzb')
4 assert hist['Status'] == 'SUCCESS/PAR'
5
6 def test_parchecker_repair(nserv, nzbget):
7 nzb_content = nzbget.load_nzb('parchecker.nzb')
8 nzb_content = nzb_content.replace('parchecker/testfile.dat?1=0:3000', 'parchecker/testfile.dat?1=0:3000!0')
9 hist = nzbget.download_nzb('parchecker.repair.nzb', nzb_content)
10 assert hist['Status'] == 'SUCCESS/PAR'
0 nzbget_options = ['ParCheck=manual', 'ParQuick=yes', 'PostStrategy=sequential']
1
2 def test_parchecker_healthy(nserv, nzbget):
3 hist = nzbget.download_nzb('parchecker.nzb')
4 assert hist['Status'] == 'SUCCESS/HEALTH'
5
6 def test_parchecker_repair(nserv, nzbget):
7 nzb_content = nzbget.load_nzb('parchecker.nzb')
8 nzb_content = nzb_content.replace('parchecker/testfile.dat?1=0:3000', 'parchecker/testfile.dat?1=0:3000!0')
9 hist = nzbget.download_nzb('parchecker.repair.nzb', nzb_content)
10 assert hist['Status'] == 'WARNING/DAMAGED'
0 nzbget_options = ['ParCheck=auto', 'ParQuick=yes', 'PostStrategy=balanced']
1
2 def test_parchecker_healthy(nserv, nzbget):
3 hist = nzbget.download_nzb('parchecker.nzb')
4 assert hist['Status'] == 'SUCCESS/HEALTH'
5
6 def test_parchecker_repair(nserv, nzbget):
7 nzb_content = nzbget.load_nzb('parchecker.nzb')
8 nzb_content = nzb_content.replace('parchecker/testfile.dat?1=0:3000', 'parchecker/testfile.dat?1=0:3000!0')
9 hist = nzbget.download_nzb('parchecker.repair.nzb', nzb_content)
10 assert hist['Status'] == 'SUCCESS/PAR'
0 nzbget_options = ['ParCheck=force', 'ParQuick=yes', 'PostStrategy=balanced']
1
2 def test_parchecker_healthy(nserv, nzbget):
3 hist = nzbget.download_nzb('parchecker.nzb')
4 assert hist['Status'] == 'SUCCESS/PAR'
5
6 def test_parchecker_repair(nserv, nzbget):
7 nzb_content = nzbget.load_nzb('parchecker.nzb')
8 nzb_content = nzb_content.replace('parchecker/testfile.dat?1=0:3000', 'parchecker/testfile.dat?1=0:3000!0')
9 hist = nzbget.download_nzb('parchecker.repair.nzb', nzb_content)
10 assert hist['Status'] == 'SUCCESS/PAR'
0 nzbget_options = ['ParCheck=manual', 'ParQuick=yes', 'PostStrategy=balanced']
1
2 def test_parchecker_healthy(nserv, nzbget):
3 hist = nzbget.download_nzb('parchecker.nzb')
4 assert hist['Status'] == 'SUCCESS/HEALTH'
5
6 def test_parchecker_repair(nserv, nzbget):
7 nzb_content = nzbget.load_nzb('parchecker.nzb')
8 nzb_content = nzb_content.replace('parchecker/testfile.dat?1=0:3000', 'parchecker/testfile.dat?1=0:3000!0')
9 hist = nzbget.download_nzb('parchecker.repair.nzb', nzb_content)
10 assert hist['Status'] == 'WARNING/DAMAGED'
0 nzbget_options = ['ParCheck=force', 'ParQuick=no', 'PostStrategy=balanced']
1
2 def test_parchecker_healthy(nserv, nzbget):
3 hist = nzbget.download_nzb('parchecker.nzb')
4 assert hist['Status'] == 'SUCCESS/PAR'
5
6 def test_parchecker_repair(nserv, nzbget):
7 nzb_content = nzbget.load_nzb('parchecker.nzb')
8 nzb_content = nzb_content.replace('parchecker/testfile.dat?1=0:3000', 'parchecker/testfile.dat?1=0:3000!0')
9 hist = nzbget.download_nzb('parchecker.repair.nzb', nzb_content)
10 assert hist['Status'] == 'SUCCESS/PAR'
0 nzbget_options = ['ParCheck=auto', 'ParQuick=no', 'PostStrategy=rocket']
1
2 def test_parchecker_healthy(nserv, nzbget):
3 hist = nzbget.download_nzb('parchecker.nzb')
4 assert hist['Status'] == 'SUCCESS/HEALTH'
5
6 def test_parchecker_repair(nserv, nzbget):
7 nzbget.api.pausepost();
8 nzb_content = nzbget.load_nzb('parchecker.nzb')
9 nzb_content = nzb_content.replace('parchecker/testfile.dat?1=0:3000', 'parchecker/testfile.dat?1=0:3000!0')
10 nzbget.append_nzb('parchecker.1.nzb', nzb_content, dupemode='FORCE')
11 nzbget.append_nzb('parchecker.2.nzb', nzb_content, dupemode='FORCE')
12 nzbget.append_nzb('parchecker.3.nzb', nzb_content, dupemode='FORCE')
13
14 while True:
15 status = nzbget.api.status()
16 if status['RemainingSizeMB'] == 0:
17 break
18 time.sleep(0.1)
19
20 nzbget.api.resumepost();
21 hist1 = nzbget.wait_nzb('parchecker.3.nzb');
22 hist2 = nzbget.wait_nzb('parchecker.3.nzb');
23 hist3 = nzbget.wait_nzb('parchecker.3.nzb');
24 assert hist1['Status'] == 'SUCCESS/PAR'
25 assert hist2['Status'] == 'SUCCESS/PAR'
26 assert hist3['Status'] == 'SUCCESS/PAR'
0 import os
1 import shutil
2 import subprocess
3 import pytest
4
5 def pytest_addoption(parser):
6 parser.addini('par2_bin', 'path to par2 binary', default=None)
7
8 @pytest.fixture(scope='session', autouse=True)
9 def prepare_testdata(request):
10 print('Preparing test data for "rename"')
11
12 nserv_datadir = pytest.config.getini('nserv_datadir')
13 nzbget_bin = pytest.config.getini('nzbget_bin')
14 par2_bin = pytest.config.getini('par2_bin')
15
16 if not os.path.exists(par2_bin):
17 pytest.exit('Cannot prepare test files. Set option "par2_bin in pytest.ini"')
18
19 if not os.path.exists(nserv_datadir):
20 print('Creating nserv datadir')
21 os.makedirs(nserv_datadir)
22
23 nzbget_srcdir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
24 testdata_dir = nzbget_srcdir + '/tests/testdata'
25
26 if not os.path.exists(nserv_datadir + '/parrename'):
27 os.makedirs(nserv_datadir + '/parrename')
28 shutil.copyfile(testdata_dir + '/rarrenamer/testfile3.part01.rar', nserv_datadir + '/parrename/testfile3.part01.rar')
29 shutil.copyfile(testdata_dir + '/rarrenamer/testfile3.part02.rar', nserv_datadir + '/parrename/testfile3.part02.rar')
30 shutil.copyfile(testdata_dir + '/rarrenamer/testfile3.part03.rar', nserv_datadir + '/parrename/testfile3.part03.rar')
31 os.chdir(nserv_datadir + '/parrename')
32 if 0 != subprocess.call([par2_bin, 'c', '-b20', 'parrename.par2', '*']):
33 pytest.exit('Test file generation failed')
34 os.rename(nserv_datadir + '/parrename/testfile3.part01.rar', nserv_datadir + '/parrename/abc.21')
35 os.rename(nserv_datadir + '/parrename/testfile3.part02.rar', nserv_datadir + '/parrename/abc.02')
36 os.rename(nserv_datadir + '/parrename/testfile3.part03.rar', nserv_datadir + '/parrename/abc.15')
37
38 def prepare_test(dirname, testfile):
39 if not os.path.exists(nserv_datadir + '/' + dirname + '.nzb'):
40 os.makedirs(nserv_datadir + '/' + dirname)
41 shutil.copyfile(testdata_dir + '/rarrenamer/' + testfile + '.part01.rar', nserv_datadir + '/' + dirname + '/abc.21')
42 shutil.copyfile(testdata_dir + '/rarrenamer/' + testfile + '.part02.rar', nserv_datadir + '/' + dirname + '/abc.02')
43 shutil.copyfile(testdata_dir + '/rarrenamer/' + testfile + '.part03.rar', nserv_datadir + '/' + dirname + '/abc.15')
44 os.chdir(nserv_datadir + '/' + dirname)
45 if 0 != subprocess.call([par2_bin, 'c', '-b20', 'parrename.par2', '*']):
46 pytest.exit('Test file generation failed')
47
48 prepare_test('rarrename3', 'testfile3')
49 prepare_test('rarrename5', 'testfile5')
50 prepare_test('rarrename3encdata', 'testfile3encdata')
51 prepare_test('rarrename5encdata', 'testfile5encdata')
52 prepare_test('rarrename3encnam', 'testfile3encnam')
53 prepare_test('rarrename5encnam', 'testfile5encnam')
54
55 if not os.path.exists(nserv_datadir + '/rarrename2sets'):
56 os.makedirs(nserv_datadir + '/rarrename2sets')
57 shutil.copyfile(testdata_dir + '/rarrenamer/testfile3.part01.rar', nserv_datadir + '/rarrename2sets/abc.21')
58 shutil.copyfile(testdata_dir + '/rarrenamer/testfile3.part02.rar', nserv_datadir + '/rarrename2sets/abc.02')
59 shutil.copyfile(testdata_dir + '/rarrenamer/testfile3.part03.rar', nserv_datadir + '/rarrename2sets/abc.15')
60 shutil.copyfile(testdata_dir + '/rarrenamer/testfile5.part01.rar', nserv_datadir + '/rarrename2sets/abc.22')
61 shutil.copyfile(testdata_dir + '/rarrenamer/testfile5.part02.rar', nserv_datadir + '/rarrename2sets/abc.03')
62 shutil.copyfile(testdata_dir + '/rarrenamer/testfile5.part03.rar', nserv_datadir + '/rarrename2sets/abc.14')
63
64 if not os.path.exists(nserv_datadir + '/rarrename3oldnam'):
65 os.makedirs(nserv_datadir + '/rarrename3oldnam')
66 shutil.copyfile(testdata_dir + '/rarrenamer/testfile3oldnam.rar', nserv_datadir + '/rarrename3oldnam/abc.61')
67 shutil.copyfile(testdata_dir + '/rarrenamer/testfile3oldnam.r00', nserv_datadir + '/rarrename3oldnam/abc.32')
68 shutil.copyfile(testdata_dir + '/rarrenamer/testfile3oldnam.r01', nserv_datadir + '/rarrename3oldnam/abc.45')
69
70 if not os.path.exists(nserv_datadir + '/rarrename3badext'):
71 os.makedirs(nserv_datadir + '/rarrename3badext')
72 shutil.copyfile(testdata_dir + '/rarrenamer/testfile3oldnam.rar', nserv_datadir + '/rarrename3badext/testfile3oldnam.rar')
73 shutil.copyfile(testdata_dir + '/rarrenamer/testfile3oldnam.r00', nserv_datadir + '/rarrename3badext/testfile3oldnam.r03')
74 shutil.copyfile(testdata_dir + '/rarrenamer/testfile3oldnam.r01', nserv_datadir + '/rarrename3badext/testfile3oldnam.r02')
75
76 if not os.path.exists(nserv_datadir + '/rarrename5badext'):
77 os.makedirs(nserv_datadir + '/rarrename5badext')
78 shutil.copyfile(testdata_dir + '/rarrenamer/testfile3.part01.rar', nserv_datadir + '/rarrename5badext/testfile3.part01.rar')
79 shutil.copyfile(testdata_dir + '/rarrenamer/testfile3.part02.rar', nserv_datadir + '/rarrename5badext/testfile3.part0002.rar')
80 shutil.copyfile(testdata_dir + '/rarrenamer/testfile3.part03.rar', nserv_datadir + '/rarrename5badext/testfile3.part03.rar')
81
82 if not os.path.exists(nserv_datadir + '/rar3ignoreext'):
83 os.makedirs(nserv_datadir + '/rar3ignoreext')
84 shutil.copyfile(testdata_dir + '/rarrenamer/testfile3.part01.rar', nserv_datadir + '/rar3ignoreext/testfile3-1.cbr')
85 shutil.copyfile(testdata_dir + '/rarrenamer/testfile3.part02.rar', nserv_datadir + '/rar3ignoreext/testfile3-2.cbr')
86 shutil.copyfile(testdata_dir + '/rarrenamer/testfile3.part03.rar', nserv_datadir + '/rar3ignoreext/testfile3-3.cbr')
87
88 if 0 != subprocess.call([nzbget_bin, '--nserv', '-d', nserv_datadir, '-v', '2', '-z', '3000', '-q']):
89 pytest.exit('Test file generation failed')
90
91 if not os.path.exists(nserv_datadir + '/rarrename3sm'):
92 os.makedirs(nserv_datadir + '/rarrename3sm')
93 shutil.copyfile(testdata_dir + '/rarrenamer/testfile3oldnam.rar', nserv_datadir + '/rarrename3sm/abc.61')
94 shutil.copyfile(testdata_dir + '/rarrenamer/testfile3oldnam.r00', nserv_datadir + '/rarrename3sm/abc.32')
95 shutil.copyfile(testdata_dir + '/rarrenamer/testfile3oldnam.r01', nserv_datadir + '/rarrename3sm/abc.45')
96 os.chdir(nserv_datadir + '/rarrename3sm')
97 if 0 != subprocess.call([par2_bin, 'c', '-b100', 'parrename.par2', '*']):
98 pytest.exit('Test file generation failed')
99 if 0 != subprocess.call([nzbget_bin, '--nserv', '-d', nserv_datadir, '-v', '2', '-z', '500', '-q']):
100 pytest.exit('Test file generation failed')
0 nzbget_options = ['ParRename=yes', 'RarRename=yes', 'UnpackIgnoreExt=.cbr']
1
2 def test_parrename(nserv, nzbget):
3 hist = nzbget.download_nzb('parrename.nzb', unpack=True)
4 assert hist['Status'] == 'SUCCESS/UNPACK'
5
6 def test_parrename_backup(nserv, nzbget):
7 nzb_content = nzbget.load_nzb('parrename.nzb')
8 nzb_content = nzb_content.replace('parrename/parrename.par2?', 'parrename/parrename.par2.damaged?')
9 hist = nzbget.download_nzb('parrename.backup.nzb', nzb_content, unpack=True)
10 assert hist['Status'] == 'SUCCESS/UNPACK'
11 log = nzbget.api.loadlog(int(hist['ID']), 0, 1000)
12 renamed = False
13 for entry in log:
14 print(entry['Text'])
15 if entry['Text'].find('Successfully renamed') > -1 and entry['Text'].find('archive') == -1:
16 renamed = True
17 assert renamed == True
18
19
20 def test_rarrename_rar3(nserv, nzbget):
21 hist = nzbget.download_nzb('rarrename3.nzb', unpack=True)
22 assert hist['Status'] == 'SUCCESS/UNPACK'
23
24 def test_rarrename_rar5(nserv, nzbget):
25 hist = nzbget.download_nzb('rarrename5.nzb', unpack=True)
26 assert hist['Status'] == 'SUCCESS/UNPACK'
27
28 def test_rarrename_rar3oldnam(nserv, nzbget):
29 hist = nzbget.download_nzb('rarrename3oldnam.nzb', unpack=True)
30 assert hist['Status'] == 'SUCCESS/UNPACK'
31
32 def test_rarrename_rar3badext(nserv, nzbget):
33 hist = nzbget.download_nzb('rarrename3badext.nzb', unpack=True)
34 assert hist['Status'] == 'SUCCESS/UNPACK'
35
36 def test_rarrename_rar5badext(nserv, nzbget):
37 hist = nzbget.download_nzb('rarrename5badext.nzb', unpack=True)
38 assert hist['Status'] == 'SUCCESS/UNPACK'
39
40 def test_rarrename_2sets(nserv, nzbget):
41 hist = nzbget.download_nzb('rarrename2sets.nzb', unpack=True)
42 assert hist['Status'] == 'SUCCESS/UNPACK'
43
44 def test_rarrename_rar3damaged(nserv, nzbget):
45 nzb_content = nzbget.load_nzb('rarrename3sm.nzb')
46 nzb_content = nzb_content.replace('abc.32?14=6500:500', 'abc.32?14=6500:500!2')
47 hist = nzbget.download_nzb('rarrename3sm.nzb', nzb_content, unpack=True)
48 assert hist['Status'] == 'SUCCESS/UNPACK'
49
50 def test_rarrename_rar3encdata(nserv, nzbget):
51 hist = nzbget.download_nzb('rarrename3encdata.nzb', unpack=True, params=[('*unpack:password', '123')])
52 assert hist['Status'] == 'SUCCESS/UNPACK'
53
54 def test_rarrename_rar5encdata(nserv, nzbget):
55 hist = nzbget.download_nzb('rarrename5encdata.nzb', unpack=True, params=[('*unpack:password', '123')])
56 assert hist['Status'] == 'SUCCESS/UNPACK'
57
58 def test_rarrename_rar3encnam(nserv, nzbget):
59 hist = nzbget.download_nzb('rarrename3encnam.nzb', unpack=True, params=[('*unpack:password', '123')])
60 assert hist['Status'] == 'SUCCESS/UNPACK'
61
62 def test_rarrename_rar5encnam(nserv, nzbget):
63 hist = nzbget.download_nzb('rarrename5encnam.nzb', unpack=True, params=[('*unpack:password', '123')])
64 assert hist['Status'] == 'SUCCESS/UNPACK'
65
66 def test_rarrename_rar3ignoreext(nserv, nzbget):
67 hist = nzbget.download_nzb('rar3ignoreext.nzb', unpack=True)
68 assert hist['Status'] == 'SUCCESS/HEALTH'
4949 void ParCheckerMock::Execute()
5050 {
5151 TestUtil::DisableCout();
52 Start();
53 while (IsRunning())
54 {
55 usleep(10*1000);
56 }
52 ParChecker::Execute();
5753 TestUtil::EnableCout();
5854 }
5955
3131 public:
3232 ParRenamerMock();
3333 void Execute();
34 int GetRenamedCount() { return m_renamed; }
35
36 protected:
37 virtual void RegisterRenamedFile(const char* oldFilename, const char* newFileName) { m_renamed++; }
38
39 private:
40 int m_renamed;
4134 };
4235
4336 ParRenamerMock::ParRenamerMock()
4942 void ParRenamerMock::Execute()
5043 {
5144 TestUtil::DisableCout();
52 m_renamed = 0;
53 Start();
54 while (IsRunning())
55 {
56 usleep(10*1000);
57 }
45 ParRenamer::Execute();
5846 TestUtil::EnableCout();
5947 }
6048
6755 ParRenamerMock parRenamer;
6856 parRenamer.Execute();
6957
70 REQUIRE(parRenamer.GetStatus() == ParRenamer::psFailed);
7158 REQUIRE(parRenamer.GetRenamedCount() == 0);
7259 }
7360
8168 FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile.dat").c_str(), (TestUtil::WorkingDir() + "/123456").c_str());
8269 parRenamer.Execute();
8370
84 REQUIRE(parRenamer.GetStatus() == ParRenamer::psSuccess);
8571 REQUIRE(parRenamer.GetRenamedCount() == 1);
8672 REQUIRE_FALSE(parRenamer.HasMissedFiles());
8773 }
9884 REQUIRE(FileSystem::DeleteFile((TestUtil::WorkingDir() + "/testfile.nfo").c_str()));
9985 parRenamer.Execute();
10086
101 REQUIRE(parRenamer.GetStatus() == ParRenamer::psSuccess);
10287 REQUIRE(parRenamer.GetRenamedCount() == 1);
10388 REQUIRE(parRenamer.HasMissedFiles());
10489 }
90
91 TEST_CASE("Par-renamer: rename dupe par", "[Par][ParRenamer][Slow][TestData]")
92 {
93 Options::CmdOptList cmdOpts;
94 cmdOpts.push_back("ParRename=yes");
95 Options options(&cmdOpts, nullptr);
96
97 ParRenamerMock parRenamer;
98 FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile.dat").c_str(), (TestUtil::WorkingDir() + "/123456").c_str());
99 FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile.vol00+1.PAR2").c_str(), (TestUtil::WorkingDir() + "/testfil2.par2").c_str());
100 parRenamer.SetDetectMissing(true);
101 parRenamer.Execute();
102
103 REQUIRE(parRenamer.GetRenamedCount() == 5);
104 REQUIRE_FALSE(parRenamer.HasMissedFiles());
105 }
106
107 TEST_CASE("Par-renamer: no par extension", "[Par][ParRenamer][Slow][TestData]")
108 {
109 Options::CmdOptList cmdOpts;
110 cmdOpts.push_back("ParRename=yes");
111 Options options(&cmdOpts, nullptr);
112
113 ParRenamerMock parRenamer;
114 FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile.par2").c_str(), (TestUtil::WorkingDir() + "/testfile").c_str());
115 parRenamer.SetDetectMissing(true);
116 parRenamer.Execute();
117
118 REQUIRE(parRenamer.GetRenamedCount() == 4);
119 REQUIRE_FALSE(parRenamer.HasMissedFiles());
120 }
0 /*
1 * This file is part of nzbget. See <http://nzbget.net>.
2 *
3 * Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19
20 #include "nzbget.h"
21
22 #include "catch.h"
23
24 #include "RarReader.h"
25 #include "FileSystem.h"
26 #include "TestUtil.h"
27
28 TEST_CASE("Rar-reader: rar3", "[Rar][RarReader][Slow][TestData]")
29 {
30 {
31 RarVolume volume((TestUtil::TestDataDir() + "/rarrenamer/testfile3.part01.rar").c_str());
32 REQUIRE(volume.Read() == true);
33 REQUIRE(volume.GetVersion() == 3);
34 REQUIRE(volume.GetMultiVolume() == true);
35 REQUIRE(volume.GetNewNaming() == true);
36 REQUIRE(volume.GetVolumeNo() == 0);
37 }
38 {
39 RarVolume volume((TestUtil::TestDataDir() + "/rarrenamer/testfile3.part02.rar").c_str());
40 REQUIRE(volume.Read() == true);
41 REQUIRE(volume.GetVersion() == 3);
42 REQUIRE(volume.GetMultiVolume() == true);
43 REQUIRE(volume.GetNewNaming() == true);
44 REQUIRE(volume.GetVolumeNo() == 1);
45 }
46 {
47 RarVolume volume((TestUtil::TestDataDir() + "/rarrenamer/testfile3.part03.rar").c_str());
48 REQUIRE(volume.Read() == true);
49 REQUIRE(volume.GetVersion() == 3);
50 REQUIRE(volume.GetMultiVolume() == true);
51 REQUIRE(volume.GetNewNaming() == true);
52 REQUIRE(volume.GetVolumeNo() == 2);
53 }
54 }
55
56 TEST_CASE("Rar-reader: rar5", "[Rar][RarReader][Slow][TestData]")
57 {
58 {
59 RarVolume volume((TestUtil::TestDataDir() + "/rarrenamer/testfile5.part01.rar").c_str());
60 REQUIRE(volume.Read() == true);
61 REQUIRE(volume.GetVersion() == 5);
62 REQUIRE(volume.GetMultiVolume() == true);
63 REQUIRE(volume.GetNewNaming() == true);
64 REQUIRE(volume.GetVolumeNo() == 0);
65 }
66 {
67 RarVolume volume((TestUtil::TestDataDir() + "/rarrenamer/testfile5.part02.rar").c_str());
68 REQUIRE(volume.Read() == true);
69 REQUIRE(volume.GetVersion() == 5);
70 REQUIRE(volume.GetMultiVolume() == true);
71 REQUIRE(volume.GetNewNaming() == true);
72 REQUIRE(volume.GetVolumeNo() == 1);
73 }
74 {
75 RarVolume volume((TestUtil::TestDataDir() + "/rarrenamer/testfile5.part03.rar").c_str());
76 REQUIRE(volume.Read() == true);
77 REQUIRE(volume.GetVersion() == 5);
78 REQUIRE(volume.GetMultiVolume() == true);
79 REQUIRE(volume.GetNewNaming() == true);
80 REQUIRE(volume.GetVolumeNo() == 2);
81 }
82 }
83
84 TEST_CASE("Rar-reader: rar3 old naming", "[Rar][RarReader][Slow][TestData]")
85 {
86 {
87 RarVolume volume((TestUtil::TestDataDir() + "/rarrenamer/testfile3oldnam.rar").c_str());
88 REQUIRE(volume.Read() == true);
89 REQUIRE(volume.GetVersion() == 3);
90 REQUIRE(volume.GetMultiVolume() == true);
91 REQUIRE(volume.GetNewNaming() == false);
92 REQUIRE(volume.GetVolumeNo() == 0);
93 }
94 {
95 RarVolume volume((TestUtil::TestDataDir() + "/rarrenamer/testfile3oldnam.r00").c_str());
96 REQUIRE(volume.Read() == true);
97 REQUIRE(volume.GetVersion() == 3);
98 REQUIRE(volume.GetMultiVolume() == true);
99 REQUIRE(volume.GetNewNaming() == false);
100 REQUIRE(volume.GetVolumeNo() == 1);
101 }
102 {
103 RarVolume volume((TestUtil::TestDataDir() + "/rarrenamer/testfile3oldnam.r01").c_str());
104 REQUIRE(volume.Read() == true);
105 REQUIRE(volume.GetVersion() == 3);
106 REQUIRE(volume.GetMultiVolume() == true);
107 REQUIRE(volume.GetNewNaming() == false);
108 REQUIRE(volume.GetVolumeNo() == 2);
109 }
110 }
111
112 #ifndef DISABLE_TLS
113
114 TEST_CASE("Rar-reader: rar3 encrypted data", "[Rar][RarReader][Slow][TestData]")
115 {
116 {
117 RarVolume volume((TestUtil::TestDataDir() + "/rarrenamer/testfile3encdata.part01.rar").c_str());
118 REQUIRE(volume.Read() == true);
119 REQUIRE(volume.GetVersion() == 3);
120 REQUIRE(volume.GetMultiVolume() == true);
121 REQUIRE(volume.GetNewNaming() == true);
122 REQUIRE(volume.GetVolumeNo() == 0);
123 }
124 {
125 RarVolume volume((TestUtil::TestDataDir() + "/rarrenamer/testfile3encdata.part02.rar").c_str());
126 REQUIRE(volume.Read() == true);
127 REQUIRE(volume.GetVersion() == 3);
128 REQUIRE(volume.GetMultiVolume() == true);
129 REQUIRE(volume.GetNewNaming() == true);
130 REQUIRE(volume.GetVolumeNo() == 1);
131 }
132 {
133 RarVolume volume((TestUtil::TestDataDir() + "/rarrenamer/testfile3encdata.part03.rar").c_str());
134 REQUIRE(volume.Read() == true);
135 REQUIRE(volume.GetVersion() == 3);
136 REQUIRE(volume.GetMultiVolume() == true);
137 REQUIRE(volume.GetNewNaming() == true);
138 REQUIRE(volume.GetVolumeNo() == 2);
139 }
140 }
141
142 TEST_CASE("Rar-reader: rar5 encrypted data", "[Rar][RarReader][Slow][TestData]")
143 {
144 {
145 RarVolume volume((TestUtil::TestDataDir() + "/rarrenamer/testfile5encdata.part01.rar").c_str());
146 REQUIRE(volume.Read() == true);
147 REQUIRE(volume.GetVersion() == 5);
148 REQUIRE(volume.GetMultiVolume() == true);
149 REQUIRE(volume.GetNewNaming() == true);
150 REQUIRE(volume.GetVolumeNo() == 0);
151 }
152 {
153 RarVolume volume((TestUtil::TestDataDir() + "/rarrenamer/testfile5encdata.part02.rar").c_str());
154 REQUIRE(volume.Read() == true);
155 REQUIRE(volume.GetVersion() == 5);
156 REQUIRE(volume.GetMultiVolume() == true);
157 REQUIRE(volume.GetNewNaming() == true);
158 REQUIRE(volume.GetVolumeNo() == 1);
159 }
160 {
161 RarVolume volume((TestUtil::TestDataDir() + "/rarrenamer/testfile5encdata.part03.rar").c_str());
162 REQUIRE(volume.Read() == true);
163 REQUIRE(volume.GetVersion() == 5);
164 REQUIRE(volume.GetMultiVolume() == true);
165 REQUIRE(volume.GetNewNaming() == true);
166 REQUIRE(volume.GetVolumeNo() == 2);
167 }
168 }
169
170 TEST_CASE("Rar-reader: rar3 encrypted names", "[Rar][RarReader][Slow][TestData]")
171 {
172 {
173 RarVolume volume((TestUtil::TestDataDir() + "/rarrenamer/testfile3encnam.part01.rar").c_str());
174 REQUIRE(volume.Read() == false);
175 REQUIRE(volume.GetVersion() == 3);
176 REQUIRE(volume.GetEncrypted() == true);
177 }
178
179 {
180 RarVolume volume((TestUtil::TestDataDir() + "/rarrenamer/testfile3encnam.part01.rar").c_str());
181 volume.SetPassword("123");
182 REQUIRE(volume.Read() == true);
183 REQUIRE(volume.GetVersion() == 3);
184 REQUIRE(volume.GetMultiVolume() == true);
185 REQUIRE(volume.GetNewNaming() == true);
186 REQUIRE(volume.GetVolumeNo() == 0);
187 }
188 {
189 RarVolume volume((TestUtil::TestDataDir() + "/rarrenamer/testfile3encnam.part02.rar").c_str());
190 volume.SetPassword("123");
191 REQUIRE(volume.Read() == true);
192 REQUIRE(volume.GetVersion() == 3);
193 REQUIRE(volume.GetMultiVolume() == true);
194 REQUIRE(volume.GetNewNaming() == true);
195 REQUIRE(volume.GetVolumeNo() == 1);
196 }
197 {
198 RarVolume volume((TestUtil::TestDataDir() + "/rarrenamer/testfile3encnam.part03.rar").c_str());
199 volume.SetPassword("123");
200 REQUIRE(volume.Read() == true);
201 REQUIRE(volume.GetVersion() == 3);
202 REQUIRE(volume.GetMultiVolume() == true);
203 REQUIRE(volume.GetNewNaming() == true);
204 REQUIRE(volume.GetVolumeNo() == 2);
205 }
206 }
207
208 TEST_CASE("Rar-reader: rar5 encrypted names", "[Rar][RarReader][Slow][TestData]")
209 {
210 {
211 RarVolume volume((TestUtil::TestDataDir() + "/rarrenamer/testfile5encnam.part01.rar").c_str());
212 volume.SetPassword("123");
213 REQUIRE(volume.Read() == true);
214 REQUIRE(volume.GetVersion() == 5);
215 REQUIRE(volume.GetMultiVolume() == true);
216 REQUIRE(volume.GetNewNaming() == true);
217 REQUIRE(volume.GetVolumeNo() == 0);
218 }
219 {
220 RarVolume volume((TestUtil::TestDataDir() + "/rarrenamer/testfile5encnam.part02.rar").c_str());
221 volume.SetPassword("123");
222 REQUIRE(volume.Read() == true);
223 REQUIRE(volume.GetVersion() == 5);
224 REQUIRE(volume.GetMultiVolume() == true);
225 REQUIRE(volume.GetNewNaming() == true);
226 REQUIRE(volume.GetVolumeNo() == 1);
227 }
228 {
229 RarVolume volume((TestUtil::TestDataDir() + "/rarrenamer/testfile5encnam.part03.rar").c_str());
230 volume.SetPassword("123");
231 REQUIRE(volume.Read() == true);
232 REQUIRE(volume.GetVersion() == 5);
233 REQUIRE(volume.GetMultiVolume() == true);
234 REQUIRE(volume.GetNewNaming() == true);
235 REQUIRE(volume.GetVolumeNo() == 2);
236 }
237
238 {
239 RarVolume volume((TestUtil::TestDataDir() + "/rarrenamer/testfile5encnam.part01.rar").c_str());
240 REQUIRE(volume.Read() == false);
241 REQUIRE(volume.GetVersion() == 5);
242 REQUIRE(volume.GetEncrypted() == true);
243 }
244 }
245
246 #endif
0 /*
1 * This file is part of nzbget. See <http://nzbget.net>.
2 *
3 * Copyright (C) 2016 Andrey Prygunkov <hugbug@users.sourceforge.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19
20 #include "nzbget.h"
21
22 #include "catch.h"
23
24 #include "Options.h"
25 #include "RarRenamer.h"
26 #include "FileSystem.h"
27 #include "TestUtil.h"
28
29 class RarRenamerMock: public RarRenamer
30 {
31 public:
32 RarRenamerMock();
33 };
34
35 RarRenamerMock::RarRenamerMock()
36 {
37 TestUtil::PrepareWorkingDir("rarrenamer");
38 SetDestDir(TestUtil::WorkingDir().c_str());
39 }
40
41 TEST_CASE("Rar-renamer: rename not needed", "[Rar][RarRenamer][Slow][TestData]")
42 {
43 RarRenamerMock rarRenamer;
44
45 rarRenamer.Execute();
46
47 REQUIRE(rarRenamer.GetRenamedCount() == 0);
48 }
49
50 TEST_CASE("Rar-renamer: rename rar3", "[Rar][RarRenamer][Slow][TestData]")
51 {
52 RarRenamerMock rarRenamer;
53
54 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile3.part01.rar").c_str(), (TestUtil::WorkingDir() + "/12345").c_str()));
55 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile3.part02.rar").c_str(), (TestUtil::WorkingDir() + "/12342").c_str()));
56 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile3.part03.rar").c_str(), (TestUtil::WorkingDir() + "/12346").c_str()));
57
58 rarRenamer.Execute();
59
60 REQUIRE(rarRenamer.GetRenamedCount() == 3);
61 }
62
63 TEST_CASE("Rar-renamer: rename rar5", "[Rar][RarRenamer][Slow][TestData]")
64 {
65 RarRenamerMock rarRenamer;
66
67 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile5.part01.rar").c_str(), (TestUtil::WorkingDir() + "/12348").c_str()));
68 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile5.part02.rar").c_str(), (TestUtil::WorkingDir() + "/12343").c_str()));
69 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile5.part03.rar").c_str(), (TestUtil::WorkingDir() + "/12344").c_str()));
70
71 rarRenamer.Execute();
72
73 REQUIRE(rarRenamer.GetRenamedCount() == 3);
74 }
75
76 TEST_CASE("Rar-renamer: missing parts", "[Rar][RarRenamer][Slow][TestData]")
77 {
78 RarRenamerMock rarRenamer;
79
80 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile5.part01.rar").c_str(), (TestUtil::WorkingDir() + "/12348").c_str()));
81 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile5.part02.rar").c_str(), (TestUtil::WorkingDir() + "/12343").c_str()));
82 REQUIRE(FileSystem::DeleteFile((TestUtil::WorkingDir() + "/testfile5.part03.rar").c_str()));
83
84 rarRenamer.Execute();
85
86 REQUIRE(rarRenamer.GetRenamedCount() == 0);
87 }
88
89 TEST_CASE("Rar-renamer: rename rar3 bad naming", "[Rar][RarRenamer][Slow][TestData]")
90 {
91 RarRenamerMock rarRenamer;
92
93 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile3.part01.rar").c_str(), (TestUtil::WorkingDir() + "/testfile3.part04.rar").c_str()));
94 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile3.part02.rar").c_str(), (TestUtil::WorkingDir() + "/testfile3.part01.rar").c_str()));
95
96 rarRenamer.Execute();
97
98 REQUIRE(rarRenamer.GetRenamedCount() == 3);
99 }
100
101 TEST_CASE("Rar-renamer: rename rar3 bad naming 2", "[Rar][RarRenamer][Slow][TestData]")
102 {
103 RarRenamerMock rarRenamer;
104
105 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile3.part02.rar").c_str(), (TestUtil::WorkingDir() + "/testfile3.part2.rar").c_str()));
106
107 rarRenamer.Execute();
108
109 REQUIRE(rarRenamer.GetRenamedCount() == 3);
110 }
111
112 TEST_CASE("Rar-renamer: rename rar3 bad naming 3", "[Rar][RarRenamer][Slow][TestData]")
113 {
114 RarRenamerMock rarRenamer;
115
116 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile3.part02.rar").c_str(), (TestUtil::WorkingDir() + "/testfile3-1.part02.rar").c_str()));
117
118 rarRenamer.Execute();
119
120 REQUIRE(rarRenamer.GetRenamedCount() == 3);
121 }
122
123 TEST_CASE("Rar-renamer: rename rar3 bad naming 4", "[Rar][RarRenamer][Slow][TestData]")
124 {
125 RarRenamerMock rarRenamer;
126
127 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile3.part02.rar").c_str(), (TestUtil::WorkingDir() + "/testfil-3.part02.rar").c_str()));
128
129 rarRenamer.Execute();
130
131 REQUIRE(rarRenamer.GetRenamedCount() == 3);
132 }
133
134 TEST_CASE("Rar-renamer: rename rar3 bad naming 5", "[Rar][RarRenamer][Slow][TestData]")
135 {
136 RarRenamerMock rarRenamer;
137
138 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile3oldnam.rar").c_str(), (TestUtil::WorkingDir() + "/testfile3oldnamA.rar").c_str()));
139
140 rarRenamer.Execute();
141
142 REQUIRE(rarRenamer.GetRenamedCount() == 1);
143 }
144
145 TEST_CASE("Rar-renamer: rename rar3 bad naming 6", "[Rar][RarRenamer][Slow][TestData]")
146 {
147 RarRenamerMock rarRenamer;
148
149 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile3oldnam.rar").c_str(), (TestUtil::WorkingDir() + "/testfile3oldnamA.rar").c_str()));
150 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile3oldnam.r00").c_str(), (TestUtil::WorkingDir() + "/testfile3oldnamB.r00").c_str()));
151 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile3oldnam.r01").c_str(), (TestUtil::WorkingDir() + "/testfile3oldnamA.r01").c_str()));
152
153 rarRenamer.Execute();
154
155 REQUIRE(rarRenamer.GetRenamedCount() == 3);
156 }
157
158 TEST_CASE("Rar-renamer: rename two sets", "[Rar][RarRenamer][Slow][TestData]")
159 {
160 RarRenamerMock rarRenamer;
161
162 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile3.part01.rar").c_str(), (TestUtil::WorkingDir() + "/12345").c_str()));
163 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile3.part02.rar").c_str(), (TestUtil::WorkingDir() + "/12342").c_str()));
164 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile3.part03.rar").c_str(), (TestUtil::WorkingDir() + "/12346").c_str()));
165 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile5.part01.rar").c_str(), (TestUtil::WorkingDir() + "/12348").c_str()));
166 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile5.part02.rar").c_str(), (TestUtil::WorkingDir() + "/12343").c_str()));
167 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile5.part03.rar").c_str(), (TestUtil::WorkingDir() + "/12344").c_str()));
168
169 rarRenamer.Execute();
170
171 REQUIRE(rarRenamer.GetRenamedCount() == 6);
172 }
173
174 TEST_CASE("Rar-renamer: rename duplicate", "[Rar][RarRenamer][Slow][TestData]")
175 {
176 RarRenamerMock rarRenamer;
177
178 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile3.part01.rar").c_str(), (TestUtil::WorkingDir() + "/12345").c_str()));
179 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile3.part02.rar").c_str(), (TestUtil::WorkingDir() + "/12342").c_str()));
180 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile3.part03.rar").c_str(), (TestUtil::WorkingDir() + "/12346").c_str()));
181 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile5.part01.rar").c_str(), (TestUtil::WorkingDir() + "/testfile3.dat.part0001.rar").c_str()));
182 REQUIRE(FileSystem::DeleteFile((TestUtil::WorkingDir() + "/testfile5.part02.rar").c_str()));
183 REQUIRE(FileSystem::DeleteFile((TestUtil::WorkingDir() + "/testfile5.part03.rar").c_str()));
184
185 rarRenamer.Execute();
186
187 REQUIRE(rarRenamer.GetRenamedCount() == 3);
188 }
189
190 #ifndef DISABLE_TLS
191
192 TEST_CASE("Rar-renamer: rename rar5 encrypted", "[Rar][RarRenamer][Slow][TestData]")
193 {
194 RarRenamerMock rarRenamer;
195 rarRenamer.SetPassword("123");
196
197 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile5encnam.part01.rar").c_str(), (TestUtil::WorkingDir() + "/12348").c_str()));
198 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile5encnam.part02.rar").c_str(), (TestUtil::WorkingDir() + "/12343").c_str()));
199 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile5encnam.part03.rar").c_str(), (TestUtil::WorkingDir() + "/12344").c_str()));
200
201 rarRenamer.Execute();
202
203 REQUIRE(rarRenamer.GetRenamedCount() == 3);
204 }
205
206 TEST_CASE("Rar-renamer: rename rar3 encrypted", "[Rar][RarRenamer][Slow][TestData]")
207 {
208 RarRenamerMock rarRenamer;
209 rarRenamer.SetPassword("123");
210
211 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile3encnam.part01.rar").c_str(), (TestUtil::WorkingDir() + "/12348").c_str()));
212 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile3encnam.part02.rar").c_str(), (TestUtil::WorkingDir() + "/12343").c_str()));
213 REQUIRE(FileSystem::MoveFile((TestUtil::WorkingDir() + "/testfile3encnam.part03.rar").c_str(), (TestUtil::WorkingDir() + "/12344").c_str()));
214
215 rarRenamer.Execute();
216
217 REQUIRE(rarRenamer.GetRenamedCount() == 3);
218 }
219
220 #endif
135135 config.values = serverValues;
136136
137137 readWebSettings(config);
138
138
139139 var serverConfig = readConfigTemplate(serverTemplateData[0].Template, undefined, HIDDEN_SECTIONS, '', '');
140140 mergeValues(serverConfig.sections, serverValues);
141141 config.push(serverConfig);
155155 scriptConfig.scan = serverTemplateData[i].ScanScript;
156156 scriptConfig.queue = serverTemplateData[i].QueueScript;
157157 scriptConfig.scheduler = serverTemplateData[i].SchedulerScript;
158 scriptConfig.defscheduler = serverTemplateData[i].TaskTime !== '';
158159 scriptConfig.feed = serverTemplateData[i].FeedScript;
159160 mergeValues(scriptConfig.sections, serverValues);
160161 config.push(scriptConfig);
163164 serverValues = null;
164165 loadComplete(config);
165166 }
166
167
167168 function readWebSettings(config)
168169 {
169170 var webTemplate = '### WEB-INTERFACE ###\n\n';
170171 var webValues = [];
171
172
172173 for (var optname in UISettings.description)
173174 {
174175 var descript = UISettings.description[optname];
176177 optname = optname[0].toUpperCase() + optname.substring(1);
177178 if (value === true) value = 'yes';
178179 if (value === false) value = 'no';
179
180
180181 descript = descript.replace(/\n/g, '\n# ').replace(/\n# \n/g, '\n#\n');
181182 webTemplate += '# ' + descript + '\n' + optname + '=' + value + '\n\n';
182
183
183184 webValues.push({Name: optname, Value: value.toString()});
184185 }
185
186
186187 var webConfig = readConfigTemplate(webTemplate, undefined, '', '', '');
187188 mergeValues(webConfig.sections, webValues);
188189 config.push(webConfig);
444445
445446 for (var i=1; i < data.length; i++)
446447 {
447 if (data[i].PostScript)
448 if (data[i].PostScript || data[i].QueueScript)
448449 {
449450 var scriptName = data[i].Name;
450451 var sectionId = (scriptName + ':').replace(/ |\/|[\.|$|\:|\*]/g, '_');
454455 option.caption = option.caption.replace(/\\/, ' \\ ').replace(/\//, ' / ');
455456
456457 option.defvalue = 'no';
457 option.description = (data[i].Template.trim().split('\n')[0].substr(1, 1000).trim() || 'Post-processing script ' + scriptName + '.');
458 option.description = (data[i].Template.trim().split('\n')[0].substr(1, 1000).trim() || 'Extension script ' + scriptName + '.');
458459 option.value = null;
459460 option.sectionId = sectionId;
460461 option.select = ['yes', 'no'];
666667 return null;
667668 }
668669
670 this.processShortcut = function(key)
671 {
672 switch (key)
673 {
674 case 'Shift+F': $('#ConfigTable_filter').focus(); return true;
675 case 'Shift+C': $('#ConfigTable_clearfilter').click(); return true;
676 }
677 }
678
669679 /*** GENERATE HTML PAGE *****************************************************************/
670680
671681 function buildOptionsContent(section, extensionsec)
684694 // option's content is hidden content anyway (***)
685695 break;
686696 }
687
697
688698 var option = section.options[i];
689699 if (!option.template)
690700 {
806816 '<span class="add-on">'+ option.select[0] +'</span>'+
807817 '</div>';
808818 }
809 else if (option.name.toLowerCase() === 'serverpassword')
819 else if (option.name.toLowerCase().indexOf('password') > -1 &&
820 option.name.toLowerCase() !== '*unpack:password')
810821 {
811822 option.type = 'password';
812 html += '<input type="password" id="' + option.formId + '" value="' + Util.textToAttr(value) + '" class="editsmall">';
823 html += '<div class="password-field input-append">' +
824 '<input type="password" id="' + option.formId + '" value="' + Util.textToAttr(value) + '" class="editsmall">'+
825 '<span class="add-on">'+
826 '<label class="checkbox">'+
827 '<input type="checkbox" onclick="Config.togglePassword(this, \'' + option.formId + '\')" /> Show'+
828 '</label>'+
829 '</span>'+
830 '</div>';
813831 }
814832 else if (option.name.toLowerCase().indexOf('username') > -1 ||
815 option.name.toLowerCase().indexOf('password') > -1 ||
816833 (option.name.indexOf('IP') > -1 && option.name.toLowerCase() !== 'authorizedip'))
817834 {
818835 option.type = 'text';
967984 $ConfigNav.append(html);
968985 }
969986 }
970
987
971988 notifyChanges();
972989
973990 $ConfigNav.append('<li class="divider hide ConfigSearch"></li>');
10511068 {
10521069 var option = section.options[k];
10531070 var optname = option.name.toLowerCase();
1054 if (optname.indexOf('scriptorder') > -1)
1071 if (optname === 'scriptorder')
10551072 {
10561073 option.editor = { caption: 'Reorder', click: 'Config.editScriptOrder' };
10571074 }
1058 if (optname.indexOf('postscript') > -1)
1059 {
1060 option.editor = { caption: 'Choose', click: 'Config.editPostScript' };
1061 }
1062 if (optname.indexOf('scanscript') > -1)
1063 {
1064 option.editor = { caption: 'Choose', click: 'Config.editScanScript' };
1065 }
1066 if (optname.indexOf('queuescript') > -1)
1067 {
1068 option.editor = { caption: 'Choose', click: 'Config.editQueueScript' };
1069 }
1070 if (optname.indexOf('feedscript') > -1)
1071 {
1072 option.editor = { caption: 'Choose', click: 'Config.editFeedScript' };
1075 if (optname === 'extensions')
1076 {
1077 option.editor = { caption: 'Choose', click: 'Config.editExtensions' };
1078 }
1079 if (optname.indexOf('category') > -1 && optname.indexOf('.extensions') > -1)
1080 {
1081 option.editor = { caption: 'Choose', click: 'Config.editCategoryExtensions' };
1082 }
1083 if (optname.indexOf('feed') > -1 && optname.indexOf('.extensions') > -1)
1084 {
1085 option.editor = { caption: 'Choose', click: 'Config.editFeedExtensions' };
10731086 }
10741087 if (optname.indexOf('task') > -1 && optname.indexOf('.param') > -1)
10751088 {
11061119 }
11071120 }
11081121 }
1109
1122
11101123 function scrollOptionIntoView(optFormId)
11111124 {
11121125 var option = findOptionById(optFormId);
11541167 $('.btn', control).removeClass('btn-primary');
11551168 $('.btn@[value=' + value + ']', control).addClass('btn-primary');
11561169 }
1170
1171 this.togglePassword = function(control, target)
1172 {
1173 var checked = $(control).is(':checked');
1174 $('#'+target).prop('type', checked ? 'text' : 'password');
1175 }
11571176
11581177 /*** CHANGE/ADD/REMOVE OPTIONS *************************************************************/
11591178
13131332 }
13141333 }
13151334 }
1316
1335
13171336 this.addSet = function(setname, sectionId)
13181337 {
13191338 // find section
13711390 option.onchange(option);
13721391 }
13731392 }
1374
1393
13751394 div.slideDown('normal', function()
13761395 {
13771396 var opts = div.children();
13891408 // swap options in two sets
13901409 var opts1 = $('.' + sectionId + '.multiid' + (direction === 'down' ? id1 : id2), $ConfigData);
13911410 var opts2 = $('.' + sectionId + '.multiid' + (direction === 'down' ? id2 : id1), $ConfigData);
1392
1411
13931412 if (opts1.length === 0 || opts2.length === 0)
13941413 {
13951414 return;
13961415 }
1397
1416
13981417 opts1.first().before(opts2);
13991418
14001419 // reformat remaining sets (captions, input IDs, etc.)
14271446 ScriptListDialog.showModal(option, config, null);
14281447 }
14291448
1430 this.editPostScript = function(optFormId)
1449 this.editExtensions = function(optFormId)
14311450 {
14321451 var option = findOptionById(optFormId);
1433 ScriptListDialog.showModal(option, config, 'post');
1434 }
1435
1436 this.editScanScript = function(optFormId)
1452 ScriptListDialog.showModal(option, config, ['post', 'scan', 'queue', 'defscheduler']);
1453 }
1454
1455 this.editCategoryExtensions = function(optFormId)
14371456 {
14381457 var option = findOptionById(optFormId);
1439 ScriptListDialog.showModal(option, config, 'scan');
1440 }
1441
1442 this.editQueueScript = function(optFormId)
1458 ScriptListDialog.showModal(option, config, ['post', 'scan', 'queue']);
1459 }
1460
1461 this.editFeedExtensions = function(optFormId)
14431462 {
14441463 var option = findOptionById(optFormId);
1445 ScriptListDialog.showModal(option, config, 'queue');
1446 }
1447
1448 this.editFeedScript = function(optFormId)
1449 {
1450 var option = findOptionById(optFormId);
1451 ScriptListDialog.showModal(option, config, 'feed');
1464 ScriptListDialog.showModal(option, config, ['feed']);
14521465 }
14531466
14541467 this.editSchedulerScript = function(optFormId)
14601473 alert('This button is to choose scheduler scripts when option TaskX.Command is set to "Script".');
14611474 return;
14621475 }
1463 ScriptListDialog.showModal(option, config, 'scheduler');
1476 ScriptListDialog.showModal(option, config, ['scheduler']);
14641477 }
14651478
14661479 this.schedulerCommandChanged = function(option)
14691482 var btnId = option.formId.replace(/Command/, 'Param_Editor');
14701483 Util.show('#' + btnId, command === 'Script');
14711484 }
1472
1485
14731486 /*** RSS FEEDS ********************************************************************/
14741487
14751488 this.editFilter = function(optFormId)
14851498 getOptionValue(findOptionByName('Feed' + option.multiid + '.Category')),
14861499 getOptionValue(findOptionByName('Feed' + option.multiid + '.Priority')),
14871500 getOptionValue(findOptionByName('Feed' + option.multiid + '.Interval')),
1488 getOptionValue(findOptionByName('Feed' + option.multiid + '.FeedScript')),
1501 getOptionValue(findOptionByName('Feed' + option.multiid + '.Extensions')),
14891502 function(filter)
14901503 {
14911504 var control = $('#' + option.formId);
15051518 getOptionValue(findOptionByName('Feed' + multiid + '.Category')),
15061519 getOptionValue(findOptionByName('Feed' + multiid + '.Priority')),
15071520 getOptionValue(findOptionByName('Feed' + multiid + '.Interval')),
1508 getOptionValue(findOptionByName('Feed' + multiid + '.FeedScript')));
1521 getOptionValue(findOptionByName('Feed' + multiid + '.Extensions')));
15091522 }
15101523
15111524 /*** TEST SERVER ********************************************************************/
15121525
15131526 var connecting = false;
1514
1527
15151528 this.testConnection = function(control, setname, sectionId)
15161529 {
15171530 if (connecting)
15871600 }
15881601 }
15891602 this.setOptionValue = setOptionValue;
1590
1603
15911604 // Checks if there are obsolete or invalid options
15921605 function invalidOptionsExist()
15931606 {
16731686 {
16741687 saveWebSettings(webSaveRequest);
16751688 }
1676
1689
16771690 if (serverSaveRequest.length > 0)
16781691 {
16791692 $('#Notif_Config_Failed_Filename').text(Options.option('ConfigFile'));
17241737 }
17251738 UISettings.save();
17261739 }
1727
1740
17281741 this.canLeaveTab = function(target)
17291742 {
17301743 if (!config || prepareSaveRequest(true).length === 0 || configSaved)
18181831 $ConfigData.children().hide();
18191832
18201833 searcher.compile(filterText);
1821
1834
18221835 var total = 0;
18231836 var available = 0;
18241837
20172030
20182031 $ScriptTable.fasttable(
20192032 {
2020 pagerContainer: $('#ScriptListDialog_ScriptTable_pager'),
2021 headerCheck: $('#ScriptListDialog_ScriptTable > thead > tr:first-child'),
2033 pagerContainer: '#ScriptListDialog_ScriptTable_pager',
20222034 infoEmpty: 'No scripts found. If you just changed option "ScriptDir", save settings and reload NZBGet.',
20232035 pageSize: 1000
20242036 });
2025
2026 $ScriptTable.on('click', 'tbody div.check',
2027 function(event) { $ScriptTable.fasttable('itemCheckClick', this.parentNode.parentNode, event); });
2028 $ScriptTable.on('click', 'thead div.check',
2029 function() { $ScriptTable.fasttable('titleCheckClick') });
2030 $ScriptTable.on('mousedown', Util.disableShiftMouseDown);
20312037
20322038 $ScriptListDialog.on('hidden', function()
20332039 {
20462052
20472053 if (orderMode)
20482054 {
2049 $('#ScriptListDialog_Title').text('Reorder scripts');
2055 $('#ScriptListDialog_Title').text('Reorder extensions');
20502056 $('#ScriptListDialog_Instruction').text('Hover mouse over table elements for reorder buttons to appear.');
20512057 }
20522058 else
20532059 {
2054 $('#ScriptListDialog_Title').text('Choose scripts');
2055 $('#ScriptListDialog_Instruction').html('Select scripts for option <strong>' + option.name + '</strong>.');
2060 $('#ScriptListDialog_Title').text('Choose extensions');
2061 $('#ScriptListDialog_Instruction').html('Select extension scripts for option <strong>' + option.name + '</strong>.');
20562062 }
20572063
20582064 $ScriptTable.toggleClass('table-hidecheck', orderMode);
20982104
20992105 var availableScripts = [];
21002106 var availableAllScripts = [];
2101 for (var i=1; i < config.length; i++)
2107 for (var i=2; i < config.length; i++)
21022108 {
21032109 availableAllScripts.push(config[i].scriptName);
2104 if (!kind || config[i][kind])
2110 var accept = !kind;
2111 if (!accept)
2112 {
2113 for (var j=0; j < kind.length; j++)
2114 {
2115 accept = accept || config[i][kind[j]];
2116 }
2117 }
2118 if (accept)
21052119 {
21062120 availableScripts.push(config[i].scriptName);
21072121 }
21932207 }
21942208 }
21952209 }
2196
2210
21972211 control.val(orderList.join(', '));
21982212 }
21992213
24962510 $SectionTable.fasttable(
24972511 {
24982512 pagerContainer: $('#RestoreSettingsDialog_SectionTable_pager'),
2499 headerCheck: $('#RestoreSettingsDialog_SectionTable > thead > tr:first-child'),
2513 rowSelect: UISettings.rowSelect,
25002514 infoEmpty: 'No sections found.',
25012515 pageSize: 1000
25022516 });
2503
2504 $SectionTable.on('click', 'tbody div.check',
2505 function(event) { $SectionTable.fasttable('itemCheckClick', this.parentNode.parentNode, event); });
2506 $SectionTable.on('click', 'thead div.check',
2507 function() { $SectionTable.fasttable('titleCheckClick') });
2508 $SectionTable.on('mousedown', Util.disableShiftMouseDown);
25092517
25102518 $RestoreSettingsDialog.on('hidden', function()
25112519 {
25782586 var $UpdateDialog;
25792587 var $UpdateProgressDialog;
25802588 var $UpdateProgressDialog_Log;
2581
2589
25822590 // State
25832591 var VersionInfo;
25842592 var PackageInfo;
26052613 Refresher.resume();
26062614 }
26072615 }
2608
2616
26092617 this.showModal = function()
26102618 {
26112619 $('#UpdateDialog_Install').hide();
26182626 $('#UpdateDialog_InstalledInfo').show();
26192627
26202628 $('#UpdateDialog_VerInstalled').text(Options.option('Version'));
2621
2629
26222630 PackageInfo = {};
26232631 VersionInfo = {};
26242632 UpdateInfo = {};
26302638
26312639 RPC.call('readurl', ['http://nzbget.net/info/nzbget-version.json?nocache=' + new Date().getTime(), 'version info'], loadedUpstreamInfo, error);
26322640 }
2633
2641
26342642 function error(e)
26352643 {
26362644 $('#UpdateDialog_CheckProgress').hide();
26432651 var obj = JSON.parse(jsonp.substr(p, 10000));
26442652 return obj;
26452653 }
2646
2654
26472655 function loadedUpstreamInfo(data)
26482656 {
26492657 VersionInfo = parseJsonP(data);
26602668 function loadGitVerData()
26612669 {
26622670 // fetching devel version number from svn viewer
2663 RPC.call('readurl', ['https://github.com/nzbget/nzbget', 'git revision info'],
2671 RPC.call('readurl', ['https://github.com/nzbget/nzbget', 'git revision info'],
26642672 function(gitRevData)
26652673 {
2666 RPC.call('readurl', ['https://raw.githubusercontent.com/nzbget/nzbget/develop/configure.ac', 'git branch info'],
2674 RPC.call('readurl', ['https://raw.githubusercontent.com/nzbget/nzbget/develop/configure.ac', 'git branch info'],
26672675 function(gitBranchData)
26682676 {
26692677 var html = document.createElement('DIV');
26712679 html = html.textContent || html.innerText || '';
26722680 html = html.replace(/(?:\r\n|\r|\n)/g, ' ');
26732681 var rev = html.match(/([0-9\,]*)\s*commits/);
2674
2682
26752683 if (rev && rev.length > 1)
26762684 {
26772685 rev = rev[1].replace(',', '');
26812689 VersionInfo['devel-version'] = ver[1] + '-r' + rev;
26822690 }
26832691 }
2684
2692
26852693 loadPackageInfo();
26862694 }, error);
26872695 }, error);
26882696 }
2689
2697
26902698 function loadPackageInfo()
26912699 {
26922700 $.get('package-info.json', loadedPackageInfo, 'html').fail(loadedAll);
26932701 }
2694
2702
26952703 function loadedPackageInfo(data)
26962704 {
26972705 PackageInfo = parseJsonP(data);
27142722 UpdateInfo = parseJsonP(data);
27152723 loadedAll();
27162724 }
2717
2725
27182726 function formatTesting(str)
27192727 {
27202728 return str.replace('-testing-', '-');
27212729 }
2722
2730
27232731 function revision(version)
27242732 {
27252733 var rev = version.match(/.*r(\d+)/);
27312739 var ver = version.match(/([\d.]+).*/);
27322740 return ver && ver.length > 1 ? parseFloat(ver[1]) : 0;
27332741 }
2734
2742
27352743 function loadedAll()
27362744 {
27372745 var installedVersion = Options.option('Version');
27502758 Util.show('#UpdateDialog_CurNotesStable', VersionInfo['stable-release-notes']);
27512759 Util.show('#UpdateDialog_CurNotesTesting', VersionInfo['testing-release-notes']);
27522760 Util.show('#UpdateDialog_CurNotesDevel', VersionInfo['devel-release-notes']);
2753
2761
27542762 $('#UpdateDialog_AvailStable').text(UpdateInfo['stable-version'] ? UpdateInfo['stable-version'] : 'not available');
27552763 $('#UpdateDialog_AvailTesting').text(UpdateInfo['testing-version'] ? formatTesting(UpdateInfo['testing-version']) : 'not available');
27562764 $('#UpdateDialog_AvailDevel').text(UpdateInfo['devel-version'] ? formatTesting(UpdateInfo['devel-version']) : 'not available');
27652773 var installedRev = revision(installedVersion);
27662774 var installedVer = vernumber(installedVersion);
27672775 var installedStable = installedRev === 0 && installedVersion.indexOf('testing') === -1;
2768
2769 var canInstallStable = UpdateInfo['stable-version'] &&
2770 ((installedStable && installedVer < vernumber(UpdateInfo['stable-version'])) ||
2776
2777 var canInstallStable = UpdateInfo['stable-version'] &&
2778 ((installedStable && installedVer < vernumber(UpdateInfo['stable-version'])) ||
27712779 (!installedStable && installedVer <= vernumber(UpdateInfo['stable-version'])));
2772 var canInstallTesting = UpdateInfo['testing-version'] &&
2773 ((installedStable && installedVer < vernumber(UpdateInfo['testing-version'])) ||
2780 var canInstallTesting = UpdateInfo['testing-version'] &&
2781 ((installedStable && installedVer < vernumber(UpdateInfo['testing-version'])) ||
27742782 (!installedStable && (installedRev === 0 || installedRev < revision(UpdateInfo['testing-version']))));
2775 var canInstallDevel = UpdateInfo['devel-version'] &&
2776 ((installedStable && installedVer < vernumber(UpdateInfo['devel-version'])) ||
2783 var canInstallDevel = UpdateInfo['devel-version'] &&
2784 ((installedStable && installedVer < vernumber(UpdateInfo['devel-version'])) ||
27772785 (!installedStable && (installedRev === 0 || installedRev < revision(UpdateInfo['devel-version']))));
27782786 Util.show('#UpdateDialog_InstallStable', canInstallStable);
27792787 Util.show('#UpdateDialog_InstallTesting', canInstallTesting);
27802788 Util.show('#UpdateDialog_InstallDevel', canInstallDevel);
2781
2789
27822790 var hasUpdateSource = PackageInfo['update-info-link'] || PackageInfo['update-info-script'];
27832791 var hasUpdateInfo = UpdateInfo['stable-version'] || UpdateInfo['testing-version'] || UpdateInfo['devel-version'];
27842792 var canUpdate = canInstallStable || canInstallTesting || canInstallDevel;
27882796 Util.show('#UpdateDialog_CheckFailed', hasUpdateSource && !hasUpdateInfo);
27892797 $('#UpdateDialog_AvailRow').toggleClass('hide', !hasUpdateInfo);
27902798 }
2791
2799
27922800 function install(e)
27932801 {
27942802 e.preventDefault();
27952803 var kind = $(this).attr('data-kind');
27962804 var script = PackageInfo['install-script'];
27972805 var info = PackageInfo['install-' + kind + '-info'];
2798
2806
27992807 if (!script)
28002808 {
28012809 alert('Something is wrong with the package configuration file "package-info.json".');
28082816 RPC.call('startupdate', [kind], updateStarted);
28092817 });
28102818 }
2811
2819
28122820 function updateStarted(started)
28132821 {
28142822 if (!started)
28292837 });
28302838 });
28312839 }
2832
2840
28332841 function updateLog()
28342842 {
28352843 RPC.call('logupdate', [0, 100], function(data)
28632871 $UpdateProgressDialog_Log.scrollTop($UpdateProgressDialog_Log.prop('scrollHeight'));
28642872 }
28652873 }
2866
2874
28672875 function updateLogTable(messages)
28682876 {
28692877 var html = '';
28792887 }
28802888 setLogContentAndScroll(html);
28812889 }
2882
2890
28832891 function checkStatus()
28842892 {
28852893 RPC.call('status', [], function(status)
29132921 }
29142922 });
29152923 }
2916
2924
29172925 }(jQuery));
2323 */
2424
2525 /*** DOWNLOADS TAB ***********************************************************/
26
26
2727 var Downloads = (new function($)
2828 {
2929 'use strict';
8181
8282 $DownloadsTable.fasttable(
8383 {
84 filterInput: $('#DownloadsTable_filter'),
85 filterClearButton: $("#DownloadsTable_clearfilter"),
86 pagerContainer: $('#DownloadsTable_pager'),
87 infoContainer: $('#DownloadsTable_info'),
88 headerCheck: $('#DownloadsTable > thead > tr:first-child'),
84 filterInput: '#DownloadsTable_filter',
85 filterClearButton: '#DownloadsTable_clearfilter',
86 pagerContainer: '#DownloadsTable_pager',
87 infoContainer: '#DownloadsTable_info',
8988 infoEmpty: '&nbsp;', // this is to disable default message "No records"
9089 pageSize: recordsPerPage,
9190 maxPages: UISettings.miniTheme ? 1 : 5,
9291 pageDots: !UISettings.miniTheme,
92 rowSelect: UISettings.rowSelect,
93 shortcuts: true,
9394 fillFieldsCallback: fillFieldsCallback,
9495 renderCellCallback: renderCellCallback,
95 updateInfoCallback: updateInfo
96 updateInfoCallback: updateInfo,
97 dragStartCallback: Refresher.pause,
98 dragEndCallback: dragEndCallback,
99 dragBlink: 'update'
96100 });
97101
98102 $DownloadsTable.on('click', 'a', itemClick);
99 $DownloadsTable.on('click', UISettings.rowSelect ? 'tbody tr' : 'tbody div.check',
100 function(event) { $DownloadsTable.fasttable('itemCheckClick', UISettings.rowSelect ? this : this.parentNode.parentNode, event); });
101 $DownloadsTable.on('click', 'thead div.check',
102 function() { $DownloadsTable.fasttable('titleCheckClick') });
103 $DownloadsTable.on('mousedown', Util.disableShiftMouseDown);
104103 }
105104
106105 this.applyTheme = function()
115114 {
116115 $('#DownloadsTable_Category').css('width', DownloadsUI.calcCategoryColumnWidth());
117116 }
118
117
119118 RPC.call('listgroups', [], groups_loaded);
120119 }
121120
122121 function groups_loaded(_groups)
123122 {
124 groups = _groups;
125 Downloads.groups = groups;
126 prepare();
123 if (!Refresher.isPaused())
124 {
125 groups = _groups;
126 Downloads.groups = groups;
127 prepare();
128 }
127129 RPC.next();
128130 }
129131
138140
139141 this.redraw = function()
140142 {
141 redraw_table();
143 if (!Refresher.isPaused())
144 {
145 redraw_table();
146 }
142147
143148 Util.show($DownloadsTabBadge, groups.length > 0);
144149 Util.show($DownloadsTabBadgeEmpty, groups.length === 0 && UISettings.miniTheme);
149154 {
150155 calcProgressLabels();
151156 }
152
157
153158 /*** TABLE *************************************************************************/
154159
155160 var SEARCH_FIELDS = ['name', 'status', 'priority', 'category', 'estimated', 'age', 'size', 'remaining'];
156
161
157162 function redraw_table()
158163 {
159164 var data = [];
203208 var progresslabel = DownloadsUI.buildProgressLabel(group, nameColumnWidth);
204209 var progress = DownloadsUI.buildProgress(group, item.data.size, item.data.left, item.data.estimated);
205210 var dupe = DownloadsUI.buildDupe(group.DupeKey, group.DupeScore, group.DupeMode);
206
211
207212 var age = new Date().getTime() / 1000 - (group.MinPostTime + UISettings.timeZoneCorrection*60*60);
208213 var propagation = '';
209214 if (group.ActiveDownloads == 0 && age < parseInt(Options.option('PropagationDelay')) * 60)
212217 }
213218
214219 var name = '<a href="#" data-nzbid="' + group.NZBID + '">' + Util.textToHtml(Util.formatNZBName(group.NZBName)) + '</a>';
220 name += DownloadsUI.buildEncryptedLabel(group.Parameters);
215221
216222 var url = '';
217223 if (group.Kind === 'URL')
218224 {
219225 url = '<span class="label label-info">URL</span> ';
220226 }
221
227
222228 var health = '';
223229 if (group.Health < 1000 && (!group.postprocess ||
224230 (group.Status === 'PP_QUEUED' && group.PostTotalTimeSec === 0)))
225231 {
226 health = ' <span class="label ' +
232 health = ' <span class="label ' +
227233 (group.Health >= group.CriticalHealth ? 'label-warning' : 'label-important') +
228234 '">health: ' + Math.floor(group.Health / 10) + '%</span> ';
229235 }
232238 var backupPercent = calcBackupPercent(group);
233239 if (backupPercent > 0)
234240 {
235 backup = ' <a href="#" data-nzbid="' + group.NZBID + '" data-area="backup" class="badge-link"><span class="label label-warning" title="using backup news servers">backup: ' +
241 backup = ' <a href="#" data-nzbid="' + group.NZBID + '" data-area="backup" class="badge-link"><span class="label label-warning" title="using backup news servers">backup: ' +
236242 (backupPercent < 10 ? Util.round1(backupPercent) : Util.round0(backupPercent)) + '%</span> ';
237243 }
238
244
239245 var category = Util.textToHtml(group.Category);
240246
241247 if (!UISettings.miniTheme)
242248 {
243 var info = name + ' ' + url + priority + dupe + health + backup + propagation + progresslabel;
244 item.fields = ['<div class="check img-check"></div>', status, info, category, item.data.age, progress, item.data.estimated];
249 var info = name + ' ' + url + dupe + health + backup + propagation + progresslabel;
250 item.fields = ['<div class="check img-check"></div>', priority, status, info, category, item.data.age, progress, item.data.estimated];
245251 }
246252 else
247253 {
248 var info = '<div class="check img-check"></div><span class="row-title">' + name + '</span>' + url +
249 ' ' + (group.Status === 'QUEUED' ? '' : status) + ' ' + priority + dupe + health + backup + propagation;
254 var info = '<div class="check img-check"></div><span class="row-title">' +
255 name + '</span>' + url + ' ' + (group.MaxPriority == 0 ? '' : priority) +
256 ' ' + (group.Status === 'QUEUED' ? '' : status) + dupe + health + backup + propagation;
250257 if (category)
251258 {
252259 info += ' <span class="label label-status">' + category + '</span>';
262269
263270 function renderCellCallback(cell, index, item)
264271 {
265 if (4 <= index && index <= 7)
272 if (index == 1)
273 {
274 cell.className = 'text-center';
275 }
276 else if (5 <= index && index <= 8)
266277 {
267278 cell.className = 'text-right';
268279 }
269280 }
270
281
271282 function calcBackupPercent(group)
272283 {
273284 var downloadedArticles = group.SuccessArticles + group.FailedArticles;
275286 {
276287 return 0;
277288 }
278
289
279290 if (minLevel === null)
280291 {
281292 for (var i=0; i < Status.status.NewsServers.length; i++)
286297 {
287298 minLevel = level;
288299 }
289 }
290 }
291
300 }
301 }
302
292303 var backupArticles = 0;
293304 for (var j=0; j < group.ServerStats.length; j++)
294305 {
336347 progressLabels.css('max-width', nameColumnWidth);
337348 progressLabels.show();
338349 }
339
350
351 this.processShortcut = function(key)
352 {
353 switch (key)
354 {
355 case 'A': Upload.addClick(); return true;
356 case 'D': case 'Delete': case 'Meta+Backspace': Downloads.deleteClick(); return true;
357 case 'E': case 'Enter': Downloads.editClick(); return true;
358 case 'U': Downloads.moveClick('up'); return true;
359 case 'N': Downloads.moveClick('down'); return true;
360 case 'T': Downloads.moveClick('top'); return true;
361 case 'B': Downloads.moveClick('bottom'); return true;
362 case 'P': Downloads.pauseClick(); return true;
363 case 'R': Downloads.resumeClick(); return true;
364 case 'M': Downloads.mergeClick(); return true;
365 }
366 return $DownloadsTable.fasttable('processShortcut', key);
367 }
368
340369 /*** EDIT ******************************************************/
341370
342371 function itemClick(e)
396425 return checkedEditIDs;
397426 }
398427
428 this.buildEditIDList = function()
429 {
430 return checkBuildEditIDList(true, true, true);
431 }
432
399433 /*** TOOLBAR: SELECTED ITEMS ******************************************************/
400434
401435 this.editClick = function()
441475 return;
442476 }
443477 notification = '#Notif_Downloads_Paused';
444 RPC.call('editqueue', ['GroupPause', 0, '', checkedEditIDs], editCompleted);
478 RPC.call('editqueue', ['GroupPause', '', checkedEditIDs], editCompleted);
445479 }
446480
447481 this.resumeClick = function()
452486 return;
453487 }
454488 notification = '#Notif_Downloads_Resumed';
455 RPC.call('editqueue', ['GroupResume', 0, '', checkedEditIDs], function()
489 RPC.call('editqueue', ['GroupResume', '', checkedEditIDs], function()
456490 {
457491 if (Options.option('ParCheck') === 'force')
458492 {
460494 }
461495 else
462496 {
463 RPC.call('editqueue', ['GroupPauseExtraPars', 0, '', checkedEditIDs], editCompleted);
497 RPC.call('editqueue', ['GroupPauseExtraPars', '', checkedEditIDs], editCompleted);
464498 }
465499 });
466500 }
499533 {
500534 if (postprocessIDs.length > 0)
501535 {
502 RPC.call('editqueue', ['PostDelete', 0, '', postprocessIDs], editCompleted);
536 RPC.call('editqueue', ['PostDelete', '', postprocessIDs], editCompleted);
503537 }
504538 else
505539 {
511545 {
512546 if (downloadIDs.length > 0)
513547 {
514 RPC.call('editqueue', [command, 0, '', downloadIDs], deletePosts);
548 RPC.call('editqueue', [command, '', downloadIDs], deletePosts);
515549 }
516550 else
517551 {
553587 }
554588
555589 notification = '';
556 RPC.call('editqueue', [EditAction, EditOffset, '', checkedEditIDs], editCompleted);
557 }
558
559 this.sort = function(order)
560 {
590 RPC.call('editqueue', [EditAction, '' + EditOffset, checkedEditIDs], editCompleted);
591 }
592
593 this.sort = function(order, e)
594 {
595 e.preventDefault();
596 e.stopPropagation();
561597 var checkedEditIDs = checkBuildEditIDList(true, true, true);
562598 notification = '#Notif_Downloads_Sorted';
563 RPC.call('editqueue', ['GroupSort', 0, order, checkedEditIDs], editCompleted);
564 }
599 RPC.call('editqueue', ['GroupSort', order, checkedEditIDs], editCompleted);
600 }
601
602 function dragEndCallback(info)
603 {
604 if (info)
605 {
606 RPC.call('editqueue', [info.direction === 'after' ? 'GroupMoveAfter' : 'GroupMoveBefore',
607 '' + info.position, info.ids], function(){ Refresher.resume(true); });
608 }
609 else
610 {
611 Refresher.resume();
612 }
613 }
614
565615 }(jQuery));
566616
567617
570620 var DownloadsUI = (new function($)
571621 {
572622 'use strict';
573
623
574624 // State
575625 var categoryColumnWidth = null;
576626 var dupeCheck = null;
577
627
578628 this.fillPriorityCombo = function(combo)
579629 {
580630 combo.empty();
606656 }
607657 return statusText;
608658 }
609
659
610660 this.buildStatus = function(group)
611661 {
612662 var statusText = Downloads.statusData[group.Status].Text;
613663 var badgeClass = '';
614
664
615665 if (group.postprocess && group.Status !== 'PP_QUEUED' && group.Status !== 'QS_QUEUED')
616666 {
617667 badgeClass = Status.status.PostPaused && group.MinPriority < 900 ? 'label-warning' : 'label-success';
629679 statusText = 'INTERNAL_ERROR (' + group.Status + ')';
630680 badgeClass = 'label-important';
631681 }
632
682
633683 return '<span class="label label-status ' + badgeClass + '">' + statusText + '</span>';
634684 }
635685
661711 remaining = '';
662712 percent = Math.round(group.PostStageProgress / 10);
663713 }
664
714
665715 if (group.Kind === 'URL')
666716 {
667717 totalsize = '';
750800
751801 this.buildPriority = function(priority)
752802 {
753 switch (priority)
754 {
755 case 0: return '';
756 case 900: return ' <span class="label label-priority label-important">force priority</span>';
757 case 100: return ' <span class="label label-priority label-important">very high priority</span>';
758 case 50: return ' <span class="label label-priority label-important">high priority</span>';
759 case -50: return ' <span class="label label-priority label-info">low priority</span>';
760 case -100: return ' <span class="label label-priority label-info">very low priority</span>';
761 }
762 if (priority > 0)
763 {
764 return ' <span class="label label-priority label-important">priority: ' + priority + '</span>';
765 }
766 else if (priority < 0)
767 {
768 return ' <span class="label label-priority label-info">priority: ' + priority + '</span>';
769 }
770 }
771
803 var text;
804
805 if (priority >= 900) text = ' <div class="icon-circle-red" title="Force priority"></div>';
806 else if (priority > 50) text = ' <div class="icon-ring-fill-red" title="Very high priority"></div>';
807 else if (priority > 0) text = ' <div class="icon-ring-red" title="High priority"></div>';
808 else if (priority == 0) text = ' <div class="icon-ring-ltgrey" title="Normal priority"></div>';
809 else if (priority >= -50) text = ' <div class="icon-ring-blue" title="Low priority"></div>';
810 else text = ' <div class="icon-ring-fill-blue" title="Very low priority"></div>';
811
812 if ([900, 100, 50, 0, -50, -100].indexOf(priority) == -1)
813 {
814 text = text.replace('priority', 'priority (' + priority + ')');
815 }
816
817 return text;
818 }
819
820 this.buildEncryptedLabel = function(parameters)
821 {
822 var encryptedPassword = '';
823
824 for (var i = 0; i < parameters.length; i++)
825 {
826 if (parameters[i]['Name'].toLowerCase() === '*unpack:password')
827 {
828 encryptedPassword = parameters[i]['Value'];
829 break;
830 }
831 }
832 return encryptedPassword != '' ?
833 ' <span class="label label-info" title="'+ Util.textToAttr(encryptedPassword) +'">encrypted</span>' : '';
834 }
835
772836 function formatDupeText(dupeKey, dupeScore, dupeMode)
773837 {
774838 dupeKey = dupeKey.replace('rageid=', '');
844908 var catWidth = widthHelper.width();
845909 categoryColumnWidth = Math.max(categoryColumnWidth, catWidth);
846910 }
847
911
848912 widthHelper.remove();
849
913
850914 categoryColumnWidth += 'px';
851915 }
852916
7070 {
7171 filterInput: '#DownloadsEdit_FileTable_filter',
7272 pagerContainer: '#DownloadsEdit_FileTable_pager',
73 headerCheck: '#DownloadsEdit_FileTable > thead > tr:first-child',
73 rowSelect: UISettings.rowSelect,
7474 pageSize: 10000,
75 hasHeader: true,
7675 renderCellCallback: fileTableRenderCellCallback
7776 });
7877
8382 pagerContainer: '#DownloadsEdit_ServStatsTable_pager',
8483 pageSize: 100,
8584 maxPages: 3,
86 hasHeader: true,
8785 renderCellCallback: EditUI.servStatsTableRenderCellCallback
8886 });
89
90 $DownloadsFileTable.on('click', UISettings.rowSelect ? 'tbody tr' : 'tbody div.check',
91 function(event) { $DownloadsFileTable.fasttable('itemCheckClick', UISettings.rowSelect ? this : this.parentNode.parentNode, event); });
92 $DownloadsFileTable.on('click', 'thead div.check',
93 function() { $DownloadsFileTable.fasttable('titleCheckClick') });
94 $DownloadsFileTable.on('mousedown', Util.disableShiftMouseDown);
9587
9688 $DownloadsEditDialog.on('hidden', function()
9789 {
404396 {
405397 var name = $('#DownloadsEdit_NZBName').val();
406398 name !== curGroup.NZBName && !curGroup.postprocess ?
407 RPC.call('editqueue', ['GroupSetName', 0, name, [curGroup.NZBID]], function()
399 RPC.call('editqueue', ['GroupSetName', name, [curGroup.NZBID]], function()
408400 {
409401 notification = '#Notif_Downloads_Saved';
410402 savePriority();
416408 {
417409 var priority = parseInt($('#DownloadsEdit_Priority').val());
418410 priority !== curGroup.MaxPriority ?
419 RPC.call('editqueue', ['GroupSetPriority', 0, ''+priority, [curGroup.NZBID]], function()
411 RPC.call('editqueue', ['GroupSetPriority', '' + priority, [curGroup.NZBID]], function()
420412 {
421413 notification = '#Notif_Downloads_Saved';
422414 saveCategory();
428420 {
429421 var category = $('#DownloadsEdit_Category').val();
430422 category !== curGroup.Category ?
431 RPC.call('editqueue', ['GroupSetCategory', 0, category, [curGroup.NZBID]], function()
423 RPC.call('editqueue', ['GroupSetCategory', category, [curGroup.NZBID]], function()
432424 {
433425 notification = '#Notif_Downloads_Saved';
434426 saveDupeKey();
441433 e.preventDefault();
442434 disableAllButtons();
443435 notification = '#Notif_Downloads_Paused';
444 RPC.call('editqueue', ['GroupPause', 0, '', [curGroup.NZBID]], completed);
436 RPC.call('editqueue', ['GroupPause', '', [curGroup.NZBID]], completed);
445437 }
446438
447439 function itemResume(e)
449441 e.preventDefault();
450442 disableAllButtons();
451443 notification = '#Notif_Downloads_Resumed';
452 RPC.call('editqueue', ['GroupResume', 0, '', [curGroup.NZBID]], function()
444 RPC.call('editqueue', ['GroupResume', '', [curGroup.NZBID]], function()
453445 {
454446 if (Options.option('ParCheck') === 'force')
455447 {
457449 }
458450 else
459451 {
460 RPC.call('editqueue', ['GroupPauseExtraPars', 0, '', [curGroup.NZBID]], completed);
452 RPC.call('editqueue', ['GroupPauseExtraPars', '', [curGroup.NZBID]], completed);
461453 }
462454 });
463455 }
472464 {
473465 disableAllButtons();
474466 notification = '#Notif_Downloads_Deleted';
475 RPC.call('editqueue', [command, 0, '', [curGroup.NZBID]], completed);
467 RPC.call('editqueue', [command, '', [curGroup.NZBID]], completed);
476468 }
477469
478470 function itemCancelPP(e)
480472 e.preventDefault();
481473 disableAllButtons();
482474 notification = '#Notif_Downloads_PostCanceled';
483 RPC.call('editqueue', ['PostDelete', 0, '', [curGroup.NZBID]], completed);
475 RPC.call('editqueue', ['PostDelete', '', [curGroup.NZBID]], completed);
484476 }
485477
486478 function categoryChange()
489481 ParamTab.reassignParams(postParams, oldCategory, category);
490482 oldCategory = category;
491483 }
492
484
493485 /*** TAB: POST-PROCESSING PARAMETERS **************************************************/
494486
495487 function saveParam()
508500 {
509501 if (paramList.length > 0)
510502 {
511 RPC.call('editqueue', ['GroupSetParameter', 0, paramList[0], [curGroup.NZBID]], function()
503 RPC.call('editqueue', ['GroupSetParameter', paramList[0], [curGroup.NZBID]], function()
512504 {
513505 notification = '#Notif_Downloads_Saved';
514506 paramList.shift();
527519 {
528520 var value = $('#DownloadsEdit_DupeKey').val();
529521 value !== curGroup.DupeKey ?
530 RPC.call('editqueue', ['GroupSetDupeKey', 0, value, [curGroup.NZBID]], function()
522 RPC.call('editqueue', ['GroupSetDupeKey', value, [curGroup.NZBID]], function()
531523 {
532524 notification = '#Notif_Downloads_Saved';
533525 saveDupeScore();
539531 {
540532 var value = $('#DownloadsEdit_DupeScore').val();
541533 value != curGroup.DupeScore ?
542 RPC.call('editqueue', ['GroupSetDupeScore', 0, value, [curGroup.NZBID]], function()
534 RPC.call('editqueue', ['GroupSetDupeScore', value, [curGroup.NZBID]], function()
543535 {
544536 notification = '#Notif_Downloads_Saved';
545537 saveDupeMode();
551543 {
552544 var value = $('#DownloadsEdit_DupeMode').val();
553545 value !== curGroup.DupeMode ?
554 RPC.call('editqueue', ['GroupSetDupeMode', 0, value, [curGroup.NZBID]], function()
546 RPC.call('editqueue', ['GroupSetDupeMode', value, [curGroup.NZBID]], function()
555547 {
556548 notification = '#Notif_Downloads_Saved';
557549 saveParam();
781773
782774 if (IDs.length > 0)
783775 {
784 RPC.call('editqueue', [command, 0, '', IDs], function()
776 RPC.call('editqueue', [command, '', IDs], function()
785777 {
786778 notification = '#Notif_Downloads_Saved';
787779 saveFilesActions(actions, commands);
817809
818810 if (hasMovedFiles)
819811 {
820 RPC.call('editqueue', ['FileReorder', 0, '', IDs], function()
812 RPC.call('editqueue', ['FileReorder', '', IDs], function()
821813 {
822814 notification = '#Notif_Downloads_Saved';
823815 completed();
10341026 {
10351027 if (category === Options.categories[i])
10361028 {
1037 scriptList = Util.parseCommaList(Options.option('Category' + (i + 1) + '.PostScript'));
1029 scriptList = Util.parseCommaList(Options.option('Category' + (i + 1) + '.Extensions'));
10381030 if (scriptList.length === 0)
10391031 {
1040 scriptList = Util.parseCommaList(Options.option('PostScript'));
1032 scriptList = Util.parseCommaList(Options.option('Extensions'));
10411033 }
10421034 if (Options.option('Category' + (i + 1) + '.Unpack') === 'yes')
10431035 {
10461038 return scriptList;
10471039 }
10481040 }
1049
1041
10501042 // empty category or category not found
1051 scriptList = Util.parseCommaList(Options.option('PostScript'));
1043 scriptList = Util.parseCommaList(Options.option('Extensions'));
10521044 if (Options.option('Unpack') === 'yes')
10531045 {
10541046 scriptList.push('*Unpack');
10551047 }
10561048 return scriptList;
10571049 }
1058
1050
10591051 this.reassignParams = function(postParams, oldCategory, newCategory)
10601052 {
10611053 var oldScriptList = buildCategoryScriptList(oldCategory);
11081100 pagerContainer: '#' + name + 'Edit_LogTable_pager',
11091101 pageSize: recordsPerPage,
11101102 maxPages: 3,
1111 hasHeader: true,
11121103 renderCellCallback: logTableRenderCellCallback
11131104 });
11141105 }
1115
1106
11161107 this.reset = function(name)
11171108 {
11181109 var $LogTable = $('#' + name + 'Edit_LogTable');
11751166
11761167 $LogTable.fasttable('update', data);
11771168 }
1178
1169
11791170 var recordsPerPage = UISettings.read('ItemLogRecordsPerPage', 10);
11801171 $('#' + name + 'LogRecordsPerPage').val(recordsPerPage);
1181
1172
11821173 $('#' + name + 'EditDialog .loading-block').show();
11831174 RPC.call('loadlog', [item.NZBID, 0, 10000], logLoaded);
11841175 }
13891380 {
13901381 var priority = $('#DownloadsMulti_Priority').val();
13911382 (priority !== oldPriority && priority !== '<multiple values>') ?
1392 RPC.call('editqueue', ['GroupSetPriority', 0, priority, multiIDList], function()
1383 RPC.call('editqueue', ['GroupSetPriority', priority, multiIDList], function()
13931384 {
13941385 notification = '#Notif_Downloads_Saved';
13951386 saveCategory();
14011392 {
14021393 var category = $('#DownloadsMulti_Category').val();
14031394 (category !== oldCategory && category !== '<multiple values>') ?
1404 RPC.call('editqueue', ['GroupApplyCategory', 0, category, multiIDList], function()
1395 RPC.call('editqueue', ['GroupApplyCategory', category, multiIDList], function()
14051396 {
14061397 notification = '#Notif_Downloads_Saved';
14071398 completed();
14761467
14771468 function merge()
14781469 {
1479 RPC.call('editqueue', ['GroupMerge', 0, '', mergeEditIDList], completed);
1470 RPC.call('editqueue', ['GroupMerge', '', mergeEditIDList], completed);
14801471 }
14811472
14821473 function completed()
15321523 function split()
15331524 {
15341525 var groupName = $('#DownloadsSplit_NZBName').val();
1535 RPC.call('editqueue', ['FileSplit', 0, groupName, splitEditIDList], completed);
1526 RPC.call('editqueue', ['FileSplit', groupName, splitEditIDList], completed);
15361527 }
15371528
15381529 function completed(result)
15911582 pagerContainer: '#HistoryEdit_ServStatsTable_pager',
15921583 pageSize: 100,
15931584 maxPages: 3,
1594 hasHeader: true,
15951585 renderCellCallback: EditUI.servStatsTableRenderCellCallback
15961586 });
15971587
16291619
16301620 else if (hist.DeleteStatus === 'NONE')
16311621 {
1632 var exParStatus = hist.ExParStatus === 'RECIPIENT' ? ' ' + '<span title="Repaired using ' + hist.ExtraParBlocks + ' par-block' +
1622 var exParStatus = hist.ExParStatus === 'RECIPIENT' ? ' ' + '<span title="Repaired using ' + hist.ExtraParBlocks + ' par-block' +
16331623 (hist.ExtraParBlocks > 1 ? 's' : '') + ' from other duplicate(s)">' + buildStatus(hist.ExParStatus, 'ExPar: ') + '</span>' :
1634 hist.ExParStatus === 'DONOR' ? ' ' + '<span title="Donated ' + -hist.ExtraParBlocks + ' par-block' +
1624 hist.ExParStatus === 'DONOR' ? ' ' + '<span title="Donated ' + -hist.ExtraParBlocks + ' par-block' +
16351625 (-hist.ExtraParBlocks > 1 ? 's' : '') + ' to repair other duplicate(s)">' + buildStatus(hist.ExParStatus, 'ExPar: ') + '</span>' : '';
16361626 status += ' ' + buildStatus(hist.ParStatus, 'Par: ') + exParStatus +
16371627 ' ' + (Options.option('Unpack') == 'yes' || hist.UnpackStatus != 'NONE' ? buildStatus(hist.UnpackStatus, 'Unpack: ') : '') +
16831673 }
16841674
16851675 $('#HistoryEdit_NZBName').val(hist.Name);
1686
1676
16871677 if (hist.Kind !== 'DUP')
16881678 {
16891679 // Category
17071697 completion = '99.9%';
17081698 }
17091699 var time = Util.formatTimeHMS(hist.DownloadTimeSec + hist.PostTotalTimeSec);
1710
1700
17111701 var table = '';
17121702 table += '<tr><td><a href="#" id="HistoryEdit_TimeStats" data-tab="HistoryEdit_TimeStatsTab" title="Size and time statistics">Total '+
17131703 '<i class="icon-forward" style="opacity:0.6;"></i></a>' +
17651755
17661756 var postLog = hist.MessageCount > 0;
17671757 Util.show('#HistoryEdit_Log', postLog);
1768
1758
17691759 EditUI.buildDNZBLinks(curHist.Parameters ? curHist.Parameters : [], 'HistoryEdit_DNZB');
17701760
17711761 enableAllButtons();
18271817 return '<span class="label label-status">' + prefix + status + '</span>';
18281818 }
18291819 }
1830
1820
18311821 function fillTimeStats()
18321822 {
18331823 var hist = curHist;
18461836
18471837 $('#HistoryEdit_TimeStatsTable tbody').html(table);
18481838 }
1849
1839
18501840 function tabClick(e)
18511841 {
18521842 e.preventDefault();
19081898 {
19091899 disableAllButtons();
19101900 notification = '#Notif_History_Deleted';
1911 RPC.call('editqueue', [command, 0, '', [curHist.ID]], completed);
1901 RPC.call('editqueue', [command, '', [curHist.ID]], completed);
19121902 }
19131903
19141904 function itemReturn(e)
19161906 e.preventDefault();
19171907 disableAllButtons();
19181908 notification = '#Notif_History_Returned';
1919 RPC.call('editqueue', ['HistoryReturn', 0, '', [curHist.ID]], completed);
1909 RPC.call('editqueue', ['HistoryReturn', '', [curHist.ID]], completed);
19201910 }
19211911
19221912 function itemRedownload(e)
19371927 {
19381928 disableAllButtons();
19391929 notification = '#Notif_History_Returned';
1940 RPC.call('editqueue', ['HistoryRedownload', 0, '', [curHist.ID]], completed);
1930 RPC.call('editqueue', ['HistoryRedownload', '', [curHist.ID]], completed);
19411931 }
19421932
19431933 function itemReprocess(e)
19511941 function reprocess()
19521942 {
19531943 notification = '#Notif_History_Reprocess';
1954 RPC.call('editqueue', ['HistoryProcess', 0, '', [curHist.ID]], completed);
1944 RPC.call('editqueue', ['HistoryProcess', '', [curHist.ID]], completed);
19551945 }
19561946
19571947 function itemRetryFailed(e)
19611951 saveCompleted = retryFailed;
19621952 saveDupeKey();
19631953 }
1964
1954
19651955 function retryFailed()
19661956 {
19671957 notification = '#Notif_History_RetryFailed';
1968 RPC.call('editqueue', ['HistoryRetryFailed', 0, '', [curHist.ID]], completed);
1969 }
1970
1958 RPC.call('editqueue', ['HistoryRetryFailed', '', [curHist.ID]], completed);
1959 }
1960
19711961 function completed()
19721962 {
19731963 $HistoryEditDialog.modal('hide');
19921982 {
19931983 var name = $('#HistoryEdit_NZBName').val();
19941984 name !== curHist.Name && !curHist.postprocess ?
1995 RPC.call('editqueue', ['HistorySetName', 0, name, [curHist.ID]], function()
1985 RPC.call('editqueue', ['HistorySetName', name, [curHist.ID]], function()
19961986 {
19971987 notification = '#Notif_History_Saved';
19981988 saveCategory();
20041994 {
20051995 var category = $('#HistoryEdit_Category').val();
20061996 category !== curHist.Category && curHist.Kind !== 'DUP' ?
2007 RPC.call('editqueue', ['HistorySetCategory', 0, category, [curHist.ID]], function()
1997 RPC.call('editqueue', ['HistorySetCategory', category, [curHist.ID]], function()
20081998 {
20091999 notification = '#Notif_History_Saved';
20102000 saveDupeKey();
20222012 {
20232013 disableAllButtons();
20242014 notification = '#Notif_History_Marked';
2025 RPC.call('editqueue', ['HistoryMarkSuccess', 0, '', [curHist.ID]], completed);
2015 RPC.call('editqueue', ['HistoryMarkSuccess', '', [curHist.ID]], completed);
20262016 }
20272017
20282018 function itemGood(e)
20352025 {
20362026 disableAllButtons();
20372027 notification = '#Notif_History_Marked';
2038 RPC.call('editqueue', ['HistoryMarkGood', 0, '', [curHist.ID]], completed);
2028 RPC.call('editqueue', ['HistoryMarkGood', '', [curHist.ID]], completed);
20392029 }
20402030
20412031 function itemBad(e)
20482038 {
20492039 disableAllButtons();
20502040 notification = '#Notif_History_Marked';
2051 RPC.call('editqueue', ['HistoryMarkBad', 0, '', [curHist.ID]], completed);
2041 RPC.call('editqueue', ['HistoryMarkBad', '', [curHist.ID]], completed);
20522042 }
20532043
20542044 /*** TAB: POST-PROCESSING PARAMETERS **************************************************/
20692059 {
20702060 if (paramList.length > 0)
20712061 {
2072 RPC.call('editqueue', ['HistorySetParameter', 0, paramList[0], [curHist.ID]], function()
2062 RPC.call('editqueue', ['HistorySetParameter', paramList[0], [curHist.ID]], function()
20732063 {
20742064 notification = '#Notif_History_Saved';
20752065 paramList.shift();
20882078 {
20892079 var value = $('#HistoryEdit_DupeKey').val();
20902080 value !== curHist.DupeKey ?
2091 RPC.call('editqueue', ['HistorySetDupeKey', 0, value, [curHist.ID]], function()
2081 RPC.call('editqueue', ['HistorySetDupeKey', value, [curHist.ID]], function()
20922082 {
20932083 notification = '#Notif_History_Saved';
20942084 saveDupeScore();
21002090 {
21012091 var value = $('#HistoryEdit_DupeScore').val();
21022092 value != curHist.DupeScore ?
2103 RPC.call('editqueue', ['HistorySetDupeScore', 0, value, [curHist.ID]], function()
2093 RPC.call('editqueue', ['HistorySetDupeScore', value, [curHist.ID]], function()
21042094 {
21052095 notification = '#Notif_History_Saved';
21062096 saveDupeMode();
21122102 {
21132103 var value = $('#HistoryEdit_DupeMode').val();
21142104 value !== curHist.DupeMode ?
2115 RPC.call('editqueue', ['HistorySetDupeMode', 0, value, [curHist.ID]], function()
2105 RPC.call('editqueue', ['HistorySetDupeMode', value, [curHist.ID]], function()
21162106 {
21172107 notification = '#Notif_History_Saved';
21182108 saveDupeBackup();
21262116 var oldValue = curHist.DeleteStatus === 'DUPE';
21272117 var value = $('#HistoryEdit_DupeBackup').is(':checked');
21282118 canChange && value !== oldValue ?
2129 RPC.call('editqueue', ['HistorySetDupeBackup', 0, value ? "YES" : "NO", [curHist.ID]], function()
2119 RPC.call('editqueue', ['HistorySetDupeBackup', value ? "YES" : "NO", [curHist.ID]], function()
21302120 {
21312121 notification = '#Notif_History_Saved';
21322122 saveParam();
2929 * HTML tables with:
3030 * 1) very fast content updates;
3131 * 2) automatic pagination;
32 * 3) search/filtering.
32 * 3) search/filtering;
33 * 4) drag and drop.
3334 *
3435 * What makes it unique and fast?
3536 * The tables are designed to be updated very often (up to 10 times per second). This has two challenges:
5051 (function($) {
5152
5253 'use strict';
53
54
5455 $.fn.fasttable = function(method)
5556 {
5657 if (methods[method])
7374 {
7475 return defaults;
7576 },
76
77
7778 init : function(options)
7879 {
7980 return this.each(function()
9091
9192 var config = {};
9293 config = $.extend(config, defaults, options);
93
94
9495 config.filterInput = $(config.filterInput);
9596 config.filterClearButton = $(config.filterClearButton);
9697 config.pagerContainer = $(config.pagerContainer);
9798 config.infoContainer = $(config.infoContainer);
98 config.headerCheck = $(config.headerCheck);
99
99 config.dragBox = $(config.dragBox);
100 config.dragContent = $(config.dragContent);
101 config.dragBadge = $(config.dragBadge);
102 config.selector = $('th.table-selector', $this);
103
100104 var searcher = new FastSearcher();
101
105
102106 // Create a timer which gets reset upon every keyup event.
103107 // Perform filter only when the timer's wait is reached (user finished typing or paused long enough to elapse the timer).
104108 // Do not perform the filter is the query has not changed.
142146 data.config.filterInput.val('');
143147 applyFilter(data, '');
144148 });
145
149
146150 config.pagerContainer.on('click', 'li', function (e)
147151 {
148152 e.preventDefault();
166170 }
167171 refresh(data);
168172 });
169
170 $this.data('fasttable', {
171 target : $this,
172 config : config,
173 pageSize : parseInt(config.pageSize),
174 maxPages : parseInt(config.maxPages),
175 pageDots : Util.parseBool(config.pageDots),
176 curPage : 1,
177 checkedRows: {},
178 checkedCount: 0,
179 lastClickedRowID: null,
180 searcher: searcher
181 });
173
174 var data = {
175 target: $this,
176 config: config,
177 pageSize: parseInt(config.pageSize),
178 maxPages: parseInt(config.maxPages),
179 pageDots: Util.parseBool(config.pageDots),
180 curPage: 1,
181 checkedRows: {},
182 checkedCount: 0,
183 lastClickedRowID: null,
184 searcher: searcher
185 };
186
187 initDragDrop(data);
188
189 $this.on('click', 'thead > tr', function(e) { titleCheckClick(data, e); });
190 $this.on('click', 'tbody > tr', function(e) { itemCheckClick(data, e); });
191
192 $this.data('fasttable', data);
182193 }
183194 });
184195 },
201212 setPageSize : setPageSize,
202213
203214 setCurPage : setCurPage,
204
215
205216 applyFilter : function(filter)
206217 {
207218 applyFilter($(this).data('fasttable'), filter);
221232 {
222233 return $(this).data('fasttable').checkedRows;
223234 },
224
235
225236 checkedCount : function()
226237 {
227238 return $(this).data('fasttable').checkedCount;
228239 },
229240
241 pageCheckedCount : function()
242 {
243 return $(this).data('fasttable').pageCheckedCount;
244 },
245
230246 checkRow : function(id, checked)
231247 {
232248 checkRow($(this).data('fasttable'), id, checked);
233249 },
234
235 itemCheckClick : itemCheckClick,
236
237 titleCheckClick : titleCheckClick
250
251 processShortcut : function(key)
252 {
253 return processShortcut($(this).data('fasttable'), key);
254 },
238255 };
239256
240257 function updateContent(content)
245262 data.content = content;
246263 }
247264 refresh(data);
265 blinkMovedRecords(data);
248266 }
249267
250268 function applyFilter(data, filter)
260278 if (filter !== '' && data.config.filterInputCallback)
261279 {
262280 data.config.filterInputCallback(filter);
263 }
281 }
264282 if (filter === '' && data.config.filterClearCallback)
265283 {
266284 data.config.filterClearCallback();
267 }
285 }
268286 }
269287
270288 function refresh(data)
273291 validateChecks(data);
274292 updatePager(data);
275293 updateInfo(data);
294 updateSelector(data);
276295 updateTable(data);
277296 }
278297
336355 item.fields = [];
337356 }
338357 }
339
358
340359 for (var j=0; j < item.fields.length; j++)
341360 {
342361 var cell = row.insertCell(row.cells.length);
347366 }
348367 }
349368 }
350
369
351370 titleCheckRedraw(data);
352
371
353372 if (data.config.renderTableCallback)
354373 {
355374 data.config.renderTableCallback(table);
356 }
357
375 }
376
358377 return table;
359378 }
360379
361380 function updateTBody(data, oldTable, newTable)
362381 {
382 var headerRows = $('thead > tr', oldTable).length;
363383 var oldTRs = oldTable.rows;
364384 var newTRs = newTable.rows;
365385 var oldTBody = $('tbody', oldTable)[0];
366 var oldTRsLength = oldTRs.length - (data.config.hasHeader ? 1 : 0); // evlt. skip header row
386 var oldTRsLength = oldTRs.length - headerRows; // evlt. skip header row
367387 var newTRsLength = newTRs.length;
368388
369389 for (var i=0; i < newTRs.length; )
373393 if (i < oldTRsLength)
374394 {
375395 // update existing row
376 var oldTR = oldTRs[i + (data.config.hasHeader ? 1 : 0)]; // evlt. skip header row
396 var oldTR = oldTRs[i + headerRows]; // evlt. skip header row
377397 var oldTDs = oldTR.cells;
378398 var newTDs = newTR.cells;
379
399
380400 oldTR.className = newTR.className;
381401 oldTR.fasttableID = newTR.fasttableID;
382402
401421 }
402422 }
403423
404 var maxTRs = newTRsLength + (data.config.hasHeader ? 1 : 0); // evlt. skip header row;
424 var maxTRs = newTRsLength + headerRows; // evlt. skip header row;
405425 while (oldTRs.length > maxTRs)
406426 {
407427 oldTable.deleteRow(oldTRs.length - 1);
425445
426446 var pagerObj = data.config.pagerContainer;
427447 var pagerHtml = buildPagerHtml(data);
428
448
429449 var oldPager = pagerObj[0];
430450 var newPager = $(pagerHtml)[0];
431
451
432452 updatePagerContent(data, oldPager, newPager);
433453 }
434454
459479 }
460480
461481 var pager = '<ul>';
462 pager += '<li' + (data.curPage === 1 || data.curPage === 0 ? ' class="disabled"' : '') + '><a href="#">&larr; Prev</a></li>';
482 pager += '<li' + (data.curPage === 1 || data.curPage === 0 ? ' class="disabled"' : '') +
483 '><a href="#" title="Previous page' + (data.config.shortcuts ? ' [Left]' : '') + '">&larr; Prev</a></li>';
463484
464485 if (iStart > 1)
465486 {
466 pager += '<li><a href="#">1</a></li>';
487 pager += '<li><a href="#"' + (data.config.shortcuts ? ' title="First page [Shift+Left]"' : '') + '>1</a></li>';
467488 if (iStart > 2 && data.pageDots)
468489 {
469490 pager += '<li class="disabled"><a href="#">&#133;</a></li>';
472493
473494 for (var j=iStart; j<=iEnd; j++)
474495 {
475 pager += '<li' + ((j===data.curPage) ? ' class="active"' : '') + '><a href="#">' + j + '</a></li>';
496 pager += '<li' + ((j===data.curPage) ? ' class="active"' : '') +
497 '><a href="#"' +
498 (data.config.shortcuts && j === 1 ? ' title="First page [Shift+Left]"' :
499 data.config.shortcuts && j === data.pageCount ? ' title="Last page [Shift+Right]"' : '') +
500 '>' + j + '</a></li>';
476501 }
477502
478503 if (iEnd != data.pageCount)
481506 {
482507 pager += '<li class="disabled"><a href="#">&#133;</a></li>';
483508 }
484 pager += '<li><a href="#">' + data.pageCount + '</a></li>';
485 }
486
487 pager += '<li' + (data.curPage === data.pageCount || data.pageCount === 0 ? ' class="disabled"' : '') + '><a href="#">Next &rarr;</a></li>';
509 pager += '<li><a href="#"' + (data.config.shortcuts ? ' title="Last page [Shift+Right]"' : '') + '>' + data.pageCount + '</a></li>';
510 }
511
512 pager += '<li' + (data.curPage === data.pageCount || data.pageCount === 0 ? ' class="disabled"' : '') +
513 '><a href="#" title="Next page' + (data.config.shortcuts ? ' [Right]' : '') + '">Next &rarr;</a></li>';
488514 pager += '</ul>';
489
515
490516 return pager;
491517 }
492
518
493519 function updatePagerContent(data, oldPager, newPager)
494520 {
495521 var oldLIs = oldPager.getElementsByTagName('li');
528554 oldPager.removeChild(oldPager.lastChild);
529555 }
530556 }
531
557
532558 function updateInfo(data)
533559 {
534560 if (data.content.length === 0)
558584 available: data.availableContent.length,
559585 filtered: data.filteredContent.length,
560586 firstRecord: firstRecord,
561 lastRecord: lastRecord
587 lastRecord: lastRecord
562588 });
589 }
590 }
591
592 function updateSelector(data)
593 {
594 data.pageCheckedCount = 0;
595 if (data.checkedCount > 0 && data.filteredContent.length > 0)
596 {
597 for (var i = (data.curPage - 1) * data.pageSize; i < Math.min(data.curPage * data.pageSize, data.filteredContent.length); i++)
598 {
599 data.pageCheckedCount += data.checkedRows[data.filteredContent[i].id] ? 1 : 0;
600 }
601 }
602 data.config.selector.css('display', data.pageCheckedCount === data.checkedCount ? 'none' : '');
603 if (data.checkedCount !== data.pageCheckedCount)
604 {
605 data.config.selector.text('' + (data.checkedCount - data.pageCheckedCount) +
606 (data.checkedCount - data.pageCheckedCount > 1 ? ' records' : ' record') +
607 ' selected on other pages');
563608 }
564609 }
565610
585630 data.curPage = parseInt(page);
586631 refresh(data);
587632 }
588
633
634 function checkedIds(data)
635 {
636 var checkedRows = data.checkedRows;
637 var checkedIds = [];
638 for (var i = 0; i < data.content.length; i++)
639 {
640 var id = data.content[i].id;
641 if (checkedRows[id])
642 {
643 checkedIds.push(id);
644 }
645 }
646 return checkedIds;
647 }
648
589649 function titleCheckRedraw(data)
590650 {
591651 var filteredContent = data.filteredContent;
604664 hasUnselectedItems = true;
605665 }
606666 }
607
667
668 var headerRow = $('thead > tr', data.target);
608669 if (hasSelectedItems && hasUnselectedItems)
609670 {
610 data.config.headerCheck.removeClass('checked').addClass('checkremove');
671 headerRow.removeClass('checked').addClass('checkremove');
611672 }
612673 else if (hasSelectedItems)
613674 {
614 data.config.headerCheck.removeClass('checkremove').addClass('checked');
675 headerRow.removeClass('checkremove').addClass('checked');
615676 }
616677 else
617678 {
618 data.config.headerCheck.removeClass('checked').removeClass('checkremove');
619 }
620 }
621
622 function itemCheckClick(row, event)
623 {
624 var data = $(this).data('fasttable');
625 var checkedRows = data.checkedRows;
626
679 headerRow.removeClass('checked').removeClass('checkremove');
680 }
681 }
682
683 function itemCheckClick(data, event)
684 {
685 var checkmark = $(event.target).hasClass('check');
686 if (data.dragging || (!checkmark && !data.config.rowSelect))
687 {
688 return;
689 }
690
691 var row = $(event.target).closest('tr', data.target)[0];
627692 var id = row.fasttableID;
628693 var doToggle = true;
694 var checkedRows = data.checkedRows;
629695
630696 if (event.shiftKey && data.lastClickedRowID != null)
631697 {
639705 }
640706
641707 data.lastClickedRowID = id;
642
708
643709 refresh(data);
644710 }
645711
646 function titleCheckClick()
647 {
648 var data = $(this).data('fasttable');
712 function titleCheckClick(data, event)
713 {
714 var checkmark = $(event.target).hasClass('check');
715 if (data.dragging || (!checkmark && !data.config.rowSelect))
716 {
717 return;
718 }
719
649720 var filteredContent = data.filteredContent;
650721 var checkedRows = data.checkedRows;
651722
678749 data.checkedCount++;
679750 }
680751 }
681
752
682753 function checkAll(data, checked)
683754 {
684755 var filteredContent = data.filteredContent;
690761
691762 refresh(data);
692763 }
693
764
694765 function checkRange(data, from, to, checked)
695766 {
696767 var filteredContent = data.filteredContent;
700771 {
701772 return false;
702773 }
703
774
704775 if (indexTo < indexFrom)
705776 {
706 var tmp = indexTo; indexTo = indexFrom; indexFrom = tmp;
707 }
708
777 var tmp = indexTo; indexTo = indexFrom; indexFrom = tmp;
778 }
779
709780 for (var i = indexFrom; i <= indexTo; i++)
710781 {
711782 checkRow(data, filteredContent[i].id, checked);
712783 }
713784
714785 return true;
715 }
786 }
716787
717788 function checkRow(data, id, checked)
718789 {
733804 data.checkedRows[id] = undefined;
734805 }
735806 }
736
807
737808 function indexOfID(content, id)
738809 {
739810 for (var i = 0; i < content.length; i++)
761832 }
762833 }
763834 }
764
835
836 //*************** DRAG-N-DROP
837
838 function initDragDrop(data)
839 {
840 data.target[0].addEventListener('mousedown', function(e) { mouseDown(data, e); }, true);
841 data.target[0].addEventListener('touchstart', function(e) { mouseDown(data, e); }, true);
842
843 data.moveIds = [];
844 data.dropAfter = false;
845 data.dropId = null;
846 data.dragging = false;
847 data.dragRow = $('');
848 data.cancelDrag = false;
849 data.downPos = null;
850 data.blinkIds = [];
851 data.blinkState = null;
852 data.wantBlink = false;
853 }
854
855 function touchToMouse(e)
856 {
857 if (e.type === 'touchstart' || e.type === 'touchmove' || e.type === 'touchend')
858 {
859 e.clientX = e.changedTouches[0].clientX;
860 e.clientY = e.changedTouches[0].clientY;
861 }
862 }
863
864 function mouseDown(data, e)
865 {
866 data.dragging = false;
867 data.dropId = null;
868 data.dragRow = $(e.target).closest('tr', data.target);
869
870 var checkmark = $(e.target).hasClass('check') ||
871 ($(e.target).find('.check').length > 0 && !$('body').hasClass('phone'));
872 var head = $(e.target).closest('tr', data.target).parent().is('thead');
873 if (head || !(checkmark || (data.config.rowSelect && e.type === 'mousedown')) ||
874 data.dragRow.length != 1 || e.ctrlKey || e.altKey || e.metaKey)
875 {
876 return;
877 }
878
879 touchToMouse(e);
880 if (e.type === 'mousedown')
881 {
882 e.preventDefault();
883 }
884
885 if (!data.config.dragEndCallback)
886 {
887 return;
888 }
889
890 data.downPos = { x: e.clientX, y: e.clientY };
891
892 data.mouseMove = function(e) { mouseMove(data, e); };
893 data.mouseUp = function(e) { mouseUp(data, e); };
894 data.keyDown = function(e) { keyDown(data, e); };
895 document.addEventListener('mousemove', data.mouseMove, true);
896 document.addEventListener('touchmove', data.mouseMove, true);
897 document.addEventListener('mouseup', data.mouseUp, true);
898 document.addEventListener('touchend', data.mouseUp, true);
899 document.addEventListener('touchcancel', data.mouseUp, true);
900 document.addEventListener('keydown', data.keyDown, true);
901 }
902
903 function mouseMove(data, e)
904 {
905 touchToMouse(e);
906 e.preventDefault();
907
908 if (e.touches && e.touches.length > 1)
909 {
910 data.cancelDrag = true;
911 mouseUp(data, e);
912 return;
913 }
914
915 if (!data.dragging)
916 {
917 if (Math.abs(data.downPos.x - e.clientX) < 5 &&
918 Math.abs(data.downPos.y - e.clientY) < 5)
919 {
920 return;
921 }
922 startDrag(data, e);
923 if (data.dragCancel)
924 {
925 mouseUp(data, e);
926 return;
927 }
928 }
929
930 updateDrag(data, e.clientX, e.clientY);
931 autoScroll(data, e.clientX, e.clientY);
932 }
933
934 function startDrag(data, e)
935 {
936 if (data.config.dragStartCallback)
937 {
938 data.config.dragStartCallback();
939 }
940
941 var offsetX = $(document).scrollLeft();
942 var offsetY = $(document).scrollTop();
943 var rf = data.dragRow.offset();
944 data.dragOffset = { x: data.downPos.x - rf.left + offsetX,
945 y: Math.min(Math.max(data.downPos.y - rf.top + offsetY, 0), data.dragRow.height()) };
946
947 var checkedRows = data.checkedRows;
948 var chkIds = checkedIds(data);
949 var id = data.dragRow[0].fasttableID;
950 data.moveIds = checkedRows[id] ? chkIds : [id];
951 data.dragging = true;
952 data.cancelDrag = false;
953
954 buildDragBox(data);
955 data.config.dragBox.css('display', 'block');
956 data.dragRow.addClass('drag-source');
957 $('html').addClass('drag-progress');
958 data.oldOverflowX = $('body').css('overflow-x');
959 $('body').css('overflow-x', 'hidden');
960 }
961
962 function buildDragBox(data)
963 {
964 var tr = data.dragRow.clone();
965 var table = data.target.clone();
966 $('tr', table).remove();
967 $('thead', table).remove();
968 $('tbody', table).append(tr);
969 table.css('margin', 0);
970 data.config.dragContent.html(table);
971 data.config.dragBadge.text(data.moveIds.length);
972 data.config.dragBadge.css('display', data.moveIds.length > 1 ? 'block' : 'none');
973 data.config.dragBox.css({left: data.target.offset().left, width: data.dragRow.width()});
974 var tds = $('td', tr);
975 $('td', data.dragRow).each(function(ind, el) { $(tds[ind]).css('width', $(el).width()); });
976 }
977
978 function updateDrag(data, x, y)
979 {
980 var offsetX = $(document).scrollLeft();
981 var offsetY = $(document).scrollTop();
982 var posX = x + offsetX;
983 var posY = y + offsetY;
984
985 data.config.dragBox.css({
986 left: posX - data.dragOffset.x,
987 top: Math.max(Math.min(posY - data.dragOffset.y, offsetY + $(window).height() - data.config.dragBox.height() - 2), offsetY + 2)});
988
989 var dt = data.config.dragBox.offset().top;
990 var dh = data.config.dragBox.height();
991
992 var rows = $('tbody > tr', data.target);
993 for (var i = 0; i < rows.length; i++)
994 {
995 var row = $(rows[i]);
996 var rt = row.offset().top;
997 var rh = row.height();
998 if (row[0] !== data.dragRow[0])
999 {
1000 if ((dt >= rt && dt <= rt + rh / 2) ||
1001 (dt < rt && i == 0))
1002 {
1003 data.dropAfter = false;
1004 row.before(data.dragRow);
1005 data.dropId = row[0].fasttableID;
1006 break;
1007 }
1008 if ((dt + dh >= rt + rh / 2 && dt + dh <= rt + rh) ||
1009 (dt + dh > rt + rh && i === rows.length - 1))
1010 {
1011 data.dropAfter = true;
1012 row.after(data.dragRow);
1013 data.dropId = row[0].fasttableID;
1014 break;
1015 }
1016 }
1017 }
1018
1019 if (data.dropId === null)
1020 {
1021 data.dropId = data.dragRow[0].fasttableID;
1022 data.dropAfter = true;
1023 }
1024 }
1025
1026 function autoScroll(data, x, y)
1027 {
1028 // works properly only if the table lays directly on the page (not in another scrollable div)
1029
1030 data.scrollStep = (y > $(window).height() - 20 ? 1 : y < 20 ? -1 : 0) * 5;
1031 if (data.scrollStep !== 0 && !data.scrollTimer)
1032 {
1033 var scroll = function()
1034 {
1035 $(document).scrollTop($(document).scrollTop() + data.scrollStep);
1036 updateDrag(data, x, y + data.scrollStep);
1037 data.scrollTimer = data.scrollStep == 0 ? null : setTimeout(scroll, 10);
1038 }
1039 data.scrollTimer = setTimeout(scroll, 500);
1040 }
1041 }
1042
1043 function mouseUp(data, e)
1044 {
1045 document.removeEventListener('mousemove', data.mouseMove, true);
1046 document.removeEventListener('touchmove', data.mouseMove, true);
1047 document.removeEventListener('mouseup', data.mouseUp, true);
1048 document.removeEventListener('touchend', data.mouseUp, true);
1049 document.removeEventListener('touchcancel', data.mouseUp, true);
1050 document.removeEventListener('keydown', data.keyDown, true);
1051
1052 if (!data.dragging)
1053 {
1054 return;
1055 }
1056
1057 data.dragging = false;
1058 data.cancelDrag = data.cancelDrag || e.type === 'touchcancel';
1059 data.dragRow.removeClass('drag-source');
1060 $('html').removeClass('drag-progress');
1061 $('body').css('overflow-x', data.oldOverflowX);
1062 data.config.dragBox.hide();
1063 data.scrollStep = 0;
1064 clearTimeout(data.scrollTimer);
1065 data.scrollTimer = null;
1066 moveRecords(data);
1067 }
1068
1069 function keyDown(data, e)
1070 {
1071 if (e.keyCode == 27) // ESC-key
1072 {
1073 data.cancelDrag = true;
1074 e.preventDefault();
1075 mouseUp(data, e);
1076 }
1077 }
1078
1079 function moveRecords(data)
1080 {
1081 if (data.dropId !== null && !data.cancelDrag &&
1082 !(data.moveIds.length == 1 && data.dropId == data.moveIds[0]))
1083 {
1084 data.blinkIds = data.moveIds;
1085 data.moveIds = [];
1086 data.blinkState = data.config.dragBlink === 'none' ? 0 : 3;
1087 data.wantBlink = data.blinkState > 0;
1088 moveRows(data);
1089 }
1090 else
1091 {
1092 data.dropId = null;
1093 }
1094
1095 if (data.dropId === null)
1096 {
1097 data.moveIds = [];
1098 }
1099
1100 refresh(data);
1101
1102 data.config.dragEndCallback(data.dropId !== null ?
1103 {
1104 ids: data.blinkIds,
1105 position: data.dropId,
1106 direction: data.dropAfter ? 'after' : 'before'
1107 } : null);
1108
1109 if (data.config.dragBlink === 'direct')
1110 {
1111 data.target.fasttable('update');
1112 }
1113 }
1114
1115 function moveRows(data)
1116 {
1117 var movedIds = data.blinkIds;
1118 var movedRecords = [];
1119
1120 for (var i = 0; i < data.content.length; i++)
1121 {
1122 var item = data.content[i];
1123 if (movedIds.indexOf(item.id) > -1)
1124 {
1125 movedRecords.push(item);
1126 data.content.splice(i, 1);
1127 i--;
1128
1129 if (item.id === data.dropId)
1130 {
1131 if (i >= 0)
1132 {
1133 data.dropId = data.content[i].id;
1134 data.dropAfter = true;
1135 }
1136 else if (i + 1 < data.content.length)
1137 {
1138 data.dropId = data.content[i + 1].id;
1139 data.dropAfter = false;
1140 }
1141 else
1142 {
1143 data.dropId = null;
1144 }
1145 }
1146 }
1147 }
1148
1149 if (data.dropId === null)
1150 {
1151 // restore content
1152 for (var j = 0; j < movedRecords.length; j++)
1153 {
1154 data.content.push(movedRecords[j]);
1155 }
1156 return;
1157 }
1158
1159 for (var i = 0; i < data.content.length; i++)
1160 {
1161 if (data.content[i].id === data.dropId)
1162 {
1163 for (var j = movedRecords.length - 1; j >= 0; j--)
1164 {
1165 data.content.splice(data.dropAfter ? i + 1 : i, 0, movedRecords[j]);
1166 }
1167 break;
1168 }
1169 }
1170 }
1171
1172 function blinkMovedRecords(data)
1173 {
1174 if (data.blinkIds.length > 0)
1175 {
1176 blinkProgress(data, data.wantBlink);
1177 data.wantBlink = false;
1178 }
1179 }
1180
1181 function blinkProgress(data, recur)
1182 {
1183 var rows = $('tr', data.target);
1184 rows.removeClass('drag-finish');
1185 rows.each(function(ind, el)
1186 {
1187 var id = el.fasttableID;
1188 if (data.blinkIds.indexOf(id) > -1 &&
1189 (data.blinkState === 1 || data.blinkState === 3 || data.blinkState === 5))
1190 {
1191 $(el).addClass('drag-finish');
1192 }
1193 });
1194
1195 if (recur && data.blinkState > 0)
1196 {
1197 setTimeout(function()
1198 {
1199 data.blinkState -= 1;
1200 blinkProgress(data, true);
1201 },
1202 150);
1203 }
1204
1205 if (data.blinkState === 0)
1206 {
1207 data.blinkIds = [];
1208 }
1209 }
1210
1211 //*************** KEYBOARD
1212
1213 function processShortcut(data, key)
1214 {
1215 switch (key)
1216 {
1217 case 'Left': data.curPage = Math.max(data.curPage - 1, 1); refresh(data); return true;
1218 case 'Shift+Left': data.curPage = 1; refresh(data); return true;
1219 case 'Right': data.curPage = Math.min(data.curPage + 1, data.pageCount); refresh(data); return true;
1220 case 'Shift+Right': data.curPage = data.pageCount; refresh(data); return true;
1221 case 'Shift+F': data.config.filterInput.focus(); return true;
1222 case 'Shift+C': data.config.filterClearButton.click(); return true;
1223 }
1224 }
1225
1226 //*************** CONFIG
1227
7651228 var defaults =
7661229 {
767 filterInput: '#table-filter',
768 filterClearButton: '#table-clear',
769 pagerContainer: '#table-pager',
770 infoContainer: '#table-info',
1230 filterInput: '#TableFilter',
1231 filterClearButton: '#TableClear',
1232 pagerContainer: '#TablePager',
1233 infoContainer: '#TableInfo',
1234 dragBox: '#TableDragBox',
1235 dragContent: '#TableDragContent',
1236 dragBadge: '#TableDragBadge',
1237 dragBlink: 'none', // none, direct, update
7711238 pageSize: 10,
7721239 maxPages: 5,
7731240 pageDots: true,
774 hasHeader: true,
1241 rowSelect: false,
1242 shortcuts: false,
7751243 infoEmpty: 'No records',
7761244 renderRowCallback: undefined,
7771245 renderCellCallback: undefined,
7821250 filterClearCallback: undefined,
7831251 fillSearchCallback: undefined,
7841252 filterCallback: undefined,
785 headerCheck: '#table-header-check'
1253 dragStartCallback: undefined,
1254 dragEndCallback: undefined
7861255 };
7871256
7881257 })(jQuery);
8011270 this.len = source.length;
8021271 this.p = 0;
8031272 }
804
1273
8051274 this.nextToken = function()
8061275 {
8071276 while (this.p < this.len)
9471416 var _this = this;
9481417 var text = term.toLowerCase();
9491418 var field;
950
1419
9511420 var command;
9521421 var commandIndex;
9531422 for (var i = 0; i < COMMANDS.length; i++)
9601429 command = cmd;
9611430 }
9621431 }
963
1432
9641433 if (command !== undefined)
9651434 {
9661435 field = term.substring(0, commandIndex);
9741443 eval: function() { return _this.evalTerm(this); }
9751444 };
9761445 }
977
1446
9781447 this.evalTerm = function(term) {
9791448 var text = term.text;
9801449 var field = term.field;
119119 {
120120 filterInput: '#FeedDialog_ItemTable_filter',
121121 pagerContainer: '#FeedDialog_ItemTable_pager',
122 headerCheck: '#FeedDialog_ItemTable > thead > tr:first-child',
122 rowSelect: UISettings.rowSelect,
123123 pageSize: pageSize,
124 hasHeader: true,
125124 renderCellCallback: itemsTableRenderCellCallback
126125 });
127
128 $ItemTable.on('click', UISettings.rowSelect ? 'tbody tr' : 'tbody div.check',
129 function(event) { $ItemTable.fasttable('itemCheckClick', UISettings.rowSelect ? this : this.parentNode.parentNode, event); });
130 $ItemTable.on('click', 'thead div.check',
131 function() { $ItemTable.fasttable('titleCheckClick') });
132 $ItemTable.on('mousedown', Util.disableShiftMouseDown);
133126
134127 $FeedDialog.on('hidden', function()
135128 {
154147 {
155148 Refresher.pause();
156149
157 $ItemTable.fasttable('update', []);
158
159150 enableAllButtons();
160151 $FeedDialog.restoreTab();
161152
162153 $('#FeedDialog_ItemTable_filter').val('');
163154 $('#FeedDialog_ItemTable_pagerBlock').hide();
155
156 $ItemTable.fasttable('update', []);
157 $ItemTable.fasttable('applyFilter', '');
164158
165159 items = null;
166160
464458 pagerContainer: '#FeedFilterDialog_ItemTable_pager',
465459 headerCheck: '',
466460 pageSize: pageSize,
467 hasHeader: true,
468461 renderCellCallback: itemsTableRenderCellCallback
469462 });
470463
614607 var differentFilenames = false;
615608
616609 var filter = $FilterInput.val().split('\n');
617
610
618611 var data = [];
619612
620613 for (var i=0; i < items.length; i++)
795788 $RematchIcon.toggleClass('icon-process', !autoUpdate);
796789 $RematchIcon.toggleClass('icon-process-auto', autoUpdate);
797790 }
798
791
799792 function filterKeyPress(event)
800793 {
801794 if (event.which == 37)
804797 alert('Percent character (%) cannot be part of a filter because it is used\nas line separator when saving filter into configuration file.');
805798 }
806799 }
807
800
808801 /*** SPLITTER ***/
809802
810803 function splitterMouseDown(e)
860853 windowResized();
861854 }
862855 }
863
856
864857 function windowResized()
865858 {
866859 if (!UISettings.miniTheme)
874867 splitterMove(newWidth - oldWidth);
875868 }
876869 }
877
870
878871 /*** LINE SELECTION ***/
879872
880873 this.selectRule = function(rule)
912905
913906 // Idea and portions of code from LinedTextArea plugin by Alan Williamson
914907 // http://files.aw20.net/jquery-linedtextarea/jquery-linedtextarea.html
915
908
916909 function initLines()
917910 {
918911 showLines = !UISettings.miniTheme;
922915 $FilterInput.scroll(updateLines);
923916 }
924917 }
925
918
926919 function updateLines()
927920 {
928921 if (!UISettings.miniTheme && showLines)
5757
5858 $HistoryTable.fasttable(
5959 {
60 filterInput: $('#HistoryTable_filter'),
61 filterClearButton: $("#HistoryTable_clearfilter"),
62 pagerContainer: $('#HistoryTable_pager'),
63 infoContainer: $('#HistoryTable_info'),
64 headerCheck: $('#HistoryTable > thead > tr:first-child'),
60 filterInput: '#HistoryTable_filter',
61 filterClearButton: '#HistoryTable_clearfilter',
62 pagerContainer: '#HistoryTable_pager',
63 infoContainer: '#HistoryTable_info',
64 rowSelect: UISettings.rowSelect,
6565 pageSize: recordsPerPage,
6666 maxPages: UISettings.miniTheme ? 1 : 5,
6767 pageDots: !UISettings.miniTheme,
68 shortcuts: true,
6869 fillFieldsCallback: fillFieldsCallback,
6970 filterCallback: filterCallback,
7071 renderCellCallback: renderCellCallback,
7273 });
7374
7475 $HistoryTable.on('click', 'a', editClick);
75 $HistoryTable.on('click', UISettings.rowSelect ? 'tbody tr' : 'tbody div.check',
76 function(event) { $HistoryTable.fasttable('itemCheckClick', UISettings.rowSelect ? this : this.parentNode.parentNode, event); });
77 $HistoryTable.on('click', 'thead div.check',
78 function() { $HistoryTable.fasttable('titleCheckClick') });
79 $HistoryTable.on('mousedown', Util.disableShiftMouseDown);
8076 }
8177
8278 this.applyTheme = function()
188184 var status = HistoryUI.buildStatus(hist);
189185
190186 var name = '<a href="#" histid="' + hist.ID + '">' + Util.textToHtml(Util.formatNZBName(hist.Name)) + '</a>';
187 name += DownloadsUI.buildEncryptedLabel(hist.Kind === 'NZB' ? hist.Parameters : []);
188
191189 var dupe = DownloadsUI.buildDupe(hist.DupeKey, hist.DupeScore, hist.DupeMode);
192190 var category = '';
193191
263261 return;
264262 }
265263
264 var pageCheckedCount = $HistoryTable.fasttable('pageCheckedCount');
265 var checkedPercentage = Util.round0(checkedCount / history.length * 100);
266
266267 var hasNzb = false;
267268 var hasUrl = false;
268269 var hasDup = false;
284285 {
285286 case 'DELETE':
286287 notification = '#Notif_History_Deleted';
287 HistoryUI.deleteConfirm(historyAction, hasNzb, hasDup, hasFailed, true, checkedCount);
288 HistoryUI.deleteConfirm(historyAction, hasNzb, hasDup, hasFailed, true, checkedCount, pageCheckedCount, checkedPercentage);
288289 break;
289290
290291 case 'REPROCESS':
320321 }
321322 notification = '#Notif_History_Marked';
322323
323 ConfirmDialog.showModal(action === 'MARKSUCCESS' ? 'HistoryEditSuccessConfirmDialog' :
324 ConfirmDialog.showModal(action === 'MARKSUCCESS' ? 'HistoryEditSuccessConfirmDialog' :
324325 action === 'MARKGOOD' ? 'HistoryEditGoodConfirmDialog' : 'HistoryEditBadConfirmDialog',
325326 function () // action
326327 {
346347 for (var id in checkedRows)
347348 {
348349 ids.push(parseInt(id));
349 }
350
351 RPC.call('editqueue', [command, 0, '', ids], function()
350 }
351
352 RPC.call('editqueue', [command, '', ids], function()
352353 {
353354 editCompleted();
354355 });
446447 $('#History_Dup').toggleClass('btn-inverse', showDup);
447448 $('#History_DupIcon').toggleClass('icon-mask', !showDup).toggleClass('icon-mask-white', showDup);
448449 Refresher.update();
450 }
451
452 this.processShortcut = function(key)
453 {
454 switch (key)
455 {
456 case 'D': case 'Delete': case 'Meta+Backspace': History.actionClick('DELETE'); return true;
457 case 'P': History.actionClick('REPROCESS'); return true;
458 case 'N': History.actionClick('REDOWNLOAD'); return true;
459 case 'M': History.actionClick('MARKSUCCESS'); return true;
460 case 'G': History.actionClick('MARKGOOD'); return true;
461 case 'B': History.actionClick('MARKBAD'); return true;
462 case 'A': History.filter('ALL'); return true;
463 case 'S': History.filter('SUCCESS'); return true;
464 case 'F': History.filter('FAILURE'); return true;
465 case 'L': History.filter('DELETED'); return true;
466 case 'U': History.filter('DUPE'); return true;
467 case 'H': History.dupClick(); return true;
468 }
469 return $HistoryTable.fasttable('processShortcut', key);
449470 }
450471
451472 }(jQuery));
493514 }
494515 return '<span class="label label-status ' + badgeClass + '">' + statusText + '</span>';
495516 }
496
497 this.deleteConfirm = function(actionCallback, hasNzb, hasDup, hasFailed, multi, selCount)
517
518 this.deleteConfirm = function(actionCallback, hasNzb, hasDup, hasFailed, multi, selCount, pageSelCount, selPercentage)
498519 {
499520 var dupeCheck = Options.option('DupeCheck') === 'yes';
500521 var dialog = null;
516537 {
517538 var hide = $('#HistoryDeleteConfirmDialog_Hide', dialog).is(':checked');
518539 var command = hasNzb && hide ? 'HistoryDelete' : 'HistoryFinalDelete';
519 actionCallback(command);
540 if (selCount - pageSelCount > 0 && selCount >= 50)
541 {
542 PurgeHistoryDialog.showModal(function(){actionCallback(command);}, selCount, selPercentage);
543 }
544 else
545 {
546 actionCallback(command);
547 }
520548 }
521549
522550 ConfirmDialog.showModal('HistoryDeleteConfirmDialog', action, init, selCount);
523551 }
524
552
525553 this.confirmMulti = function(multi)
526554 {
527555 if (multi === undefined || !multi)
533561 }
534562 }
535563 }(jQuery));
564
565 /*** PURGE HISTORY DIALOG *****************************************************/
566
567 var PurgeHistoryDialog = (new function($)
568 {
569 'use strict';
570
571 // Controls
572 var $PurgeHistoryDialog;
573
574 // State
575 var actionCallback;
576
577 this.init = function()
578 {
579 $PurgeHistoryDialog = $('#PurgeHistoryDialog');
580 }
581
582 this.showModal = function(_actionCallback, count, percentage)
583 {
584 actionCallback = _actionCallback;
585 $('#PurgeHistoryDialog_count,#PurgeHistoryDialog_count2').text(count);
586 $('#PurgeHistoryDialog_percentage').text(percentage);
587 Util.centerDialog($PurgeHistoryDialog, true);
588 $PurgeHistoryDialog.modal({backdrop: 'static'});
589 }
590
591 this.delete = function(event)
592 {
593 event.preventDefault(); // avoid scrolling
594 $PurgeHistoryDialog.modal('hide');
595 actionCallback();
596 }
597 }(jQuery));
Binary diff not shown
Binary diff not shown
7979 <div id="PlayBlock">
8080 <div id="PlayPauseBg"><div class="img-download-bg"></div></div>
8181 <div id="PlayButton" class="img-download-orange" style="display:none;">
82 <div class="PlayBlockInner" onclick="Status.playClick()" title="Download paused (click to resume)">
82 <div id="PlayPauseButton" class="PlayBlockInner" onclick="Status.playClick()" title="Download paused (click to resume) [Shift+P]">
8383 <div class="img-download-btn"></div>
8484 </div>
8585 </div>
8686 <div id="PauseButton" class="img-download-green">
87 <div class="PlayBlockInner" onclick="Status.playClick()" title="Download active (click to pause)">
87 <div class="PlayBlockInner" onclick="Status.playClick()" title="Download active (click to pause) [Shift+P]">
8888 <div class="img-download-btn"></div>
8989 </div>
9090 </div>
100100 <li class="menu-header">Pause for</li>
101101 <li><a href="#" onclick="Status.scheduledPauseClick(30*60)"><table><tr><td></td><td>30 Minutes</td></tr></table></a></li>
102102 <li><a href="#" onclick="Status.scheduledPauseClick(3*60*60)"><table><tr><td></td><td>3 Hours</td></tr></table></a></li>
103 <li><a href="#" onclick="Status.scheduledPauseDialogClick()"><table><tr><td></td><td>Custom...</td></tr></table></a></li>
103 <li><a href="#" onclick="Status.scheduledPauseDialogClick()" id="ScheduledPauseButton" title="Temporary pause for... [Shift+T]"><table><tr><td></td><td>Custom...</td></tr></table></a></li>
104104 <li class="divider"></li>
105105 <li><a data-toggle="modal" href="#PauseHelp"><table><tr><td></td><td>Quick Help</td></tr></table></a></li>
106106 </ul>
109109
110110 <!-- SPEED/TIME -->
111111 <div id="InfoBlock">
112 <div href="#" onclick="LimitDialog.clicked(event)" title="Current speed (click to set speed limit)"><i class="icon-plane" id="StatusSpeedIcon"></i> <span id="StatusSpeed">--- KB/s</span></div>
113 <div href="#" onclick="Status.statDialogClick()" title="Remaining time (click for more statistics)"><i class="icon-time" id="StatusTimeIcon"></i> <span id="StatusTime">--h --m</span></div>
112 <div href="#" onclick="LimitDialog.clicked(event)" title="Current speed (click to set speed limit) [Shift+L]"><i class="icon-plane" id="StatusSpeedIcon"></i> <span id="StatusSpeed">--- KB/s</span></div>
113 <div href="#" onclick="Status.statDialogClick()" title="Remaining time (click for more statistics) [Shift+A]"><i class="icon-time" id="StatusTimeIcon"></i> <span id="StatusTime">--h --m</span></div>
114114 </div>
115115
116116 <!-- REFRESH MENU -->
117117 <div id="RefreshBlockDesktop" class="pull-right phone-hide">
118118 <div class="btn-toolbar" id="RefreshBlock">
119119 <div class="btn-group">
120 <button class="btn btn-inverse" id="RefreshButton"><i class="icon-refresh" id="RefreshAnimation"></i></button>
120 <button class="btn btn-inverse" id="RefreshButton" title="Refresh [Shift+R]"><i class="icon-refresh" id="RefreshAnimation"></i></button>
121121 <button class="btn btn-inverse dropdown-toggle" data-toggle="dropdown"><span class="caret"></span></button>
122122 <ul class="dropdown-menu menu-refresh" id="RefreshMenu">
123123 <li class="menu-header">Automatic refresh</li>
152152 </ul>
153153 </div>
154154
155 <input id="DownloadsTable_filter" class="search-query" type="text" placeholder="Search downloads">
156 <i id="DownloadsTable_clearfilter" class="icon-remove-white search-clear" title="Clear"></i>
157
158 <input id="HistoryTable_filter" class="search-query" type="text" placeholder="Search history">
159 <i id="HistoryTable_clearfilter" class="icon-remove-white search-clear" title="Clear"></i>
160
161 <input id="MessagesTable_filter" class="search-query" type="text" placeholder="Search messages">
162 <i id="MessagesTable_clearfilter" class="icon-remove-white search-clear" title="Clear"></i>
163
164 <input id="ConfigTable_filter" class="search-query" type="text" placeholder="Search settings">
165 <i id="ConfigTable_clearfilter" class="icon-remove-white search-clear" title="Clear"></i>
155 <input id="DownloadsTable_filter" class="search-query" type="text" placeholder="Search downloads" title="Search downloads [Shift+F]">
156 <i id="DownloadsTable_clearfilter" class="icon-remove-white search-clear" title="Clear [Shift+C]"></i>
157
158 <input id="HistoryTable_filter" class="search-query" type="text" placeholder="Search history" title="Search history [Shift+F]">
159 <i id="HistoryTable_clearfilter" class="icon-remove-white search-clear" title="Clear [Shift+C]"></i>
160
161 <input id="MessagesTable_filter" class="search-query" type="text" placeholder="Search messages" title="Search messages [Shift+F]">
162 <i id="MessagesTable_clearfilter" class="icon-remove-white search-clear" title="Clear [Shift+C]"></i>
163
164 <input id="ConfigTable_filter" class="search-query" type="text" placeholder="Search settings" title="Search settings [Shift+F]">
165 <i id="ConfigTable_clearfilter" class="icon-remove-white search-clear" title="Clear [Shift+C]"></i>
166166 </div>
167167 </div>
168168
169169 <!-- TABS -->
170170 <div class="nav" id="NavLinks">
171171 <ul class="nav">
172 <li class="active"><a data-toggle="tab" href="#DownloadsTab" id="DownloadsTabLink"><span class="phone-hide">Downloads </span><i class="icon-downloads-white phone-only inline"></i><br class="phone-only"><span class="badge" id="DownloadsTabBadge" style="display: none;"></span><span class="badge badge-empty" id="DownloadsTabBadgeEmpty">&nbsp;</span></a></li>
173 <li><a data-toggle="tab" href="#HistoryTab" id="HistoryTabLink"><span class="phone-hide">History </span><i class="icon-history-white phone-only inline"></i><br class="phone-only"><span class="badge" id="HistoryTabBadge" style="display: none;"></span><span class="badge badge-empty" id="HistoryTabBadgeEmpty">&nbsp;</span></a></li>
174 <li><a data-toggle="tab" href="#MessagesTab" id="MessagesTabLink"><span class="phone-hide">Messages </span><i class="icon-messages-white phone-only inline"></i><br class="phone-only"><span class="badge" id="MessagesTabBadge" style="display: none;"></span><span class="badge badge-empty" id="MessagesTabBadgeEmpty">&nbsp;</span></a></li>
175 <li><a data-toggle="tab" href="#ConfigTab" id="ConfigTabLink"><span class="phone-hide">Settings </span><i class="icon-settings-white phone-only inline"></i><br class="phone-only"><span class="badge" id="ConfigTabBadge" style="display: none;"></span><span class="phone-only"><span class="badge badge-empty" id="ConfigTabBadgeEmpty">&nbsp;</span></span></a></li>
172 <li class="active"><a data-toggle="tab" href="#DownloadsTab" id="DownloadsTabLink"><span class="phone-hide" title="Downloads [Shift+D]">Downloads </span><i class="icon-downloads-white phone-only inline"></i><br class="phone-only"><span class="badge" id="DownloadsTabBadge" style="display: none;"></span><span class="badge badge-empty" id="DownloadsTabBadgeEmpty">&nbsp;</span></a></li>
173 <li><a data-toggle="tab" href="#HistoryTab" id="HistoryTabLink"><span class="phone-hide" title="History [Shift+H]">History </span><i class="icon-history-white phone-only inline"></i><br class="phone-only"><span class="badge" id="HistoryTabBadge" style="display: none;"></span><span class="badge badge-empty" id="HistoryTabBadgeEmpty">&nbsp;</span></a></li>
174 <li><a data-toggle="tab" href="#MessagesTab" id="MessagesTabLink"><span class="phone-hide" title="Messages [Shift+M]">Messages </span><i class="icon-messages-white phone-only inline"></i><br class="phone-only"><span class="badge" id="MessagesTabBadge" style="display: none;"></span><span class="badge badge-empty" id="MessagesTabBadgeEmpty">&nbsp;</span></a></li>
175 <li><a data-toggle="tab" href="#ConfigTab" id="ConfigTabLink"><span class="phone-hide" title="Settings [Shift+S]">Settings </span><i class="icon-settings-white phone-only inline"></i><br class="phone-only"><span class="badge" id="ConfigTabBadge" style="display: none;"></span><span class="phone-only"><span class="badge badge-empty" id="ConfigTabBadgeEmpty">&nbsp;</span></span></a></li>
176176 </ul>
177177 </div>
178178
227227
228228 <!-- ADD BUTTON -->
229229 <div class="btn-group" id="TBAddButton">
230 <button class="btn" onclick="Upload.addClick()" title="Add nzb-files"><i class="icon-plus"></i><span class="btn-caption"> Add</span></button>
230 <button class="btn" onclick="Upload.addClick()" title="Add nzb-files [A]"><i class="icon-plus"></i><span class="btn-caption"> Add</span></button>
231231 </div>
232232
233233 <!-- NZB-ACTIONS BUTTONS -->
234234 <div class="btn-group phone-hide">
235 <button class="btn" onclick="Downloads.editClick()" title="Edit selected nzb-files"><i class="icon-edit"></i><span class="btn-caption"> Edit</span></button>
236 <button class="btn" onclick="Downloads.mergeClick()" title="Merge selected nzb-files"><i class="icon-merge"></i><span class="btn-caption"> Merge</span></button>
237 <button class="btn" onclick="Downloads.pauseClick()" title="Pause selected nzb-files"><i class="icon-pause"></i><span class="btn-caption"> Pause</span></button>
238 <button class="btn" onclick="Downloads.resumeClick()" title="Resume selected nzb-files"><i class="icon-play"></i><span class="btn-caption"> Resume</span></button>
239 <button class="btn" onclick="Downloads.deleteClick()" title="Delete selected nzb-files"><i class="icon-trash"></i><span class="btn-caption"> Delete</span></button>
235 <button class="btn" onclick="Downloads.editClick()" title="Edit selected nzb-files [Enter]"><i class="icon-edit"></i><span class="btn-caption"> Edit</span></button>
236 <button class="btn" onclick="Downloads.mergeClick()" title="Merge selected nzb-files [M]"><i class="icon-merge"></i><span class="btn-caption"> Merge</span></button>
237 <button class="btn" onclick="Downloads.pauseClick()" title="Pause selected nzb-files [P]"><i class="icon-pause"></i><span class="btn-caption"> Pause</span></button>
238 <button class="btn" onclick="Downloads.resumeClick()" title="Resume selected nzb-files [R]"><i class="icon-play"></i><span class="btn-caption"> Resume</span></button>
239 <button class="btn" onclick="Downloads.deleteClick()" title="Delete selected nzb-files [D]"><i class="icon-trash"></i><span class="btn-caption"> Delete</span></button>
240240 </div>
241241 <div class="btn-group phone-only inline"><button class="btn" onclick="Downloads.editClick()" title="Edit selected nzb-files"><i class="icon-edit"></i><span class="btn-caption"> Edit</span></button></div>
242242 <div class="btn-group phone-only inline"><button class="btn" onclick="Downloads.mergeClick()" title="Merge selected nzb-files"><i class="icon-merge"></i><span class="btn-caption"> Merge</span></button></div>
246246
247247 <!-- MOVE BUTTONS -->
248248 <div class="btn-group phone-hide">
249 <button class="btn" onclick="Downloads.moveClick('top')" title="Move selected nzb-files to the top"><i class="icon-top"></i></button>
250 <button class="btn" onclick="Downloads.moveClick('up')" title="Move selected nzb-files up"><i class="icon-up"></i></button>
251 <button class="btn" onclick="Downloads.moveClick('down')" title="Move selected nzb-files down"><i class="icon-down"></i></button>
252 <button class="btn" onclick="Downloads.moveClick('bottom')" title="Move selected nzb-files to the bottom"><i class="icon-bottom"></i></button>
249 <button class="btn" onclick="Downloads.moveClick('top')" title="Move selected nzb-files to the top [T]"><i class="icon-top"></i></button>
250 <button class="btn" onclick="Downloads.moveClick('up')" title="Move selected nzb-files up [U]"><i class="icon-up"></i></button>
251 <button class="btn" onclick="Downloads.moveClick('down')" title="Move selected nzb-files down [N]"><i class="icon-down"></i></button>
252 <button class="btn" onclick="Downloads.moveClick('bottom')" title="Move selected nzb-files to the bottom [B]"><i class="icon-bottom"></i></button>
253253 </div>
254254 <div class="btn-group phone-only inline"><button class="btn" onclick="Downloads.moveClick('top')" title="Move selected nzb-files to the top"><i class="icon-top"></i></button></div>
255255 <div class="btn-group phone-only inline"><button class="btn" onclick="Downloads.moveClick('up')" title="Move selected nzb-files up"><i class="icon-up"></i></button></div>
289289 <div class="pagination pull-right"><ul id="DownloadsTable_pager"></ul></div>
290290 </div>
291291
292 <table class="table table-striped table-bordered table-check table-cancheck datatable" id="DownloadsTable">
292 <table class="table table-striped table-bordered table-check table-cancheck table-drag datatable" id="DownloadsTable">
293293 <thead>
294294 <tr>
295295 <th><div class="check img-check"></div></th>
296 <th width="14px" class="text-center"><a href="#" onclick="Downloads.sort('priority', event)" title="Priority">P</a></th>
296297 <th width="95px">Status</th>
297 <th id="DownloadsTable_Name"><a href="#" onclick="Downloads.sort('name')">Name</a></th>
298 <th id="DownloadsTable_Category"><a href="#" onclick="Downloads.sort('category')">Category</a></th>
299 <th width="30px" class="text-right"><a href="#" onclick="Downloads.sort('age')">Age</a></th>
300 <th width="120px"><div style="float:left; position:relative; width:50%; text-align:left;"><a href="#"onclick="Downloads.sort('size')">Size</a></div><div style="float:right; position:relative; width:50%; text-align:right;"><a href="#" onclick="Downloads.sort('left')">Left</a></div></th>
301 <th width="58px" class="text-right">Est. Time</th>
298 <th id="DownloadsTable_Name"><a href="#" onclick="Downloads.sort('name', event)">Name</a></th>
299 <th id="DownloadsTable_Category"><a href="#" onclick="Downloads.sort('category', event)">Category</a></th>
300 <th width="30px" class="text-right"><a href="#" onclick="Downloads.sort('age', event)">Age</a></th>
301 <th width="120px"><div style="float:left; position:relative; width:50%; text-align:left;"><a href="#"onclick="Downloads.sort('size', event)">Size</a></div><div style="float:right; position:relative; width:50%; text-align:right;"><a href="#" onclick="Downloads.sort('left', event)">Left</a></div></th>
302 <th width="60px" class="text-right">Est. Time</th>
303 </tr>
304 <tr>
305 <th colspan="8" class="table-selector">10 records selected on other pages</th>
302306 </tr>
303307 </thead>
304308 <tbody></tbody>
324328
325329 <div class="btn-toolbar form-inline section-toolbar" id ="HistoryTab_Toolbar">
326330 <div class="btn-group">
327 <button class="btn" onclick="History.actionClick('DELETE')" title="Delete selected records"><i class="icon-trash"></i><span class="btn-caption"> Delete</span></button>
331 <button class="btn" onclick="History.actionClick('DELETE')" title="Delete selected records [D]"><i class="icon-trash"></i><span class="btn-caption"> Delete</span></button>
328332 <button class="btn dropdown-toggle" data-toggle="dropdown" title="More actions"><span class="btn-caption"> More </span><span class="phone-only inline">&nbsp;</span><span class="caret"></span></button>
329333 <ul class="dropdown-menu">
330334 <li class="menu-header">Actions</li>
331 <li><a href="#" id="HistoryTab_Reprocess" onclick="History.actionClick('REPROCESS')"><i class="icon-process"></i> Post-Process Again</a></li>
332 <li><a href="#" id="HistoryTab_Redownload" onclick="History.actionClick('REDOWNLOAD')"><i class="icon-downloads"></i> Download Again</a></li>
333 <li><a href="#" id="HistoryTab_MarkSuccess" onclick="History.actionClick('MARKSUCCESS')"><i class="icon-ok"></i> Mark as Success</a></li>
334 <li><a href="#" id="HistoryTab_MarkGood" onclick="History.actionClick('MARKGOOD')"><i class="icon-plus"></i> Mark as Good</a></li>
335 <li><a href="#" id="HistoryTab_MarkBad" onclick="History.actionClick('MARKBAD')"><i class="icon-minus"></i> Mark as Bad</a></li>
335 <li><a href="#" id="HistoryTab_Reprocess" onclick="History.actionClick('REPROCESS')" title="Post-process selected records again [P]"><i class="icon-process"></i> Post-Process Again</a></li>
336 <li><a href="#" id="HistoryTab_Redownload" onclick="History.actionClick('REDOWNLOAD')" title="Download selected records again [N]"><i class="icon-downloads"></i> Download Again</a></li>
337 <li><a href="#" id="HistoryTab_MarkSuccess" onclick="History.actionClick('MARKSUCCESS')" title="Mark selected records as success [M]"><i class="icon-ok"></i> Mark as Success</a></li>
338 <li><a href="#" id="HistoryTab_MarkGood" onclick="History.actionClick('MARKGOOD')" title=" Mark selected records as good [G]"><i class="icon-plus"></i> Mark as Good</a></li>
339 <li><a href="#" id="HistoryTab_MarkBad" onclick="History.actionClick('MARKBAD')" title="Mark selected records as bad [B]"><i class="icon-minus"></i> Mark as Bad</a></li>
336340 </ul>
337341 </div>
338
342
339343 <!-- STATUS FILTER BUTTONS -->
340344 <div class="btn-group phone-hide">
341 <button class="btn history-filter btn-inverse" onclick="History.filter('ALL')" title="Show All">All &nbsp;<span class="badge badge-active" id="History_Badge_ALL">?</span></button>
342 <button class="btn history-filter" onclick="History.filter('SUCCESS')" title="Show only Success">Success &nbsp;<span class="badge" id="History_Badge_SUCCESS">?</span></button>
343 <button class="btn history-filter" onclick="History.filter('FAILURE')" title="Show only Failure">Failure &nbsp;<span class="badge" id="History_Badge_FAILURE">?</span></button>
344 <button class="btn history-filter" onclick="History.filter('DELETED')" title="Show only Warning">Deleted &nbsp;<span class="badge" id="History_Badge_DELETED">?</span></button>
345 <button class="btn history-filter" onclick="History.filter('DUPE')" title="Show only Dupe">Dupe &nbsp;<span class="badge" id="History_Badge_DUPE">?</span></button>
345 <button class="btn history-filter btn-inverse" onclick="History.filter('ALL')" title="Show All [A]">All &nbsp;<span class="badge badge-active" id="History_Badge_ALL">?</span></button>
346 <button class="btn history-filter" onclick="History.filter('SUCCESS')" title="Show only Success [S]">Success &nbsp;<span class="badge" id="History_Badge_SUCCESS">?</span></button>
347 <button class="btn history-filter" onclick="History.filter('FAILURE')" title="Show only Failure [F]">Failure &nbsp;<span class="badge" id="History_Badge_FAILURE">?</span></button>
348 <button class="btn history-filter" onclick="History.filter('DELETED')" title="Show only Deleted [L]">Deleted &nbsp;<span class="badge" id="History_Badge_DELETED">?</span></button>
349 <button class="btn history-filter" onclick="History.filter('DUPE')" title="Show only Dupe [U]">Dupe &nbsp;<span class="badge" id="History_Badge_DUPE">?</span></button>
346350 </div>
347351 <div class="btn-group phone-only inline"><button class="btn history-filter btn-inverse" onclick="History.filter('ALL')" title="Show All">A &nbsp;<span class="badge badge-active" id="History_Badge_ALL2">?</span></button></div>
348352 <div class="btn-group phone-only inline"><button class="btn history-filter" onclick="History.filter('SUCCESS')" title="Show only Success">S &nbsp;<span class="badge" id="History_Badge_SUCCESS2">?</span></button></div>
349353 <div class="btn-group phone-only inline"><button class="btn history-filter" onclick="History.filter('FAILURE')" title="Show only Failure">F &nbsp;<span class="badge" id="History_Badge_FAILURE2">?</span></button></div>
350354 <div class="btn-group phone-only inline"><button class="btn history-filter" onclick="History.filter('DELETED')" title="Show only Warning">D &nbsp;<span class="badge" id="History_Badge_DELETED2">?</span></button></div>
351355 <div class="btn-group phone-only inline"><button class="btn history-filter" onclick="History.filter('DUPE')" title="Show only Dupe">U &nbsp;<span class="badge" id="History_Badge_DUPE2">?</span></button></div>
352
356
353357 <div class="btn-group">
354 <button class="btn" id="History_Dup" onclick="History.dupClick()" title="Show hidden records kept for duplicate check"><i class="icon-mask" id="History_DupIcon"></i><span class="btn-caption"> Hidden</span></button>
358 <button class="btn" id="History_Dup" onclick="History.dupClick()" title="Show hidden records kept for duplicate check [H]"><i class="icon-mask" id="History_DupIcon"></i><span class="btn-caption"> Hidden</span></button>
355359 </div>
356360
357361 <div class="btn-group phone-only inline" id="HistoryRecordsPerPageBlockPhone">
378382 </div>
379383
380384 <table class="table table-striped table-bordered table-check table-cancheck datatable" id="HistoryTable">
381 <thead><tr>
382 <th><div class="check img-check"></div></th>
383 <th width="75px">Status</th>
384 <th width="170px" class="text-center">Time</th>
385 <th>Name</th>
386 <th id="HistoryTable_Category">Category</th>
387 <th width="30px" class="text-right">Age</th>
388 <th width="70px" class="text-right">Size</th>
389 </tr></thead>
385 <thead>
386 <tr>
387 <th><div class="check img-check"></div></th>
388 <th width="75px">Status</th>
389 <th width="170px" class="text-center">Time</th>
390 <th>Name</th>
391 <th id="HistoryTable_Category">Category</th>
392 <th width="30px" class="text-right">Age</th>
393 <th width="70px" class="text-right">Size</th>
394 </tr>
395 <tr>
396 <th colspan="7" class="table-selector">10 records selected on other pages</th>
397 </tr>
398 </thead>
390399 <tbody></tbody>
391400 </table>
392401
407416
408417 <!-- EDIT BUTTONS -->
409418 <div class="btn-group">
410 <button class="btn" onclick="Messages.clearClick()" title="Delete all messages"><i class="icon-trash"></i> <span class="btn-caption">Clear</span></button>
419 <button class="btn" onclick="Messages.clearClick()" title="Delete all messages [D]"><i class="icon-trash"></i> <span class="btn-caption">Clear</span></button>
411420 </div>
412421
413422 <!-- KIND FILTER BUTTONS -->
414423 <div class="btn-group phone-hide">
415 <button class="btn btn-inverse" onclick="Messages.filter('ALL')" title="Show All">All &nbsp;<span class="badge badge-active" id="Messages_Badge_ALL">?</span></button>
416 <button class="btn" onclick="Messages.filter('DETAIL')" title="Show only Detail">Detail &nbsp;<span class="badge" id="Messages_Badge_DETAIL">?</span></button>
417 <button class="btn" onclick="Messages.filter('INFO')" title="Show only Info">Info &nbsp;<span class="badge" id="Messages_Badge_INFO">?</span></button>
418 <button class="btn" onclick="Messages.filter('WARNING')" title="Show only Warning">Warning &nbsp;<span class="badge" id="Messages_Badge_WARNING">?</span></button>
419 <button class="btn" onclick="Messages.filter('ERROR')" title="Show only Error">Error &nbsp;<span class="badge" id="Messages_Badge_ERROR">?</span></button>
424 <button class="btn btn-inverse" onclick="Messages.filter('ALL')" title="Show All [A]">All &nbsp;<span class="badge badge-active" id="Messages_Badge_ALL">?</span></button>
425 <button class="btn" onclick="Messages.filter('DETAIL')" title="Show only Detail [T]">Detail &nbsp;<span class="badge" id="Messages_Badge_DETAIL">?</span></button>
426 <button class="btn" onclick="Messages.filter('INFO')" title="Show only Info [I]">Info &nbsp;<span class="badge" id="Messages_Badge_INFO">?</span></button>
427 <button class="btn" onclick="Messages.filter('WARNING')" title="Show only Warning [W]">Warning &nbsp;<span class="badge" id="Messages_Badge_WARNING">?</span></button>
428 <button class="btn" onclick="Messages.filter('ERROR')" title="Show only Error [E]">Error &nbsp;<span class="badge" id="Messages_Badge_ERROR">?</span></button>
420429 </div>
421430 <div class="btn-group phone-only inline"><button class="btn btn-inverse" onclick="Messages.filter('ALL')" title="Show All">A &nbsp;<span class="badge badge-active" id="Messages_Badge_ALL2">?</span></button></div>
422431 <div class="btn-group phone-only inline"><button class="btn" onclick="Messages.filter('DETAIL')" title="Show only Detail">D &nbsp;<span class="badge" id="Messages_Badge_DETAIL2">?</span></button></div>
545554
546555 <h4>Extension scripts settings</h4>
547556 <p>
548 When NZBGet finishes download of a nzb-file it can execute post-processing scripts for further processing (e-mail notification, etc.).
557 When NZBGet finishes download of a nzb-file it can execute post-processing scripts for further processing (e-mail notification, etc.).
549558 To configure scripts use options in section <em><strong>EXTENSION SCRIPTS</strong></em>.
550559 </p>
551560 <p>
767776 <li class="volume-menu-template hide"><a href="#"></a></li>
768777 </ul>
769778 </div>
770
779
771780 <div class="btn-group phone-only inline"><button class="btn volume-range btn-inverse" id="StatDialog_Volume_MIN2" onclick="StatDialog.chooseRange('MIN')" title="Show last 60 seconds">60 s</button></div>
772781 <div class="btn-group phone-only inline"><button class="btn volume-range" id="StatDialog_Volume_HOUR2" onclick="StatDialog.chooseRange('HOUR')" title="Show last 60 minutes">60 m</button></div>
773782 <div class="btn-group phone-only inline"><button class="btn volume-range" id="StatDialog_Volume_DAY2" onclick="StatDialog.chooseRange('DAY')" title="Show last 24 hours">24 h</button></div>
776785 <div class="btn-group phone-only inline" id="StatDialog_MonthBlockPhone">
777786 <button class="btn volume-range dropdown-toggle" id="StatDialog_Volume_MONTH4" data-toggle="dropdown"><span class="caret"></span></button>
778787 </div>
779
788
780789 <div class="btn-group">
781790 <button class="btn dropdown-toggle" id="StatDialog_Server" data-toggle="dropdown" title="News server"> <span id="StatDialog_ServerCap">All news servers</span>&nbsp;<span class="caret"></span></button>
782791 <ul class="dropdown-menu menu-check" id="StatDialog_ServerMenu">
787796 </ul>
788797 </div>
789798 </div>
790
799
791800 <div id="StatDialog_Tooltip">Total</div>
792801 <div id="StatDialog_ChartBlock"></div>
793
802
794803 <hr>
795804 <div id="StatDialog_CountersBlock">
796805 <div class="row-fluid" id="StatDialog_Counters">
891900 <form>
892901 <fieldset>
893902 <div class="control-group">
894 <label class="control-label" for="PauseForInputGroup">Pause for</label>
903 <label class="control-label" for="PauseForInputGroup">Pause</label>
895904 <div class="controls" id="PauseForInputGroup">
896 <div class="input-append">
897 <input class="input-mini" id="PauseForInput" size="16" type="text"><span class="add-on">Minutes</span>
898 </div>
899 </div>
900 </div>
905 <input class="input-mini" id="PauseForInput" size="16" type="text">
906 </div>
907 </div>
908 <p id="PauseForPreview" class="text-center invisible">
909 Will remain paused until <strong></strong>
910 </p>
911 <p class="alert alert-info">
912 For example "30m", "2h" or "=18:00". See "Quick Help" for details.
913 </p>
901914 </fieldset>
902915 </form>
903916 </div>
904917 </div>
905918 <div class="modal-footer">
919 <div class="btn-group pull-left">
920 <a data-toggle="modal" href="#ScheduledPauseDialogHelp" class="btn pull-left">Quick Help</a>
921 </div>
922
906923 <div class="dialog-transmit hide" style="position:absolute;margin-left:260px;" id="ScheduledPause_Transmit"><img src="img/transmit.gif"></div>
907924 <a href="#" class="btn" data-dismiss="modal">Cancel</a>
908925 <a href="#" class="btn btn-primary" onclick="Status.pauseForClick()">Pause</a>
949966 <p>With command <strong>Pause download</strong> you can manually pause the download queue.</p>
950967 <p>The pausing of <strong>post-processing</strong> depends on how the post-processing-script handles screen output and may not always work.</p>
951968 <p>The <strong>Pause/Resume Button</strong> (the round one) pauses or resumes all three activities: download, post-processing and nzb directory scan.</p>
969 </div>
970 </div>
971
972 <!-- *** QUICK HELP: EXPLAINING SCHEDULED PAUSE FORMATS ************************************************************ -->
973
974 <div class="modal no-footer hide" id="ScheduledPauseDialogHelp">
975 <div class="modal-header">
976 <a class="close" href="#" data-dismiss="modal"><i class="icon-close"></i></a>
977 <h3>Help: Explaining temporary pause time formats</h3>
978 </div>
979 <div class="modal-body">
980 <p>Below you'll find the accepted formats</p>
981 <table class="table table table-condensed table-striped">
982 <thead>
983 <tr>
984 <th>Format</th>
985 <th>Example</th>
986 <th>Description</th>
987 </tr>
988 </thead>
989 <tbody>
990 <tr>
991 <td>##(m)</td>
992 <td>30m</td>
993 <td>
994 Pause for 30 minutes <br />
995 <em>"m" is optional</em>
996 </td>
997 </tr>
998 <tr>
999 <td>##h</td>
1000 <td>3h</td>
1001 <td>Pause for 3 hours</td>
1002 </tr>
1003 <tr>
1004 <td>##:##</td>
1005 <td>1:30</td>
1006 <td>Pause for 1 hour and 30 min</td>
1007 </tr>
1008 <tr>
1009 <td>=##:##</td>
1010 <td>=16:30</td>
1011 <td>Pause until 16:30 (4:30PM)</td>
1012 </tr>
1013 <tr>
1014 <td>=##</td>
1015 <td>=16</td>
1016 <td>Pause until 16:00 (4:00PM)</td>
1017 </tr>
1018 <tr>
1019 <td>=##:##AM|PM</td>
1020 <td>=4:30PM</td>
1021 <td>Pause until 4:30PM (16:30)</td>
1022 </tr>
1023 <tr>
1024 <td>=##AM|PM</td>
1025 <td>=4PM</td>
1026 <td>Pause until 4:00PM (16:00)</td>
1027 </tr>
1028 </tbody>
1029 </table>
9521030 </div>
9531031 </div>
9541032
20432121 </p>
20442122
20452123 <p id="HistoryDeleteConfirmDialog_DeleteWillCleanup" class="confirm-help-block">
2046 Selected records will be deleted from history. For failed downloads
2124 Selected records will be deleted from history. For failed downloads
20472125 all downloaded files will be deleted.
20482126 </p>
20492127
21772255 </p>
21782256 </div>
21792257 <div id="StatDialogResetConfirmDialog_OK">Reset</div>
2258 </div>
2259
2260 <!-- *** PURGE HISTORY DIALOG ************************************** -->
2261
2262 <div class="modal modal-mini modal-padded modal-center hide" id="PurgeHistoryDialog">
2263 <div class="modal-header">
2264 <a class="close" href='#' data-dismiss="modal"><i class="icon-close"></i></a>
2265 <h3>Purging history</h3>
2266 </div>
2267
2268 <div class="modal-body">
2269 <div>
2270 <p>
2271 You are going to delete <span id="PurgeHistoryDialog_count">250</span> history records
2272 which represent <span id="PurgeHistoryDialog_percentage">250</span>% of the history.
2273 </p>
2274 <p>
2275 Really sure about that?
2276 </p>
2277 </div>
2278 </div>
2279
2280 <div class="modal-footer">
2281 <a href="#" class="btn btn-danger pull-left" onclick="PurgeHistoryDialog.delete(event)">Delete <span id="PurgeHistoryDialog_count2">250</span> records</a>
2282 <a href="#" class="btn btn-primary" data-dismiss="modal">Cancel</a>
2283 </div>
21802284 </div>
21812285
21822286 <!-- *** ADD FILES DIALOG ************************************************************ -->
24792583 <h3>Update NZBGet</h3>
24802584 </div>
24812585 <div class="modal-body">
2482
2586
24832587 <p id="UpdateDialog_InstalledInfo" style="margin-top:0px; margin-bottom:5px;">Currently installed: <span id="UpdateDialog_VerInstalled"></span></p>
24842588 <p id="UpdateDialog_CheckProgress" class="hide">Checking for updates <img style="margin-left:5px;vertical-align:top;margin-top:1px;" src="img/transmit.gif" width="16px" height="16px"></p>
24852589 <p id="UpdateDialog_CheckFailed" class="hide failure-message"><strong>Check failed. Please try again later.</strong></p>
24862590 <p id="UpdateDialog_UpdateAvail"><strong>An update is available for your platform!</strong></p>
24872591 <p id="UpdateDialog_UpdateNotAvail" class="hide"><strong>No updates are available for your platform.</strong></p>
2488
2592
24892593 <div id="UpdateDialog_UpdateNoInfo" class="hide">
24902594 <p><strong>Automatic updates are not configured for your platform.</strong></p>
24912595 <p class="alert alert-info">
24942598 If you create a public package please read <a href="http://nzbget.net/Packaging" target="_blank">"About Packaging"</a>.
24952599 </p>
24962600 </div>
2497
2601
24982602 <table id="UpdateDialog_Versions" class="table" style="margin-top:10px;">
24992603 <thead>
25002604 <tr>
25432647 </div>
25442648 <div class="modal-body">
25452649 <pre id="UpdateProgressDialog_Log">
2546 </pre>
2650 </pre>
25472651 </div>
25482652 </div>
25492653
26142718 <div id="ShutdownConfirmDialog_OK">Shutdown</div>
26152719 </div>
26162720
2721 <!-- *** DRAG-N-DROP ************************************************************ -->
2722
2723 <div id="TableDragBox"><span id="TableDragBadge" class="badge"></span><div id="TableDragContent"></div></div>
2724
26172725 <!-- *** NOTIFICATIONS ************************************************************ -->
26182726
26192727 <div id="Notif_AddFiles" data-duration="1000" class="alert alert-inverse alert-center alert-center-small hide">
3535 /*** Web-interface configuration *************/
3636
3737 // Options having descriptions can be edited directly in web-interface on settings page.
38
38
3939 this.description = [];
40
40
4141 this.description['activityAnimation'] = 'Animation on play/pause button (yes, no).';
4242 this.activityAnimation = true;
4343
5656
5757 this.description['dupeBadges'] = 'Show badges with duplicate info in downloads and history (yes, no).';
5858 this.dupeBadges = false;
59
59
6060 this.description['rowSelect'] = 'Select records by clicking on any part of the row, not just on the check mark (yes, no).';
6161 this.rowSelect = false;
6262
7474 '%(VARNAME-)% - show variable value with hyphen inside parenthesis;\n' +
7575 '%[VARNAME-]% - show variable value with hyphen inside square brackets.\n\n' +
7676 'Examples:\n' +
77 ' "%(COUNT-)% NZBGet" - show number of downloads in parenthesis followed by a hyphen; don\'t show "(0) - " if queue is empty;\n' +
77 ' "%(COUNT-)% NZBGet" - show number of downloads in parenthesis followed by a hyphen; don\'t show "(0) - " if queue is empty;\n' +
7878 ' "%PAUSE% %(COUNT-)% NZBGet" - as above but also show pause-indicator if paused;\n' +
7979 ' "%[COUNT]% %SPEED-% NZBGet" - show number of downloads and speed if not null (default setting).';
8080 this.windowTitle = '%[COUNT]% %SPEED-% NZBGet';
8282 this.description['refreshRetries'] = 'Number of refresh attempts if a communication error occurs (0-99).\n\n' +
8383 'If all attempts fail, an error is displayed and the automatic refresh stops.'
8484 this.refreshRetries = 4;
85
85
8686 // Time zone correction in hours.
8787 // You shouldn't require this unless you can't set the time zone on your computer/device properly.
8888 this.timeZoneCorrection = 0;
9191 // The choosen interval is saved in web-browser and then restored.
9292 // The default value sets the interval on first use only.
9393 this.refreshInterval = 1;
94
94
9595 // URL for communication with NZBGet via JSON-RPC
9696 this.rpcUrl = './jsonrpc';
9797
102102 this.miniTheme = false;
103103 this.showEditButtons = true;
104104 this.connectionError = false;
105
105
106106 this.load = function()
107107 {
108108 this.refreshInterval = parseFloat(this.read('RefreshInterval', this.refreshInterval));
130130 this.write('WindowTitle', this.windowTitle);
131131 this.write('RefreshRetries', this.refreshRetries);
132132 }
133
133
134134 this.read = function(key, def)
135135 {
136136 var v = localStorage.getItem(key);
155155
156156 $(document).ready(function()
157157 {
158 Frontend.init();
158 Frontend.init();
159159 });
160160
161161
173173 var switchingTheme = false;
174174 var activeTab = 'Downloads';
175175 var lastTab = '';
176
176
177177 this.init = function()
178178 {
179179 window.onerror = error;
180
180
181181 if (!checkBrowser())
182182 {
183183 return;
187187
188188 UISettings.load();
189189 Refresher.init();
190
190
191191 initControls();
192192 switchTheme();
193193 windowResized();
215215 DownloadsMergeDialog.init();
216216 DownloadsSplitDialog.init();
217217 HistoryEditDialog.init();
218
218 PurgeHistoryDialog.init();
219
219220 $(window).resize(windowResized);
220221
221222 initialized = true;
222223
223224 Refresher.update();
224225 }
225
226
226227 function initControls()
227228 {
228229 mobileSafari = $.browser.safari && navigator.userAgent.toLowerCase().match(/(iphone|ipod|ipad)/) != null;
237238 $('#Navbar a[data-toggle="tab"]').on('show', beforeTabShow);
238239 $('#Navbar a[data-toggle="tab"]').on('shown', afterTabShow);
239240 setupSearch();
240
241
241242 $('li > a:has(table)').addClass('has-table');
242
243 $(document).on("keypress", "form", function(event)
244 {
245 // since we use form-tags for a workaround for fieldset-tag,
246 // and we don't have a proper processing of ENTER-key inside forms,
247 // we disable ENTER-key to prevent automatic form submitting.
248 return event.keyCode != 13;
249 });
250
243 $(document).on('keydown', keyDown);
251244 $(window).scroll(windowScrolled);
252245 }
253
246
254247 function checkBrowser()
255248 {
256249 if ($.browser.msie && parseInt($.browser.version, 10) < 9)
262255 return true;
263256 }
264257
265 function error(message, source, lineno)
266 {
267 if (source == "")
258 function error(message, source, lineno)
259 {
260 if (source == '')
268261 {
269262 // ignore false errors without source information (sometimes happen in Safari)
270263 return false;
271264 }
272
265
273266 $('#FirstUpdateInfo').hide();
274267 $('#ErrorAlert-title').text('Error in ' + source + ' (line ' + lineno + ')');
275268 $('#ErrorAlert-text').text(message);
279272 {
280273 Refresher.pause();
281274 }
282
275
283276 return false;
284277 }
285
278
286279 this.loadCompleted = function()
287280 {
288281 Downloads.redraw();
297290 $('#Navbar').show();
298291 $('#MainTabContent').show();
299292 $('#version').text(Options.option('Version'));
293 selectInitialTab();
300294 windowResized();
301295 firstLoad = false;
302296 }
303297 }
304
298
299 function selectInitialTab()
300 {
301 var location = window.location.toString();
302 var link = null;
303 if (location.indexOf('#downloads') > -1)
304 link = 'DownloadsTabLink';
305 else if (location.indexOf('#history') > -1)
306 link = 'HistoryTabLink';
307 else if (location.indexOf('#messages') > -1)
308 link = 'MessagesTabLink';
309 else if (location.indexOf('#settings') > -1)
310 link = 'ConfigTabLink';
311 if (link)
312 {
313 $('#DownloadsTab').removeClass('fade');
314 $('#' + link).click();
315 $('#DownloadsTab').addClass('fade');
316 }
317 }
318
305319 function beforeTabShow(e)
306320 {
307321 var tabname = $(e.target).attr('href');
315329
316330 lastTab = activeTab;
317331 activeTab = tabname;
318
332
319333 $('#SearchBlock .search-query, #SearchBlock .search-clear').hide();
320334 $('#' + activeTab + 'Table_filter, #' + activeTab + 'Table_clearfilter').show();
321335
325339 case 'Messages': Messages.show(); break;
326340 case 'History': History.show(); break;
327341 }
328
342
329343 FilterMenu.setTab(activeTab);
330344 }
331345
359373
360374 $('.navbar-search').show();
361375 beforeTabShow({target: $('#DownloadsTabLink')});
376 }
377
378 function keyDown(e)
379 {
380 var key = Util.keyName(e);
381
382 var modals = $('.modal:visible');
383 if (modals.length > 0)
384 {
385 if (key === 'Enter' && !Util.wantsReturn(e.target))
386 {
387 var primaryButton = $('.btn-primary:visible', modals.last());
388 if (primaryButton.length === 1)
389 {
390 primaryButton.click();
391 }
392 return false;
393 }
394 return;
395 }
396
397 var filterBox = $('#DownloadsTable_filter, #HistoryTable_filter, #MessagesTable_filter, #ConfigTable_filter');
398 if (filterBox.is(':focus') && (key === 'Escape' || key === 'Enter'))
399 {
400 filterBox.blur();
401 return false;
402 }
403
404 if (!(Util.isInputControl(e.target) && $(e.target).is(':visible')))
405 {
406 switch (activeTab)
407 {
408 case 'Downloads': if (Downloads.processShortcut(key)) return false;
409 case 'History': if (History.processShortcut(key)) return false;
410 case 'Messages': if (Messages.processShortcut(key)) return false;
411 case 'Config': if (Config.processShortcut(key)) return false;
412 }
413 switch (key)
414 {
415 case 'Shift+D': $('#DownloadsTabLink').click(); return false;
416 case 'Shift+H': $('#HistoryTabLink').click(); return false;
417 case 'Shift+M': $('#MessagesTabLink').click(); return false;
418 case 'Shift+S': $('#ConfigTabLink').click(); return false;
419 case 'Shift+L': $('#StatusSpeed').click(); return false;
420 case 'Shift+A': $('#StatusTime').click(); return false;
421 case 'Shift+R': $('#RefreshButton').click(); return false;
422 case 'Shift+P': $('#PlayPauseButton').click(); return false;
423 case 'Shift+T': $('#ScheduledPauseButton').click(); return false;
424 }
425 }
362426 }
363427
364428 function windowScrolled()
395459 alignPopupMenu('#StatDialog_MonthMenu', true);
396460
397461 alignCenterDialogs();
398
462
399463 if (initialized)
400464 {
401465 Downloads.resize();
441505 }
442506 }
443507 this.alignPopupMenu = alignPopupMenu;
444
508
445509 function alignCenterDialogs()
446510 {
447511 $.each($('.modal-center'), function(index, element) {
506570 control.lastOuterWidth = control.outerWidth();
507571 }
508572 }
509
573
510574 function switchTheme()
511575 {
512576 switchingTheme = true;
576640 var refreshing = false;
577641 var refreshNeeded = false;
578642 var refreshErrors = 0;
579
643
580644 this.init = function()
581645 {
582646 RPC.rpcUrl = UISettings.rpcUrl;
646710 return;
647711 }
648712 }
649
713
650714 Refresher.pause();
651715 UISettings.connectionError = true;
652716 $('#FirstUpdateInfo').hide();
658722 // stop animations
659723 Status.redraw();
660724 }
661
725
662726 $('html, body').animate({scrollTop: 0 }, 400);
663727 };
664728
679743 scheduleNextRefresh();
680744 }
681745
746 this.isPaused = function()
747 {
748 return refreshPaused > 0;
749 }
750
682751 this.pause = function()
683752 {
684753 clearTimeout(refreshTimer);
685754 refreshPaused++;
686755 }
687756
688 this.resume = function()
757 this.resume = function(wantUpdate)
689758 {
690759 refreshPaused--;
691
692 if (refreshPaused === 0 && UISettings.refreshInterval > 0)
760 if (refreshPaused === 0 && wantUpdate)
761 {
762 this.update();
763 }
764 else if (refreshPaused === 0 && UISettings.refreshInterval > 0)
693765 {
694766 countSeconds();
695767 }
704776 scheduleNextRefresh();
705777 }
706778 }
707
779
708780 function refreshClick()
709781 {
710782 if (indicatorFrame > 10)
823895
824896 // Controls
825897 var $ConfirmDialog;
826
898
827899 // State
828900 var actionCallback;
829
901 var confirmed = false;
902
830903 this.init = function()
831904 {
832905 $ConfirmDialog = $('#ConfirmDialog');
855928 {
856929 initCallback($ConfirmDialog);
857930 }
858
931
859932 Util.centerDialog($ConfirmDialog, true);
933 confirmed = false;
860934 $ConfirmDialog.modal({backdrop: 'static'});
861
935
862936 // avoid showing multiple backdrops when the modal is shown from other modal
863937 var backdrops = $('.modal-backdrop');
864938 if (backdrops.length > 1)
869943
870944 function hidden()
871945 {
946 if (confirmed)
947 {
948 actionCallback($ConfirmDialog);
949 }
950
872951 // confirm dialog copies data from other nodes
873952 // the copied DOM nodes must be destroyed
874953 $('#ConfirmDialog_Title').empty();
879958 function click(event)
880959 {
881960 event.preventDefault(); // avoid scrolling
882 actionCallback($ConfirmDialog);
961 confirmed = true;
883962 $ConfirmDialog.modal('hide');
884963 }
885964 }(jQuery));
893972
894973 // Controls
895974 var $AlertDialog;
896
975
897976 this.init = function()
898977 {
899978 $AlertDialog = $('#AlertDialog');
914993 var PopupNotification = (new function($)
915994 {
916995 'use strict';
917
996
918997 this.show = function(alert, completeFunc)
919998 {
920999 if (UISettings.showNotifications || $(alert).hasClass('alert-error'))
2020 * In this module:
2121 * 1) Messages tab.
2222 */
23
23
2424 /*** MESSAGES TAB *********************************************************************/
25
25
2626 var Messages = (new function($)
2727 {
2828 'use strict';
29
29
3030 // Controls
3131 var $MessagesTable;
3232 var $MessagesTabBadge;
4545 this.init = function(options)
4646 {
4747 updateTabInfo = options.updateTabInfo;
48
48
4949 $MessagesTable = $('#MessagesTable');
5050 $MessagesTabBadge = $('#MessagesTabBadge');
5151 $MessagesTabBadgeEmpty = $('#MessagesTabBadgeEmpty');
6565 pageSize: recordsPerPage,
6666 maxPages: UISettings.miniTheme ? 1 : 5,
6767 pageDots: !UISettings.miniTheme,
68 shortcuts: true,
6869 fillFieldsCallback: fillFieldsCallback,
6970 fillSearchCallback: fillSearchCallback,
7071 filterCallback: filterCallback,
7576
7677 this.applyTheme = function()
7778 {
78 $MessagesTable.fasttable('setPageSize', UISettings.read('MessagesRecordsPerPage', 10),
79 $MessagesTable.fasttable('setPageSize', UISettings.read('MessagesRecordsPerPage', 10),
7980 UISettings.miniTheme ? 1 : 5, !UISettings.miniTheme);
8081 }
8182
8485 activeTab = true;
8586 this.redraw();
8687 }
87
88
8889 this.hide = function()
8990 {
9091 activeTab = false;
9192 }
92
93
9394 this.update = function()
9495 {
9596 if (maxMessages === null)
9798 maxMessages = parseInt(Options.option('LogBufferSize'));
9899 initFilterButtons();
99100 }
100
101
101102 if (lastID === 0)
102103 {
103104 RPC.call('log', [0, maxMessages], loaded);
113114 merge(newMessages);
114115 RPC.next();
115116 }
116
117
117118 function merge(newMessages)
118119 {
119120 if (lastID === 0)
139140 for (var i=0; i < messages.length; i++)
140141 {
141142 var message = messages[i];
142
143
143144 var item =
144145 {
145146 id: message.ID,
203204 cell.className = 'text-center';
204205 }
205206 }
206
207
207208 function updateInfo(stat)
208209 {
209210 updateTabInfo($MessagesTabBadge, stat);
237238 Util.show($('#Messages_Badge_ERROR, #Messages_Badge_ERROR2').closest('.btn'), error);
238239 Util.show($('#Messages_Badge_ALL, #Messages_Badge_ALL2').closest('.btn'), detail || info || warning || error);
239240 }
240
241
241242 function updateFilterButtons()
242243 {
243244 var countDebug = 0;
307308 notification = null;
308309 }
309310 }
310
311
312 this.processShortcut = function(key)
313 {
314 switch (key)
315 {
316 case 'A': Messages.filter('ALL'); return true;
317 case 'T': Messages.filter('DETAIL'); return true;
318 case 'I': Messages.filter('INFO'); return true;
319 case 'W': Messages.filter('WARNING'); return true;
320 case 'E': Messages.filter('ERROR'); return true;
321 case 'D': case 'Delete': case 'Meta+Backspace': Messages.clearClick(); return true;
322 }
323 return $MessagesTable.fasttable('processShortcut', key);
324 }
325
311326 }(jQuery));
5252 var $StatDialog;
5353 var $ScheduledPauseDialog;
5454 var $PauseForInput;
55 var $PauseForPreview;
5556
5657 // State
5758 var status;
6061 var playInitialized = false;
6162 var modalShown = false;
6263 var titleGen = [];
64
65 var validTimePatterns = [
66 /^=\d{1,2}(:[0-5][0-9])?$/, // 24h exact
67 /^=\d{1,2}(:[0-5][0-9])?(AM|PM)$/i, // 12h exact
68 /^\d+(:[0-5][0-9])?$/, // 24h relative
69 /^\d+(h|m)?$/i, // relative minutes or hours
70 ];
6371
6472 this.init = function()
6573 {
8088 $StatusURLs = $('#StatusURLs');
8189 $ScheduledPauseDialog = $('#ScheduledPauseDialog')
8290 $PauseForInput = $('#PauseForInput');
91 $PauseForPreview = $('#PauseForPreview');
8392
8493 if (UISettings.setFocus)
8594 {
9099 }
91100
92101 $PlayAnimation.hover(function() { $PlayBlock.addClass('hover'); }, function() { $PlayBlock.removeClass('hover'); });
102 $PauseForInput.keyup(function(e)
103 {
104 if (e.which == 13) return;
105
106 calculateSeconds($(this).val());
107 });
93108
94109 // temporary pause the play animation if any modal is shown (to avoid artifacts in safari)
95110 $('body >.modal').on('show', modalShow);
184199 $StatusTime.toggleClass('orange', statWarning);
185200 $StatusTimeIcon.toggleClass('icon-time', !statWarning);
186201 $StatusTimeIcon.toggleClass('icon-time-orange', statWarning);
187
202
188203 updateTitle();
189204 }
190205
308323 this.scheduledPauseDialogClick = function()
309324 {
310325 $PauseForInput.val('');
326 $PauseForPreview.addClass('invisible');
311327 $ScheduledPauseDialog.modal();
312328 }
313329
314330 this.pauseForClick = function()
315331 {
316332 var val = $PauseForInput.val();
317 var minutes = parseInt(val);
318
319 if (isNaN(minutes) || minutes <= 0)
333 var seconds = calculateSeconds(val);
334
335 if (isNaN(seconds) || seconds <= 0)
320336 {
321337 return;
322338 }
323339
324340 $ScheduledPauseDialog.modal('hide');
325 this.scheduledPauseClick(minutes * 60);
341 this.scheduledPauseClick(seconds);
342 }
343
344 function isTimeInputValid(str)
345 {
346 for (var i = 0; i < validTimePatterns.length; i++)
347 {
348 if (validTimePatterns[i].test(str)) return true;
349 }
350 }
351
352 function calculateSeconds(parsable) {
353 parsable = parsable.toLowerCase();
354
355 if (!isTimeInputValid(parsable))
356 {
357 $PauseForPreview.addClass('invisible');
358 return;
359 }
360
361 var now = new Date(), future = new Date();
362 var hours = 0, minutes = 0;
363
364 var mode = /^=/.test(parsable) ? 'exact' : 'relative';
365 var indicator = (parsable.match(/h|m|am|pm$/i) || [])[0];
366 var parsedTime = parsable.match(/(\d+):?(\d+)?/) || [];
367 var primaryValue = parsedTime[1];
368 var secondaryValue = parsedTime[2];
369 var is12H = (indicator === 'am' || indicator === 'pm')
370
371 if (indicator === undefined && secondaryValue === undefined)
372 {
373 if (mode === 'exact') hours = parseInt(primaryValue);
374 else minutes = parseInt(primaryValue);
375 }
376 else if (indicator === 'm')
377 {
378 minutes = parseInt(primaryValue);
379 }
380 else
381 {
382 hours = parseInt(primaryValue);
383 if (secondaryValue) minutes = parseInt(secondaryValue);
384 if (indicator === 'pm' && hours < 12) hours += 12;
385 }
386
387 if ((mode !== 'exact' && (is12H || (hours > 0 && minutes > 59))) ||
388 (mode === 'exact' && (hours < 0 || hours > 23 || minutes < 0 || minutes > 59)))
389 {
390 $PauseForPreview.addClass('invisible');
391 return;
392 }
393
394 if (mode === 'exact')
395 {
396 future.setHours(hours, minutes, 0, 0);
397
398 if (future < now) future.setDate(now.getDate() + 1);
399 }
400 else
401 {
402 future.setHours(now.getHours() + hours, now.getMinutes() + minutes);
403 }
404
405 $PauseForPreview.find('strong')
406 .text((future.getDay() !== now.getDay()) ? future.toLocaleString() : future.toLocaleTimeString())
407 .end()
408 .removeClass('invisible');
409
410 return (future - now)/1000;
326411 }
327412
328413 function modalShow()
385470 case '%[VAR]%': return '[' + value + ']';
386471 case '%[VAR-]%': return '[' + value + '] - ';
387472 }
388
473
389474 return Downloads.groups.length > 0 ? '' + Downloads.groups.length + ' - ' : '';
390475 };
391
476
392477 function fill(varname, paramFunc)
393478 {
394479 titleGen['%' + varname + '%'] = function() { return format('%VAR%', paramFunc); };
10231108 function chartMouseExit(env, serie, index, mouseAreaData)
10241109 {
10251110 mouseOverIndex = -1;
1026 var title = curRange === 'MIN' ? '60 seconds' :
1027 curRange === 'HOUR' ? '60 minutes' :
1028 curRange === 'DAY' ? '24 hours' :
1111 var title = curRange === 'MIN' ? '60 seconds' :
1112 curRange === 'HOUR' ? '60 minutes' :
1113 curRange === 'DAY' ? '24 hours' :
10291114 curRange === 'MONTH' ? $('#StatDialog_Volume_MONTH').text() : 'Sum';
1030
1115
10311116 $StatDialog_Tooltip.html(title + ': <span class="stat-size">' + Util.formatSizeMB(chartData.sumMB, chartData.sumLo) + '</span>');
10321117 }
10331118
11001185 var epochDay = Math.ceil((date.getTime() - date.getTimezoneOffset() * 60*1000) / (1000*24*60*60));
11011186 return epochDay;
11021187 }
1103
1188
11041189 function updateMonthList()
11051190 {
11061191 monthListInitialized = true;
13331418
13341419 $ServerTable.fasttable(
13351420 {
1336 pagerContainer: $('#LimitDialog_ServerTable_pager'),
1337 headerCheck: $('#LimitDialog_ServerTable > thead > tr:first-child'),
1338 hasHeader: false,
1421 pagerContainer: '#LimitDialog_ServerTable_pager',
1422 rowSelect: UISettings.rowSelect,
13391423 pageSize: 100
13401424 });
1341
1342 $ServerTable.on('click', 'tbody div.check',
1343 function(event) { $ServerTable.fasttable('itemCheckClick', this.parentNode.parentNode, event); });
1344 $ServerTable.on('click', 'thead div.check',
1345 function() { $ServerTable.fasttable('titleCheckClick') });
1346 $ServerTable.on('mousedown', Util.disableShiftMouseDown);
13471425
13481426 if (UISettings.setFocus)
13491427 {
15201598 var $Table_filter;
15211599 var tabName;
15221600 var items;
1523
1601
15241602 this.init = function()
15251603 {
15261604 $SaveFilterDialog = $('#SaveFilterDialog');
15641642 im.attr('data-id', i);
15651643 insertPos.before(item);
15661644 }
1567
1645
15681646 Util.show('#FilterMenu_Empty', items.length === 0);
1569
1647
15701648 if (UISettings.miniTheme)
15711649 {
15721650 Frontend.alignPopupMenu('#FilterMenu');
16171695 $SaveFilterInput.val(name);
16181696 $SaveFilterDialog.modal();
16191697 }
1620
1698
16211699 this.saveClick = function()
16221700 {
16231701 $SaveFilterDialog.modal('hide');
16351713 return;
16361714 }
16371715 }
1638
1716
16391717 // doesn't exist - add new
16401718 items.push({name: name, filter: filter});
16411719 save();
705705 background-position: -432px -144px;
706706 }
707707
708 .icon-ring-red {
709 background-position: -528px -16px;
710 }
711
712 .icon-ring-fill-red {
713 background-position: -560px -16px;
714 }
715
716 .icon-circle-red {
717 background-position: -496px -16px;
718 }
719
720 .icon-ring-blue {
721 background-position: -528px -48px;
722 }
723
724 .icon-ring-fill-blue {
725 background-position: -560px -48px;
726 }
727
728 .icon-ring-ltgrey {
729 background-position: -496px -48px;
730 }
731
708732 /* END: Icons */
709733
710734 .btn-toolbar {
740764
741765 .controls .label-status {
742766 line-height: 22px;
767 }
768
769 .invisible {
770 visibility: hidden;
743771 }
744772
745773 /* links in black color */
960988 margin-bottom: 0px;
961989 }
962990
963 /* text-align for tables */
964 td.text-right,th.text-right {
991 .table-bordered {
992 border-left: 1px solid #dddddd;
993 }
994
995 .table-bordered th,
996 .table-bordered td {
997 border-left: none;
998 }
999
1000 .text-right {
9651001 text-align: right;
9661002 }
9671003
968 /* text-align for tables */
969 td.text-center,th.text-center {
1004 .text-center,
1005 table td.text-center,
1006 table th.text-center {
9701007 text-align: center;
9711008 }
9721009
9911028 .table-nonbordered td {
9921029 border: none;
9931030 }
994
1031
1032 table > thead > tr > th.table-selector {
1033 text-align: center;
1034 background: repeating-linear-gradient(-45deg, #FFFFD8, #FFFFD8 6px, #E7E8D1 6px, #E7E8D1 12px);
1035 padding: 2px;
1036 font-size: 12px;
1037 line-height: 14px;
1038 color: #966C38;
1039 text-shadow: 0 -1px 0 rgba(255, 255, 255, 0.75), 0 1px 0 rgba(255, 255, 255, 0.75), -1px 0 0 rgba(255, 255, 255, 0.75);
1040 }
1041
9951042 /* END: Tables */
9961043
9971044
10001047 table.table-check > tbody > tr > td:first-child {
10011048 width: 14px;
10021049 height: 14px;
1050 padding-left: 6px;
10031051 }
10041052
10051053 div.check {
10321080 }
10331081
10341082 tr.checked,
1035 tr.checked td {
1036 background-color: #FFFFE1;
1037 }
1038
1039 .table-striped tbody tr.checked:nth-child(odd) td {
1040 background-color: #FFFFE1;
1083 tr.checked td,
1084 tr.checked:nth-child(odd) .progress {
1085 background-color: #FFFFE8;
1086 }
1087
1088 .table-striped tbody tr.checked:nth-child(odd) td,
1089 .checked .progress {
1090 background-color: #FFFFD8;
10411091 }
10421092
10431093 .table tbody tr.checked:hover,
10441094 .table tbody tr.checked:hover td {
1045 background-color: #FFFFCC;
1095 background-color: #FFFFC0;
10461096 }
10471097
10481098 .check-simple tr.checked,
19241974 cursor: pointer;
19251975 }
19261976
1977 /* DRAG-N-DROP */
1978
1979 #TableDragBox {
1980 position: absolute;
1981 left: 100px;
1982 top: 100px;
1983 text-align: center;
1984 border: 1px solid #ccc;
1985 background-color: #f8f8f8;
1986 display: none;
1987 cursor: grabbing;
1988 cursor: -webkit-grabbing;
1989 z-index: 5000;
1990 }
1991
1992 #TableDragBox .badge {
1993 position: absolute;
1994 left: 0px;
1995 top: -12px;
1996 text-align: center;
1997 padding: 1px 6px;
1998 color: #fff;
1999 background-color: #D70015;
2000 }
2001
2002 .phone #TableDragBox .badge {
2003 font-size: 22px;
2004 top: -20px;
2005 padding: 6px 10px;
2006 border-radius: 12px;
2007 margin
2008 }
2009
2010 #TableDragBox .table-bordered {
2011 border: none;
2012 }
2013
2014 /* drag grip */
2015 table.table-drag > tbody > tr:hover > td:first-child,
2016 #TableDragBox table.table-drag > tbody > tr > td:first-child {
2017 background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAAECAYAAACk7+45AAAAFUlEQVQI12O8e/fufwYGBgYmBpwAAHhsA5rVjhOVAAAAAElFTkSuQmCC);
2018 background-position: 1px 1px;
2019 background-repeat: repeat-y;
2020 }
2021
2022 .phone table.table-drag > tbody > tr:hover > td:first-child,
2023 .phone #TableDragBox table.table-drag > tbody > tr > td:first-child {
2024 background: none;
2025 cursor: default;
2026 }
2027
2028 table.table-drag > tbody > tr > td:first-child {
2029 cursor: move;
2030 cursor: -moz-grab;
2031 cursor: -webkit-grab;
2032 }
2033
2034 table.table-drag > tbody > tr > td:first-child > div.check {
2035 cursor: default;
2036 }
2037
2038 #TableDragBox table.table-drag > tbody > tr > td:first-child,
2039 #TableDragBox table.table-drag > tbody > tr > td:first-child > div.check {
2040 cursor: move;
2041 cursor: -moz-grabbing;
2042 cursor: -webkit-grabbing;
2043 }
2044
2045 tr.drag-source > td {
2046 background-color: #fff !important;
2047 color: #fff !important;
2048 visibility: visible;
2049 }
2050
2051 tr.drag-source > td > * {
2052 visibility: hidden !important;
2053 }
2054
2055 .table-striped tbody tr.drag-finish td,
2056 .table-striped tbody tr.drag-finish .progress,
2057 .table-striped tbody tr.drag-finish td a {
2058 background-color: #E6FAE4 !important;
2059 }
2060
19272061
19282062 /****************************************************************************/
19292063 /* SMARTPHONE THEME */
21622296 padding-bottom: 10px;
21632297 }
21642298
2165 .phone table.table-check tbody td {
2299 .phone table.table-check > tbody > tr > td {
21662300 padding-left: 60px;
21672301 }
21682302
24662600 font-size: 18px;
24672601 }
24682602
2603 .phone #TableDragTip {
2604 font-size: 18px;
2605 }
2606
24692607 /*** STATISTICS DIALOG */
24702608
24712609 .phone #StatDialog_Tooltip {
2222 * 2) Slideable tab dialog extension;
2323 * 3) Communication via JSON-RPC.
2424 */
25
25
2626 /*** UTILITY FUNCTIONS *********************************************************/
2727
2828 var Util = (new function($)
155155 return Util.round0(bytesPerSec / 1024.0) + '&nbsp;KB/s';
156156 }
157157 }
158
158
159159 this.formatAge = function(time)
160160 {
161161 if (time == 0)
239239 {
240240 return ''+value == 'true';
241241 }
242
242
243243 this.disableShiftMouseDown = function(event)
244244 {
245245 // disable default shift+click behaviour, which is to select a text
249249 event.preventDefault();
250250 }
251251 }
252
252
253253 this.centerDialog = function(dialog, center)
254254 {
255255 var $elem = $(dialog);
286286 {
287287 return false;
288288 }
289
289
290290 var blob = new Blob([content], {type: type});
291291
292292 if (navigator.msSaveBlob)
310310 return true;
311311 }
312312
313 var keyMap = {
314 8:'Backspace', 9:'Tab', 13:'Enter', 27:'Escape', 33:'PgUp', 34:'PgDn',
315 35:'End', 36:'Home', 37:'Left', 38:'Up', 39:'Right', 40:'Down', 45:'Insert', 46:'Delete',
316 48:'0', 49:'1', 50:'2', 51:'3', 52:'4', 53:'5', 54:'6', 55:'7', 56:'8', 59:'9',
317 65:'A', 66:'B', 67:'C', 68:'D', 69:'E', 70:'F', 71:'G', 72:'H', 73:'I', 74:'J', 75:'K',
318 76:'L', 77:'M', 78:'N', 79:'O', 80:'P', 81:'Q', 82:'R', 83:'S', 84:'T', 85:'U', 86:'V',
319 87:'W', 88:'X', 89:'Y', 90:'Z'};
320
321 this.keyName = function(keyEvent)
322 {
323 return (keyEvent.metaKey ? 'Meta+' : '') + (keyEvent.ctrlKey ? 'Ctrl+' : '') +
324 (keyEvent.altKey ? 'Alt+' : '') + (keyEvent.shiftKey ? 'Shift+' : '') +
325 keyMap[keyEvent.keyCode];
326 }
327
328 this.isInputControl = function(target)
329 {
330 return target.tagName == 'INPUT' || target.tagName == 'SELECT' ||
331 target.tagName == 'TEXTAREA' || target.isContentEditable;
332 }
333
334 this.wantsReturn = function(target)
335 {
336 return target.tagName == 'TEXTAREA';
337 }
338
313339 }(jQuery));
314340
315341
318344 var TabDialog = (new function($)
319345 {
320346 'use strict';
321
347
322348 this.extend = function(dialog)
323349 {
324350 dialog.restoreTab = restoreTab;
325351 dialog.switchTab = switchTab;
326352 dialog.maximize = maximize;
327353 }
328
354
329355 function maximize(options)
330356 {
331357 var bodyPadding = 15;
413439 body.css({position: '', height: oldBodyHeight});
414440 dialog.css('overflow', 'hidden');
415441 fromTab.css({position: 'absolute', left: leftPos, width: oldTabWidth, height: oldBodyHeight});
416 toTab.css({position: 'absolute', width: newTabWidth, height: oldBodyHeight,
442 toTab.css({position: 'absolute', width: newTabWidth, height: oldBodyHeight,
417443 left: sign * ((options.back ? newTabWidth : oldTabWidth) + bodyPadding*2)});
418444 fromTab.show();
419445 dialog.toggleClass(toggleClass);
457483 dialog.animate({width: newDialogWidth, 'margin-left': newDialogMarginLeft}, duration);
458484 }
459485
460 fromTab.animate({left: sign * -((options.back ? newTabWidth : oldTabWidth) + bodyPadding*2),
486 fromTab.animate({left: sign * -((options.back ? newTabWidth : oldTabWidth) + bodyPadding*2),
461487 height: newBodyHeight + bodyPadding}, duration);
462488 toTab.animate({left: leftPos, height: newBodyHeight + bodyPadding}, duration, function()
463489 {
468494 dialog.toggleClass(toggleClass);
469495 if (fullscreen)
470496 {
471 body.css({position: 'absolute', height: '', left: 0, right: 0,
497 body.css({position: 'absolute', height: '', left: 0, right: 0,
472498 top: header.outerHeight(),
473499 bottom: footer.outerHeight(),
474500 'max-height': 'inherit'});
496522 var RPC = (new function($)
497523 {
498524 'use strict';
499
525
500526 // Properties
501527 this.rpcUrl;
502528 this.defaultFailureCallback;
506532 this.call = function(method, params, completed_callback, failure_callback, timeout)
507533 {
508534 var _this = this;
509
535
510536 var request = JSON.stringify({nocache: new Date().getTime(), method: method, params: params});
511537 var xhr = new XMLHttpRequest();
512538
513539 xhr.open('post', this.rpcUrl);
514
540
515541 if (XAuthToken !== undefined)
516542 {
517543 xhr.setRequestHeader('X-Auth-Token', XAuthToken);
518544 }
519
545
520546 if (timeout)
521547 {
522548 xhr.timeout = timeout;
8989 CTEXT "Version 15.0-testing-r1200",IDC_ABOUT_VERSION,16,70,144,8
9090 CTEXT "Lightweight usenet downloader",IDC_STATIC,16,83,144,8
9191 CTEXT "http://nzbget.net",IDC_ABOUT_HOMEPAGE,55,144,64,9,SS_NOTIFY
92 CTEXT "Copyright © 2007-2016 Andrey Prygunkov",IDC_STATIC,16,156,144,8
92 CTEXT "Copyright © 2007-2017 Andrey Prygunkov",IDC_STATIC,16,156,144,8
9393 ICON "",IDC_ABOUT_ICON,67,7,21,20
9494 CTEXT "NZBGet",IDC_ABOUT_NAME,16,51,144,16
9595 CTEXT "The package includes other software; see program's folder for licenses.",IDC_STATIC,16,121,144,20