Compare commits

...

311 Commits
v9.1 ... v12.0

Author SHA1 Message Date
Andrey Prygunkov
fada56ab2e version 12.0 (2 Jan. 2014) 2014-01-21 20:47:15 +00:00
Andrey Prygunkov
4b228b32f0 OSX-app: restarting via troubleshooting-menu could result in an error message "Could not establish connection to background process" although the process was successfully restarted 2014-01-02 21:06:03 +00:00
Andrey Prygunkov
f66189de6b updated version string (preparing to release 12.0) 2014-01-02 20:32:11 +00:00
Andrey Prygunkov
743805ecd5 updated ChangeLog 2014-01-01 21:27:29 +00:00
Andrey Prygunkov
6c9dcea08c fixed: for rar-files with old naming scheme only files with extensions rxx and sxx were deleted during cleanup leaving the files with extensions txx, uxx, etc. 2013-12-25 22:43:58 +00:00
Andrey Prygunkov
fa1944090d fixed wrong size of progress wheel during adding of URL in add dialog 2013-12-24 21:00:17 +00:00
Andrey Prygunkov
04cf428619 fixed: duplicate mode was not saved from history dialog 2013-12-24 18:38:10 +00:00
Andrey Prygunkov
86d6b00886 added new command "Download again" for history items; new action "HistoryRedownload" of RPC-method "editqueue"; for controlling via command line: new action "A" of subcommand "H" of command "--edit/-E" 2013-12-21 21:39:49 +00:00
Andrey Prygunkov
38429d98df fixed a potential problem in incorrect using of one library function 2013-12-19 21:28:45 +00:00
Andrey Prygunkov
0c9667fe58 fixed memory leak in RSS feed parser (Posix only) 2013-12-19 20:28:50 +00:00
Andrey Prygunkov
3c6bb7be4c 1) NZBIDs are now generated with more care avoiding numbering holes possible with previous versions; 2) fixed: new NZBIDs were generated on each refresh of web-ui (bug introduced in r811); 3) for queue disk state written by versions r811-r920 the NZBIDs are renumbered starting from 1 2013-12-18 20:19:42 +00:00
Andrey Prygunkov
40fa732122 do not closing rss filter dialog if a communication error occurs during first fetching of rss and the filter was already edited by user (this allows to save the filter) 2013-12-16 21:15:49 +00:00
Andrey Prygunkov
01e2e25bce fixed potential segfault 2013-12-12 23:43:38 +00:00
Andrey Prygunkov
29e916dcdd NZBGet.app for OSX: fixed: one text message was not properly shown 2013-12-10 20:44:58 +00:00
Andrey Prygunkov
1bfa7610ae improved error reporting when creation of temporary output file fails 2013-12-10 20:37:02 +00:00
Andrey Prygunkov
fb94c32bb4 fixed: when deleting download, if all remaining queued files are par2-files the disk cleanup should not be performed, but it was sometimes 2013-12-10 20:25:07 +00:00
Andrey Prygunkov
94ad26d818 fixed: RSS feed filter fields "age" and "size" did not work (bug introduced in r908) 2013-12-03 21:39:18 +00:00
Andrey Prygunkov
f323addc1c added new option "TimeCorrection" to adjust conversion from system time to local local (solves issues with scheduler when using a binary compiled for other platform) 2013-11-28 21:03:01 +00:00
Andrey Prygunkov
5559c91c0e do not closing rss filter dialog if a communication error occurs during editing of rss filter (this allows to save the filter into clipboard at least) 2013-11-28 20:46:32 +00:00
Andrey Prygunkov
6cc5eab94b fixed: some of actions for remote command "--edit/-E" did not work properly (bug introduced in r900) 2013-11-24 20:15:41 +00:00
Andrey Prygunkov
c2a3450c8f refactor: removed many unneeded pointer-not-null-safety-checks 2013-11-24 19:29:52 +00:00
Andrey Prygunkov
01e1ec0794 fixed line endings in one source file 2013-11-24 13:47:36 +00:00
Andrey Prygunkov
ea381cde90 fixed encoding issue for non-ASCII characters in DNZB-Headers 2013-11-18 20:37:20 +00:00
Andrey Prygunkov
26074c67c6 extended RSS filters: 1) added search field "description"; 2) any newznab attribute can now be used as search field with prefix "attr-" (for example "attr-genre"); 3) removed search fields "genre" and "rating" (use "attr-genre" and "attr-rating" instead 2013-11-17 21:57:32 +00:00
Andrey Prygunkov
e2b13fcda5 fixed: for downloads deleted by health-check status was shown as "DELETED-HEALTH" instead of "FAILURE" 2013-11-14 20:17:45 +00:00
Andrey Prygunkov
0130852d9a added scheduler command "FetchFeed"; renamed RPC-method "fetchfeeds" to "fetchfeed" and added parameter "ID" 2013-11-12 20:54:45 +00:00
Andrey Prygunkov
a027af9e84 if unpack fails with write error (usually because of not enough space on disk) this is shown as status "Unpack: space" in web-interface; this unpack-status is handled as "success" by duplicate handling (no download of other duplicate); also added new unpack-status "wrong password" (only for rar5-archives); env.var. NZBPP_UNPACKSTATUS has two new possible values: 3 (write error) and 4 (wrong password); updated pp-script "EMail.py" to support new unpack-statuses 2013-11-08 21:54:44 +00:00
Andrey Prygunkov
ce81b3d4da added status filter buttons to history page 2013-11-07 21:01:44 +00:00
Andrey Prygunkov
96d8ff3cb7 added new scheduler commands "ActivateServer" and "DeactivateServer"; combined options "TaskX.DownloadRate" and "TaskX.Process" into one option "TaskX.Param", also used by two new commands 2013-11-07 20:55:33 +00:00
Andrey Prygunkov
b67c354fdb better handling of obfuscated nzb-files containing multiple files with same names; removed option "StrictParName" which was not working good with obfuscated files; if more par-files are required for repair the files with strict names are tried first and then other par-files 2013-11-07 20:14:23 +00:00
Andrey Prygunkov
9a610197ea when a duplicate backup is returned from history to download queue its paused-state is now correctly restored 2013-11-05 21:11:47 +00:00
Andrey Prygunkov
1109c3423c reworked duplicate handling: 1) when a duplicate is added to queue it is now moved to history as dupe-backup instead of being paused in download queue; 2) if download fails the best duplicate is moved from history back to queue for download (if there are no duplicates in queue); this makes it easier to manage download queue without worrying about properly pausing/unpausing duplicates); 3) badges with duplicate info are not shown in the list of downloads and history anymore; if necessary they can be activated by manually editing setting "dupeBadges" in index.js; 4) when deleting downloads from queue there are three options now: "move to history", "move to history as duplicate" and "delete without history tracking"; 5) new actions "GroupDupeDelete" and "GroupFinalDelete" in addition to "GroupDelete" in RPC-method "editqueue"; 6) DUPE-mark for history records can now be set or cleared via history dialog; 7) new action "HistorySetDupeBackup" in RPC-method "editqueue"; 8) when deleting downloads from queue the messages about deleted individual files are now printed as "detail" instead of "info"; 9) removed command "Mark as duplicates" from edit dialog for multiple selected downloads and from RPC-method "editqueue"; the command is not needed anymore since all duplicate properties are now changeable 2013-11-04 20:59:20 +00:00
Andrey Prygunkov
18fcb8d0ad if unpack did not find archive files the par-check is not requested anymore if par-rename was already done 2013-10-30 20:15:07 +00:00
Andrey Prygunkov
a5845ed0d9 improved par-check: if main par2-file is corrupted and can not be loaded other par2-files are downloaded and then used as replacement for main par2-file 2013-10-30 20:06:18 +00:00
Andrey Prygunkov
3392fa59fe improved support of non-ascii characters in file names on windows (again) 2013-10-25 20:09:25 +00:00
Andrey Prygunkov
95e816572a small restructure in settings order: 1) combined sections "REMOTE CONTROL" and "PERMISSIONS" into one section with name "SECURITY"; 2) moved sections "CATEGORIES" and "RSS FEEDS" higher in the section list 2013-10-24 20:33:40 +00:00
Andrey Prygunkov
3acb3aab9f addition to r894: commited missing changes in another file 2013-10-24 20:24:43 +00:00
Andrey Prygunkov
509860d890 fixed: if unpack fails the created destination directory was not automatically removed (only if option "InterDir" was active) 2013-10-24 20:17:51 +00:00
Andrey Prygunkov
61dcc467ee added support for rar5-format when checking signatures of archives with non-standard file extensions 2013-10-24 20:15:59 +00:00
Andrey Prygunkov
ae6601d9e3 improved handling of non-ascii characters in file names on windows 2013-10-23 19:22:02 +00:00
Andrey Prygunkov
33733af3c5 when option "UnpackCleanupDisk" is active all archive files are now deleted from download directory without relying on output printed by unrar; this solves issues with non-ascii-characters in archive file names on some platforms and especially in combination with rar5 2013-10-23 19:16:20 +00:00
Andrey Prygunkov
da7c0ab7d6 variable substitution now works for all options, including "FeedX.Filter"; if variable could not be found no error is printed anymore because character sequence used to define variable referenece can be part of feed filter and therefore should not be reported as error 2013-10-22 21:34:36 +00:00
Andrey Prygunkov
afd156b51f when option "InterDir" is used the intermediate destination directory names now include unique number to avoid several downloads with same name to use the same directory and interfere with each other 2013-10-22 21:17:02 +00:00
Andrey Prygunkov
5d549b7c60 option "InterDir" is now active by default 2013-10-22 21:05:13 +00:00
Andrey Prygunkov
1b5671dc87 increased width of Update-dialog to accommodate 80 character columns; this improves output of console tools like wget relying on standard terminal width 2013-10-22 21:04:22 +00:00
Andrey Prygunkov
87e2893505 for external script exec-permissions are now added automatically; this makes installation of pp-scripts and other scripts easier 2013-10-21 20:24:02 +00:00
Andrey Prygunkov
89443e342f if option "ParRename" is disabled (not recommended) unpacker does not initiate par-rename anymore, instead the full par-verify is performed then; refactor: simplified the code requesting par-rename after unpack 2013-10-20 20:59:49 +00:00
Andrey Prygunkov
de5ed803ed for archives including par-files for renaming of extracted files the par-renaming now works for extracted sub-directories too 2013-10-19 21:16:53 +00:00
Andrey Prygunkov
528f9a7ec4 added new files to VC project 2013-10-18 20:38:41 +00:00
Andrey Prygunkov
a1f7656fe4 addition to r879: removed check if download has downloaded files 2013-10-17 22:00:37 +00:00
Andrey Prygunkov
a5703a55eb added automatic updates: new button "Check for updates" on settings tab of web-interface, in section "SYSTEM", initiates check and shows dialog allowing to install new version; it is possible to choose between stable, testing and development branches; this feature is for end-users using binary packages created and updated by maintainers, who need to write an update script specific for platform; the script is then called by NZBGet when user clicks on install-button; the script must download and install new version; for more info visit http://nzbget.sourceforge.net/Packaging 2013-10-17 19:35:43 +00:00
Andrey Prygunkov
1275b85465 option "DiskSpace" now checks space on "InterDir" in addition to "DestDir" 2013-10-17 19:11:46 +00:00
Andrey Prygunkov
52dc2738a1 when removing duplicates from queue after successful download now removing only unpaused items which does not have any downloaded articles; this prevents deletion of higher score duplicates 2013-10-17 19:09:40 +00:00
Andrey Prygunkov
7c0f7cbdc2 addition to r877: commited missing changes in header-file 2013-10-17 18:56:26 +00:00
Andrey Prygunkov
c14ef8bd13 fixed: in RSS filters when using substitution variables referring to matches a wrong variable could be substituted if substring search did not start with star-character 2013-10-17 18:52:16 +00:00
Andrey Prygunkov
ce62ae9f50 fixed: superfluous spaces in RSS filter caused non-matching 2013-10-14 20:33:52 +00:00
Andrey Prygunkov
133347f884 support for rar-archives with non-standard extensions is now limited to file extensions consisting of digits; this is to avoid extracting of rar-archives having non-rar extensions on purpose (example: .cbr) 2013-10-09 19:47:11 +00:00
Andrey Prygunkov
ca4f56cb04 fixed: detection of par-files inside archives did not work properly 2013-10-09 19:45:35 +00:00
Andrey Prygunkov
f512ae973d history records with failed script status are now shown as "PP-FAILURE" in history list (instead of just "FAILURE") 2013-10-09 19:43:53 +00:00
Andrey Prygunkov
04cceb314e updated descriptions of few options 2013-10-09 19:38:49 +00:00
Andrey Prygunkov
4138e10788 removed option "Also delete downloaded files" from history delete confirmation dialog; for failed downloads cleanup is now performed if option "DeleteCleanupDisk" is active; in RPC-Method "editqueue" removed actions "HistoryDeleteCleanup" and "HistoryFinalDeleteCleanup" 2013-10-09 19:37:44 +00:00
Andrey Prygunkov
5d11b9aa97 added special handling for files ".AppleDouble" and ".DS_Store" during unpack to avoid problems on NAS having support for AFP protocol (used on Mac OSX) 2013-10-08 19:44:06 +00:00
Andrey Prygunkov
6033c2b3ce fixed: invalid "Offset" passed to RPC-method "editqueue" or command line action "-E/--edit" could crash the program 2013-10-08 19:41:26 +00:00
Andrey Prygunkov
dfb28dc155 history records can now be either permanently deleted or just hidden from history list; hiding (instead of deleting) is recommended for proper work of duplicate handling; in addition it is now possible to delete downloaded files; RPC-method "editqueue" has now four actions to delete history records: "HistoryDelete", "HistoryDeleteCleanup", "HistoryFinalDelete", "HistoryFinalDeleteCleanup"; action "HistoryDelete" which has existed before now hides records, already hidden records are ignored; button "Old" in web-interface on history tab renamed to "Hidden"; badge "DUP", used to distinguish old history records changed to "hidden" 2013-10-08 19:35:59 +00:00
Andrey Prygunkov
756b29d9ed fixed a potential seg. fault in a commonly used function 2013-10-07 21:15:46 +00:00
Andrey Prygunkov
dc7a3af768 destination directory for option "DestDir" is not checked/created on program start anymore (only when a download starts); this helps when DestDir is mounted to a network drive which is not available on program start 2013-10-07 19:29:36 +00:00
Andrey Prygunkov
baf3a2d915 addition to r863: fixed: option "UrlForce" did not really worked 2013-10-07 16:15:38 +00:00
Andrey Prygunkov
25832ab2ea option "HealthCheck" is now set to "Delete" by default; the previously default setting "Pause" did not work well with automatic duplicate handling 2013-10-07 16:14:14 +00:00
Andrey Prygunkov
c95c0401eb added new option "UrlForce" to allow URL-downloads (including fetching of RSS feeds and nzb-files from feeds) even if download is paused; the option is active by default 2013-10-06 18:55:09 +00:00
Andrey Prygunkov
936580a924 moved command "Duplicate properties" from actions-menu into button "Dupe" near "PP-Parameters"; renamed button "PP-Parameters" to "Postprocess" and button "PP-Messages" (visible only during post-processing) to "Log" to free space for new Dupe-button 2013-10-05 13:03:52 +00:00
Andrey Prygunkov
00df147688 fixed: NZBGet.app (OSX): when the option "Show web-interface on start" was active, it took too long to initialize and could result in an error message "Could not establish connection to background process" 2013-10-03 20:35:01 +00:00
Andrey Prygunkov
8273dcfdfc fixed: error reading diskstate when upgrading from r808 or an older version 2013-10-03 13:34:33 +00:00
Andrey Prygunkov
81e2dc3635 improved parsing of multi-episodes from titles when generating dupekeys using item-options "rageid" or "series" and season/episode numbers 2013-10-01 16:52:23 +00:00
Andrey Prygunkov
94611cd80b fixed: when generating dupekeys with item-options "rageid" or "series" the season/episode numbers were not parsed from title if they were not used in the filter string 2013-10-01 16:49:12 +00:00
Andrey Prygunkov
28af81142f added two new item-options for RSS filter rules "Accept" and "Options": option "rageid" generates duplicate key using custom rageid and season/episode numbers; option "series" generates duplicate key using custom series name (any unique string) and season/episode numbers 2013-09-30 19:38:26 +00:00
Andrey Prygunkov
b3fd3ec0ba hiding badges for dupekeys in downloads/history lists if option "DupeCheck" is disabled 2013-09-30 19:31:27 +00:00
Andrey Prygunkov
44518d5b33 fixed: if download failed an existing queued duplicate was not automatically unpaused 2013-09-30 19:29:53 +00:00
Andrey Prygunkov
323e74f50f fixed: space character was not separator in word search mode in RSS filter 2013-09-29 10:47:25 +00:00
Andrey Prygunkov
a972e755d1 changes in duplicate handling: 1) when comparing two items if the both have dupekey only dupekeys ae compared (names not checked); 2) when a new item without dupekey is added and there is another items with the same name having dupekey its dupekey was copied to new item, this is disabled now; 3) fixed: command "Mark as Good" in history removed all duplicates but should remove only records with status "DUPE" 2013-09-28 21:16:16 +00:00
Andrey Prygunkov
49b6292f7f changes in duplicate handling: removed internal field "DupeMark" showing the item having duplicates; this flag was not always in sync with reality and it was used only to show (or not) badges with duplicate key in web-interface; now badges are always shown for items having non-empty duplicate keys; the badges becomes red if duplicate mode is set to "force" 2013-09-28 19:53:20 +00:00
Andrey Prygunkov
dd27dc1503 removed command "Unmark Duplicate" from actions menu and from command line syntax; the duplicate mark is removed automatically once the duplicate mode is set to "force"; otherwise manually removing duplicate mark does not make much sense since the titles are checked for duplicates anyway 2013-09-27 20:45:24 +00:00
Andrey Prygunkov
547ed1fd26 fixed compiler warning 2013-09-27 20:25:10 +00:00
Andrey Prygunkov
c387b0d069 duplicate properties (dupekey, dupescore and dupemode) can now be viewed and changed in download-edit-dialog and history-edit-dialog via new command "Duplicate properties" in actions menu 2013-09-27 19:56:16 +00:00
Andrey Prygunkov
ba2af4d84d fixed: in rss feed filter the substitution variable did not work 2013-09-26 21:06:43 +00:00
Andrey Prygunkov
20b7c6a823 do not showing dupemode in the build-filter-dialog if the mode is set to default value "score" 2013-09-26 20:22:28 +00:00
Andrey Prygunkov
b2edab0452 1) refactor: moved feed related classes from unit "DownloadInfo" to new unit "FeedInfo"; 2) rss filter fields "season" and "episode" are now available for all feeds (not restricted to newznab); if the feed does not have the fields, they are automatically parsed from feed title; 3) fields "season" and "episode" can now be used as substitution variables in option "dupekey" of rss filter command "Options" 2013-09-26 19:37:25 +00:00
Andrey Prygunkov
a72dc67268 added new search fields to RSS feed filter: imdbid, priority, dupekey and dupescore 2013-09-26 19:15:59 +00:00
Andrey Prygunkov
11b9745268 added OR-operator and groups (braces) to RSS filter syntax 2013-09-26 19:10:26 +00:00
Andrey Prygunkov
1d1d49a3c9 addition to r755: fixed: passwords containing special characters such as TAB were not properly read from nzb-files metatag 2013-09-24 20:42:32 +00:00
Andrey Prygunkov
31a4e7a22c by adding nzb-file to queue via RPC-methods "append" and "appendurl" the actual format of the file is checked and if nzb-format is detected the file is added even if it does not have .nzb extension 2013-09-24 19:35:54 +00:00
Andrey Prygunkov
dca13f0749 better handling of queued duplicates in command "Mark as bad" 2013-09-24 19:33:35 +00:00
Andrey Prygunkov
d377eee11c when adding nzb-file in in dupemode "score" the file is skipped if dup-history has a success-item with the same or higher score and recent history does not have a success-duplicate 2013-09-24 19:31:56 +00:00
Andrey Prygunkov
196052efed removed prefix "dupe" from badges in download and history lists 2013-09-24 19:28:36 +00:00
Andrey Prygunkov
694c5104fa fixed: incorrect health calculation for downloads consisting of only par-files 2013-09-23 20:26:29 +00:00
Andrey Prygunkov
acbf765851 fixed compiler warning 2013-09-23 20:24:31 +00:00
Andrey Prygunkov
ef06dfb7b3 replaced parameter "NoDupeCheck (yes, no)" with "DupeMode (score, all, force)" when adding nzb-files to queue using RPC-methods "append" and "appendurl"; changed option "nodupe" to "dupemode" in RSS filter commands "Append" and "Options" 2013-09-23 20:18:54 +00:00
Andrey Prygunkov
613432ef2c tuned algorithm calculating maximum threads limit to allow more threads for backup server connections (related to option "TreadLimit" removed in v11); this may sometimes increase speed when backup servers were used 2013-09-22 13:39:43 +00:00
Andrey Prygunkov
512dc87b3f fixed: items were removed from history too soon (option "KeepHistory") 2013-09-22 12:59:30 +00:00
Andrey Prygunkov
480f034301 fixed few compiler warnings 2013-09-20 21:55:47 +00:00
Andrey Prygunkov
39275ce133 improved smart duplicates features: added functions "Mark as Bad" and "Mark as Good" for history items; when a history item having success-status is marked as bad: 1) it is considered as failure by any duplicate check performed later; 2) if history has duplicates with dupe-status (dupe-backups) they are all moved (as paused) to download queue and one of them (with the highest duplicate score) is unpaused (downloaded); when a history item is marked as good: 1) it is considered as success by any duplicate check performed later; 2) no other duplicates will be added to history as dupe-backups anymore; 3) if history has duplicates with dupe-status (dupe-backups) they are all removed from recent history (moved to dup-history); new actions "HistoryMarkBad" and "HistoryMarkGood" in RPC-method "editqueue"; new actions "B" and "G" of command "--edit/-E" for history items (subcommand "H") 2013-09-20 20:45:07 +00:00
Andrey Prygunkov
c73fa0b42d addition to r817: fixed: error by reading dup-history from disk state 2013-09-19 19:45:31 +00:00
Andrey Prygunkov
3d4ed43337 changed format of generated dupekey for tv shows; now using season and episode exactly as they are passed by rss feed 2013-09-18 20:47:46 +00:00
Andrey Prygunkov
74067fd515 source nzb-files are now deleted when download-item leaves queue and history (option "NzbCleanupDisk") 2013-09-18 20:27:47 +00:00
Andrey Prygunkov
9538771eef refactor: addition to r825: small optimization 2013-09-18 20:09:32 +00:00
Andrey Prygunkov
7ecb968e23 if download was deleted by duplicate check its status in the history is now shown as "DUPE" instead of just "DELETED" 2013-09-18 19:49:59 +00:00
Andrey Prygunkov
c9365732d9 option values in RSS filter command "Options" can now refer to pattern groups in regular expressions 2013-09-17 19:33:56 +00:00
Andrey Prygunkov
d41c13ac29 fixed: if duplicate check has prevented adding file to queue the unneeded disk state files were not deleted from queue directory 2013-09-17 19:18:33 +00:00
Andrey Prygunkov
1b2fa2b2e8 extended word/substring search in RSS feed filters with pattern character "#" which matches one digit 2013-09-17 19:06:11 +00:00
Andrey Prygunkov
b9a9113abe addition to r820: fixed: when old disk state was converted the content hashes were not initialized (bug introduced in r816) 2013-09-17 19:02:59 +00:00
Andrey Prygunkov
1dd9dbec6c option values in RSS filter command "Options" can now refer to pattern groups in search string 2013-09-16 20:18:58 +00:00
Andrey Prygunkov
84efe5447b fixed compiler warning 2013-09-16 19:51:27 +00:00
Andrey Prygunkov
61bab55d11 fixed: when old disk state was converted the content hashes were not initialized (bug introduced in r816) 2013-09-16 19:49:45 +00:00
Andrey Prygunkov
85d05153f7 changed syntax of option "dupekey" of command "Option" in RSS filter: option "dupekey" now sets duplicate key (overrides existing key); option "dupekey+" adds to existing duplicate key 2013-09-15 14:02:35 +00:00
Andrey Prygunkov
f9bc316c98 rss filter command "Options" can now increase priority and dupe scrore using new option names "priority+" and "dupecore+" 2013-09-15 08:50:06 +00:00
Andrey Prygunkov
c4adc8d9be improved detection of same nzb-files acquired from different sources (nzb-sites): 1) the order of individual files as well as the order of articles in nzb-files do not matter; 2) individual files having extensions listed in option "ExtCleanupDisk" are now excluded from content comparison (unless these are par2-files, which are never excluded) 2013-09-14 21:20:31 +00:00
Andrey Prygunkov
169719c62d improved duplicate check: when history item expires (as defined by option "KeepHistory") and the duplicate check is active (option "DupeCheck") the item is not completely deleted from history; instead the amount of stored data reduces to minimum required for duplicate check (about 150 bytes vs 2000 bytes for full history item); such old history items are not shown in web-interface by default (to avoid transferring of large amount of history items); new button "Old" in web-interface to show old history items; the items are marked with badge "DUP"; 2013-09-13 20:13:09 +00:00
Andrey Prygunkov
a509a491af improved duplicate check: 1) added check for nzb-file content to avoid queueing of exactly same files (even with different names); 2) when nzb-file is added with option "Disable duplicate check" the option now works only during adding, it does not exclude the file from later checks (when adding other files) 2013-09-12 15:54:14 +00:00
Andrey Prygunkov
aed8e26062 extended add-dialog with options "Add paused" and "Disable duplicate check" 2013-09-11 20:21:49 +00:00
Andrey Prygunkov
cec414126d improved smart duplicates: nzb-files now have field "DupeScore" which can be set from rss filter using command "Options"; items with higher duplicate scores are downloaded even if the history already has successfully downloaded item (with lower score); changed the syntax of rss filter command "Options" to allow use of more options (and easy add new options in the future); new options "DupeScore", "DupeKey" and "NoDupe" to fine tune duplicate handling; updated description of option "DupeCheck" 2013-09-11 20:18:52 +00:00
Andrey Prygunkov
6bf96ab808 addition to r811: fixed: items added from feed view dialog were not marked as duplicates 2013-09-09 20:52:51 +00:00
Andrey Prygunkov
d8d9f72985 added smart duplicates feature: similar nzb-files are automatically marked as duplicates; queue items can be also manually marked as duplicates using new commands in multi-edit-dialog (action menu); duplicate-mark be manually removed using using new command in multi-edit-dialog and edit-dialog (action menu); duplicates are added in paused state; if download of first duplicates fail, another duplicate is unpaused; if download succeeds all remaining duplicates are removed from queue; an item marked as duplicate has field "DupeKey" which has the same value for all duplicates of the title; this field is shown in web-interface near nzb-name (in a short form to save screen place); new actions "GroupMarkDupe" and "GroupUnMarkDupe" of RPC-command "editqueue" to manually mark/unmark duplicates; new subcommands "DM" and "DU" of command "--edit/-E" to manually mark/unmark duplicates;;; if url-download results in a file without nzb-extension a history item with status "Scan: skipped" is created to inform user about this fact; RPC-commands "listgroups", "postqueue" and "history" now return more info about nzb-item (many new fields); removed option "MergeNzb" because it conflicts with duplicate handling, items can be merged manually if necessary 2013-09-09 20:31:17 +00:00
Andrey Prygunkov
cecb2e8f4c failed article downloads are now logged as "detail" instead of "warning" to reduce number of warnings for downloads removed from server (DMCA); one warning is printed for a file with a summary of number of failed downloads for the file 2013-09-08 20:34:53 +00:00
Andrey Prygunkov
d9ae28d3ed fixed compiler errors when configured with switch "--disable-parcheck" 2013-09-03 20:51:51 +00:00
Andrey Prygunkov
761078625e addition to r807: corrected makefile 2013-09-01 09:28:06 +00:00
Andrey Prygunkov
be37a75928 set svn keywords 2013-08-31 21:14:39 +00:00
Andrey Prygunkov
884e9fb3c9 created NZBGet.app - NZBGet is now a user friendly Mac OSX application with easy installation and seamless integration into OS UI: works in background, accessible via menubar icon 2013-08-31 21:05:31 +00:00
Andrey Prygunkov
8821502a81 updated support for DNZB-Headers: removed "X-DNZB-Link", added "X-DNZB-Details" 2013-08-29 19:56:29 +00:00
Andrey Prygunkov
f66c012df6 pp-scripts can now set post-processing parameters by printing command "[NZB] NZBPR_varname=value"; this allows scripts which are executed sooner to pass data for scripts executed later 2013-08-28 22:27:50 +00:00
Andrey Prygunkov
baf996fc06 new env-var "NZBPP_FINALDIR" passed to pp-scripts 2013-08-28 21:59:02 +00:00
Andrey Prygunkov
e56a1d3274 refactor: small code rework in passing parameters to post-processing scripts 2013-08-28 20:11:05 +00:00
Andrey Prygunkov
b04af33fb5 addition to r794: removed mistakenly added pp-parameter "NZBPP_DELETED"; since post-processing is not performed for deleted downloads, this parameter has no use 2013-08-28 19:17:41 +00:00
Andrey Prygunkov
1f53d32a62 new option "ParRename" to force par-renaming as a first post-processing step (active by default); this saves an unpack attempt and is even more useful if unpack is disabled 2013-08-28 15:08:37 +00:00
Andrey Prygunkov
534aeb3d07 addition to r795: renamed option "ApprovedIP" to "AuthorizedIP" 2013-08-26 22:07:43 +00:00
Andrey Prygunkov
a3693d0a45 refactor: fixed compiler warnings regarding "printf" 2013-08-26 16:05:29 +00:00
Andrey Prygunkov
967a6bd4a4 fixed potential buffer overflow in remote client 2013-08-26 10:02:45 +00:00
Andrey Prygunkov
c589c9b9ec addition to r795: removed a (wrong) tip about router from option decription 2013-08-24 18:56:47 +00:00
Andrey Prygunkov
d10ad4835b added new option "ApprovedIP" to set the list of IP-addresses which may connect without authorization 2013-08-23 22:05:29 +00:00
Andrey Prygunkov
38a273b195 added collecting of server usage statistical data for each download: number of successful and failed article downloads per news server; new page in history dialog shows collected statistics; new fields in RPC-method "history": ServerStats (array), TotalArticles, SuccessArticles, FailedArticles; new env. vars passed to pp-scripts: NZBPP_TOTALARTICLES, NZBPP_SUCCESSARTICLES, NZBPP_FAILEDARTICLES and per used news server: NZBPP_SERVERX_SUCCESSARTICLES, NZBPP_SERVERX_FAILEDARTICLES; also new env.vars: DELETED, HEALTHDELETED 2013-08-16 21:53:32 +00:00
Andrey Prygunkov
9618f46188 fixed scrolling to the top of page happening by clicking on items in downloads/history lists and on action-buttons in edit-download and history dialogs 2013-08-15 17:21:01 +00:00
Andrey Prygunkov
2562b384bc addition to r779: fixed: health were not shown for items with status "PP-QUEUED" 2013-08-14 22:00:39 +00:00
Andrey Prygunkov
bc8d133b69 post-processing progress label is now automatically trimmed if it doesn't fill into one line; this avoids layout breaking if the text is too long 2013-08-14 21:10:02 +00:00
Andrey Prygunkov
beb9967ad0 addition to r775: fixed: the confirmation by leaving settings page could be sometimes shown even if there were no changes made 2013-08-14 21:07:00 +00:00
Andrey Prygunkov
433baf0923 added support for http redirects when fetching URLs 2013-08-13 17:22:45 +00:00
Andrey Prygunkov
423da8b785 addition to r782: fixed: when adding files to queue the info about category and priority could get lost for some files 2013-08-12 18:39:17 +00:00
Andrey Prygunkov
f4708d2b1a fixed: final directory were not properly shown (Windows only) (bug introduced in r765) 2013-08-12 18:35:28 +00:00
Andrey Prygunkov
b00b7f7c31 critical health was sometimes not calculated properly on certain CPU architectures (mipsel) 2013-08-11 14:58:19 +00:00
Andrey Prygunkov
ec4110cb2c 1) when a duplicate file was detected in collection it was automatically deleted (if option DupeCheck is active) but the total size of collection was not updated; 2) when deleting individual files the total count of files in collection was not updated 2013-08-10 20:30:33 +00:00
Andrey Prygunkov
b2f02c7fa6 addition to r779: added calculation of critical health for old items in download queue (added to queue with program versions older than r779) 2013-08-10 20:03:14 +00:00
Andrey Prygunkov
aeb561c240 added automatic par-renaming of extracted files if archive includes par-files 2013-08-10 09:04:33 +00:00
Andrey Prygunkov
97a6abca0e fixed: when multiple nzb-files were added via URL (rss including) at the same time the info about category and priority could get lost for some of files 2013-08-09 20:37:41 +00:00
Andrey Prygunkov
5aa3a29288 addition to r779: added missing include to avoid compilation error on some systems 2013-08-08 21:46:42 +00:00
Andrey Prygunkov
bc49e7c48e all table columns except "Name" now have fixed widths to avoid annoying layout changes especially during post-processing when long status messages are displayed in the name-column 2013-08-08 21:23:29 +00:00
Andrey Prygunkov
9ba10446e9 added download health monitoring: health indicates download status, whether the file is damaged and how much; new option "HealthCheck" to define what to do with bad downloads (pause, delete, none); par-check is now automatically started for downloads having health below 100%, this works independently of unpack (even if unpack is disabled); for downloads having health less than critical health no par-check is performed (it would fail); new fields "Health" and "CriticalHealth" are returned by RPC-Method "listgroups"; new fields "Health", "CriticalHealth", "Deleted" and "HealthDeleted" are returned by RPC-Method "history"; new parameters "NZBPP_HEALTH" and "NZBPP_CRITICALHEALTH" are passed to pp-scripts; manually deleted downloads now have history status "deleted" (instead of "unknown") 2013-08-08 21:09:36 +00:00
Andrey Prygunkov
a4c686876f added filter buttons to messages tab (info, warning, etc.); also changed the color of filter buttons in feed view and feed filter dialogs (from blue to black) 2013-08-07 20:09:43 +00:00
Andrey Prygunkov
f31ba7dea3 small correction in help text 2013-08-05 20:49:28 +00:00
Andrey Prygunkov
897946c1ce added fields "rageid", "season", "episode" and command "=" to rss filters 2013-08-05 18:41:02 +00:00
Andrey Prygunkov
802266e3aa added confirmation dialog by leaving settings page if there are unsaved changes 2013-08-05 18:09:10 +00:00
Andrey Prygunkov
d9f89f7457 added menu "View" to settings page which allows to switch to "Compact Mode" when option descriptions are hidden 2013-08-05 18:05:04 +00:00
Andrey Prygunkov
b871d84379 added support for fields "rating" and "genre" in rss filters 2013-08-04 21:41:50 +00:00
Andrey Prygunkov
0375309060 fixed: rss filter commands "<=" and ">=" did not work 2013-08-04 15:35:07 +00:00
Andrey Prygunkov
a5ca653df8 fixed: crash on certain invalid rss filter strings 2013-08-03 21:06:35 +00:00
Andrey Prygunkov
ec194a15fb fixed: colons in regular expressins could cause incorrect parsing of rss filters 2013-08-03 09:56:40 +00:00
Andrey Prygunkov
eaf5d71b40 small changes in button captions: edit dialogs called from settings page (choose script, choose order, build rss filter) now have buttons "Discard/Apply" instead of "Close/Save"; in all other dialogs button "Close" renamed to "Cancel" unless it was the only button in dialog 2013-08-02 21:03:58 +00:00
Andrey Prygunkov
7a9ee279ed reversed the order of priorities in comboboxes in dialogs: the highest priority - at the top, the lowest - at the bottom 2013-08-02 16:46:24 +00:00
Andrey Prygunkov
827acdadea 1) added multiline filters for RSS feeds; new dialog to build filters in web-interface; 2) refactor: the length of configuration option values is now unlimited (previously was limited to 1000 characters; unlimited needed for long feed filters) 2013-08-02 15:54:11 +00:00
Andrey Prygunkov
ef99b2057a addition to r765: fixed small memory leak 2013-07-29 15:58:59 +00:00
Andrey Prygunkov
c938714b70 pp-scripts which move files can now inform the program about new location by printing text "[NZB] FINALDIR=/path/to/files"; the final path is then shown in history dialog instead of download path 2013-07-28 21:27:12 +00:00
Andrey Prygunkov
21e3dd30fd fixed: URLs for nzb-files were not properly read from RSS feeds of certains sites 2013-07-28 17:47:56 +00:00
Andrey Prygunkov
b271ab4721 addition to r757: fixed: statistic dialog had a scroll bar 2013-07-27 21:57:40 +00:00
Andrey Prygunkov
3abe382f44 program can now be stopped via web-interface: new button "shutdown" in section "SYSTEM" 2013-07-27 16:19:27 +00:00
Andrey Prygunkov
88a6b702d2 updated VC project file 2013-07-26 21:34:52 +00:00
Andrey Prygunkov
497d1af8bf fixed: download could hang if there were defined active news servers with 0 connections (ServerX.Active=yes, ServerX.Connections=0) (bug introduced in r743) 2013-07-26 20:38:57 +00:00
Andrey Prygunkov
cc4b6acd14 options "DeleteCleanupDisk" and "NzbCleanupDisk" are now active by default (in the example config file) 2013-07-26 20:14:32 +00:00
Andrey Prygunkov
1714e2331c combined rss filter commands @ and " into one command to make filters more intuitive 2013-07-26 20:09:36 +00:00
Andrey Prygunkov
4e419ec16d small change in css: slightly reduced the max height of modal dialogs to better work on notebooks 2013-07-25 20:13:26 +00:00
Andrey Prygunkov
5e0f214a8f fixed: malformed nzb-file could cause a memory leak 2013-07-25 19:20:06 +00:00
Andrey Prygunkov
da1727e5e4 added support for metatag "password" in nzb-files 2013-07-25 18:32:07 +00:00
Andrey Prygunkov
101be2eeb1 added confirmation by deleting download or history item from edit-dialog 2013-07-25 18:25:13 +00:00
Andrey Prygunkov
e69015204a when saving/restoring the feed status (last update time) the feeds are identified by url and filter (previously only by url) 2013-07-25 18:22:56 +00:00
Andrey Prygunkov
1b203e3292 fully implemented feed filters 2013-07-24 21:32:15 +00:00
Andrey Prygunkov
1ad8bd212c refactor: small rework of NZBParameterList-class 2013-07-24 21:09:56 +00:00
Andrey Prygunkov
3711f30d01 new logo (thanks dogzipp for the logo) 2013-07-24 21:01:27 +00:00
Andrey Prygunkov
85d59d25df 1) DirectNZB headers X-DNZB-MoreInfo, X-DNZB-Report and X-DNZB-Link are now processed when downloading URLs and the links "More Info", "External Link" and "Report This NZB" are shown in download-edit-dialog and in history-dialog; 2) combined all footer buttons into one button "Actions" with menu: in download-edit-dialog: "Pause/Resume", "Delete" and "Cancel Post-Processing", in history-dialog: "Delete", "Post-Process Again" and "Download Remaining Files (Return to Queue)" 2013-07-23 21:21:14 +00:00
Andrey Prygunkov
6d7f55a435 added missing svn-keywords 2013-07-22 20:39:49 +00:00
Andrey Prygunkov
c22ca18a82 added filtering for RSS feeds via new option "FeedX.Filter" (not all filter commands are implemented yet but this is mentioned in the option description) 2013-07-22 20:38:21 +00:00
Andrey Prygunkov
ec48959600 changed the way how option "Unpack" works: instead of enabling/disabling the unpacker as a whole, it now defines the initial value of post-processing parameter "Unpack" for nzb-file when it is added to queue; this makes it now possible to disable Unpack globally but still enable it for selected nzb-files; new option "CategoryX.Unpack" to set unpack on a per category basis 2013-07-21 20:44:13 +00:00
Andrey Prygunkov
67634c4a71 fixed compilation error on Linux (bug introduced in r743) 2013-07-20 14:05:21 +00:00
Andrey Prygunkov
c31d38a562 fixed: certain characters printed by pp-scripts could crash the program 2013-07-20 14:02:26 +00:00
Andrey Prygunkov
6b0499b82e news servers can now be temporarily disabled via speed limit dialog without reloading of the program; new option "ServerX.Active" to disable servers via settings; new option "ServerX.Name" to use for logging and in UI 2013-07-20 07:15:21 +00:00
Andrey Prygunkov
046364283f fixed: choosing local files didn't work in Opera 2013-07-18 19:21:38 +00:00
Andrey Prygunkov
85880f9bd1 in RPC-Method "appendurl" parameter "addtop" adds nzb to the top of the main download queue (not only to the top of the URL queue) 2013-07-17 19:12:23 +00:00
Andrey Prygunkov
5fd436e5c5 added switch "Titles/Filenames" to feed view dialog for rss feeds with "bad" titles 2013-07-17 19:11:35 +00:00
Andrey Prygunkov
01533cbf9f better parsing of rss feeds of certain nzb-sites (now using enclosure-tag if possible) (Windows only) 2013-07-17 19:02:41 +00:00
Andrey Prygunkov
f5c8276fdc addition to r734: fixed possible matching bug 2013-07-16 22:18:09 +00:00
Andrey Prygunkov
05f2b81025 better parsing of rss feeds of certain nzb-sites (now using enclosure-tag if possible) (POSIX only) 2013-07-16 21:41:53 +00:00
Andrey Prygunkov
9dfd6cecad added filter buttons (all, new, fetched, backlog) to feed view dialog 2013-07-16 21:11:08 +00:00
Andrey Prygunkov
2febf837e5 fixed: restoring of settings didn't work for multi-sections (servers, categories, etc.) if they were empty 2013-07-16 18:47:52 +00:00
Andrey Prygunkov
ac954bba11 refactor: small speed/memory optimization in aliases support for categories 2013-07-16 18:46:41 +00:00
Andrey Prygunkov
2bda4fef5b made alias-matching case-insensitive 2013-07-15 22:27:14 +00:00
Andrey Prygunkov
5a815592dc added new option "CategoryX.Aliases" to configure category name matching with nzb-sites; especially useful with rss feeds 2013-07-15 21:28:55 +00:00
Andrey Prygunkov
3f4c6ce144 added more debug logging to feed manager 2013-07-15 21:28:26 +00:00
Andrey Prygunkov
19e0c53d1e destination directory for option "CategoryX.DestDir" is not checked/created on program start anymore (only when a download starts for that category); this helps when certain categories are configured for external disks, which are not always connected 2013-07-15 20:07:50 +00:00
Andrey Prygunkov
95963b2289 download queue is now saved in a more secure way to avoid potential loss of queue if the program crashes during saving of queue 2013-07-15 19:53:01 +00:00
Andrey Prygunkov
4cd21cad9c fixed: crash after downloading of an URL (happen only on certain systems) 2013-07-15 19:13:36 +00:00
Andrey Prygunkov
fcf1d7d502 improved compatibility with certain nzb-sites when fetching nzb-files (original nzb-filenames were sometimes not detected properly) 2013-07-14 21:27:13 +00:00
Andrey Prygunkov
e92d04771d toolbar button "Rss Feeds" is now visible only if there are feeds defined in settings 2013-07-14 18:39:07 +00:00
Andrey Prygunkov
ce43190ca6 fixed: crash after executing of remote commands (bug introduced in r722) 2013-07-14 15:47:49 +00:00
Andrey Prygunkov
284dbbad24 addition to r722: added missing file to Makefile 2013-07-14 07:08:10 +00:00
Andrey Prygunkov
1e115a5eab addition to r722: added missing file 2013-07-14 06:39:36 +00:00
Andrey Prygunkov
f18a355469 added rss feeds support: 1) new options "FeedX.Name", "FeedX.URL", "FeedX.Interval", "FeedX.PauseNzb", "FeedX.Category", "FeedX.Priority" (section "Rss Feeds"); 2) new option "FeedHistory" (section "Download Queue"); 3) Button "Preview Feed" on settings tab near each feed definition; 4) new toolbar button "Feeds" on downloads tab with menu to view feeds or fetch new nzbs from all feeds (the button is visible only if there are feeds defined in settings); 5) new dialog to see feed content showing status of each item (new, fetched, backlog) with ability to manually fetch selected items 2013-07-13 22:00:49 +00:00
Andrey Prygunkov
8ba95bb82a updated version string to 12.0-testing 2013-07-12 21:16:55 +00:00
Andrey Prygunkov
ee5a2a320e updated version string (preparing to release 11.0) 2013-07-01 19:18:41 +00:00
Andrey Prygunkov
738fd3da58 updated ChangeLog 2013-07-01 18:48:07 +00:00
Andrey Prygunkov
decc08934c removed a superfluous page scroll after clicking on option in web-interface settings 2013-06-26 20:52:10 +00:00
Andrey Prygunkov
5f5b7f92cf improved configure-script: defining of symbol "FILE_OFFSET_BITS=64", required on some systems, is not necessary anymore 2013-06-20 18:18:05 +00:00
Andrey Prygunkov
20fa280171 imporved error message in web-interface displayed when the template configuration file could not be found 2013-06-19 18:24:50 +00:00
Andrey Prygunkov
e5fa2ef750 fixed: support for splitted files (.001, .002, etc.) were broken 2013-06-18 21:56:23 +00:00
Andrey Prygunkov
bd31e25757 configure-script now defines "SIGCHLD_HANDLER" by default on all systems including BSD; this eliminates the need of configure-parameter "--enable-sigchld-handler" on 64-Bit BSD; the trade-off: 32-Bit BSD now requires "--disable-sigchld-handler" 2013-06-18 19:06:11 +00:00
Andrey Prygunkov
5b3113d96b 1) when a nzb-file is added via web-interface or via remote call the file is now put into incoming nzb-directory (option "NzbDir") and then scanned; this has two advantages over the old behavior when the file was parsed directly in memory: the file serves as a backup for troubleshootings and the file is processed by nzbprocess-script (if defined in option "NzbProcess") making the pre-processing much easier; 2) new env-var parameters are passed to NzbProcess-script: NZBNP_NZBNAME, NZBNP_CATEGORY, NZBNP_PRIORITY, NZBNP_TOP, NZBNP_PAUSED; 3) new commands for use in NzbProcess-scripts: "[NZB] TOP=1" to add nzb to the top of queue and "[NZB] PAUSED=1" to add nzb-file in paused state 2013-06-17 20:39:46 +00:00
Andrey Prygunkov
84b4f7695b when a nzb-file whose name ends with ".queued" is added via web-interface the ".queued"-part is automatically removed 2013-06-16 13:00:57 +00:00
Andrey Prygunkov
fc8ea3bcd0 fixed: if an error occurs when a RPC-client or web-browser communicates with nzbget the program could crash 2013-06-15 15:07:11 +00:00
Andrey Prygunkov
9051a4df4d fixed: if the last file of collection was detected as duplicate after the download of the first article the file was deleted from queue (that's OK) but the post-processing was not triggered (that's a bug) 2013-06-13 20:51:38 +00:00
Andrey Prygunkov
a7b42b6c97 fixed: pp-scripts "Logger.py" and "EMail.py" failed trying to get post-processing log from nzbget if option "ControlUsername" were set to a non-default value 2013-06-12 20:25:06 +00:00
Andrey Prygunkov
f7675b1e46 addition to r693: fixed: unicode characters were not properly encoded in JSON-RPC response 2013-06-12 20:14:45 +00:00
Andrey Prygunkov
db1117d892 new option "ControlUsername" to define login user name (if you don't like default username "nzbget") 2013-06-05 21:09:28 +00:00
Andrey Prygunkov
4b14e19229 removed option "RenameBroken"; it caused problems in par-checker (the option existed since early program versions before the par-check was added) 2013-06-04 21:04:00 +00:00
Andrey Prygunkov
606021fb8a removed option "AppendNzbDir"; if it was disabled that caused problems in par-checker and unpacker; the option is now assumed always active 2013-06-04 20:56:17 +00:00
Andrey Prygunkov
b15499c1dd removed option "ProcessLogKind"; scripts should use prefixes ([INFO], [DETAIL], etc); messages printed without prefixes are added as [INFO] 2013-06-03 20:56:56 +00:00
Andrey Prygunkov
950588cb65 addition to r698: if options of the section "Terminal" were missed in the config file, they were written with empty values causing warnings on program start 2013-06-03 20:35:18 +00:00
Andrey Prygunkov
7991c06543 fixed: in the option "NzbAddedProcess" the env-var parameter with nzb-name was passed in "NZBNA_NAME", should be "NZBNA_NZBNAME"; the old parameter name "NZBNA_NAME" is still supported for compatibility 2013-06-02 21:11:09 +00:00
Andrey Prygunkov
b335c4ca05 updated svn log URL 2013-06-02 21:10:14 +00:00
Andrey Prygunkov
cf3773dd28 added functions to backup and restore settings from web-interface; when restoring it's possible to choose what sections to restore (for example only news servers settings or only settings of a certain pp-script) or restore the whole configuration 2013-06-02 19:20:31 +00:00
Andrey Prygunkov
2a3740e49f added check for directory existence in pp-script <Logger> to avoid script failure if the directory was deleted by one of the previous scripts 2013-05-28 20:22:59 +00:00
Andrey Prygunkov
bcbd30ff6e addition to r694: fixed: a directory check/creation could fail if the directory was just created in another thread 2013-05-26 21:10:08 +00:00
Andrey Prygunkov
571ab9602f 1) additional comment to r693 (ArticleDownloader.cpp, line 632): fixed: the program could hang if the destination file could not be created; 2) improved thread synchronisation to avoid (short-time) lockings of the program during creation of destination files 2013-05-26 20:42:15 +00:00
Andrey Prygunkov
cfab6a3bb6 more detailed error message if a directory could not be created (<DstDir>, <NzbDir>, etc.); the message includes error text reported by OS such as <permission denied> or similar 2013-05-26 13:47:23 +00:00
Andrey Prygunkov
7c9ab59aff addition to r649 (unicode support in XML-RPC and JSON-RPC): fixed a typo which could prevent the filtering of invalid xml-characters 2013-05-23 20:47:42 +00:00
Andrey Prygunkov
7b1d1129a8 when unpacking the unpack start time is now measured after receiving of unrar copyright message; this provides better unpack time estimation in a case when user uses unpack-script to do some things before executing unrar (for example sending Wake-On-Lan message to the destination NAS); it works with unrar only, it's not possible with 7-Zip because it buffers printed messages 2013-05-23 20:40:46 +00:00
Andrey Prygunkov
bf3e8fe3a9 the maximum number of download threads are now managed automatically taking into account the number of allowed connections to news servers; removed option <ThreadLimit> 2013-05-22 20:25:19 +00:00
Andrey Prygunkov
baeac17d5b when the program is reloaded, a message with version number is printed like on start 2013-05-22 20:09:21 +00:00
Andrey Prygunkov
68ce6dea4b if a communication error occurs in web-interface, it retries multiple times before giving up with an error message 2013-05-21 20:41:08 +00:00
Andrey Prygunkov
00df4b8920 small correction in a log-message: removed <Request:> from message <Request: Queue collection...> 2013-05-21 20:35:42 +00:00
Andrey Prygunkov
36814514b7 new parameter (env. var) <NZBPP_NZBID> is passed to pp_scripts and contains an internal ID of NZB-file 2013-05-21 20:34:42 +00:00
Andrey Prygunkov
381a9a28b0 pp-scripts terminated with unknown status are now considered failed (status=FAILURE instead of status=UNKNOWN) 2013-05-21 20:32:41 +00:00
Andrey Prygunkov
5c364896d3 added support for rar-files with non-standard extensions (such as .001, etc.) 2013-05-21 20:21:52 +00:00
Andrey Prygunkov
07ce1d44a9 fixed: remote command <--list> for history items may fail with segfault on certain par-status 2013-05-16 20:57:04 +00:00
Andrey Prygunkov
1348ac86f7 added setting of post-processing parameters for history items; pp-parameters can now be viewed and changed in history dialog in web-interface; useful before post-processing again; new action <HistorySetParameter> in RPC-method <editqueue>; new action <O> in remote command <--edit/-E> for history items (subcommand <H>) 2013-05-16 20:54:13 +00:00
Andrey Prygunkov
b4c4855a9b option <ControlPassword> can now be set to en empty value to disable authentication; useful if nzbget works behind other web-server with its own authentication 2013-05-15 19:45:48 +00:00
Andrey Prygunkov
7a1001a70b fixed: error in IE when loading web-interface (bug introduced in r673) 2013-05-15 16:51:13 +00:00
Andrey Prygunkov
340a8130e9 addition to r677: added missing headers, causing compilation error in newer gcc versions 2013-05-15 16:45:36 +00:00
Andrey Prygunkov
9eb8de27d2 addition to r676: fixed crash on Linux with uClibc 2013-05-14 20:52:39 +00:00
Andrey Prygunkov
476a43a5bf configuration can now be saved in web-interface even if there were no changes made but if obsolete or invalid options were detected in the config file; the saving removes invalid entries from config file 2013-05-14 20:27:23 +00:00
Andrey Prygunkov
9ab955d026 refactor: more consistent using of c-headers 2013-05-14 20:20:52 +00:00
Andrey Prygunkov
bcedb32cf0 1) fixed compilation error on Linux (bug introduced in r675); 2) refactor: small corrections in class <ParCoordinator> 2013-05-14 20:18:17 +00:00
Andrey Prygunkov
6bc266f13c Par-checker and renamer now add messages into the log of pp-item (like unpack- and pp-scripts-messages); these message now appear in the log created by scripts Logger.py and EMail.py 2013-05-13 21:00:08 +00:00
Andrey Prygunkov
ed36feeb0a fixed: by deleting of a partially downloaded nzb-file from queue, when the option <DeleteCleanupDisk> was active, the file <_brokenlog.txt> was not deleted preventing the directory from automatic deletion 2013-05-13 20:13:28 +00:00
Andrey Prygunkov
85400cd8f6 fixed: for pp-scripts saved using windows line endings (CR,LF) the descriptions of options were not displayed correctly on settings page 2013-05-08 21:53:18 +00:00
Andrey Prygunkov
2866697b32 fixed: crash when adding malformed nzb-files with certain structure (Windows only) 2013-05-08 21:51:48 +00:00
Andrey Prygunkov
f1a99e1194 when deleting downloads via web-interface a proper hint regarding deleting of already downloaded files from disk depending on option <DeleteCleanupDisk> is displayed 2013-05-08 21:50:47 +00:00
Andrey Prygunkov
db47ddf3dc when download is resumed in web-interface the option <ParCheck=Force> is respected and all par2-files are resumed (not only main par2-file) 2013-05-08 21:45:22 +00:00
Andrey Prygunkov
2cc4dbd2ba made pp-scripts EMail.py and Logger.py compatible with python3 (python2 is OK too) 2013-05-07 18:02:15 +00:00
Andrey Prygunkov
a38eef2971 fixed: symbol <DISABLE_TLS> must be defined in project settings, defining it in <win32.h> didn't work properly (Windows only) 2013-05-06 19:52:54 +00:00
Andrey Prygunkov
e3e197a917 1) option <ExtCleanupDisk> now checks not only file extensions but any substring at the end of file name (in particular this allows to delete file _brokenlog.txt); 2) fixed: when option <InterDir> was used the files extracted from archives were not processed/deleted by option <ExtCleanupDisk> 2013-05-06 18:58:18 +00:00
Andrey Prygunkov
361d0befb6 addition to r665: fixed: crash on starting of download 2013-05-06 18:32:16 +00:00
Andrey Prygunkov
ba35c662ea small improvement in multithread synchronisation: do not create mutexex for each file-object but instead only for active objects (which are being downloaded at the moment) 2013-05-05 21:17:23 +00:00
Andrey Prygunkov
45ce763c71 small improvement in multithread synchronisation of download queue 2013-05-05 21:16:02 +00:00
Andrey Prygunkov
73c85a0013 fixed: when a duplicate file was detected during download the program could hang 2013-05-05 20:59:22 +00:00
Andrey Prygunkov
26361630c2 addition to r660: fixed: downloads were always checked when option <ParCheck> was set to <Auto> 2013-05-04 07:45:00 +00:00
Andrey Prygunkov
96c30c509b fixed: spaces in option <ExtCleanupDisk> prevented its correct operation 2013-05-02 20:48:32 +00:00
Andrey Prygunkov
d9b9786486 improved par-check: added support for manual par-check; if option <ParCheck> is set to <Manual> and a damaged download is detected the program downloads all par2-files but doesn't perform par-check; the user must perform par-check/repair manually then (possibly on another, faster computer); old values <yes/no> of option <ParCheck> renamed to <Force> and <Auto> respectively; when set to <Force> all par2-files are always downloaded; removed option <LoadPars> since its functionality is now covered by option <ParCheck>; Result of par-check can now have new value <Manual repair necessary>; field <ParStatus> in RPC-method <history> can have new value <MANUAL>; parameter <NZBPP_PARSTATUS> for pp-script can have new value <4 = manual repair necessary>; extended pp-script <EMail.py> to handle ParStatus=4 (manual) 2013-05-02 20:40:36 +00:00
Andrey Prygunkov
bb9cea260d small improvements in formatting of option descriptions in web-interface 2013-05-02 20:03:51 +00:00
Andrey Prygunkov
958c2f97ec refactor: discarding of download queue is now less complicated and not depend on diskstate version 2013-04-30 20:12:07 +00:00
Andrey Prygunkov
27651f17bf improvement in JSON-/XML-RPC: all ID fields including NZBID are now persistent and remain their values after restart; this allows for third-party software to identify nzb-files by ID; method <history> now returns ID of NZB-file in the field <NZBID>; in versions up to 0.8.0 the field <NZBID> was used to identify history items in the edit-commands <HistoryDelete>, <HistoryReturn>, <HistoryProcess>; since version 9 field <ID> is used for this purpose; in versions 9-10 field <NZBID> still existed and had the same value as field <ID> for compatibility with version 0.8.0; the compatibility is not provided anymore; this change was needed to provide a consistent using of field <NZBID> across all RPC-methods 2013-04-30 20:10:10 +00:00
Andrey Prygunkov
71621f7bb5 eliminated a compiler warning 2013-04-30 20:06:54 +00:00
Andrey Prygunkov
d8add46215 automatic deletion of backup-source files after successful par-repair; important when repairing renamed rar-files since this could cause failure during unpack 2013-04-29 20:46:09 +00:00
Andrey Prygunkov
e459f570d5 improved unicode support in pp-script Logger.py 2013-04-29 19:25:59 +00:00
Andrey Prygunkov
5b5057dee0 fixed: failed to read download queue from disk if post-processing queue was not empty 2013-04-29 19:24:44 +00:00
Andrey Prygunkov
f21becb37d added link to catalog of pp-scripts to web-interface 2013-04-29 18:21:06 +00:00
Andrey Prygunkov
9d03eb1ad4 fixed: when option <InterDir> was active and the download after unpack contained rar-file with the same name as one of original files (sometimes happen with included subtitles) the original rar-file was kept with name <.rar_duplicate1> even if the option <UnpackCleanupDisk> was active 2013-04-28 20:29:19 +00:00
Andrey Prygunkov
cb90c5e616 when logging messages from a post-processing script, a short name of the script is now used as prefix if possible; a short name doesn't include subdirectory name or file extension; RPC-method <configtemplates> returns new field <DisplayName> representing the short name of the script which is recommended for using in UI 2013-04-26 20:01:53 +00:00
Andrey Prygunkov
77059f2db0 improved unicode support in XML-RPC and JSON-RPC 2013-04-26 19:56:41 +00:00
Andrey Prygunkov
fb72c36a48 if a news-server returns empty or bad article (this may be caused by errors on the news server), the program tries again from the same or other servers (in previous versions the article was marked as failed without other download attempts) 2013-04-26 19:55:30 +00:00
Andrey Prygunkov
b2b215a061 updated ChangeLog 2013-04-24 20:26:05 +00:00
Andrey Prygunkov
8d313e4cf8 removed pp-script Cleanup.sh (its functionality is now part of the main program) 2013-04-24 20:24:30 +00:00
Andrey Prygunkov
6bb760375e added option <ExtCleanupDisk> to automatically delete unwanted files (with specified extensions) after successful par-check or unpack 2013-04-24 20:16:04 +00:00
Andrey Prygunkov
025cd043d3 history dialog now shows status of every script 2013-04-23 18:20:52 +00:00
Andrey Prygunkov
3f368d4a8e fixed: download time in statistics were incorrect if the computer were put into standby (thanks Frank Kuypers for the patch) 2013-04-21 20:23:32 +00:00
Andrey Prygunkov
449e41e435 removed unused file from repository 2013-04-21 19:56:28 +00:00
Andrey Prygunkov
bf0062be52 addition to r639: eliminated a compiler warning 2013-04-21 19:43:14 +00:00
Andrey Prygunkov
3c025c8b52 updated forum URL in about dialog in web-interface 2013-04-19 18:53:56 +00:00
Andrey Prygunkov
6dc3d954c5 fixed: authorization to news-server was forced even when username/password were empty (bug introduced in r634) 2013-04-19 18:44:32 +00:00
Andrey Prygunkov
c9b7a11a89 fixed: by adding nzb-files with assigned category and empty option <CategoryX.DefScript> the global option <DefScript> should be used but it wasn't 2013-04-19 17:34:17 +00:00
Andrey Prygunkov
33cb2d108e fixed: if a download didn't have any par-files and the option <ParCheck> was active, the par-check was started anyway and then failed 2013-04-18 21:04:48 +00:00
Andrey Prygunkov
e053e74b58 fixed: scripts containg spaces in their names were not assigned to nzb-files by adding to queue (when defined in option <DefScript>) 2013-04-18 20:28:31 +00:00
Andrey Prygunkov
4e35fc2fbe improved multiscripts: 1) first level subfolders in the ppscripts-directory (option <ScriptDir>) are now scanned for scripts too; 2) only files containing script definition signature are considered scripts and are shown in web-interface; 3) these changes allows to easily install collections of scripts (scripts bundles by just putting a folder with multiple scripts into ppscripts-directory); 2013-04-18 20:24:30 +00:00
Andrey Prygunkov
4768d8e459 if username and password are defined for a news-server the authentication is now forced (in previous versions the authentication was performed only if requested by server); needed for servers supporting both anonimous (restricted) and authorized (full access) accounts 2013-04-17 20:34:15 +00:00
Andrey Prygunkov
3598cc1d85 refactor: restructured class <Connection> 2013-04-17 20:21:46 +00:00
Andrey Prygunkov
e3a895b88c updated README 2013-04-15 20:22:16 +00:00
Andrey Prygunkov
61eff3ddf0 removed old example post-processing script 2013-04-15 20:17:24 +00:00
Andrey Prygunkov
e9268984ae added post-processing scripts EMail.py and Logger.py 2013-04-15 20:16:06 +00:00
Andrey Prygunkov
f28b35bd28 reworked concept of post-processing scripts: multiple scripts can be assigned to each nzb-file; all assigned scripts are executed after the nzb-file is downloaded and internally processed (unpack, repair); option <PostProcess> is obsolete; new option <ScriptDir> sets directory where all pp-scripts must be stored; new option <DefScript> sets the default list of pp-scripts to be assigned to nzb-file when it's added to queue; new option <CategoryX.DefScript> to set the default list of pp-scripts on a category basis; the execution order of pp-scripts can be set using new option <ScriptOrder>; there are no separate configuration files for pp-scripts; configuration options and pp-parameters are defined in the pp-scripts; script configuration options are saved in nzbget configuration file (nzbget.conf); changed parameters list of RPC-methods <loadconfig> and <saveconfig>; new RPC-method <configtemplates> returns configuration descriptions for the program and for all pp-scripts; configuration of all scripts can be done in web-interface; the pp-scripts assigned to a particular nzb-file can be viewed and changed in web-interface on page <pp-parameters> in the edit download dialog; option <PostPauseQueue> renamed to <ScriptPauseQueue> (the old name is still recognized); new option <ConfigTemplate> to define the location of template configuration file (in previous versions it must be always stored in <WebDir>) 2013-04-15 20:06:05 +00:00
Andrey Prygunkov
a86618c2c2 fixed: when options <DirectWrite> and <ContinuePartial> were both active, a restart or reload of the program during download may cause damaged files in the active download 2013-04-08 20:19:05 +00:00
Andrey Prygunkov
1f1a4b8fb8 fixed potential segfault which could happen with file paths longer than 1024 characters 2013-04-07 15:14:20 +00:00
Andrey Prygunkov
a1d0be34c2 updated README 2013-04-07 15:10:42 +00:00
Andrey Prygunkov
ef0a04cc1c improved unicode (utf8) support: non-ascii characters are now correctly transferred via JSON-RPC; correct displaying of nzb-names and paths in web-interface; it is now possible to use non-ascii characters on settings page for option values (such as paths or category names) 2013-04-06 21:03:05 +00:00
Andrey Prygunkov
284262b7da added new feature <split download> which creates new download from selected files of source download; new command <Split> in web-interface in edit download dialog on page <Files>; new action <S> in remote command <--edit/-E>; new action <FileSplit> in JSON-/XML-RPC method <editqueue> 2013-04-06 20:54:00 +00:00
Andrey Prygunkov
58b0a17986 reworked post-processor queue: 1) only one job is created for each nzb-file; no more separate jobs are created for par-collections within one nzb-file; 2) option <AllowReProcess> removed; a post-processing script is called only once per nzb-file, this behavior cannot be altered anymore; 3) with a new feature <Split> (see next commits) individual par-collections can be processed separately in a more effective way than before 2013-04-06 20:25:07 +00:00
Andrey Prygunkov
57abe00c62 updated version string to 11.0-testing 2013-04-06 12:58:50 +00:00
Andrey Prygunkov
d014407ba4 updated ChangeLog 2013-03-31 14:37:57 +00:00
Andrey Prygunkov
2e8bfa16f9 fixed: articles with decoding errors (incomplete or damaged posts) caused infinite retry-loop in downloader 2013-03-31 14:36:15 +00:00
Andrey Prygunkov
bf34713b0c updated version string to 10.1 2013-03-31 14:10:57 +00:00
132 changed files with 27673 additions and 6508 deletions

View File

@@ -34,7 +34,7 @@
#include <stdlib.h>
#include <string.h>
#include <cstdio>
#include <stdio.h>
#ifdef WIN32
#include <direct.h>
#else
@@ -77,22 +77,10 @@ ArticleDownloader::~ArticleDownloader()
{
debug("Destroying ArticleDownloader");
if (m_szTempFilename)
{
free(m_szTempFilename);
}
if (m_szArticleFilename)
{
free(m_szArticleFilename);
}
if (m_szInfoName)
{
free(m_szInfoName);
}
if (m_szOutputFilename)
{
free(m_szOutputFilename);
}
free(m_szTempFilename);
free(m_szArticleFilename);
free(m_szInfoName);
free(m_szOutputFilename);
}
void ArticleDownloader::SetTempFilename(const char* v)
@@ -136,6 +124,9 @@ void ArticleDownloader::Run()
debug("Entering ArticleDownloader-loop");
SetStatus(adRunning);
BuildOutputFilename();
m_szResultFilename = m_pArticleInfo->GetResultFilename();
if (g_pOptions->GetContinuePartial())
@@ -155,18 +146,19 @@ void ArticleDownloader::Run()
int iRetries = g_pOptions->GetRetries() > 0 ? g_pOptions->GetRetries() : 1;
int iRemainedRetries = iRetries;
ServerPool::Servers failedServers;
Servers failedServers;
failedServers.reserve(g_pServerPool->GetServers()->size());
NewsServer* pWantServer = NULL;
NewsServer* pLastServer = NULL;
int iLevel = 0;
int iServerConfigGeneration = g_pServerPool->GetGeneration();
while (!IsStopped())
{
Status = adFailed;
SetStatus(adWaiting);
while (!IsStopped() && !m_pConnection)
while (!m_pConnection && !(IsStopped() || iServerConfigGeneration != g_pServerPool->GetGeneration()))
{
m_pConnection = g_pServerPool->GetConnection(iLevel, pWantServer, &failedServers);
usleep(5 * 1000);
@@ -174,7 +166,8 @@ void ArticleDownloader::Run()
SetLastUpdateTimeNow();
SetStatus(adRunning);
if (IsStopped() || g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2())
if (IsStopped() || g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2() ||
iServerConfigGeneration != g_pServerPool->GetGeneration())
{
Status = adRetry;
break;
@@ -188,10 +181,15 @@ void ArticleDownloader::Run()
bool bConnected = m_pConnection && m_pConnection->Connect();
if (bConnected && !IsStopped())
{
// Okay, we got a Connection. Now start downloading.
detail("Downloading %s @ server%i (%s)", m_szInfoName,
m_pConnection->GetNewsServer()->GetID(), m_pConnection->GetHost());
NewsServer* pNewsServer = m_pConnection->GetNewsServer();
detail("Downloading %s @ %s (%s)", m_szInfoName, pNewsServer->GetName(), m_pConnection->GetHost());
Status = Download();
if (Status == adFinished || Status == adFailed || Status == adNotFound || Status == adCrcError)
{
m_ServerStats.SetStat(pNewsServer->GetID(), Status == adFinished ? 1 : 0, Status == adFinished ? 0 : 1, false);
}
}
if (bConnected)
@@ -226,24 +224,21 @@ void ArticleDownloader::Run()
iRemainedRetries--;
}
if (!bConnected || (Status == adFailed && iRemainedRetries > 1))
if (!bConnected || (Status == adFailed && iRemainedRetries > 0))
{
pWantServer = pLastServer;
}
if (Status == adNotFound || Status == adCrcError || (Status == adFailed && iRemainedRetries == 0))
{
failedServers.push_back(pLastServer);
}
if (pWantServer && !IsStopped() &&
!(g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2()))
if (pWantServer &&
!(IsStopped() || g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2() ||
iServerConfigGeneration != g_pServerPool->GetGeneration()))
{
detail("Waiting %i sec to retry", g_pOptions->GetRetryInterval());
SetStatus(adWaiting);
int msec = 0;
while (!IsStopped() && (msec < g_pOptions->GetRetryInterval() * 1000) &&
!(g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2()))
while (!(IsStopped() || g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2() ||
iServerConfigGeneration != g_pServerPool->GetGeneration()) &&
msec < g_pOptions->GetRetryInterval() * 1000)
{
usleep(100 * 1000);
msec += 100;
@@ -252,7 +247,8 @@ void ArticleDownloader::Run()
SetStatus(adRunning);
}
if (IsStopped() || g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2())
if (IsStopped() || g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2() ||
iServerConfigGeneration != g_pServerPool->GetGeneration())
{
Status = adRetry;
break;
@@ -260,26 +256,31 @@ void ArticleDownloader::Run()
if (!pWantServer)
{
failedServers.push_back(pLastServer);
// if all servers from current level were tried, increase level
// if all servers from all levels were tried, break the loop with failure status
bool bAllServersOnLevelFailed = true;
for (ServerPool::Servers::iterator it = g_pServerPool->GetServers()->begin(); it != g_pServerPool->GetServers()->end(); it++)
for (Servers::iterator it = g_pServerPool->GetServers()->begin(); it != g_pServerPool->GetServers()->end(); it++)
{
NewsServer* pCandidateServer = *it;
if (pCandidateServer->GetLevel() == iLevel)
if (pCandidateServer->GetNormLevel() == iLevel)
{
bool bServerFailed = false;
for (ServerPool::Servers::iterator it = failedServers.begin(); it != failedServers.end(); it++)
bool bServerFailed = !pCandidateServer->GetActive() || pCandidateServer->GetMaxConnections() == 0;
if (!bServerFailed)
{
NewsServer* pIgnoreServer = *it;
if (pIgnoreServer == pCandidateServer ||
(pIgnoreServer->GetGroup() > 0 && pIgnoreServer->GetGroup() == pCandidateServer->GetGroup() &&
pIgnoreServer->GetLevel() == pCandidateServer->GetLevel()))
for (Servers::iterator it = failedServers.begin(); it != failedServers.end(); it++)
{
bServerFailed = true;
break;
}
NewsServer* pIgnoreServer = *it;
if (pIgnoreServer == pCandidateServer ||
(pIgnoreServer->GetGroup() > 0 && pIgnoreServer->GetGroup() == pCandidateServer->GetGroup() &&
pIgnoreServer->GetNormLevel() == pCandidateServer->GetNormLevel()))
{
bServerFailed = true;
break;
}
}
}
if (!bServerFailed)
{
@@ -291,14 +292,14 @@ void ArticleDownloader::Run()
if (bAllServersOnLevelFailed)
{
if (iLevel < g_pServerPool->GetMaxLevel())
if (iLevel < g_pServerPool->GetMaxNormLevel())
{
detail("Article %s @ all level %i servers failed, increasing level", m_szInfoName, iLevel);
iLevel++;
}
else
{
warn("Article %s @ all servers failed", m_szInfoName);
detail("Article %s @ all servers failed", m_szInfoName);
Status = adFailed;
break;
}
@@ -328,7 +329,7 @@ void ArticleDownloader::Run()
if (Status == adFailed)
{
warn("Download %s failed", m_szInfoName);
detail("Download %s failed", m_szInfoName);
}
SetStatus(Status);
@@ -369,7 +370,7 @@ ArticleDownloader::EStatus ArticleDownloader::Download()
for (int retry = 3; retry > 0; retry--)
{
szResponse = m_pConnection->Request(tmp);
if (szResponse && !strncmp(szResponse, "2", 1))
if ((szResponse && !strncmp(szResponse, "2", 1)) || m_pConnection->GetAuthError())
{
break;
}
@@ -420,8 +421,8 @@ ArticleDownloader::EStatus ArticleDownloader::Download()
{
if (!IsStopped())
{
warn("Article %s @ server%i (%s) failed: Unexpected end of article", m_szInfoName,
m_pConnection->GetNewsServer()->GetID(), m_pConnection->GetHost());
detail("Article %s @ %s (%s) failed: Unexpected end of article", m_szInfoName,
m_pConnection->GetNewsServer()->GetName(), m_pConnection->GetHost());
}
Status = adFailed;
break;
@@ -455,8 +456,8 @@ ArticleDownloader::EStatus ArticleDownloader::Download()
if (strncmp(p, m_pArticleInfo->GetMessageID(), strlen(m_pArticleInfo->GetMessageID())))
{
if (char* e = strrchr(p, '\r')) *e = '\0'; // remove trailing CR-character
warn("Article %s @ server%i (%s) failed: Wrong message-id, expected %s, returned %s", m_szInfoName,
m_pConnection->GetNewsServer()->GetID(), m_pConnection->GetHost(), m_pArticleInfo->GetMessageID(), p);
detail("Article %s @ %s (%s) failed: Wrong message-id, expected %s, returned %s", m_szInfoName,
m_pConnection->GetNewsServer()->GetName(), m_pConnection->GetHost(), m_pArticleInfo->GetMessageID(), p);
Status = adFailed;
break;
}
@@ -484,8 +485,8 @@ ArticleDownloader::EStatus ArticleDownloader::Download()
if (!bEnd && Status == adRunning && !IsStopped())
{
warn("Article %s @ server%i (%s) failed: article incomplete", m_szInfoName,
m_pConnection->GetNewsServer()->GetID(), m_pConnection->GetHost());
detail("Article %s @ %s (%s) failed: article incomplete", m_szInfoName,
m_pConnection->GetNewsServer()->GetName(), m_pConnection->GetHost());
Status = adFailed;
}
@@ -512,21 +513,21 @@ ArticleDownloader::EStatus ArticleDownloader::CheckResponse(const char* szRespon
{
if (!IsStopped())
{
warn("Article %s @ server%i (%s) failed, %s: Connection closed by remote host", m_szInfoName,
m_pConnection->GetNewsServer()->GetID(), m_pConnection->GetHost(), szComment);
detail("Article %s @ %s (%s) failed, %s: Connection closed by remote host", m_szInfoName,
m_pConnection->GetNewsServer()->GetName(), m_pConnection->GetHost(), szComment);
}
return adConnectError;
}
else if (m_pConnection->GetAuthError() || !strncmp(szResponse, "400", 3) || !strncmp(szResponse, "499", 3))
{
warn("Article %s @ server%i (%s) failed, %s: %s", m_szInfoName,
m_pConnection->GetNewsServer()->GetID(), m_pConnection->GetHost(), szComment, szResponse);
detail("Article %s @ %s (%s) failed, %s: %s", m_szInfoName,
m_pConnection->GetNewsServer()->GetName(), m_pConnection->GetHost(), szComment, szResponse);
return adConnectError;
}
else if (!strncmp(szResponse, "41", 2) || !strncmp(szResponse, "42", 2) || !strncmp(szResponse, "43", 2))
{
warn("Article %s @ server%i (%s) failed, %s: %s", m_szInfoName,
m_pConnection->GetNewsServer()->GetID(), m_pConnection->GetHost(), szComment, szResponse);
detail("Article %s @ %s (%s) failed, %s: %s", m_szInfoName,
m_pConnection->GetNewsServer()->GetName(), m_pConnection->GetHost(), szComment, szResponse);
return adNotFound;
}
else if (!strncmp(szResponse, "2", 1))
@@ -537,8 +538,8 @@ ArticleDownloader::EStatus ArticleDownloader::CheckResponse(const char* szRespon
else
{
// unknown error, no special handling
warn("Article %s @ server%i (%s) failed, %s: %s", m_szInfoName,
m_pConnection->GetNewsServer()->GetID(), m_pConnection->GetHost(), szComment, szResponse);
detail("Article %s @ %s (%s) failed, %s: %s", m_szInfoName,
m_pConnection->GetNewsServer()->GetName(), m_pConnection->GetHost(), szComment, szResponse);
return adFailed;
}
}
@@ -563,13 +564,13 @@ bool ArticleDownloader::Write(char* szLine, int iLen)
}
else
{
warn("Decoding %s failed: unsupported encoding", m_szInfoName);
detail("Decoding %s failed: unsupported encoding", m_szInfoName);
return false;
}
if (!bOK)
{
debug("Failed line: %s", szLine);
warn("Decoding %s failed", m_szInfoName);
detail("Decoding %s failed", m_szInfoName);
}
return bOK;
}
@@ -588,10 +589,13 @@ bool ArticleDownloader::PrepareFile(char* szLine)
{
if (!strncmp(szLine, "=ybegin ", 8))
{
if (g_pOptions->GetDupeCheck())
if (g_pOptions->GetDupeCheck() &&
m_pFileInfo->GetNZBInfo()->GetDupeMode() != dmForce &&
!m_pFileInfo->GetNZBInfo()->GetManyDupeFiles())
{
m_pFileInfo->LockOutputFile();
if (!m_pFileInfo->GetOutputInitialized())
bool bOutputInitialized = m_pFileInfo->GetOutputInitialized();
if (!bOutputInitialized)
{
char* pb = strstr(szLine, " name=");
if (pb)
@@ -605,11 +609,6 @@ bool ArticleDownloader::PrepareFile(char* szLine)
strncpy(m_szArticleFilename, pb, pe - pb);
m_szArticleFilename[pe - pb] = '\0';
}
if (m_pFileInfo->IsDupe(m_szArticleFilename))
{
m_bDuplicate = true;
return false;
}
}
}
if (!g_pOptions->GetDirectWrite())
@@ -617,6 +616,12 @@ bool ArticleDownloader::PrepareFile(char* szLine)
m_pFileInfo->SetOutputInitialized(true);
}
m_pFileInfo->UnlockOutputFile();
if (!bOutputInitialized && m_szArticleFilename &&
Util::FileExists(m_pFileInfo->GetNZBInfo()->GetDestDir(), m_szArticleFilename))
{
m_bDuplicate = true;
return false;
}
}
if (g_pOptions->GetDirectWrite())
@@ -631,6 +636,7 @@ bool ArticleDownloader::PrepareFile(char* szLine)
long iArticleFilesize = atol(pb);
if (!CreateOutputFile(iArticleFilesize))
{
m_pFileInfo->UnlockOutputFile();
return false;
}
m_pFileInfo->SetOutputInitialized(true);
@@ -657,7 +663,9 @@ bool ArticleDownloader::PrepareFile(char* szLine)
m_pOutFile = fopen(szFilename, bDirectWrite ? "rb+" : "wb");
if (!m_pOutFile)
{
error("Could not %s file %s", bDirectWrite ? "open" : "create", szFilename);
char szSysErrStr[256];
error("Could not %s file %s! Errcode: %i, %s", bDirectWrite ? "open" : "create", szFilename,
errno, Util::GetLastErrorMessage(szSysErrStr, sizeof(szSysErrStr)));
return false;
}
if (g_pOptions->GetWriteBufferSize() == -1)
@@ -676,30 +684,74 @@ bool ArticleDownloader::PrepareFile(char* szLine)
/* creates output file and subdirectores */
bool ArticleDownloader::CreateOutputFile(int iSize)
{
if (g_pOptions->GetDirectWrite() && Util::FileExists(m_szOutputFilename) &&
Util::FileSize(m_szOutputFilename) == iSize)
{
// keep existing old file from previous program session
return true;
}
// delete eventually existing old file from previous program session
remove(m_szOutputFilename);
// ensure the directory exist
char szDestDir[1024];
int iMaxlen = Util::BaseFileName(m_szOutputFilename) - m_szOutputFilename;
strncpy(szDestDir, m_szOutputFilename, iMaxlen < 1024 ? iMaxlen : 1024-1);
if (iMaxlen > 1024-1) iMaxlen = 1024-1;
strncpy(szDestDir, m_szOutputFilename, iMaxlen);
szDestDir[iMaxlen] = '\0';
char szErrBuf[1024];
if (!Util::ForceDirectories(szDestDir))
if (!Util::ForceDirectories(szDestDir, szErrBuf, sizeof(szErrBuf)))
{
error("Could not create directory %s! Errcode: %i", szDestDir, errno);
error("Could not create directory %s: %s", szDestDir, szErrBuf);
return false;
}
if (!Util::CreateSparseFile(m_szOutputFilename, iSize))
{
error("Could not create file %s!", m_szOutputFilename);
error("Could not create file %s", m_szOutputFilename);
return false;
}
return true;
}
void ArticleDownloader::BuildOutputFilename()
{
char szFilename[1024];
snprintf(szFilename, 1024, "%s%i.%03i", g_pOptions->GetTempDir(), m_pFileInfo->GetID(), m_pArticleInfo->GetPartNumber());
szFilename[1024-1] = '\0';
m_pArticleInfo->SetResultFilename(szFilename);
char tmpname[1024];
snprintf(tmpname, 1024, "%s.tmp", szFilename);
tmpname[1024-1] = '\0';
SetTempFilename(tmpname);
if (g_pOptions->GetDirectWrite())
{
m_pFileInfo->LockOutputFile();
if (m_pFileInfo->GetOutputFilename())
{
strncpy(szFilename, m_pFileInfo->GetOutputFilename(), 1024);
szFilename[1024-1] = '\0';
}
else
{
snprintf(szFilename, 1024, "%s%c%i.out.tmp", m_pFileInfo->GetNZBInfo()->GetDestDir(), (int)PATH_SEPARATOR, m_pFileInfo->GetID());
szFilename[1024-1] = '\0';
m_pFileInfo->SetOutputFilename(szFilename);
}
m_pFileInfo->UnlockOutputFile();
SetOutputFilename(szFilename);
}
}
ArticleDownloader::EStatus ArticleDownloader::DecodeCheck()
{
bool bDirectWrite = g_pOptions->GetDirectWrite() && m_eFormat == Decoder::efYenc;
@@ -720,8 +772,8 @@ ArticleDownloader::EStatus ArticleDownloader::DecodeCheck()
}
else
{
warn("Decoding %s failed: no binary data or unsupported encoding format", m_szInfoName);
return adFatalError;
detail("Decoding %s failed: no binary data or unsupported encoding format", m_szInfoName);
return adFailed;
}
Decoder::EStatus eStatus = pDecoder->Check();
@@ -765,27 +817,27 @@ ArticleDownloader::EStatus ArticleDownloader::DecodeCheck()
remove(m_szResultFilename);
if (eStatus == Decoder::eCrcError)
{
warn("Decoding %s failed: CRC-Error", m_szInfoName);
detail("Decoding %s failed: CRC-Error", m_szInfoName);
return adCrcError;
}
else if (eStatus == Decoder::eArticleIncomplete)
{
warn("Decoding %s failed: article incomplete", m_szInfoName);
detail("Decoding %s failed: article incomplete", m_szInfoName);
return adFailed;
}
else if (eStatus == Decoder::eInvalidSize)
{
warn("Decoding %s failed: size mismatch", m_szInfoName);
detail("Decoding %s failed: size mismatch", m_szInfoName);
return adFailed;
}
else if (eStatus == Decoder::eNoBinaryData)
{
warn("Decoding %s failed: no binary data found", m_szInfoName);
detail("Decoding %s failed: no binary data found", m_szInfoName);
return adFailed;
}
else
{
warn("Decoding %s failed", m_szInfoName);
detail("Decoding %s failed", m_szInfoName);
return adFailed;
}
}
@@ -899,25 +951,16 @@ void ArticleDownloader::CompleteFileParts()
}
// Ensure the DstDir is created
if (!Util::ForceDirectories(szNZBDestDir))
char szErrBuf[1024];
if (!Util::ForceDirectories(szNZBDestDir, szErrBuf, sizeof(szErrBuf)))
{
error("Could not create directory %s! Errcode: %i", szNZBDestDir, errno);
error("Could not create directory %s: %s", szNZBDestDir, szErrBuf);
SetStatus(adJoined);
return;
}
char ofn[1024];
snprintf(ofn, 1024, "%s%c%s", szNZBDestDir, (int)PATH_SEPARATOR, m_pFileInfo->GetFilename());
ofn[1024-1] = '\0';
// prevent overwriting existing files
int dupcount = 0;
while (Util::FileExists(ofn))
{
dupcount++;
snprintf(ofn, 1024, "%s%c%s_duplicate%d", szNZBDestDir, (int)PATH_SEPARATOR, m_pFileInfo->GetFilename(), dupcount);
ofn[1024-1] = '\0';
}
Util::MakeUniqueFilename(ofn, 1024, szNZBDestDir, m_pFileInfo->GetFilename());
FILE* outfile = NULL;
char tmpdestfile[1024];
@@ -1011,10 +1054,7 @@ void ArticleDownloader::CompleteFileParts()
}
}
if (buffer)
{
free(buffer);
}
free(buffer);
if (outfile)
{
@@ -1040,7 +1080,8 @@ void ArticleDownloader::CompleteFileParts()
debug("Checking old dir for: %s", m_szOutputFilename);
char szOldDestDir[1024];
int iMaxlen = Util::BaseFileName(m_szOutputFilename) - m_szOutputFilename;
strncpy(szOldDestDir, m_szOutputFilename, iMaxlen < 1024 ? iMaxlen : 1024-1);
if (iMaxlen > 1024-1) iMaxlen = 1024-1;
strncpy(szOldDestDir, m_szOutputFilename, iMaxlen);
szOldDestDir[iMaxlen] = '\0';
if (Util::DirEmpty(szOldDestDir))
{
@@ -1065,28 +1106,9 @@ void ArticleDownloader::CompleteFileParts()
}
else
{
warn("%i of %i article downloads failed for \"%s\"", iBrokenCount, m_pFileInfo->GetArticles()->size(), InfoFilename);
if (g_pOptions->GetRenameBroken())
{
char brokenfn[1024];
snprintf(brokenfn, 1024, "%s_broken", ofn);
brokenfn[1024-1] = '\0';
if (Util::MoveFile(ofn, brokenfn))
{
detail("Renaming broken file from %s to %s", ofn, brokenfn);
}
else
{
warn("Renaming broken file from %s to %s failed", ofn, brokenfn);
}
strncpy(ofn, brokenfn, 1024);
ofn[1024-1] = '\0';
}
else
{
detail("Not renaming broken file %s", ofn);
}
warn("%i of %i article downloads failed for \"%s\"",
iBrokenCount + m_pFileInfo->GetMissedArticles(),
m_pFileInfo->GetTotalArticles(), InfoFilename);
if (g_pOptions->GetCreateBrokenLog())
{
@@ -1094,12 +1116,14 @@ void ArticleDownloader::CompleteFileParts()
snprintf(szBrokenLogName, 1024, "%s%c_brokenlog.txt", szNZBDestDir, (int)PATH_SEPARATOR);
szBrokenLogName[1024-1] = '\0';
FILE* file = fopen(szBrokenLogName, "ab");
fprintf(file, "%s (%i/%i)%s", m_pFileInfo->GetFilename(), m_pFileInfo->GetArticles()->size() - iBrokenCount, m_pFileInfo->GetArticles()->size(), LINE_ENDING);
fprintf(file, "%s (%i/%i)%s", m_pFileInfo->GetFilename(),
m_pFileInfo->GetTotalArticles() - iBrokenCount - m_pFileInfo->GetMissedArticles(),
m_pFileInfo->GetTotalArticles(), LINE_ENDING);
fclose(file);
}
}
// the locking is needed for accessing the memebers of NZBInfo
// the locking is needed for accessing the members of NZBInfo
g_pDownloadQueueHolder->LockQueue();
m_pFileInfo->GetNZBInfo()->GetCompletedFiles()->push_back(strdup(ofn));
if (strcmp(m_pFileInfo->GetNZBInfo()->GetDestDir(), szNZBDestDir))
@@ -1120,9 +1144,10 @@ bool ArticleDownloader::MoveCompletedFiles(NZBInfo* pNZBInfo, const char* szOldD
}
// Ensure the DstDir is created
if (!Util::ForceDirectories(pNZBInfo->GetDestDir()))
char szErrBuf[1024];
if (!Util::ForceDirectories(pNZBInfo->GetDestDir(), szErrBuf, sizeof(szErrBuf)))
{
error("Could not create directory %s! Errcode: %i", pNZBInfo->GetDestDir(), errno);
error("Could not create directory %s: %s", pNZBInfo->GetDestDir(), szErrBuf);
return false;
}
@@ -1138,13 +1163,7 @@ bool ArticleDownloader::MoveCompletedFiles(NZBInfo* pNZBInfo, const char* szOldD
if (strcmp(szFileName, szNewFileName))
{
// prevent overwriting of existing files
int dupcount = 0;
while (Util::FileExists(szNewFileName))
{
dupcount++;
snprintf(szNewFileName, 1024, "%s%c%s_duplicate%d", pNZBInfo->GetDestDir(), (int)PATH_SEPARATOR, Util::BaseFileName(szFileName), dupcount);
szNewFileName[1024-1] = '\0';
}
Util::MakeUniqueFilename(szNewFileName, 1024, pNZBInfo->GetDestDir(), Util::BaseFileName(szFileName));
detail("Moving file %s to %s", szFileName, szNewFileName);
if (Util::MoveFile(szFileName, szNewFileName))

View File

@@ -2,7 +2,7 @@
* This file is part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2009 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -46,7 +46,6 @@ public:
adFinished,
adFailed,
adRetry,
adDecodeError,
adCrcError,
adDecoding,
adJoining,
@@ -73,15 +72,20 @@ private:
UDecoder m_UDecoder;
FILE* m_pOutFile;
bool m_bDuplicate;
ServerStatList m_ServerStats;
EStatus Download();
bool Write(char* szLine, int iLen);
bool PrepareFile(char* szLine);
bool CreateOutputFile(int iSize);
void BuildOutputFilename();
EStatus DecodeCheck();
void FreeConnection(bool bKeepConnected);
EStatus CheckResponse(const char* szResponse, const char* szComment);
void SetStatus(EStatus eStatus) { m_eStatus = eStatus; }
const char* GetTempFilename() { return m_szTempFilename; }
void SetTempFilename(const char* v);
void SetOutputFilename(const char* v);
public:
ArticleDownloader();
@@ -91,14 +95,12 @@ public:
void SetArticleInfo(ArticleInfo* pArticleInfo) { m_pArticleInfo = pArticleInfo; }
ArticleInfo* GetArticleInfo() { return m_pArticleInfo; }
EStatus GetStatus() { return m_eStatus; }
ServerStatList* GetServerStats() { return &m_ServerStats; }
virtual void Run();
virtual void Stop();
bool Terminate();
time_t GetLastUpdateTime() { return m_tLastUpdateTime; }
void SetLastUpdateTimeNow() { m_tLastUpdateTime = ::time(NULL); }
const char* GetTempFilename() { return m_szTempFilename; }
void SetTempFilename(const char* v);
void SetOutputFilename(const char* v);
const char* GetArticleFilename() { return m_szArticleFilename; }
void SetInfoName(const char* v);
const char* GetInfoName() { return m_szInfoName; }

View File

@@ -2,7 +2,7 @@
* This file is part of nzbget
*
* Copyright (C) 2005 Bo Cordes Petersen <placebodk@sourceforge.net>
* Copyright (C) 2007-2011 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -34,8 +34,7 @@
#include <stdlib.h>
#include <string.h>
#include <cstdio>
#include <fstream>
#include <stdio.h>
#ifndef WIN32
#include <unistd.h>
#include <sys/socket.h>
@@ -53,11 +52,13 @@
#include "PrePostProcessor.h"
#include "Util.h"
#include "DownloadInfo.h"
#include "Scanner.h"
extern Options* g_pOptions;
extern QueueCoordinator* g_pQueueCoordinator;
extern UrlCoordinator* g_pUrlCoordinator;
extern PrePostProcessor* g_pPrePostProcessor;
extern Scanner* g_pScanner;
extern void ExitProc();
extern void Reload();
@@ -103,9 +104,10 @@ void BinRpcProcessor::Execute()
return;
}
if (strcmp(m_MessageBase.m_szPassword, g_pOptions->GetControlPassword()))
if ((strlen(g_pOptions->GetControlUsername()) > 0 && strcmp(m_MessageBase.m_szUsername, g_pOptions->GetControlUsername())) ||
strcmp(m_MessageBase.m_szPassword, g_pOptions->GetControlPassword()))
{
warn("nzbget request received on port %i from %s, but password invalid", g_pOptions->GetControlPort(), m_pConnection->GetRemoteAddr());
warn("nzbget request received on port %i from %s, but username or password invalid", g_pOptions->GetControlPort(), m_pConnection->GetRemoteAddr());
return;
}
@@ -341,9 +343,10 @@ void DownloadBinCommand::Execute()
return;
}
char* pRecvBuffer = (char*)malloc(ntohl(DownloadRequest.m_iTrailingDataLength) + 1);
int iBufLen = ntohl(DownloadRequest.m_iTrailingDataLength);
char* pRecvBuffer = (char*)malloc(iBufLen);
if (!m_pConnection->Recv(pRecvBuffer, ntohl(DownloadRequest.m_iTrailingDataLength)))
if (!m_pConnection->Recv(pRecvBuffer, iBufLen))
{
error("invalid request");
free(pRecvBuffer);
@@ -352,35 +355,17 @@ void DownloadBinCommand::Execute()
int iPriority = ntohl(DownloadRequest.m_iPriority);
bool bAddPaused = ntohl(DownloadRequest.m_bAddPaused);
NZBFile* pNZBFile = NZBFile::CreateFromBuffer(DownloadRequest.m_szFilename, DownloadRequest.m_szCategory, pRecvBuffer, ntohl(DownloadRequest.m_iTrailingDataLength));
if (pNZBFile)
{
info("Request: Queue collection %s", DownloadRequest.m_szFilename);
for (NZBFile::FileInfos::iterator it = pNZBFile->GetFileInfos()->begin(); it != pNZBFile->GetFileInfos()->end(); it++)
{
FileInfo* pFileInfo = *it;
pFileInfo->SetPriority(iPriority);
pFileInfo->SetPaused(bAddPaused);
}
g_pQueueCoordinator->AddNZBFileToQueue(pNZBFile, ntohl(DownloadRequest.m_bAddFirst));
delete pNZBFile;
char tmp[1024];
snprintf(tmp, 1024, "Collection %s added to queue", Util::BaseFileName(DownloadRequest.m_szFilename));
tmp[1024-1] = '\0';
SendBoolResponse(true, tmp);
}
else
{
char tmp[1024];
snprintf(tmp, 1024, "Download Request failed for %s", Util::BaseFileName(DownloadRequest.m_szFilename));
tmp[1024-1] = '\0';
SendBoolResponse(false, tmp);
}
bool bAddTop = ntohl(DownloadRequest.m_bAddFirst);
bool bOK = g_pScanner->AddExternalFile(DownloadRequest.m_szFilename, DownloadRequest.m_szCategory,
iPriority, NULL, 0, dmScore, NULL, bAddTop, bAddPaused, NULL, pRecvBuffer, iBufLen) != Scanner::asFailed;
char tmp[1024];
snprintf(tmp, 1024, bOK ? "Collection %s added to queue" : "Download Request failed for %s",
Util::BaseFileName(DownloadRequest.m_szFilename));
tmp[1024-1] = '\0';
SendBoolResponse(bOK, tmp);
free(pRecvBuffer);
}
@@ -578,10 +563,7 @@ void ListBinCommand::Execute()
g_pQueueCoordinator->UnlockQueue();
if (pRegEx)
{
delete pRegEx;
}
delete pRegEx;
ListResponse.m_iNrTrailingNZBEntries = htonl(iNrNZBEntries);
ListResponse.m_iNrTrailingPPPEntries = htonl(iNrPPPEntries);
@@ -627,10 +609,7 @@ void ListBinCommand::Execute()
m_pConnection->Send(buf, bufsize);
}
if (buf)
{
free(buf);
}
free(buf);
}
void LogBinCommand::Execute()
@@ -797,7 +776,7 @@ void EditQueueBinCommand::Execute()
}
else
{
bOK = g_pPrePostProcessor->QueueEditList(&cIDList, (PrePostProcessor::EEditAction)iAction, iOffset);
bOK = g_pPrePostProcessor->QueueEditList(&cIDList, (PrePostProcessor::EEditAction)iAction, iOffset, szText);
}
free(pBuf);
@@ -847,7 +826,6 @@ void PostQueueBinCommand::Execute()
{
PostInfo* pPostInfo = *it;
bufsize += strlen(pPostInfo->GetNZBInfo()->GetFilename()) + 1;
bufsize += strlen(pPostInfo->GetParFilename()) + 1;
bufsize += strlen(pPostInfo->GetInfoName()) + 1;
bufsize += strlen(pPostInfo->GetNZBInfo()->GetDestDir()) + 1;
bufsize += strlen(pPostInfo->GetProgressLabel()) + 1;
@@ -870,15 +848,12 @@ void PostQueueBinCommand::Execute()
pPostQueueAnswer->m_iTotalTimeSec = htonl((int)(pPostInfo->GetStartTime() ? tCurTime - pPostInfo->GetStartTime() : 0));
pPostQueueAnswer->m_iStageTimeSec = htonl((int)(pPostInfo->GetStageTime() ? tCurTime - pPostInfo->GetStageTime() : 0));
pPostQueueAnswer->m_iNZBFilenameLen = htonl(strlen(pPostInfo->GetNZBInfo()->GetFilename()) + 1);
pPostQueueAnswer->m_iParFilename = htonl(strlen(pPostInfo->GetParFilename()) + 1);
pPostQueueAnswer->m_iInfoNameLen = htonl(strlen(pPostInfo->GetInfoName()) + 1);
pPostQueueAnswer->m_iDestDirLen = htonl(strlen(pPostInfo->GetNZBInfo()->GetDestDir()) + 1);
pPostQueueAnswer->m_iProgressLabelLen = htonl(strlen(pPostInfo->GetProgressLabel()) + 1);
bufptr += sizeof(SNZBPostQueueResponseEntry);
strcpy(bufptr, pPostInfo->GetNZBInfo()->GetFilename());
bufptr += ntohl(pPostQueueAnswer->m_iNZBFilenameLen);
strcpy(bufptr, pPostInfo->GetParFilename());
bufptr += ntohl(pPostQueueAnswer->m_iParFilename);
strcpy(bufptr, pPostInfo->GetInfoName());
bufptr += ntohl(pPostQueueAnswer->m_iInfoNameLen);
strcpy(bufptr, pPostInfo->GetNZBInfo()->GetDestDir());
@@ -964,7 +939,7 @@ void ScanBinCommand::Execute()
bool bSyncMode = ntohl(ScanRequest.m_bSyncMode);
g_pPrePostProcessor->ScanNZBDir(bSyncMode);
g_pScanner->ScanNZBDir(bSyncMode);
SendBoolResponse(true, bSyncMode ? "Scan-Command completed" : "Scan-Command scheduled successfully");
}
@@ -1026,7 +1001,7 @@ void HistoryBinCommand::Execute()
pListAnswer->m_iSizeHi = htonl(iSizeHi);
pListAnswer->m_iFileCount = htonl(pNZBInfo->GetFileCount());
pListAnswer->m_iParStatus = htonl(pNZBInfo->GetParStatus());
pListAnswer->m_iScriptStatus = htonl(pNZBInfo->GetScriptStatus());
pListAnswer->m_iScriptStatus = htonl(pNZBInfo->GetScriptStatuses()->CalcTotalStatus());
}
else if (pHistoryInfo->GetKind() == HistoryInfo::hkUrlInfo)
{

567
ChangeLog
View File

@@ -1,3 +1,557 @@
nzbget-12.0:
- added RSS feeds support:
- new options "FeedX.Name", "FeedX.URL", "FeedX.Filter",
"FeedX.Interval", "FeedX.PauseNzb", "FeedX.Category",
"FeedX.Priority" (section "Rss Feeds");
- new option "FeedHistory" (section "Download Queue");
- button "Preview Feed" on settings tab near each feed definition;
- new toolbar button "Feeds" on downloads tab with menu to
view feeds or fetch new nzbs from all feeds (the button is
visible only if there are feeds defined in settings);
- new dialog to see feed content showing status of each item (new,
fetched, backlog) with ability to manually fetch selected items;
- powerful filters for RSS feeds;
- new dialog to build filters in web-interface with instant preview;
- added download health monitoring:
- health indicates download status, whether the file is damaged
and how much;
- 100% health means no download errors occurred; 0% means all
articles failed;
- there is also a critical health which is calculated for each
nzb-file based on number and size of par-files;
- if during download the health goes down below 100% a health
badge appears near download name indicating the necessity of
par-repair; the indicator can be orange (repair may be possible)
or red (unrepairable) if the health goes down below critical health;
- new option "HealthCheck" to define what to do with unhealthy
(unrepairable) downloads (pause, delete, none);
- health and critical health are displayed in download-edit dialog;
health is displayed in history dialog; if download was aborted
(HealthCheck=delete) this is indicated in history dialog;
- health allows to determine download status for downloads which
have unpack and/or par-check disabled; for such downloads the
status in history is shown based on health: success (health=100%),
damaged (health > critical) or failure (health < critical);
- par-check is now automatically started for downloads having
health below 100%; this works independently of unpack (even if
unpack is disabled);
- for downloads having health less than critical health no par-check
is performed (it would fail); Instead the par-check status is
set to "failure" automatically saving time of actual par-check;
- new fields "Health" and "CriticalHealth" are returned by
RPC-Method "listgroups";
- new fields "Health", "CriticalHealth", "Deleted" and "HealthDeleted"
are returned by RPC-Method "history";
- new parameters "NZBPP_HEALTH" and "NZBPP_CRITICALHEALTH" are passed
to pp-scripts;
- added collecting of server usage statistical data for each download:
- number of successful and failed article downloads per news server;
- new page in history dialog shows collected statistics;
- new fields in RPC-method "history": ServerStats (array),
TotalArticles, SuccessArticles, FailedArticles;
- new env. vars passed to pp-scripts: NZBPP_TOTALARTICLES,
NZBPP_SUCCESSARTICLES, NZBPP_FAILEDARTICLES and per used news
server: NZBPP_SERVERX_SUCCESSARTICLES, NZBPP_SERVERX_FAILEDARTICLES;
- also new env.var HEALTHDELETED;
- added smart duplicates feature:
- mostly for use with RSS feeds;
- automatic detection of duplicate nzb-files to avoid download of
duplicates;
- nzb-files can be also manually marked as duplicates;
- if download fails - automatically choose another release (duplicate);
- if download succeeds all remaining duplicates are skipped (not downloaded);
- download items have new properties to tune duplicate handling
behavior: duplicate key, duplicate score and duplicate mode;
- if download was deleted by duplicate check its status in the
history is shown as "DUPE";
- new actions "GroupSetDupeKey", "GroupSetDupeScore", "GroupSetDupeMode",
"HistorySetDupeKey", "HistorySetDupeScore", "HistorySetDupeMode",
"HistoryMarkBad" and "HistoryMarkGood" of RPC-command "editqueue";
new actions "B" and "G" of command "--edit/-E" for history items
(subcommand "H");
- when deleting downloads from queue there are three options now:
"move to history", "move to history as duplicate" and "delete
without history tracking";
- new actions "GroupDupeDelete", "GroupFinalDelete" and
"HistorySetDupeBackup" in RPC-method "editqueue";
- RPC-commands "listgroups", "postqueue" and "history" now return
more info about nzb-item (many new fields);
- removed option "MergeNzb" because it conflicts with duplicate
handling, items can be merged manually if necessary;
- automatic detection of exactly same nzb-files (same content)
coming from different sources (different RSS feeds etc.);
individual files (inside nzb-file) having extensions listed in
option "ExtCleanupDisk" are excluded from content comparison
(unless these are par2-files, which are never excluded);
- when history item expires (as defined by option "KeepHistory")
and the duplicate check is active (option "DupeCheck") the item
is not completely deleted from history; instead the amount of
stored data reduces to minimum required for duplicate check
(about 200 bytes vs 2000 bytes for full history item);
- such old history items are not shown in web-interface by default
(to avoid transferring of large amount of history items);
- new button "Hidden" in web-interface to show hidden history items;
the items are marked with badge "hidden";
- RPC-method "editqueue" has now two actions to delete history
records: "HistoryDelete", "HistoryFinal"; action "HistoryDelete"
which has existed before now hides records, already hidden records
are ignored;
- added functions "Mark as Bad" and "Mark as Good" for history
items;
- duplicate properties (dupekey, dupescore and dupemode) can now
be viewed and changed in download-edit-dialog and
history-edit-dialog via new button "Dupe";
- for full documentation see http://nzbget.sourceforge.net/RSS#Duplicates;
- created NZBGet.app - NZBGet is now a user friendly Mac OSX application
with easy installation and seamless integration into OS UI:
works in background, is controlled from a web-browser, few
important functions are accessible via menubar icon;
- better Windows package:
- unrar is included;
- several options are set to better defaults;
- all paths are set as relative paths to program directory;
the program can be started after installation without editing
anything in config;
- included two new batch-files:
- nzbget-start.bat - starts program in normal mode (dos box);
- nzbget-recovery-mode.bat - starts with empty password (dos box);
- both batch files open browser window with correct address;
- config-file template is stored in nzbget.conf.template;
- nzbget.conf is not included in the package. When the program is
started for the first time (using one of batch files) the template
config file is copied into nzbget.conf;
- updates will be easy in the future: to update the program all
files from newer archive must be extracted over old files. Since
the archive doesn't have nzbget.conf, the existing config is kept
unmodified. The template config file will be updated;
- added file README-WINDOWS.txt with small instructions;
- version string now includes revision number (like "r789");
- added automatic updates:
- new button "Check for updates" on settings tab of web-interface,
in section "SYSTEM", initiates check and shows dialog allowing to
install new version;
- it is possible to choose between stable, testing and development
branches;
- this feature is for end-users using binary packages created and
updated by maintainers, who need to write an update script specific
for platform;
- the script is then called by NZBGet when user clicks on install-button;
- the script must download and install new version;
- for more info visit http://nzbget.sourceforge.net/Packaging;
- news servers can now be temporarily disabled via speed limit dialog
without reloading of the program:
- new option "ServerX.Active" to disable servers via settings;
- new option "ServerX.Name" to use for logging and in UI;
- changed the way how option "Unpack" works:
- instead of enabling/disabling the unpacker as a whole, it now
defines the initial value of post-processing parameter "Unpack"
for nzb-file when it is added to queue;
- this makes it now possible to disable Unpack globally but still
enable it for selected nzb-files;
- new option "CategoryX.Unpack" to set unpack on a per category basis;
- combined all footer buttons into one button "Actions" with menu:
- in download-edit-dialog: "Pause/Resume", "Delete" and "Cancel
Post-Processing";
- in history-dialog: "Delete", "Post-Process Again" and "Download
Remaining Files (Return to Queue)";
- DirectNZB headers X-DNZB-MoreInfo and X-DNZB-Details are now processed
when downloading URLs and the links "More Info" and "Details" are shown
in download-edit-dialog and in history-dialog in Actions menu;
- program can now be stopped via web-interface: new button "shutdown"
in section "SYSTEM";
- added menu "View" to settings page which allows to switch to "Compact Mode"
when option descriptions are hidden;
- added confirmation dialog by leaving settings page if there are unsaved
changes;
- downloads manually deleted from queue are shown with status "deleted"
in the history (instead of "unknown");
- all table columns except "Name" now have fixed widths to avoid annoying
layout changes especially during post-processing when long status messages
are displayed in the name-column;
- added filter buttons to messages tab (info, warning, etc.);
- added automatic par-renaming of extracted files if archive includes
par-files;
- added support for http redirects when fetching URLs;
- added new command "Download again" for history items; new action
"HistoryRedownload" of RPC-method "editqueue"; for controlling via command
line: new action "A" of subcommand "H" of command "--edit/-E";
- download queue is now saved in a more safe way to avoid potential loss
of queue if the program crashes during saving of queue;
- destination directory for option "CategoryX.DestDir" is not checked/created
on program start anymore (only when a download starts for that category);
this helps when certain categories are configured for external disks,
which are not always connected;
- added new option "CategoryX.Aliases" to configure category name matching
with nzb-sites; especially useful with rss feeds;
- in RPC-Method "appendurl" parameter "addtop" adds nzb to the top of
the main download queue (not only to the top of the URL queue);
- new logo (thanks to dogzipp for the logo);
- added support for metatag "password" in nzb-files;
- pp-scripts which move files can now inform the program about new
location by printing text "[NZB] FINALDIR=/path/to/files"; the final
path is then shown in history dialog instead of download path;
- new env-var "NZBPP_FINALDIR" passed to pp-scripts;
- pp-scripts can now set post-processing parameters by printing
command "[NZB] NZBPR_varname=value"; this allows scripts which are
executed sooner to pass data for scripts executed later;
- added new option "AuthorizedIP" to set the list of IP-addresses which
may connect without authorization;
- new option "ParRename" to force par-renaming as a first post-processing
step (active by default); this saves an unpack attempt and is even more
useful if unpack is disabled;
- post-processing progress label is now automatically trimmed if it
doesn't fill into one line; this avoids layout breaking if the text
is too long;
- reversed the order of priorities in comboboxes in dialogs: the highest
priority - at the top, the lowest - at the bottom;
- small changes in button captions: edit dialogs called from settings
page (choose script, choose order, build rss filter) now have buttons
"Discard/Apply" instead of "Close/Save"; in all other dialogs button
"Close" renamed to "Cancel" unless it was the only button in dialog;
- small change in css: slightly reduced the max height of modal dialogs
to better work on notebooks;
- options "DeleteCleanupDisk" and "NzbCleanupDisk" are now active by
default (in the example config file);
- extended add-dialog with options "Add paused" and "Disable duplicate check";
- source nzb-files are now deleted when download-item leaves queue and
history (option "NzbCleanupDisk");
- when deleting downloads from queue the messages about deleted
individual files are now printed as "detail" instead of "info";
- failed article downloads are now logged as "detail" instead of
"warning" to reduce number of warnings for downloads removed from
server (DMCA); one warning is printed for a file with a summary of
number of failed downloads for the file;
- tuned algorithm calculating maximum threads limit to allow more
threads for backup server connections (related to option "TreadLimit"
removed in v11); this may sometimes increase speed when backup servers
were used;
- by adding nzb-file to queue via RPC-methods "append" and "appendurl"
the actual format of the file is checked and if nzb-format is detected
the file is added even if it does not have .nzb extension;
- added new option "UrlForce" to allow URL-downloads (including fetching
of RSS feeds and nzb-files from feeds) even if download is paused;
the option is active by default;
- destination directory for option "DestDir" is not checked/created on
program start anymore (only when a download starts); this helps when
DestDir is mounted to a network drive which is not available on program start;
- added special handling for files ".AppleDouble" and ".DS_Store" during
unpack to avoid problems on NAS having support for AFP protocol (used
on Mac OSX);
- history records with failed script status are now shown as "PP-FAILURE"
in history list (instead of just "FAILURE");
- option "DiskSpace" now checks space on "InterDir" in addition to
"DestDir";
- support for rar-archives with non-standard extensions is now limited
to file extensions consisting of digits; this is to avoid extracting
of rar-archives having non-rar extensions on purpose (example: .cbr);
- if option "ParRename" is disabled (not recommended) unpacker does
not initiate par-rename anymore, instead the full par-verify is
performed then;
- for external script the exec-permissions are now added automatically;
this makes the installation of pp-scripts and other scripts easier;
- option "InterDir" is now active by default;
- when option "InterDir" is used the intermediate destination directory
names now include unique numbers to avoid several downloads with same
name to use the same directory and interfere with each other;
- when option "UnpackCleanupDisk" is active all archive files are now
deleted from download directory without relying on output printed by
unrar; this solves issues with non-ascii-characters in archive file
names on some platforms and especially in combination with rar5;
- improved handling of non-ascii characters in file names on windows;
- added support for rar5-format when checking signatures of archives
with non-standard file extensions;
- small restructure in settings order:
- combined sections "REMOTE CONTROL" and "PERMISSIONS" into one
section with name "SECURITY";
- moved sections "CATEGORIES" and "RSS FEEDS" higher in the
section list;
- improved par-check: if main par2-file is corrupted and can not be
loaded other par2-files are downloaded and then used as replacement
for main par2-file;
- if unpack did not find archive files the par-check is not requested
anymore if par-rename was already done;
- better handling of obfuscated nzb-files containing multiple files
with same names; removed option "StrictParName" which was not working
good with obfuscated files; if more par-files are required for repair
the files with strict names are tried first and then other par-files;
- added new scheduler commands "ActivateServer", "DeactivateServer" and
"FetchFeed"; combined options "TaskX.DownloadRate" and "TaskX.Process"
into one option "TaskX.Param", also used by new commands;
- added status filter buttons to history page;
- if unpack fails with write error (usually because of not enough space
on disk) this is shown as status "Unpack: space" in web-interface;
this unpack-status is handled as "success" by duplicate handling
(no download of other duplicate); also added new unpack-status "wrong
password" (only for rar5-archives); env.var. NZBPP_UNPACKSTATUS has
two new possible values: 3 (write error) and 4 (wrong password);
updated pp-script "EMail.py" to support new unpack-statuses;
- fixed a potential seg. fault in a commonly used function;
- added new option "TimeCorrection" to adjust conversion from system
time to local time (solves issues with scheduler when using a
binary compiled for other platform);
- NZBIDs are now generated with more care avoiding numbering holes
possible in previous versions;
- fixed: invalid "Offset" passed to RPC-method "editqueue" or command
line action "-E/--edit" could crash the program;
- fixed: crash after downloading of an URL (happen only on certain systems);
- fixed: restoring of settings didn't work for multi-sections (servers,
categories, etc.) if they were empty;
- fixed: choosing local files didn't work in Opera;
- fixed: certain characters printed by pp-scripts could crash the
program;
- fixed: malformed nzb-file could cause a memory leak;
- fixed: when a duplicate file was detected in collection it was
automatically deleted (if option DupeCheck is active) but the
total size of collection was not updated;
- when deleting individual files the total count of files in collection
was not updated;
- fixed: when multiple nzb-files were added via URL (rss including) at
the same time the info about category and priority could get lost for
some of files;
- fixed: if unpack fails the created destination directory was not
automatically removed (only if option "InterDir" was active);
- fixed scrolling to the top of page happening by clicking on items in
downloads/history lists and on action-buttons in edit-download and
history dialogs;
- fixed potential buffer overflow in remote client;
- improved error reporting when creation of temporary output file fails;
- fixed: when deleting download, if all remaining queued files are
par2-files the disk cleanup should not be performed, but it was
sometimes;
- fixed a potential problem in incorrect using of one library function.
nzbget-11.0:
- reworked concept of post-processing scripts:
- multiple scripts can be assigned to each nzb-file;
- all assigned scripts are executed after the nzb-file is
downloaded and internally processed (unpack, repair);
- option <PostProcess> is obsolete;
- new option <ScriptDir> sets directory where all pp-scripts must
be stored;
- new option <DefScript> sets the default list of pp-scripts to
be assigned to nzb-file when it's added to queue;
- new option <CategoryX.DefScript> to set the default list of
pp-scripts on a category basis;
- the execution order of pp-scripts can be set using new option
<ScriptOrder>;
- there are no separate configuration files for pp-scripts;
- configuration options and pp-parameters are defined in the
pp-scripts;
- script configuration options are saved in nzbget configuration
file (nzbget.conf);
- changed parameters list of RPC-methods <loadconfig> and
<saveconfig>;
- new RPC-method <configtemplates> returns configuration
descriptions for the program and for all pp-scripts;
- configuration of all scripts can be done in web-interface;
- the pp-scripts assigned to a particular nzb-file can be viewed
and changed in web-interface on page <pp-parameters> in the
edit download dialog;
- option <PostPauseQueue> renamed to <ScriptPauseQueue> (the old
name is still recognized);
- new option <ConfigTemplate> to define the location of template
configuration file (in previous versions it must be always
stored in <WebDir>);
- history dialog shows status of every script;
- the old example post-processing script replaced with two new scripts:
- EMail.py - sends E-Mail notification;
- Logger.py - saves the full post-processing log of the job into
file _postprocesslog.txt;
- both pp-scripts are written in python and work on Windows too
(in addition to Linux, Mac, etc.);
- added possibility to set post-processing parameters for history items:
- pp-parameters can now be viewed and changed in history dialog
in web-interface;
- useful before post-processing again;
- new action <HistorySetParameter> in RPC-method <editqueue>;
- new action <O> in remote command <--edit/-E> for history items
(subcommand <H>);
- added new feature <split download> which creates new download from
selected files of source download;
- new command <Split> in web-interface in edit download dialog
on page <Files>;
- new action <S> in remote command <--edit/-E>;
- new action <FileSplit> in JSON-/XML-RPC method <editqueue>;
- added support for manual par-check:
- if option <ParCheck> is set to <Manual> and a damaged download
is detected the program downloads all par2-files but doesn't
perform par-check; the user must perform par-check/repair
manually then (possibly on another, faster computer);
- old values <yes/no> of option <ParCheck> renamed to <Force>
and <Auto> respectively;
- when set to <Force> all par2-files are always downloaded;
- removed option <LoadPars> since its functionality is now
covered by option <ParCheck>;
- result of par-check can now have new value <Manual repair
necessary>;
- field <ParStatus> in RPC-method <history> can have new value
<MANUAL>;
- parameter <NZBPP_PARSTATUS> for pp-script can have new value
<4 = manual repair necessary>;
- when download is resumed in web-interface the option <ParCheck=Force>
is respected and all par2-files are resumed (not only main par2-file);
- automatic deletion of backup-source files after successful par-repair;
important when repairing renamed rar-files since this could cause
failure during unpack;
- par-checker and renamer now add messages into the log of pp-item
(like unpack- and pp-scripts-messages); these message now appear in
the log created by scripts Logger.py and EMail.py;
- when a nzb-file is added via web-interface or via remote call the
file is now put into incoming nzb-directory (option "NzbDir") and
then scanned; this has two advantages over the old behavior when the
file was parsed directly in memory:
- the file serves as a backup for troubleshootings;
- the file is processed by nzbprocess-script (if defined in
option "NzbProcess") making the pre-processing much easier;
- new env-var parameters are passed to NzbProcess-script: NZBNP_NZBNAME,
NZBNP_CATEGORY, NZBNP_PRIORITY, NZBNP_TOP, NZBNP_PAUSED;
- new commands for use in NzbProcess-scripts: "[NZB] TOP=1" to add nzb
to the top of queue and "[NZB] PAUSED=1" to add nzb-file in paused state;
- reworked post-processor queue:
- only one job is created for each nzb-file; no more separate
jobs are created for par-collections within one nzb-file;
- option <AllowReProcess> removed; a post-processing script is
called only once per nzb-file, this behavior cannot be altered
anymore;
- with a new feature <Split> individual par-collections can be
processed separately in a more effective way than before
- improved unicode (utf8) support:
- non-ascii characters are now correctly transferred via JSON-RPC;
- correct displaying of nzb-names and paths in web-interface;
- it is now possible to use non-ascii characters on settings page
for option values (such as paths or category names);
- improved unicode support in XML-RPC and JSON-RPC;
- if username and password are defined for a news-server the
authentication is now forced (in previous versions the authentication
was performed only if requested by server); needed for servers
supporting both anonymous (restricted) and authorized (full access)
accounts;
- added option <ExtCleanupDisk> to automatically delete unwanted files
(with specified extensions or names) after successful par-check or unpack;
- improvement in JSON-/XML-RPC:
- all ID fields including NZBID are now persistent and remain
their values after restart;
- this allows for third-party software to identify nzb-files by
ID;
- method <history> now returns ID of NZB-file in the field
<NZBID>;
- in versions up to 0.8.0 the field <NZBID> was used to identify
history items in the edit-commands <HistoryDelete>,
<HistoryReturn>, <HistoryProcess>; since version 9 field <ID>
is used for this purpose; in versions 9-10 field <NZBID> still
existed and had the same value as field <ID> for compatibility
with version 0.8.0; the compatibility is not provided anymore;
this change was needed to provide a consistent using of field
<NZBID> across all RPC-methods;
- added support for rar-files with non-standard extensions (such as
.001, etc.);
- added functions to backup and restore settings from web-interface;
when restoring it's possible to choose what sections to restore
(for example only news servers settings or only settings of a
certain pp-script) or restore the whole configuration;
- new option "ControlUsername" to define login user name (if you don't
like default username "nzbget");
- if a communication error occurs in web-interface, it retries multiple
times before giving up with an error message;
- the maximum number of download threads are now managed automatically
taking into account the number of allowed connections to news servers;
removed option <ThreadLimit>;
- pp-scripts terminated with unknown status are now considered failed
(status=FAILURE instead of status=UNKNOWN);
- new parameter (env. var) <NZBPP_NZBID> is passed to pp_scripts and
contains an internal ID of NZB-file;
- improved thread synchronisation to avoid (short-time) lockings of
the program during creation of destination files;
- more detailed error message if a directory could not be created
(<DstDir>, <NzbDir>, etc.); the message includes error text reported
by OS such as <permission denied> or similar;
- when unpacking the unpack start time is now measured after receiving
of unrar copyright message; this provides better unpack time
estimation in a case when user uses unpack-script to do some things
before executing unrar (for example sending Wake-On-Lan message to
the destination NAS); it works with unrar only, it's not possible
with 7-Zip because it buffers printed messages;
- when the program is reloaded, a message with version number is
printed like on start;
- configuration can now be saved in web-interface even if there were
no changes made but if obsolete or invalid options were detected in
the config file; the saving removes invalid entries from config file;
- option <ControlPassword> can now be set to en empty value to disable
authentication; useful if nzbget works behind other web-server with
its own authentication;
- when deleting downloads via web-interface a proper hint regarding
deleting of already downloaded files from disk depending on option
<DeleteCleanupDisk> is displayed;
- if a news-server returns empty or bad article (this may be caused
by errors on the news server), the program tries again from the same
or other servers (in previous versions the article was marked as
failed without other download attempts);
- when a nzb-file whose name ends with ".queued" is added via web-
interface the ".queued"-part is automatically removed;
- small improvement in multithread synchronization of download queue;
- added link to catalog of pp-scripts to web-interface;
- updated forum URL in about dialog in web-interface;
- small correction in a log-message: removed <Request:> from message
<Request: Queue collection...>;
- removed option "ProcessLogKind"; scripts should use prefixes ([INFO],
[DETAIL], etc); messages printed without prefixes are added as [INFO];
- removed option "AppendNzbDir"; if it was disabled that caused problems
in par-checker and unpacker; the option is now assumed always active;
- removed option "RenameBroken"; it caused problems in par-checker
(the option existed since early program versions before the par-check
was added);
- configure-script now defines "SIGCHLD_HANDLER" by default on all
systems including BSD; this eliminates the need of configure-
parameter "--enable-sigchld-handler" on 64-Bit BSD; the trade-off:
32-Bit BSD now requires "--disable-sigchld-handler";
- improved configure-script: defining of symbol "FILE_OFFSET_BITS=64",
required on some systems, is not necessary anymore;
- fixed: in the option "NzbAddedProcess" the env-var parameter with
nzb-name was passed in "NZBNA_NAME", should be "NZBNA_NZBNAME";
the old parameter name "NZBNA_NAME" is still supported for
compatibility;
- fixed: download time in statistics were incorrect if the computer
was put into standby (thanks Frank Kuypers for the patch);
- fixed: when option <InterDir> was active and the download after
unpack contained rar-file with the same name as one of original
files (sometimes happen with included subtitles) the original
rar-file was kept with name <.rar_duplicate1> even if the option
<UnpackCleanupDisk> was active;
- fixed: failed to read download queue from disk if post-processing
queue was not empty;
- fixed: when a duplicate file was detected during download the
program could hang;
- fixed: symbol <DISABLE_TLS> must be defined in project settings;
defining it in <win32.h> didn't work properly (Windows only);
- fixed: crash when adding malformed nzb-files with certain
structure (Windows only);
- fixed: by deleting of a partially downloaded nzb-file from queue,
when the option <DeleteCleanupDisk> was active, the file
<_brokenlog.txt> was not deleted preventing the directory from
automatic deletion;
- fixed: if an error occurs when a RPC-client or web-browser
communicates with nzbget the program could crash;
- fixed: if the last file of collection was detected as duplicate
after the download of the first article the file was deleted from
queue (that's OK) but the post-processing was not triggered
(that's a bug);
- fixed: support for splitted files (.001, .002, etc.) were broken.
nzbget-10.2:
- fixed potential segfault which could happen with file paths longer
than 1024 characters;
- fixed: when options <DirectWrite> and <ContinuePartial> were both
active, a restart or reload of the program during download may cause
damaged files in the active download;
- increased width of speed indication ui-element to avoid layout
breaking on some linux-browsers;
- fixed a race condition in unpacker which could lead to a segfault
(although the chances were low because the code wasn't executed often).
nzbget-10.1:
- fixed: articles with decoding errors (incomplete or damaged posts)
caused infinite retry-loop in downloader.
nzbget-10.0:
- added built-in unpack:
- rar and 7-zip formats are supported (via external Unrar and
@@ -157,6 +711,17 @@ nzbget-10.0:
and in configuration options were not displayed properly and could be
discarded on saving;
nzbget-9.1:
- added full par-scan feature needed to par-check/repair files which
were renamed after creation of par-files:
- new option <ParScan> to activate full par-scan (always or automatic);
the automatic full par-scan activates if missing files are detected
during par-check, this avoids unnecessary full scan for normal
(not renamed) par sets;
- improved the post-processing script to better handle renamed rar-files;
- replaced a browser error message when trying to add local files in
IE9 with a better message dialog;
nzbget-9.0:
- changed version naming scheme by removing the leading zero: current
version is now called 9.0 instead of 0.9.0 (it's really the 9th major
@@ -873,7 +1438,7 @@ nzbget-0.3.0:
of completing and state (paused or not) for every file is printed.
The header of queue shows number of total files, number of unpaused
files and size for all and unpaused files. Better using of screen estate
space <EFBFBD> no more empty lines and separate header for status (total seven
space - no more empty lines and separate header for status (total seven
lines gain). The messages are printed on several lines (if they not fill
in one line) without trimming now;
- configure.ac-file updated to work with recent versions of autoconf/automake.

View File

@@ -37,7 +37,7 @@
#include <stdlib.h>
#include <string.h>
#include <cstdio>
#include <stdio.h>
#ifdef WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
@@ -164,21 +164,11 @@ Connection::~Connection()
Disconnect();
if (m_szHost)
{
free(m_szHost);
}
if (m_szCipher)
{
free(m_szCipher);
}
free(m_szHost);
free(m_szCipher);
free(m_szReadBuf);
#ifndef DISABLE_TLS
if (m_pTLSSocket)
{
delete m_pTLSSocket;
}
delete m_pTLSSocket;
#endif
}
@@ -195,10 +185,7 @@ void Connection::SetSuppressErrors(bool bSuppressErrors)
void Connection::SetCipher(const char* szCipher)
{
if (m_szCipher)
{
free(m_szCipher);
}
free(m_szCipher);
m_szCipher = szCipher ? strdup(szCipher) : NULL;
}
@@ -219,7 +206,7 @@ bool Connection::Connect()
}
else
{
Connection::DoDisconnect();
DoDisconnect();
}
return bRes;
@@ -243,35 +230,120 @@ bool Connection::Disconnect()
return bRes;
}
int Connection::Bind()
bool Connection::Bind()
{
debug("Binding");
if (m_eStatus == csListening)
{
return 0;
return true;
}
int iRes = DoBind();
if (iRes == 0)
#ifdef HAVE_GETADDRINFO
struct addrinfo addr_hints, *addr_list, *addr;
char iPortStr[sizeof(int) * 4 + 1]; // is enough to hold any converted int
memset(&addr_hints, 0, sizeof(addr_hints));
addr_hints.ai_family = AF_UNSPEC; // Allow IPv4 or IPv6
addr_hints.ai_socktype = SOCK_STREAM,
addr_hints.ai_flags = AI_PASSIVE; // For wildcard IP address
sprintf(iPortStr, "%d", m_iPort);
int res = getaddrinfo(m_szHost, iPortStr, &addr_hints, &addr_list);
if (res != 0)
{
m_eStatus = csListening;
error("Could not resolve hostname %s", m_szHost);
return false;
}
m_iSocket = INVALID_SOCKET;
for (addr = addr_list; addr != NULL; addr = addr->ai_next)
{
m_iSocket = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
if (m_iSocket != INVALID_SOCKET)
{
int opt = 1;
setsockopt(m_iSocket, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt));
res = bind(m_iSocket, addr->ai_addr, addr->ai_addrlen);
if (res != -1)
{
// Connection established
break;
}
// Connection failed
closesocket(m_iSocket);
m_iSocket = INVALID_SOCKET;
}
}
freeaddrinfo(addr_list);
#else
struct sockaddr_in sSocketAddress;
memset(&sSocketAddress, 0, sizeof(sSocketAddress));
sSocketAddress.sin_family = AF_INET;
if (!m_szHost || strlen(m_szHost) == 0)
{
sSocketAddress.sin_addr.s_addr = htonl(INADDR_ANY);
}
else
{
sSocketAddress.sin_addr.s_addr = ResolveHostAddr(m_szHost);
if (sSocketAddress.sin_addr.s_addr == (unsigned int)-1)
{
return false;
}
}
sSocketAddress.sin_port = htons(m_iPort);
m_iSocket = socket(PF_INET, SOCK_STREAM, 0);
if (m_iSocket == INVALID_SOCKET)
{
ReportError("Socket creation failed for %s", m_szHost, true, 0);
return false;
}
int opt = 1;
setsockopt(m_iSocket, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt));
int res = bind(m_iSocket, (struct sockaddr *) &sSocketAddress, sizeof(sSocketAddress));
if (res == -1)
{
// Connection failed
closesocket(m_iSocket);
m_iSocket = INVALID_SOCKET;
}
#endif
if (m_iSocket == INVALID_SOCKET)
{
ReportError("Binding socket failed for %s", m_szHost, true, 0);
return false;
}
if (listen(m_iSocket, 100) < 0)
{
ReportError("Listen on socket failed for %s", m_szHost, true, 0);
return false;
}
m_eStatus = csListening;
return iRes;
return true;
}
int Connection::WriteLine(const char* pBuffer)
{
//debug("Connection::write(char* line)");
//debug("Connection::WriteLine");
if (m_eStatus != csConnected)
{
return -1;
}
int iRes = DoWriteLine(pBuffer);
int iRes = send(m_iSocket, pBuffer, strlen(pBuffer), 0);
return iRes;
}
@@ -306,9 +378,73 @@ char* Connection::ReadLine(char* pBuffer, int iSize, int* pBytesRead)
return NULL;
}
char* res = DoReadLine(pBuffer, iSize, pBytesRead);
return res;
char* pBufPtr = pBuffer;
iSize--; // for trailing '0'
int iBytesRead = 0;
int iBufAvail = m_iBufAvail; // local variable is faster
char* szBufPtr = m_szBufPtr; // local variable is faster
while (iSize)
{
if (!iBufAvail)
{
iBufAvail = recv(m_iSocket, m_szReadBuf, CONNECTION_READBUFFER_SIZE, 0);
if (iBufAvail < 0)
{
ReportError("Could not receive data on socket", NULL, true, 0);
break;
}
else if (iBufAvail == 0)
{
break;
}
szBufPtr = m_szReadBuf;
m_szReadBuf[iBufAvail] = '\0';
}
int len = 0;
char* p = (char*)memchr(szBufPtr, '\n', iBufAvail);
if (p)
{
len = (int)(p - szBufPtr + 1);
}
else
{
len = iBufAvail;
}
if (len > iSize)
{
len = iSize;
}
memcpy(pBufPtr, szBufPtr, len);
pBufPtr += len;
szBufPtr += len;
iBufAvail -= len;
iBytesRead += len;
iSize -= len;
if (p)
{
break;
}
}
*pBufPtr = '\0';
m_iBufAvail = iBufAvail > 0 ? iBufAvail : 0; // copy back to member
m_szBufPtr = szBufPtr; // copy back to member
if (pBytesRead)
{
*pBytesRead = iBytesRead;
}
if (pBufPtr == pBuffer)
{
return NULL;
}
return pBuffer;
}
Connection* Connection::Accept()
@@ -320,13 +456,17 @@ Connection* Connection::Accept()
return NULL;
}
SOCKET iRes = DoAccept();
if (iRes == INVALID_SOCKET)
SOCKET iSocket = accept(m_iSocket, NULL, NULL);
if (iSocket == INVALID_SOCKET && m_eStatus != csCancelled)
{
ReportError("Could not accept connection", NULL, true, 0);
}
if (iSocket == INVALID_SOCKET)
{
return NULL;
}
Connection* pCon = new Connection(iRes, m_bTLS);
Connection* pCon = new Connection(iSocket, m_bTLS);
return pCon;
}
@@ -508,200 +648,12 @@ bool Connection::DoDisconnect()
return true;
}
int Connection::DoWriteLine(const char* pBuffer)
{
//debug("Connection::doWrite()");
return send(m_iSocket, pBuffer, strlen(pBuffer), 0);
}
char* Connection::DoReadLine(char* pBuffer, int iSize, int* pBytesRead)
{
//debug( "Connection::DoReadLine()" );
char* pBufPtr = pBuffer;
iSize--; // for trailing '0'
int iBytesRead = 0;
int iBufAvail = m_iBufAvail; // local variable is faster
char* szBufPtr = m_szBufPtr; // local variable is faster
while (iSize)
{
if (!iBufAvail)
{
iBufAvail = recv(m_iSocket, m_szReadBuf, CONNECTION_READBUFFER_SIZE, 0);
if (iBufAvail < 0)
{
ReportError("Could not receive data on socket", NULL, true, 0);
break;
}
else if (iBufAvail == 0)
{
break;
}
szBufPtr = m_szReadBuf;
m_szReadBuf[iBufAvail] = '\0';
}
int len = 0;
char* p = (char*)memchr(szBufPtr, '\n', iBufAvail);
if (p)
{
len = (int)(p - szBufPtr + 1);
}
else
{
len = iBufAvail;
}
if (len > iSize)
{
len = iSize;
}
memcpy(pBufPtr, szBufPtr, len);
pBufPtr += len;
szBufPtr += len;
iBufAvail -= len;
iBytesRead += len;
iSize -= len;
if (p)
{
break;
}
}
*pBufPtr = '\0';
m_iBufAvail = iBufAvail > 0 ? iBufAvail : 0; // copy back to member
m_szBufPtr = szBufPtr; // copy back to member
if (pBytesRead)
{
*pBytesRead = iBytesRead;
}
if (pBufPtr == pBuffer)
{
return NULL;
}
return pBuffer;
}
void Connection::ReadBuffer(char** pBuffer, int *iBufLen)
{
*iBufLen = m_iBufAvail;
*pBuffer = m_szBufPtr;
m_iBufAvail = 0;
};
int Connection::DoBind()
{
debug("Do binding");
#ifdef HAVE_GETADDRINFO
struct addrinfo addr_hints, *addr_list, *addr;
char iPortStr[sizeof(int) * 4 + 1]; // is enough to hold any converted int
memset(&addr_hints, 0, sizeof(addr_hints));
addr_hints.ai_family = AF_UNSPEC; // Allow IPv4 or IPv6
addr_hints.ai_socktype = SOCK_STREAM,
addr_hints.ai_flags = AI_PASSIVE; // For wildcard IP address
sprintf(iPortStr, "%d", m_iPort);
int res = getaddrinfo(m_szHost, iPortStr, &addr_hints, &addr_list);
if (res != 0)
{
error("Could not resolve hostname %s", m_szHost);
return -1;
}
m_iSocket = INVALID_SOCKET;
for (addr = addr_list; addr != NULL; addr = addr->ai_next)
{
m_iSocket = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
if (m_iSocket != INVALID_SOCKET)
{
int opt = 1;
setsockopt(m_iSocket, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt));
res = bind(m_iSocket, addr->ai_addr, addr->ai_addrlen);
if (res != -1)
{
// Connection established
break;
}
// Connection failed
closesocket(m_iSocket);
m_iSocket = INVALID_SOCKET;
}
}
freeaddrinfo(addr_list);
#else
struct sockaddr_in sSocketAddress;
memset(&sSocketAddress, 0, sizeof(sSocketAddress));
sSocketAddress.sin_family = AF_INET;
if (!m_szHost || strlen(m_szHost) == 0)
{
sSocketAddress.sin_addr.s_addr = htonl(INADDR_ANY);
}
else
{
sSocketAddress.sin_addr.s_addr = ResolveHostAddr(m_szHost);
if (sSocketAddress.sin_addr.s_addr == (unsigned int)-1)
{
return -1;
}
}
sSocketAddress.sin_port = htons(m_iPort);
m_iSocket = socket(PF_INET, SOCK_STREAM, 0);
if (m_iSocket == INVALID_SOCKET)
{
ReportError("Socket creation failed for %s", m_szHost, true, 0);
return -1;
}
int opt = 1;
setsockopt(m_iSocket, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt));
int res = bind(m_iSocket, (struct sockaddr *) &sSocketAddress, sizeof(sSocketAddress));
if (res == -1)
{
// Connection failed
closesocket(m_iSocket);
m_iSocket = INVALID_SOCKET;
}
#endif
if (m_iSocket == INVALID_SOCKET)
{
ReportError("Binding socket failed for %s", m_szHost, true, 0);
return -1;
}
if (listen(m_iSocket, 100) < 0)
{
ReportError("Listen on socket failed for %s", m_szHost, true, 0);
return -1;
}
return 0;
}
SOCKET Connection::DoAccept()
{
SOCKET iSocket = accept(m_iSocket, NULL, NULL);
if (iSocket == INVALID_SOCKET && m_eStatus != csCancelled)
{
ReportError("Could not accept connection", NULL, true, 0);
}
return iSocket;
}
};
void Connection::Cancel()
{
@@ -779,11 +731,7 @@ bool Connection::StartTLS(bool bIsClient, const char* szCertFile, const char* sz
{
debug("Starting TLS");
if (m_pTLSSocket)
{
delete m_pTLSSocket;
}
delete m_pTLSSocket;
m_pTLSSocket = new TLSSocket(m_iSocket, bIsClient, szCertFile, szKeyFile, m_szCipher);
m_pTLSSocket->SetSuppressErrors(m_bSuppressErrors);

View File

@@ -72,12 +72,8 @@ protected:
Connection(SOCKET iSocket, bool bTLS);
void ReportError(const char* szMsgPrefix, const char* szMsgArg, bool PrintErrCode, int herrno);
virtual bool DoConnect();
virtual bool DoDisconnect();
int DoBind();
int DoWriteLine(const char* pBuffer);
char* DoReadLine(char* pBuffer, int iSize, int* pBytesRead);
SOCKET DoAccept();
bool DoConnect();
bool DoDisconnect();
#ifndef HAVE_GETADDRINFO
unsigned int ResolveHostAddr(const char* szHost);
#endif
@@ -92,9 +88,9 @@ public:
virtual ~Connection();
static void Init();
static void Final();
bool Connect();
bool Disconnect();
int Bind();
virtual bool Connect();
virtual bool Disconnect();
bool Bind();
bool Send(const char* pBuffer, int iSize);
bool Recv(char* pBuffer, int iSize);
int TryRecv(char* pBuffer, int iSize);

View File

@@ -60,18 +60,12 @@ Decoder::~ Decoder()
{
debug("Destroying Decoder");
if (m_szArticleFilename)
{
free(m_szArticleFilename);
}
free(m_szArticleFilename);
}
void Decoder::Clear()
{
if (m_szArticleFilename)
{
free(m_szArticleFilename);
}
free(m_szArticleFilename);
m_szArticleFilename = NULL;
}
@@ -268,10 +262,7 @@ BreakLoop:
pb += 6; //=strlen(" name=")
char* pe;
for (pe = pb; *pe != '\0' && *pe != '\n' && *pe != '\r'; pe++) ;
if (m_szArticleFilename)
{
free(m_szArticleFilename);
}
free(m_szArticleFilename);
m_szArticleFilename = (char*)malloc(pe - pb + 1);
strncpy(m_szArticleFilename, pb, pe - pb);
m_szArticleFilename[pe - pb] = '\0';
@@ -404,10 +395,7 @@ unsigned int UDecoder::DecodeBuffer(char* buffer, int len)
// extracting filename
char* pe;
for (pe = pb; *pe != '\0' && *pe != '\n' && *pe != '\r'; pe++) ;
if (m_szArticleFilename)
{
free(m_szArticleFilename);
}
free(m_szArticleFilename);
m_szArticleFilename = (char*)malloc(pe - pb + 1);
strncpy(m_szArticleFilename, pb, pe - pb);
m_szArticleFilename[pe - pb] = '\0';

View File

File diff suppressed because it is too large Load Diff

View File

@@ -27,10 +27,13 @@
#define DISKSTATE_H
#include "DownloadInfo.h"
#include "FeedInfo.h"
#include "NewsServer.h"
class DiskState
{
private:
int fscanf(FILE* infile, const char* Format, ...);
int ParseFormatVersion(const char* szFormatSignature);
bool SaveFileInfo(FileInfo* pFileInfo, const char* szFilename);
bool LoadFileInfo(FileInfo* pFileInfo, const char* szFilename, bool bFileSummary, bool bArticles);
@@ -45,19 +48,32 @@ private:
bool LoadUrlQueue(DownloadQueue* pDownloadQueue, FILE* infile, int iFormatVersion);
void SaveUrlInfo(UrlInfo* pUrlInfo, FILE* outfile);
bool LoadUrlInfo(UrlInfo* pUrlInfo, FILE* infile, int iFormatVersion);
void SaveDupInfo(DupInfo* pDupInfo, FILE* outfile);
bool LoadDupInfo(DupInfo* pDupInfo, FILE* infile, int iFormatVersion);
void SaveHistory(DownloadQueue* pDownloadQueue, FILE* outfile);
bool LoadHistory(DownloadQueue* pDownloadQueue, FILE* infile, int iFormatVersion);
int FindNZBInfoIndex(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo);
bool SaveFeedStatus(Feeds* pFeeds, FILE* outfile);
bool LoadFeedStatus(Feeds* pFeeds, FILE* infile, int iFormatVersion);
bool SaveFeedHistory(FeedHistory* pFeedHistory, FILE* outfile);
bool LoadFeedHistory(FeedHistory* pFeedHistory, FILE* infile, int iFormatVersion);
void CalcCriticalHealth(DownloadQueue* pDownloadQueue);
bool SaveServerStats(Servers* pServers, FILE* outfile);
bool LoadServerStats(Servers* pServers, FILE* infile, int iFormatVersion);
void ConvertDupeKey(char* buf, int bufsize);
public:
bool DownloadQueueExists();
bool PostQueueExists(bool bCompleted);
bool SaveDownloadQueue(DownloadQueue* pDownloadQueue);
bool LoadDownloadQueue(DownloadQueue* pDownloadQueue);
bool SaveFile(FileInfo* pFileInfo);
bool LoadArticles(FileInfo* pFileInfo);
bool DiscardDownloadQueue();
void DiscardDownloadQueue();
bool DiscardFile(FileInfo* pFileInfo);
bool SaveFeeds(Feeds* pFeeds, FeedHistory* pFeedHistory);
bool LoadFeeds(Feeds* pFeeds, FeedHistory* pFeedHistory);
bool SaveStats(Servers* pServers);
bool LoadStats(Servers* pServers);
void CleanupTempDir(DownloadQueue* pDownloadQueue);
};

View File

File diff suppressed because it is too large Load Diff

View File

@@ -85,25 +85,36 @@ private:
char* m_szFilename;
long long m_lSize;
long long m_lRemainingSize;
long long m_lSuccessSize;
long long m_lFailedSize;
long long m_lMissedSize;
int m_iTotalArticles;
int m_iMissedArticles;
int m_iFailedArticles;
int m_iSuccessArticles;
time_t m_tTime;
bool m_bPaused;
bool m_bDeleted;
bool m_bFilenameConfirmed;
bool m_bParFile;
int m_iCompleted;
bool m_bOutputInitialized;
char* m_szOutputFilename;
Mutex m_mutexOutputFile;
Mutex* m_pMutexOutputFile;
int m_iPriority;
bool m_bExtraPriority;
int m_iActiveDownloads;
bool m_bAutoDeleted;
static int m_iIDGen;
static int m_iIDMax;
public:
FileInfo();
~FileInfo();
int GetID() { return m_iID; }
void SetID(int s);
void SetID(int iID);
static void ResetGenID(bool bMax);
NZBInfo* GetNZBInfo() { return m_pNZBInfo; }
void SetNZBInfo(NZBInfo* pNZBInfo);
Articles* GetArticles() { return &m_Articles; }
@@ -115,10 +126,24 @@ public:
void MakeValidFilename();
bool GetFilenameConfirmed() { return m_bFilenameConfirmed; }
void SetFilenameConfirmed(bool bFilenameConfirmed) { m_bFilenameConfirmed = bFilenameConfirmed; }
void SetSize(long long s) { m_lSize = s; m_lRemainingSize = s; }
void SetSize(long long lSize) { m_lSize = lSize; m_lRemainingSize = lSize; }
long long GetSize() { return m_lSize; }
long long GetRemainingSize() { return m_lRemainingSize; }
void SetRemainingSize(long long s) { m_lRemainingSize = s; }
void SetRemainingSize(long long lRemainingSize) { m_lRemainingSize = lRemainingSize; }
long long GetMissedSize() { return m_lMissedSize; }
void SetMissedSize(long long lMissedSize) { m_lMissedSize = lMissedSize; }
long long GetSuccessSize() { return m_lSuccessSize; }
void SetSuccessSize(long long lSuccessSize) { m_lSuccessSize = lSuccessSize; }
long long GetFailedSize() { return m_lFailedSize; }
void SetFailedSize(long long lFailedSize) { m_lFailedSize = lFailedSize; }
int GetTotalArticles() { return m_iTotalArticles; }
void SetTotalArticles(int iTotalArticles) { m_iTotalArticles = iTotalArticles; }
int GetMissedArticles() { return m_iMissedArticles; }
void SetMissedArticles(int iMissedArticles) { m_iMissedArticles = iMissedArticles; }
int GetFailedArticles() { return m_iFailedArticles; }
void SetFailedArticles(int iFailedArticles) { m_iFailedArticles = iFailedArticles; }
int GetSuccessArticles() { return m_iSuccessArticles; }
void SetSuccessArticles(int iSuccessArticles) { m_iSuccessArticles = iSuccessArticles; }
time_t GetTime() { return m_tTime; }
void SetTime(time_t tTime) { m_tTime = tTime; }
bool GetPaused() { return m_bPaused; }
@@ -126,7 +151,9 @@ public:
bool GetDeleted() { return m_bDeleted; }
void SetDeleted(bool Deleted) { m_bDeleted = Deleted; }
int GetCompleted() { return m_iCompleted; }
void SetCompleted(int s) { m_iCompleted = s; }
void SetCompleted(int iCompleted) { m_iCompleted = iCompleted; }
bool GetParFile() { return m_bParFile; }
void SetParFile(bool bParFile) { m_bParFile = bParFile; }
void ClearArticles();
void LockOutputFile();
void UnlockOutputFile();
@@ -134,13 +161,14 @@ public:
void SetOutputFilename(const char* szOutputFilename);
bool GetOutputInitialized() { return m_bOutputInitialized; }
void SetOutputInitialized(bool bOutputInitialized) { m_bOutputInitialized = bOutputInitialized; }
bool IsDupe(const char* szFilename);
int GetPriority() { return m_iPriority; }
void SetPriority(int iPriority) { m_iPriority = iPriority; }
bool GetExtraPriority() { return m_bExtraPriority; }
void SetExtraPriority(bool bExtraPriority) { m_bExtraPriority = bExtraPriority; };
int GetActiveDownloads() { return m_iActiveDownloads; }
void SetActiveDownloads(int iActiveDownloads) { m_iActiveDownloads = iActiveDownloads; }
void SetActiveDownloads(int iActiveDownloads);
bool GetAutoDeleted() { return m_bAutoDeleted; }
void SetAutoDeleted(bool bAutoDeleted) { m_bAutoDeleted = bAutoDeleted; }
};
typedef std::deque<FileInfo*> FileQueue;
@@ -182,7 +210,15 @@ public:
int GetActiveDownloads() { return m_iActiveDownloads; }
};
typedef std::deque<GroupInfo*> GroupQueue;
typedef std::deque<GroupInfo*> GroupQueueBase;
class GroupQueue : public GroupQueueBase
{
public:
~GroupQueue();
void Clear();
};
class NZBParameter
{
@@ -206,7 +242,79 @@ typedef std::deque<NZBParameter*> NZBParameterListBase;
class NZBParameterList : public NZBParameterListBase
{
public:
~NZBParameterList();
void SetParameter(const char* szName, const char* szValue);
NZBParameter* Find(const char* szName, bool bCaseSensitive);
void Clear();
void CopyFrom(NZBParameterList* pSourceParameters);
};
class ScriptStatus
{
public:
enum EStatus
{
srNone,
srFailure,
srSuccess
};
private:
char* m_szName;
EStatus m_eStatus;
friend class ScriptStatusList;
public:
ScriptStatus(const char* szName, EStatus eStatus);
~ScriptStatus();
const char* GetName() { return m_szName; }
EStatus GetStatus() { return m_eStatus; }
};
typedef std::deque<ScriptStatus*> ScriptStatusListBase;
class ScriptStatusList : public ScriptStatusListBase
{
public:
~ScriptStatusList();
void Add(const char* szScriptName, ScriptStatus::EStatus eStatus);
void Clear();
ScriptStatus::EStatus CalcTotalStatus();
};
class ServerStat
{
private:
int m_iServerID;
int m_iSuccessArticles;
int m_iFailedArticles;
public:
ServerStat(int iServerID);
int GetServerID() { return m_iServerID; }
int GetSuccessArticles() { return m_iSuccessArticles; }
void SetSuccessArticles(int iSuccessArticles) { m_iSuccessArticles = iSuccessArticles; }
int GetFailedArticles() { return m_iFailedArticles; }
void SetFailedArticles(int iFailedArticles) { m_iFailedArticles = iFailedArticles; }
};
typedef std::vector<ServerStat*> ServerStatListBase;
class ServerStatList : public ServerStatListBase
{
public:
~ServerStatList();
void SetStat(int iServerID, int iSuccessArticles, int iFailedArticles, bool bAdd);
void Add(ServerStatList* pServerStats);
void Clear();
};
enum EDupeMode
{
dmScore,
dmAll,
dmForce
};
class NZBInfoList;
@@ -228,7 +336,8 @@ public:
psSkipped,
psFailure,
psSuccess,
psRepairPossible
psRepairPossible,
psManual
};
enum EUnpackStatus
@@ -236,15 +345,16 @@ public:
usNone,
usSkipped,
usFailure,
usSuccess
usSuccess,
usSpace,
usPassword
};
enum EScriptStatus
enum ECleanupStatus
{
srNone,
srUnknown,
srFailure,
srSuccess
csNone,
csFailure,
csSuccess
};
enum EMoveStatus
@@ -254,6 +364,21 @@ public:
msSuccess
};
enum EDeleteStatus
{
dsNone,
dsManual,
dsHealth,
dsDupe
};
enum EMarkStatus
{
ksNone,
ksBad,
ksGood
};
typedef std::vector<char*> Files;
typedef std::deque<Message*> Messages;
@@ -263,53 +388,109 @@ private:
char* m_szFilename;
char* m_szName;
char* m_szDestDir;
char* m_szFinalDir;
char* m_szCategory;
int m_iFileCount;
int m_iParkedFileCount;
long long m_lSize;
long long m_lSuccessSize;
long long m_lFailedSize;
long long m_lCurrentSuccessSize;
long long m_lCurrentFailedSize;
long long m_lParSize;
long long m_lParSuccessSize;
long long m_lParFailedSize;
long long m_lParCurrentSuccessSize;
long long m_lParCurrentFailedSize;
int m_iTotalArticles;
int m_iSuccessArticles;
int m_iFailedArticles;
Files m_completedFiles;
bool m_bPostProcess;
ERenameStatus m_eRenameStatus;
EParStatus m_eParStatus;
EUnpackStatus m_eUnpackStatus;
EScriptStatus m_eScriptStatus;
ECleanupStatus m_eCleanupStatus;
EMoveStatus m_eMoveStatus;
EDeleteStatus m_eDeleteStatus;
EMarkStatus m_eMarkStatus;
bool m_bDeletePaused;
bool m_bManyDupeFiles;
char* m_szQueuedFilename;
bool m_bDeleted;
bool m_bDeleting;
bool m_bAvoidHistory;
bool m_bHealthPaused;
bool m_bParCleanup;
bool m_bParManual;
bool m_bCleanupDisk;
bool m_bUnpackCleanedUpDisk;
char* m_szDupeKey;
int m_iDupeScore;
EDupeMode m_eDupeMode;
unsigned int m_iFullContentHash;
unsigned int m_iFilteredContentHash;
NZBInfoList* m_Owner;
NZBParameterList m_ppParameters;
ScriptStatusList m_scriptStatuses;
ServerStatList m_ServerStats;
Mutex m_mutexLog;
Messages m_Messages;
int m_iIDMessageGen;
static int m_iIDGen;
static int m_iIDMax;
friend class NZBInfoList;
public:
NZBInfo();
NZBInfo(bool bPersistent = true);
~NZBInfo();
void AddReference();
void Retain();
void Release();
int GetID() { return m_iID; }
void SetID(int iID);
static void ResetGenID(bool bMax);
const char* GetFilename() { return m_szFilename; }
void SetFilename(const char* szFilename);
static void MakeNiceNZBName(const char* szNZBFilename, char* szBuffer, int iSize, bool bRemoveExt);
const char* GetDestDir() { return m_szDestDir; } // needs locking (for shared objects)
void SetDestDir(const char* szDestDir); // needs locking (for shared objects)
const char* GetFinalDir() { return m_szFinalDir; } // needs locking (for shared objects)
void SetFinalDir(const char* szFinalDir); // needs locking (for shared objects)
const char* GetCategory() { return m_szCategory; } // needs locking (for shared objects)
void SetCategory(const char* szCategory); // needs locking (for shared objects)
const char* GetName() { return m_szName; } // needs locking (for shared objects)
void SetName(const char* szName); // needs locking (for shared objects)
long long GetSize() { return m_lSize; }
void SetSize(long long lSize) { m_lSize = lSize; }
int GetFileCount() { return m_iFileCount; }
void SetFileCount(int iFileCount) { m_iFileCount = iFileCount; }
int GetParkedFileCount() { return m_iParkedFileCount; }
void SetParkedFileCount(int iParkedFileCount) { m_iParkedFileCount = iParkedFileCount; }
long long GetSize() { return m_lSize; }
void SetSize(long long lSize) { m_lSize = lSize; }
long long GetSuccessSize() { return m_lSuccessSize; }
void SetSuccessSize(long long lSuccessSize) { m_lSuccessSize = lSuccessSize; }
long long GetFailedSize() { return m_lFailedSize; }
void SetFailedSize(long long lFailedSize) { m_lFailedSize = lFailedSize; }
long long GetCurrentSuccessSize() { return m_lCurrentSuccessSize; }
void SetCurrentSuccessSize(long long lCurrentSuccessSize) { m_lCurrentSuccessSize = lCurrentSuccessSize; }
long long GetCurrentFailedSize() { return m_lCurrentFailedSize; }
void SetCurrentFailedSize(long long lCurrentFailedSize) { m_lCurrentFailedSize = lCurrentFailedSize; }
long long GetParSize() { return m_lParSize; }
void SetParSize(long long lParSize) { m_lParSize = lParSize; }
long long GetParSuccessSize() { return m_lParSuccessSize; }
void SetParSuccessSize(long long lParSuccessSize) { m_lParSuccessSize = lParSuccessSize; }
long long GetParFailedSize() { return m_lParFailedSize; }
void SetParFailedSize(long long lParFailedSize) { m_lParFailedSize = lParFailedSize; }
long long GetParCurrentSuccessSize() { return m_lParCurrentSuccessSize; }
void SetParCurrentSuccessSize(long long lParCurrentSuccessSize) { m_lParCurrentSuccessSize = lParCurrentSuccessSize; }
long long GetParCurrentFailedSize() { return m_lParCurrentFailedSize; }
void SetParCurrentFailedSize(long long lParCurrentFailedSize) { m_lParCurrentFailedSize = lParCurrentFailedSize; }
int GetTotalArticles() { return m_iTotalArticles; }
void SetTotalArticles(int iTotalArticles) { m_iTotalArticles = iTotalArticles; }
int GetSuccessArticles() { return m_iSuccessArticles; }
void SetSuccessArticles(int iSuccessArticles) { m_iSuccessArticles = iSuccessArticles; }
int GetFailedArticles() { return m_iFailedArticles; }
void SetFailedArticles(int iFailedArticles) { m_iFailedArticles = iFailedArticles; }
void BuildDestDirName();
void BuildFinalDirName(char* szFinalDirBuf, int iBufSize);
Files* GetCompletedFiles() { return &m_completedFiles; } // needs locking (for shared objects)
@@ -322,14 +503,26 @@ public:
void SetParStatus(EParStatus eParStatus) { m_eParStatus = eParStatus; }
EUnpackStatus GetUnpackStatus() { return m_eUnpackStatus; }
void SetUnpackStatus(EUnpackStatus eUnpackStatus) { m_eUnpackStatus = eUnpackStatus; }
ECleanupStatus GetCleanupStatus() { return m_eCleanupStatus; }
void SetCleanupStatus(ECleanupStatus eCleanupStatus) { m_eCleanupStatus = eCleanupStatus; }
EMoveStatus GetMoveStatus() { return m_eMoveStatus; }
void SetMoveStatus(EMoveStatus eMoveStatus) { m_eMoveStatus = eMoveStatus; }
EScriptStatus GetScriptStatus() { return m_eScriptStatus; }
void SetScriptStatus(EScriptStatus eScriptStatus) { m_eScriptStatus = eScriptStatus; }
EDeleteStatus GetDeleteStatus() { return m_eDeleteStatus; }
void SetDeleteStatus(EDeleteStatus eDeleteStatus) { m_eDeleteStatus = eDeleteStatus; }
EMarkStatus GetMarkStatus() { return m_eMarkStatus; }
void SetMarkStatus(EMarkStatus eMarkStatus) { m_eMarkStatus = eMarkStatus; }
const char* GetQueuedFilename() { return m_szQueuedFilename; }
void SetQueuedFilename(const char* szQueuedFilename);
bool GetDeleted() { return m_bDeleted; }
void SetDeleted(bool bDeleted) { m_bDeleted = bDeleted; }
bool GetDeleting() { return m_bDeleting; }
void SetDeleting(bool bDeleting) { m_bDeleting = bDeleting; }
bool GetDeletePaused() { return m_bDeletePaused; }
void SetDeletePaused(bool bDeletePaused) { m_bDeletePaused = bDeletePaused; }
bool GetManyDupeFiles() { return m_bManyDupeFiles; }
void SetManyDupeFiles(bool bManyDupeFiles) { m_bManyDupeFiles = bManyDupeFiles; }
bool GetAvoidHistory() { return m_bAvoidHistory; }
void SetAvoidHistory(bool bAvoidHistory) { m_bAvoidHistory = bAvoidHistory; }
bool GetHealthPaused() { return m_bHealthPaused; }
void SetHealthPaused(bool bHealthPaused) { m_bHealthPaused = bHealthPaused; }
bool GetParCleanup() { return m_bParCleanup; }
void SetParCleanup(bool bParCleanup) { m_bParCleanup = bParCleanup; }
bool GetCleanupDisk() { return m_bCleanupDisk; }
@@ -337,7 +530,20 @@ public:
bool GetUnpackCleanedUpDisk() { return m_bUnpackCleanedUpDisk; }
void SetUnpackCleanedUpDisk(bool bUnpackCleanedUpDisk) { m_bUnpackCleanedUpDisk = bUnpackCleanedUpDisk; }
NZBParameterList* GetParameters() { return &m_ppParameters; } // needs locking (for shared objects)
void SetParameter(const char* szName, const char* szValue); // needs locking (for shared objects)
ScriptStatusList* GetScriptStatuses() { return &m_scriptStatuses; } // needs locking (for shared objects)
ServerStatList* GetServerStats() { return &m_ServerStats; }
int CalcHealth();
int CalcCriticalHealth();
const char* GetDupeKey() { return m_szDupeKey; } // needs locking (for shared objects)
void SetDupeKey(const char* szDupeKey); // needs locking (for shared objects)
int GetDupeScore() { return m_iDupeScore; }
void SetDupeScore(int iDupeScore) { m_iDupeScore = iDupeScore; }
EDupeMode GetDupeMode() { return m_eDupeMode; }
void SetDupeMode(EDupeMode eDupeMode) { m_eDupeMode = eDupeMode; }
unsigned int GetFullContentHash() { return m_iFullContentHash; }
void SetFullContentHash(unsigned int iFullContentHash) { m_iFullContentHash = iFullContentHash; }
unsigned int GetFilteredContentHash() { return m_iFilteredContentHash; }
void SetFilteredContentHash(unsigned int iFilteredContentHash) { m_iFilteredContentHash = iFilteredContentHash; }
void AppendMessage(Message::EKind eKind, time_t tTime, const char* szText);
Messages* LockMessages();
void UnlockMessages();
@@ -370,61 +576,15 @@ public:
ptFinished
};
enum ERenameStatus
{
rsNone,
rsSkipped,
rsFailure,
rsSuccess
};
enum EParStatus
{
psNone,
psSkipped,
psFailure,
psSuccess,
psRepairPossible
};
enum ERequestParCheck
{
rpNone,
rpCurrent,
rpAll
};
enum EUnpackStatus
{
usNone,
usSkipped,
usFailure,
usSuccess
};
enum EScriptStatus
{
srNone,
srUnknown,
srFailure,
srSuccess
};
typedef std::deque<Message*> Messages;
private:
int m_iID;
NZBInfo* m_pNZBInfo;
char* m_szParFilename;
char* m_szInfoName;
bool m_bWorking;
bool m_bDeleted;
ERenameStatus m_eRenameStatus;
EParStatus m_eParStatus;
EUnpackStatus m_eUnpackStatus;
EScriptStatus m_eScriptStatus;
ERequestParCheck m_eRequestParCheck;
bool m_bRequestParRename;
bool m_bRequestParCheck;
EStage m_eStage;
char* m_szProgressLabel;
int m_iFileProgress;
@@ -438,6 +598,7 @@ private:
int m_iIDMessageGen;
static int m_iIDGen;
static int m_iIDMax;
public:
PostInfo();
@@ -445,8 +606,6 @@ public:
int GetID() { return m_iID; }
NZBInfo* GetNZBInfo() { return m_pNZBInfo; }
void SetNZBInfo(NZBInfo* pNZBInfo);
const char* GetParFilename() { return m_szParFilename; }
void SetParFilename(const char* szParFilename);
const char* GetInfoName() { return m_szInfoName; }
void SetInfoName(const char* szInfoName);
EStage GetStage() { return m_eStage; }
@@ -465,18 +624,8 @@ public:
void SetWorking(bool bWorking) { m_bWorking = bWorking; }
bool GetDeleted() { return m_bDeleted; }
void SetDeleted(bool bDeleted) { m_bDeleted = bDeleted; }
ERenameStatus GetRenameStatus() { return m_eRenameStatus; }
void SetRenameStatus(ERenameStatus eRenameStatus) { m_eRenameStatus = eRenameStatus; }
EParStatus GetParStatus() { return m_eParStatus; }
void SetParStatus(EParStatus eParStatus) { m_eParStatus = eParStatus; }
EUnpackStatus GetUnpackStatus() { return m_eUnpackStatus; }
void SetUnpackStatus(EUnpackStatus eUnpackStatus) { m_eUnpackStatus = eUnpackStatus; }
ERequestParCheck GetRequestParCheck() { return m_eRequestParCheck; }
void SetRequestParCheck(ERequestParCheck eRequestParCheck) { m_eRequestParCheck = eRequestParCheck; }
bool GetRequestParRename() { return m_bRequestParRename; }
void SetRequestParRename(bool bRequestParRename) { m_bRequestParRename = bRequestParRename; }
EScriptStatus GetScriptStatus() { return m_eScriptStatus; }
void SetScriptStatus(EScriptStatus eScriptStatus) { m_eScriptStatus = eScriptStatus; }
bool GetRequestParCheck() { return m_bRequestParCheck; }
void SetRequestParCheck(bool bRequestParCheck) { m_bRequestParCheck = bRequestParCheck; }
void AppendMessage(Message::EKind eKind, const char* szText);
Thread* GetPostThread() { return m_pPostThread; }
void SetPostThread(Thread* pPostThread) { m_pPostThread = pPostThread; }
@@ -499,7 +648,9 @@ public:
aiRunning,
aiFinished,
aiFailed,
aiRetry
aiRetry,
aiScanSkipped,
aiScanFailed
};
private:
@@ -508,17 +659,23 @@ private:
char* m_szNZBFilename;
char* m_szCategory;
int m_iPriority;
char* m_szDupeKey;
int m_iDupeScore;
EDupeMode m_eDupeMode;
bool m_bAddTop;
bool m_bAddPaused;
bool m_bForce;
EStatus m_eStatus;
static int m_iIDGen;
static int m_iIDMax;
public:
UrlInfo();
~UrlInfo();
int GetID() { return m_iID; }
void SetID(int s);
void SetID(int iID);
static void ResetGenID(bool bMax);
const char* GetURL() { return m_szURL; } // needs locking (for shared objects)
void SetURL(const char* szURL); // needs locking (for shared objects)
const char* GetNZBFilename() { return m_szNZBFilename; } // needs locking (for shared objects)
@@ -527,18 +684,71 @@ public:
void SetCategory(const char* szCategory); // needs locking (for shared objects)
int GetPriority() { return m_iPriority; }
void SetPriority(int iPriority) { m_iPriority = iPriority; }
const char* GetDupeKey() { return m_szDupeKey; }
void SetDupeKey(const char* szDupeKey);
int GetDupeScore() { return m_iDupeScore; }
void SetDupeScore(int iDupeScore) { m_iDupeScore = iDupeScore; }
EDupeMode GetDupeMode() { return m_eDupeMode; }
void SetDupeMode(EDupeMode eDupeMode) { m_eDupeMode = eDupeMode; }
bool GetAddTop() { return m_bAddTop; }
void SetAddTop(bool bAddTop) { m_bAddTop = bAddTop; }
bool GetAddPaused() { return m_bAddPaused; }
void SetAddPaused(bool bAddPaused) { m_bAddPaused = bAddPaused; }
void GetName(char* szBuffer, int iSize); // needs locking (for shared objects)
static void MakeNiceName(const char* szURL, const char* szNZBFilename, char* szBuffer, int iSize);
bool GetForce() { return m_bForce; }
void SetForce(bool bForce) { m_bForce = bForce; }
EStatus GetStatus() { return m_eStatus; }
void SetStatus(EStatus Status) { m_eStatus = Status; }
};
typedef std::deque<UrlInfo*> UrlQueue;
class DupInfo
{
public:
enum EStatus
{
dsUndefined,
dsSuccess,
dsFailed,
dsDeleted,
dsDupe,
dsBad,
dsGood
};
private:
char* m_szName;
char* m_szDupeKey;
int m_iDupeScore;
EDupeMode m_eDupeMode;
long long m_lSize;
unsigned int m_iFullContentHash;
unsigned int m_iFilteredContentHash;
EStatus m_eStatus;
public:
DupInfo();
~DupInfo();
const char* GetName() { return m_szName; } // needs locking (for shared objects)
void SetName(const char* szName); // needs locking (for shared objects)
const char* GetDupeKey() { return m_szDupeKey; } // needs locking (for shared objects)
void SetDupeKey(const char* szDupeKey); // needs locking (for shared objects)
int GetDupeScore() { return m_iDupeScore; }
void SetDupeScore(int iDupeScore) { m_iDupeScore = iDupeScore; }
EDupeMode GetDupeMode() { return m_eDupeMode; }
void SetDupeMode(EDupeMode eDupeMode) { m_eDupeMode = eDupeMode; }
long long GetSize() { return m_lSize; }
void SetSize(long long lSize) { m_lSize = lSize; }
unsigned int GetFullContentHash() { return m_iFullContentHash; }
void SetFullContentHash(unsigned int iFullContentHash) { m_iFullContentHash = iFullContentHash; }
unsigned int GetFilteredContentHash() { return m_iFilteredContentHash; }
void SetFilteredContentHash(unsigned int iFilteredContentHash) { m_iFilteredContentHash = iFilteredContentHash; }
EStatus GetStatus() { return m_eStatus; }
void SetStatus(EStatus Status) { m_eStatus = Status; }
};
class HistoryInfo
{
public:
@@ -546,7 +756,8 @@ public:
{
hkUnknown,
hkNZBInfo,
hkUrlInfo
hkUrlInfo,
hkDupInfo
};
private:
@@ -556,16 +767,20 @@ private:
time_t m_tTime;
static int m_iIDGen;
static int m_iIDMax;
public:
HistoryInfo(NZBInfo* pNZBInfo);
HistoryInfo(UrlInfo* pUrlInfo);
HistoryInfo(DupInfo* pDupInfo);
~HistoryInfo();
int GetID() { return m_iID; }
void SetID(int s);
void SetID(int iID);
static void ResetGenID(bool bMax);
EKind GetKind() { return m_eKind; }
NZBInfo* GetNZBInfo() { return (NZBInfo*)m_pInfo; }
UrlInfo* GetUrlInfo() { return (UrlInfo*)m_pInfo; }
DupInfo* GetDupInfo() { return (DupInfo*)m_pInfo; }
void DiscardUrlInfo() { m_pInfo = NULL; }
time_t GetTime() { return m_tTime; }
void SetTime(time_t tTime) { m_tTime = tTime; }

583
DupeCoordinator.cpp Normal file
View File

@@ -0,0 +1,583 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#ifdef WIN32
#include <direct.h>
#else
#include <unistd.h>
#endif
#include <set>
#include <algorithm>
#include "nzbget.h"
#include "Options.h"
#include "Log.h"
#include "Util.h"
#include "DiskState.h"
#include "NZBFile.h"
#include "QueueCoordinator.h"
#include "DupeCoordinator.h"
extern QueueCoordinator* g_pQueueCoordinator;
extern Options* g_pOptions;
extern DiskState* g_pDiskState;
bool DupeCoordinator::IsDupeSuccess(NZBInfo* pNZBInfo)
{
bool bFailure =
pNZBInfo->GetDeleteStatus() != NZBInfo::dsNone ||
pNZBInfo->GetMarkStatus() == NZBInfo::ksBad ||
pNZBInfo->GetParStatus() == NZBInfo::psFailure ||
pNZBInfo->GetUnpackStatus() == NZBInfo::usFailure ||
pNZBInfo->GetUnpackStatus() == NZBInfo::usPassword ||
(pNZBInfo->GetParStatus() == NZBInfo::psSkipped &&
pNZBInfo->GetUnpackStatus() == NZBInfo::usSkipped &&
pNZBInfo->CalcHealth() < pNZBInfo->CalcCriticalHealth());
return !bFailure;
}
bool DupeCoordinator::SameNameOrKey(const char* szName1, const char* szDupeKey1,
const char* szName2, const char* szDupeKey2)
{
bool bHasDupeKeys = !Util::EmptyStr(szDupeKey1) && !Util::EmptyStr(szDupeKey2);
return (bHasDupeKeys && !strcmp(szDupeKey1, szDupeKey2)) ||
(!bHasDupeKeys && !strcmp(szName1, szName2));
}
/**
Check if the title was already downloaded or is already queued:
- if there is a duplicate with exactly same content (via hash-check)
in queue or in history - the new item is skipped;
- if there is a duplicate marked as good in history - the new item is skipped;
- if there is a duplicate with success-status in dup-history but
there are no duplicates in recent history - the new item is skipped;
- if queue has a duplicate with the same or higher score - the new item
is moved to history as dupe-backup;
- if queue has a duplicate with lower score - the existing item is moved
to history as dupe-backup (unless it is in post-processing stage) and
the new item is added to queue;
- if queue doesn't have duplicates - the new item is added to queue.
*/
void DupeCoordinator::NZBFound(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo)
{
debug("Checking duplicates for %s", pNZBInfo->GetName());
GroupQueue groupQueue;
pDownloadQueue->BuildGroups(&groupQueue);
// find duplicates in download queue with exactly same content
for (GroupQueue::iterator it = groupQueue.begin(); it != groupQueue.end(); it++)
{
GroupInfo* pGroupInfo = *it;
NZBInfo* pGroupNZBInfo = pGroupInfo->GetNZBInfo();
bool bSameContent = (pNZBInfo->GetFullContentHash() > 0 &&
pNZBInfo->GetFullContentHash() == pGroupNZBInfo->GetFullContentHash()) ||
(pNZBInfo->GetFilteredContentHash() > 0 &&
pNZBInfo->GetFilteredContentHash() == pGroupNZBInfo->GetFilteredContentHash());
// if there is a duplicate with exactly same content (via hash-check)
// in queue - the new item is skipped
if (pGroupNZBInfo != pNZBInfo && bSameContent)
{
if (!strcmp(pNZBInfo->GetName(), pGroupNZBInfo->GetName()))
{
warn("Skipping duplicate %s, already queued", pNZBInfo->GetName());
}
else
{
warn("Skipping duplicate %s, already queued as %s",
pNZBInfo->GetName(), pGroupNZBInfo->GetName());
}
// Flag saying QueueCoordinator to skip nzb-file
pNZBInfo->SetDeleteStatus(NZBInfo::dsManual);
DeleteQueuedFile(pNZBInfo->GetQueuedFilename());
return;
}
}
// find duplicates in post queue with exactly same content
for (PostQueue::iterator it = pDownloadQueue->GetPostQueue()->begin(); it != pDownloadQueue->GetPostQueue()->end(); it++)
{
PostInfo* pPostInfo = *it;
bool bSameContent = (pNZBInfo->GetFullContentHash() > 0 &&
pNZBInfo->GetFullContentHash() == pPostInfo->GetNZBInfo()->GetFullContentHash()) ||
(pNZBInfo->GetFilteredContentHash() > 0 &&
pNZBInfo->GetFilteredContentHash() == pPostInfo->GetNZBInfo()->GetFilteredContentHash());
// if there is a duplicate with exactly same content (via hash-check)
// in queue - the new item is skipped;
if (bSameContent)
{
if (!strcmp(pNZBInfo->GetName(), pPostInfo->GetNZBInfo()->GetName()))
{
warn("Skipping duplicate %s, already queued", pNZBInfo->GetName());
}
else
{
warn("Skipping duplicate %s, already queued as %s",
pNZBInfo->GetName(), pPostInfo->GetNZBInfo()->GetName());
}
// Flag saying QueueCoordinator to skip nzb-file
pNZBInfo->SetDeleteStatus(NZBInfo::dsManual);
DeleteQueuedFile(pNZBInfo->GetQueuedFilename());
return;
}
}
// find duplicates in history
bool bSkip = false;
bool bGood = false;
bool bSameContent = false;
const char* szDupeName = NULL;
// find duplicates in queue having exactly same content
// also: nzb-files having duplicates marked as good are skipped
// also (only in score mode): nzb-files having success-duplicates in dup-history but don't having duplicates in recent history are skipped
for (HistoryList::iterator it = pDownloadQueue->GetHistoryList()->begin(); it != pDownloadQueue->GetHistoryList()->end(); it++)
{
HistoryInfo* pHistoryInfo = *it;
if (pHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo &&
((pNZBInfo->GetFullContentHash() > 0 &&
pNZBInfo->GetFullContentHash() == pHistoryInfo->GetNZBInfo()->GetFullContentHash()) ||
(pNZBInfo->GetFilteredContentHash() > 0 &&
pNZBInfo->GetFilteredContentHash() == pHistoryInfo->GetNZBInfo()->GetFilteredContentHash())))
{
bSkip = true;
bSameContent = true;
szDupeName = pHistoryInfo->GetNZBInfo()->GetName();
break;
}
if (pHistoryInfo->GetKind() == HistoryInfo::hkDupInfo &&
((pNZBInfo->GetFullContentHash() > 0 &&
pNZBInfo->GetFullContentHash() == pHistoryInfo->GetDupInfo()->GetFullContentHash()) ||
(pNZBInfo->GetFilteredContentHash() > 0 &&
pNZBInfo->GetFilteredContentHash() == pHistoryInfo->GetDupInfo()->GetFilteredContentHash())))
{
bSkip = true;
bSameContent = true;
szDupeName = pHistoryInfo->GetDupInfo()->GetName();
break;
}
if (pHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo &&
pHistoryInfo->GetNZBInfo()->GetDupeMode() != dmForce &&
pHistoryInfo->GetNZBInfo()->GetMarkStatus() == NZBInfo::ksGood &&
SameNameOrKey(pHistoryInfo->GetNZBInfo()->GetName(), pHistoryInfo->GetNZBInfo()->GetDupeKey(),
pNZBInfo->GetName(), pNZBInfo->GetDupeKey()))
{
bSkip = true;
bGood = true;
szDupeName = pHistoryInfo->GetNZBInfo()->GetName();
break;
}
if (pHistoryInfo->GetKind() == HistoryInfo::hkDupInfo &&
pHistoryInfo->GetDupInfo()->GetDupeMode() != dmForce &&
(pHistoryInfo->GetDupInfo()->GetStatus() == DupInfo::dsGood ||
(pNZBInfo->GetDupeMode() == dmScore &&
pHistoryInfo->GetDupInfo()->GetStatus() == DupInfo::dsSuccess &&
pNZBInfo->GetDupeScore() <= pHistoryInfo->GetDupInfo()->GetDupeScore())) &&
SameNameOrKey(pHistoryInfo->GetDupInfo()->GetName(), pHistoryInfo->GetDupInfo()->GetDupeKey(),
pNZBInfo->GetName(), pNZBInfo->GetDupeKey()))
{
bSkip = true;
bGood = pHistoryInfo->GetDupInfo()->GetStatus() == DupInfo::dsGood;
szDupeName = pHistoryInfo->GetDupInfo()->GetName();
break;
}
}
if (!bSameContent && !bGood && pNZBInfo->GetDupeMode() == dmScore)
{
// nzb-files having success-duplicates in recent history (with different content) are added to history for backup
for (HistoryList::iterator it = pDownloadQueue->GetHistoryList()->begin(); it != pDownloadQueue->GetHistoryList()->end(); it++)
{
HistoryInfo* pHistoryInfo = *it;
if (pHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo &&
pHistoryInfo->GetNZBInfo()->GetDupeMode() != dmForce &&
SameNameOrKey(pHistoryInfo->GetNZBInfo()->GetName(), pHistoryInfo->GetNZBInfo()->GetDupeKey(),
pNZBInfo->GetName(), pNZBInfo->GetDupeKey()) &&
pNZBInfo->GetDupeScore() <= pHistoryInfo->GetNZBInfo()->GetDupeScore() &&
IsDupeSuccess(pHistoryInfo->GetNZBInfo()))
{
// Flag saying QueueCoordinator to skip nzb-file
pNZBInfo->SetDeleteStatus(NZBInfo::dsDupe);
info("Collection %s is duplicate to %s", pNZBInfo->GetName(), pHistoryInfo->GetNZBInfo()->GetName());
return;
}
}
}
if (bSkip)
{
if (!strcmp(pNZBInfo->GetName(), szDupeName))
{
warn("Skipping duplicate %s, found in history with %s", pNZBInfo->GetName(),
bSameContent ? "exactly same content" : bGood ? "good status" : "success status");
}
else
{
warn("Skipping duplicate %s, found in history %s with %s",
pNZBInfo->GetName(), szDupeName,
bSameContent ? "exactly same content" : bGood ? "good status" : "success status");
}
// Flag saying QueueCoordinator to skip nzb-file
pNZBInfo->SetDeleteStatus(NZBInfo::dsManual);
DeleteQueuedFile(pNZBInfo->GetQueuedFilename());
return;
}
// find duplicates in download queue and post-queue and handle both items according to their scores:
// only one item remains in queue and another one is moved to history as dupe-backup
if (pNZBInfo->GetDupeMode() == dmScore)
{
// find duplicates in download queue
for (GroupQueue::iterator it = groupQueue.begin(); it != groupQueue.end(); it++)
{
GroupInfo* pGroupInfo = *it;
NZBInfo* pGroupNZBInfo = pGroupInfo->GetNZBInfo();
if (pGroupNZBInfo != pNZBInfo &&
pGroupNZBInfo->GetDupeMode() != dmForce &&
SameNameOrKey(pGroupNZBInfo->GetName(), pGroupNZBInfo->GetDupeKey(),
pNZBInfo->GetName(), pNZBInfo->GetDupeKey()))
{
// if queue has a duplicate with the same or higher score - the new item
// is moved to history as dupe-backup
if (pNZBInfo->GetDupeScore() <= pGroupNZBInfo->GetDupeScore())
{
// Flag saying QueueCoordinator to skip nzb-file
pNZBInfo->SetDeleteStatus(NZBInfo::dsDupe);
info("Collection %s is duplicate to %s", pNZBInfo->GetName(), pGroupNZBInfo->GetName());
return;
}
// if queue has a duplicate with lower score - the existing item is moved
// to history as dupe-backup (unless it is in post-processing stage) and
// the new item is added to queue
else
{
// unless it is in post-processing stage
bool bPostProcess = false;
for (PostQueue::iterator it = pDownloadQueue->GetPostQueue()->begin(); it != pDownloadQueue->GetPostQueue()->end(); it++)
{
PostInfo* pPostInfo = *it;
if (pPostInfo->GetNZBInfo() == pGroupNZBInfo)
{
bPostProcess = true;
break;
}
}
if (!bPostProcess)
{
// the existing queue item is moved to history as dupe-backup
info("Moving collection %s with lower duplicate score to history", pGroupNZBInfo->GetName());
pGroupNZBInfo->SetDeleteStatus(NZBInfo::dsDupe);
g_pQueueCoordinator->GetQueueEditor()->LockedEditEntry(pDownloadQueue, pGroupInfo->GetLastID(), false, QueueEditor::eaGroupDelete, 0, NULL);
}
}
}
}
// find duplicates in post queue
for (PostQueue::iterator it = pDownloadQueue->GetPostQueue()->begin(); it != pDownloadQueue->GetPostQueue()->end(); it++)
{
PostInfo* pPostInfo = *it;
// if queue has a duplicate with the same or higher score - the new item
// is moved to history as dupe-backup;
if (pPostInfo->GetNZBInfo()->GetDupeMode() != dmForce &&
pNZBInfo->GetDupeScore() <= pPostInfo->GetNZBInfo()->GetDupeScore() &&
SameNameOrKey(pPostInfo->GetNZBInfo()->GetName(), pPostInfo->GetNZBInfo()->GetDupeKey(),
pNZBInfo->GetName(), pNZBInfo->GetDupeKey()))
{
// Flag saying QueueCoordinator to skip nzb-file
pNZBInfo->SetDeleteStatus(NZBInfo::dsDupe);
info("Collection %s is duplicate to %s", pNZBInfo->GetName(), pPostInfo->GetNZBInfo()->GetName());
return;
}
}
}
}
/**
- if download of an item fails and there are duplicates in history -
return the best duplicate from historyto queue for download;
- if download of an item completes successfully - nothing extra needs to be done;
*/
void DupeCoordinator::NZBCompleted(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo)
{
debug("Processing duplicates for %s", pNZBInfo->GetName());
if (pNZBInfo->GetDupeMode() == dmScore && !IsDupeSuccess(pNZBInfo))
{
ReturnBestDupe(pDownloadQueue, pNZBInfo, pNZBInfo->GetName(), pNZBInfo->GetDupeKey());
}
}
/**
Returns the best duplicate from history to download queue.
*/
void DupeCoordinator::ReturnBestDupe(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, const char* szNZBName, const char* szDupeKey)
{
// check if history (recent or dup) has other success-duplicates or good-duplicates
bool bHistoryDupe = false;
int iHistoryScore = 0;
for (HistoryList::iterator it = pDownloadQueue->GetHistoryList()->begin(); it != pDownloadQueue->GetHistoryList()->end(); it++)
{
HistoryInfo* pHistoryInfo = *it;
bool bGoodDupe = false;
if (pHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo &&
pHistoryInfo->GetNZBInfo()->GetDupeMode() != dmForce &&
(IsDupeSuccess(pHistoryInfo->GetNZBInfo()) ||
pHistoryInfo->GetNZBInfo()->GetMarkStatus() == NZBInfo::ksGood) &&
SameNameOrKey(pHistoryInfo->GetNZBInfo()->GetName(), pHistoryInfo->GetNZBInfo()->GetDupeKey(), szNZBName, szDupeKey))
{
if (!bHistoryDupe || pHistoryInfo->GetNZBInfo()->GetDupeScore() > iHistoryScore)
{
iHistoryScore = pHistoryInfo->GetNZBInfo()->GetDupeScore();
}
bHistoryDupe = true;
bGoodDupe = pHistoryInfo->GetNZBInfo()->GetMarkStatus() == NZBInfo::ksGood;
}
if (pHistoryInfo->GetKind() == HistoryInfo::hkDupInfo &&
pHistoryInfo->GetDupInfo()->GetDupeMode() != dmForce &&
(pHistoryInfo->GetDupInfo()->GetStatus() == DupInfo::dsSuccess ||
pHistoryInfo->GetDupInfo()->GetStatus() == DupInfo::dsGood) &&
SameNameOrKey(pHistoryInfo->GetDupInfo()->GetName(), pHistoryInfo->GetDupInfo()->GetDupeKey(), szNZBName, szDupeKey))
{
if (!bHistoryDupe || pHistoryInfo->GetDupInfo()->GetDupeScore() > iHistoryScore)
{
iHistoryScore = pHistoryInfo->GetDupInfo()->GetDupeScore();
}
bHistoryDupe = true;
bGoodDupe = pHistoryInfo->GetDupInfo()->GetStatus() == DupInfo::dsGood;
}
if (bGoodDupe)
{
// another duplicate with good-status exists - exit without moving other dupes to queue
return;
}
}
// check if duplicates exist in post-processing queue
bool bPostDupe = false;
int iPostScore = 0;
for (PostQueue::iterator it = pDownloadQueue->GetPostQueue()->begin(); it != pDownloadQueue->GetPostQueue()->end(); it++)
{
PostInfo* pPostInfo = *it;
if (pPostInfo->GetNZBInfo() != pNZBInfo &&
pPostInfo->GetNZBInfo()->GetDupeMode() != dmForce &&
SameNameOrKey(pPostInfo->GetNZBInfo()->GetName(), pPostInfo->GetNZBInfo()->GetDupeKey(), szNZBName, szDupeKey) &&
(!bPostDupe || pPostInfo->GetNZBInfo()->GetDupeScore() > iPostScore))
{
iPostScore = pPostInfo->GetNZBInfo()->GetDupeScore();
bPostDupe = true;
}
}
// check if duplicates exist in download queue
GroupQueue groupQueue;
pDownloadQueue->BuildGroups(&groupQueue);
bool bQueueDupe = false;
int iQueueScore = 0;
for (GroupQueue::iterator it = groupQueue.begin(); it != groupQueue.end(); it++)
{
GroupInfo* pGroupInfo = *it;
NZBInfo* pGroupNZBInfo = pGroupInfo->GetNZBInfo();
if (pGroupNZBInfo != pNZBInfo &&
pGroupNZBInfo->GetDupeMode() != dmForce &&
SameNameOrKey(pGroupNZBInfo->GetName(), pGroupNZBInfo->GetDupeKey(), szNZBName, szDupeKey) &&
(!bQueueDupe || pGroupNZBInfo->GetDupeScore() > iQueueScore))
{
iQueueScore = pGroupNZBInfo->GetDupeScore();
bQueueDupe = true;
}
}
// find dupe-backup with highest score, whose score is also higher than other
// success-duplicates and higher than already queued items
HistoryInfo* pHistoryDupe = NULL;
for (HistoryList::iterator it = pDownloadQueue->GetHistoryList()->begin(); it != pDownloadQueue->GetHistoryList()->end(); it++)
{
HistoryInfo* pHistoryInfo = *it;
if (pHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo &&
pHistoryInfo->GetNZBInfo()->GetDupeMode() != dmForce &&
pHistoryInfo->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsDupe &&
pHistoryInfo->GetNZBInfo()->CalcHealth() >= pHistoryInfo->GetNZBInfo()->CalcCriticalHealth() &&
pHistoryInfo->GetNZBInfo()->GetMarkStatus() != NZBInfo::ksBad &&
(!bHistoryDupe || pHistoryInfo->GetNZBInfo()->GetDupeScore() > iHistoryScore) &&
(!bPostDupe || pHistoryInfo->GetNZBInfo()->GetDupeScore() > iPostScore) &&
(!bQueueDupe || pHistoryInfo->GetNZBInfo()->GetDupeScore() > iQueueScore) &&
(!pHistoryDupe || pHistoryInfo->GetNZBInfo()->GetDupeScore() > pHistoryDupe->GetNZBInfo()->GetDupeScore()) &&
SameNameOrKey(pHistoryInfo->GetNZBInfo()->GetName(), pHistoryInfo->GetNZBInfo()->GetDupeKey(), szNZBName, szDupeKey))
{
pHistoryDupe = pHistoryInfo;
}
}
// move that dupe-backup from history to download queue
if (pHistoryDupe)
{
info("Found duplicate %s for %s", pHistoryDupe->GetNZBInfo()->GetName(), szNZBName);
HistoryRedownload(pDownloadQueue, pHistoryDupe);
}
}
void DupeCoordinator::HistoryMark(DownloadQueue* pDownloadQueue, HistoryInfo* pHistoryInfo, bool bGood)
{
char szNZBName[1024];
pHistoryInfo->GetName(szNZBName, 1024);
info("Marking %s as %s", szNZBName, (bGood ? "good" : "bad"));
if (pHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo)
{
pHistoryInfo->GetNZBInfo()->SetMarkStatus(bGood ? NZBInfo::ksGood : NZBInfo::ksBad);
}
else if (pHistoryInfo->GetKind() == HistoryInfo::hkDupInfo)
{
pHistoryInfo->GetDupInfo()->SetStatus(bGood ? DupInfo::dsGood : DupInfo::dsBad);
}
else
{
error("Could not mark %s as bad: history item has wrong type", szNZBName);
return;
}
if (!g_pOptions->GetDupeCheck() ||
(pHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo &&
pHistoryInfo->GetNZBInfo()->GetDupeMode() == dmForce) ||
(pHistoryInfo->GetKind() == HistoryInfo::hkDupInfo &&
pHistoryInfo->GetDupInfo()->GetDupeMode() == dmForce))
{
return;
}
if (bGood)
{
// mark as good
// moving all duplicates from history to dup-history
HistoryCleanup(pDownloadQueue, pHistoryInfo);
}
else
{
// mark as bad
const char* szDupeKey = pHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo ? pHistoryInfo->GetNZBInfo()->GetDupeKey() :
pHistoryInfo->GetKind() == HistoryInfo::hkDupInfo ? pHistoryInfo->GetDupInfo()->GetDupeKey() :
NULL;
ReturnBestDupe(pDownloadQueue, NULL, szNZBName, szDupeKey);
}
}
void DupeCoordinator::HistoryCleanup(DownloadQueue* pDownloadQueue, HistoryInfo* pMarkHistoryInfo)
{
const char* szDupeKey = pMarkHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo ? pMarkHistoryInfo->GetNZBInfo()->GetDupeKey() :
pMarkHistoryInfo->GetKind() == HistoryInfo::hkDupInfo ? pMarkHistoryInfo->GetDupInfo()->GetDupeKey() :
NULL;
const char* szNZBName = pMarkHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo ? pMarkHistoryInfo->GetNZBInfo()->GetName() :
pMarkHistoryInfo->GetKind() == HistoryInfo::hkDupInfo ? pMarkHistoryInfo->GetDupInfo()->GetName() :
NULL;
bool bChanged = false;
int index = 0;
// traversing in a reverse order to delete items in order they were added to history
// (just to produce the log-messages in a more logical order)
for (HistoryList::reverse_iterator it = pDownloadQueue->GetHistoryList()->rbegin(); it != pDownloadQueue->GetHistoryList()->rend(); )
{
HistoryInfo* pHistoryInfo = *it;
if (pHistoryInfo->GetKind() == HistoryInfo::hkNZBInfo &&
pHistoryInfo->GetNZBInfo()->GetDupeMode() != dmForce &&
pHistoryInfo->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsDupe &&
pHistoryInfo != pMarkHistoryInfo &&
SameNameOrKey(pHistoryInfo->GetNZBInfo()->GetName(), pHistoryInfo->GetNZBInfo()->GetDupeKey(), szNZBName, szDupeKey))
{
HistoryTransformToDup(pDownloadQueue, pHistoryInfo, index);
index++;
it = pDownloadQueue->GetHistoryList()->rbegin() + index;
bChanged = true;
}
else
{
it++;
index++;
}
}
if (bChanged && g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode())
{
g_pDiskState->SaveDownloadQueue(pDownloadQueue);
}
}
void DupeCoordinator::HistoryTransformToDup(DownloadQueue* pDownloadQueue, HistoryInfo* pHistoryInfo, int rindex)
{
char szNiceName[1024];
pHistoryInfo->GetName(szNiceName, 1024);
// replace history element
DupInfo* pDupInfo = new DupInfo();
pDupInfo->SetName(pHistoryInfo->GetNZBInfo()->GetName());
pDupInfo->SetDupeKey(pHistoryInfo->GetNZBInfo()->GetDupeKey());
pDupInfo->SetDupeScore(pHistoryInfo->GetNZBInfo()->GetDupeScore());
pDupInfo->SetDupeMode(pHistoryInfo->GetNZBInfo()->GetDupeMode());
pDupInfo->SetSize(pHistoryInfo->GetNZBInfo()->GetSize());
pDupInfo->SetFullContentHash(pHistoryInfo->GetNZBInfo()->GetFullContentHash());
pDupInfo->SetFilteredContentHash(pHistoryInfo->GetNZBInfo()->GetFilteredContentHash());
pDupInfo->SetStatus(
pHistoryInfo->GetNZBInfo()->GetMarkStatus() == NZBInfo::ksGood ? DupInfo::dsGood :
pHistoryInfo->GetNZBInfo()->GetMarkStatus() == NZBInfo::ksBad ? DupInfo::dsBad :
pHistoryInfo->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsDupe ? DupInfo::dsDupe :
pHistoryInfo->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsManual ? DupInfo::dsDeleted :
IsDupeSuccess(pHistoryInfo->GetNZBInfo()) ? DupInfo::dsSuccess :
DupInfo::dsFailed);
HistoryInfo* pNewHistoryInfo = new HistoryInfo(pDupInfo);
pNewHistoryInfo->SetTime(pHistoryInfo->GetTime());
(*pDownloadQueue->GetHistoryList())[pDownloadQueue->GetHistoryList()->size() - 1 - rindex] = pNewHistoryInfo;
DeleteQueuedFile(pHistoryInfo->GetNZBInfo()->GetQueuedFilename());
delete pHistoryInfo;
info("Collection %s removed from history", szNiceName);
}

53
DupeCoordinator.h Normal file
View File

@@ -0,0 +1,53 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
*/
#ifndef DUPECOORDINATOR_H
#define DUPECOORDINATOR_H
#include <deque>
#include "DownloadInfo.h"
class DupeCoordinator
{
private:
bool IsDupeSuccess(NZBInfo* pNZBInfo);
void ReturnBestDupe(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, const char* szNZBName, const char* szDupeKey);
void HistoryReturnDupe(DownloadQueue* pDownloadQueue, HistoryInfo* pHistoryInfo);
void HistoryCleanup(DownloadQueue* pDownloadQueue, HistoryInfo* pMarkHistoryInfo);
bool SameNameOrKey(const char* szName1, const char* szDupeKey1, const char* szName2, const char* szDupeKey2);
protected:
virtual void HistoryRedownload(DownloadQueue* pDownloadQueue, HistoryInfo* pHistoryInfo) = 0;
virtual void DeleteQueuedFile(const char* szQueuedFile) = 0;
public:
void NZBCompleted(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo);
void NZBFound(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo);
void HistoryMark(DownloadQueue* pDownloadQueue, HistoryInfo* pHistoryInfo, bool bGood);
void HistoryTransformToDup(DownloadQueue* pDownloadQueue, HistoryInfo* pHistoryInfo, int rindex);
};
#endif

742
FeedCoordinator.cpp Normal file
View File

@@ -0,0 +1,742 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/stat.h>
#ifndef WIN32
#include <unistd.h>
#include <sys/time.h>
#endif
#include "nzbget.h"
#include "FeedCoordinator.h"
#include "Options.h"
#include "WebDownloader.h"
#include "Log.h"
#include "Util.h"
#include "FeedFile.h"
#include "FeedFilter.h"
#include "UrlCoordinator.h"
#include "DiskState.h"
extern Options* g_pOptions;
extern UrlCoordinator* g_pUrlCoordinator;
extern DiskState* g_pDiskState;
FeedCoordinator::FeedCacheItem::FeedCacheItem(const char* szUrl, int iCacheTimeSec,const char* szCacheId,
time_t tLastUsage, FeedItemInfos* pFeedItemInfos)
{
m_szUrl = strdup(szUrl);
m_iCacheTimeSec = iCacheTimeSec;
m_szCacheId = strdup(szCacheId);
m_tLastUsage = tLastUsage;
m_pFeedItemInfos = pFeedItemInfos;
m_pFeedItemInfos->Retain();
}
FeedCoordinator::FeedCacheItem::~FeedCacheItem()
{
free(m_szUrl);
free(m_szCacheId);
m_pFeedItemInfos->Release();
}
FeedCoordinator::FeedCoordinator()
{
debug("Creating FeedCoordinator");
m_bForce = false;
m_bSave = false;
m_UrlCoordinatorObserver.m_pOwner = this;
g_pUrlCoordinator->Attach(&m_UrlCoordinatorObserver);
}
FeedCoordinator::~FeedCoordinator()
{
debug("Destroying FeedCoordinator");
// Cleanup
debug("Deleting FeedDownloaders");
for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++)
{
delete *it;
}
m_ActiveDownloads.clear();
debug("Deleting Feeds");
for (Feeds::iterator it = m_Feeds.begin(); it != m_Feeds.end(); it++)
{
delete *it;
}
m_Feeds.clear();
debug("Deleting FeedCache");
for (FeedCache::iterator it = m_FeedCache.begin(); it != m_FeedCache.end(); it++)
{
delete *it;
}
m_FeedCache.clear();
debug("FeedCoordinator destroyed");
}
void FeedCoordinator::AddFeed(FeedInfo* pFeedInfo)
{
m_Feeds.push_back(pFeedInfo);
}
void FeedCoordinator::Run()
{
debug("Entering FeedCoordinator-loop");
m_mutexDownloads.Lock();
if (g_pOptions->GetServerMode() && g_pOptions->GetSaveQueue() && g_pOptions->GetReloadQueue())
{
g_pDiskState->LoadFeeds(&m_Feeds, &m_FeedHistory);
}
m_mutexDownloads.Unlock();
int iSleepInterval = 100;
int iUpdateCounter = 0;
int iCleanupCounter = 60000;
while (!IsStopped())
{
usleep(iSleepInterval * 1000);
iUpdateCounter += iSleepInterval;
if (iUpdateCounter >= 1000)
{
// this code should not be called too often, once per second is OK
if (!(g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2()) || m_bForce || g_pOptions->GetUrlForce())
{
m_mutexDownloads.Lock();
time_t tCurrent = time(NULL);
if ((int)m_ActiveDownloads.size() < g_pOptions->GetUrlConnections())
{
m_bForce = false;
// check feed list and update feeds
for (Feeds::iterator it = m_Feeds.begin(); it != m_Feeds.end(); it++)
{
FeedInfo* pFeedInfo = *it;
if (((pFeedInfo->GetInterval() > 0 &&
(tCurrent - pFeedInfo->GetLastUpdate() >= pFeedInfo->GetInterval() * 60 ||
tCurrent < pFeedInfo->GetLastUpdate())) ||
pFeedInfo->GetFetch()) &&
pFeedInfo->GetStatus() != FeedInfo::fsRunning)
{
StartFeedDownload(pFeedInfo, pFeedInfo->GetFetch());
}
else if (pFeedInfo->GetFetch())
{
m_bForce = true;
}
}
}
m_mutexDownloads.Unlock();
}
CheckSaveFeeds();
ResetHangingDownloads();
iUpdateCounter = 0;
}
iCleanupCounter += iSleepInterval;
if (iCleanupCounter >= 60000)
{
// clean up feed history once a minute
CleanupHistory();
CleanupCache();
CheckSaveFeeds();
iCleanupCounter = 0;
}
}
// waiting for downloads
debug("FeedCoordinator: waiting for Downloads to complete");
bool completed = false;
while (!completed)
{
m_mutexDownloads.Lock();
completed = m_ActiveDownloads.size() == 0;
m_mutexDownloads.Unlock();
CheckSaveFeeds();
usleep(100 * 1000);
ResetHangingDownloads();
}
debug("FeedCoordinator: Downloads are completed");
debug("Exiting FeedCoordinator-loop");
}
void FeedCoordinator::Stop()
{
Thread::Stop();
debug("Stopping UrlDownloads");
m_mutexDownloads.Lock();
for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++)
{
(*it)->Stop();
}
m_mutexDownloads.Unlock();
debug("UrlDownloads are notified");
}
void FeedCoordinator::ResetHangingDownloads()
{
const int TimeOut = g_pOptions->GetTerminateTimeout();
if (TimeOut == 0)
{
return;
}
m_mutexDownloads.Lock();
time_t tm = ::time(NULL);
for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end();)
{
FeedDownloader* pFeedDownloader = *it;
if (tm - pFeedDownloader->GetLastUpdateTime() > TimeOut &&
pFeedDownloader->GetStatus() == FeedDownloader::adRunning)
{
debug("Terminating hanging download %s", pFeedDownloader->GetInfoName());
if (pFeedDownloader->Terminate())
{
error("Terminated hanging download %s", pFeedDownloader->GetInfoName());
pFeedDownloader->GetFeedInfo()->SetStatus(FeedInfo::fsUndefined);
}
else
{
error("Could not terminate hanging download %s", pFeedDownloader->GetInfoName());
}
m_ActiveDownloads.erase(it);
// it's not safe to destroy pFeedDownloader, because the state of object is unknown
delete pFeedDownloader;
it = m_ActiveDownloads.begin();
continue;
}
it++;
}
m_mutexDownloads.Unlock();
}
void FeedCoordinator::LogDebugInfo()
{
debug(" FeedCoordinator");
debug(" ----------------");
m_mutexDownloads.Lock();
debug(" Active Downloads: %i", m_ActiveDownloads.size());
for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++)
{
FeedDownloader* pFeedDownloader = *it;
pFeedDownloader->LogDebugInfo();
}
m_mutexDownloads.Unlock();
}
void FeedCoordinator::StartFeedDownload(FeedInfo* pFeedInfo, bool bForce)
{
debug("Starting new FeedDownloader for %", pFeedInfo->GetName());
FeedDownloader* pFeedDownloader = new FeedDownloader();
pFeedDownloader->SetAutoDestroy(true);
pFeedDownloader->Attach(this);
pFeedDownloader->SetFeedInfo(pFeedInfo);
pFeedDownloader->SetURL(pFeedInfo->GetUrl());
if (strlen(pFeedInfo->GetName()) > 0)
{
pFeedDownloader->SetInfoName(pFeedInfo->GetName());
}
else
{
char szUrlName[1024];
UrlInfo::MakeNiceName(pFeedInfo->GetUrl(), "", szUrlName, sizeof(szUrlName));
pFeedDownloader->SetInfoName(szUrlName);
}
pFeedDownloader->SetForce(bForce || g_pOptions->GetUrlForce());
char tmp[1024];
if (pFeedInfo->GetID() > 0)
{
snprintf(tmp, 1024, "%sfeed-%i.tmp", g_pOptions->GetTempDir(), pFeedInfo->GetID());
}
else
{
snprintf(tmp, 1024, "%sfeed-%i-%i.tmp", g_pOptions->GetTempDir(), (int)time(NULL), rand());
}
tmp[1024-1] = '\0';
pFeedDownloader->SetOutputFilename(tmp);
pFeedInfo->SetStatus(FeedInfo::fsRunning);
pFeedInfo->SetForce(bForce);
pFeedInfo->SetFetch(false);
m_ActiveDownloads.push_back(pFeedDownloader);
pFeedDownloader->Start();
}
void FeedCoordinator::Update(Subject* pCaller, void* pAspect)
{
debug("Notification from FeedDownloader received");
FeedDownloader* pFeedDownloader = (FeedDownloader*) pCaller;
if ((pFeedDownloader->GetStatus() == WebDownloader::adFinished) ||
(pFeedDownloader->GetStatus() == WebDownloader::adFailed) ||
(pFeedDownloader->GetStatus() == WebDownloader::adRetry))
{
FeedCompleted(pFeedDownloader);
}
}
void FeedCoordinator::FeedCompleted(FeedDownloader* pFeedDownloader)
{
debug("Feed downloaded");
FeedInfo* pFeedInfo = pFeedDownloader->GetFeedInfo();
bool bStatusOK = pFeedDownloader->GetStatus() == WebDownloader::adFinished;
if (bStatusOK)
{
pFeedInfo->SetOutputFilename(pFeedDownloader->GetOutputFilename());
}
// delete Download from Queue
m_mutexDownloads.Lock();
for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++)
{
FeedDownloader* pa = *it;
if (pa == pFeedDownloader)
{
m_ActiveDownloads.erase(it);
break;
}
}
m_mutexDownloads.Unlock();
if (bStatusOK)
{
if (!pFeedInfo->GetPreview())
{
FeedFile* pFeedFile = FeedFile::Create(pFeedInfo->GetOutputFilename());
remove(pFeedInfo->GetOutputFilename());
m_mutexDownloads.Lock();
if (pFeedFile)
{
ProcessFeed(pFeedInfo, pFeedFile->GetFeedItemInfos());
delete pFeedFile;
}
pFeedInfo->SetLastUpdate(time(NULL));
pFeedInfo->SetForce(false);
m_bSave = true;
m_mutexDownloads.Unlock();
}
pFeedInfo->SetStatus(FeedInfo::fsFinished);
}
else
{
pFeedInfo->SetStatus(FeedInfo::fsFailed);
}
}
void FeedCoordinator::FilterFeed(FeedInfo* pFeedInfo, FeedItemInfos* pFeedItemInfos)
{
debug("Filtering feed %s", pFeedInfo->GetName());
FeedFilter* pFeedFilter = NULL;
if (pFeedInfo->GetFilter() && strlen(pFeedInfo->GetFilter()) > 0)
{
pFeedFilter = new FeedFilter(pFeedInfo->GetFilter());
}
for (FeedItemInfos::iterator it = pFeedItemInfos->begin(); it != pFeedItemInfos->end(); it++)
{
FeedItemInfo* pFeedItemInfo = *it;
pFeedItemInfo->SetMatchStatus(FeedItemInfo::msAccepted);
pFeedItemInfo->SetMatchRule(0);
pFeedItemInfo->SetPauseNzb(pFeedInfo->GetPauseNzb());
pFeedItemInfo->SetPriority(pFeedInfo->GetPriority());
pFeedItemInfo->SetAddCategory(pFeedInfo->GetCategory());
pFeedItemInfo->SetDupeScore(0);
pFeedItemInfo->SetDupeMode(dmScore);
pFeedItemInfo->BuildDupeKey(NULL, NULL);
if (pFeedFilter)
{
pFeedFilter->Match(pFeedItemInfo);
}
}
delete pFeedFilter;
}
void FeedCoordinator::ProcessFeed(FeedInfo* pFeedInfo, FeedItemInfos* pFeedItemInfos)
{
debug("Process feed %s", pFeedInfo->GetName());
FilterFeed(pFeedInfo, pFeedItemInfos);
bool bFirstFetch = pFeedInfo->GetLastUpdate() == 0;
int iAdded = 0;
for (FeedItemInfos::iterator it = pFeedItemInfos->begin(); it != pFeedItemInfos->end(); it++)
{
FeedItemInfo* pFeedItemInfo = *it;
if (pFeedItemInfo->GetMatchStatus() == FeedItemInfo::msAccepted)
{
FeedHistoryInfo* pFeedHistoryInfo = m_FeedHistory.Find(pFeedItemInfo->GetUrl());
FeedHistoryInfo::EStatus eStatus = FeedHistoryInfo::hsUnknown;
if (bFirstFetch)
{
eStatus = FeedHistoryInfo::hsBacklog;
}
else if (!pFeedHistoryInfo)
{
DownloadItem(pFeedInfo, pFeedItemInfo);
eStatus = FeedHistoryInfo::hsFetched;
iAdded++;
}
if (pFeedHistoryInfo)
{
pFeedHistoryInfo->SetLastSeen(time(NULL));
}
else
{
m_FeedHistory.Add(pFeedItemInfo->GetUrl(), eStatus, time(NULL));
}
}
}
if (iAdded)
{
info("%s has %i new item(s)", pFeedInfo->GetName(), iAdded);
}
else
{
detail("%s has no new items", pFeedInfo->GetName());
}
}
void FeedCoordinator::DownloadItem(FeedInfo* pFeedInfo, FeedItemInfo* pFeedItemInfo)
{
debug("Download %s from %s", pFeedItemInfo->GetUrl(), pFeedInfo->GetName());
UrlInfo* pUrlInfo = new UrlInfo();
pUrlInfo->SetURL(pFeedItemInfo->GetUrl());
// add .nzb-extension if not present
char szNZBName[1024];
strncpy(szNZBName, pFeedItemInfo->GetFilename(), 1024);
szNZBName[1024-1] = '\0';
char* ext = strrchr(szNZBName, '.');
if (ext && !strcasecmp(ext, ".nzb"))
{
*ext = '\0';
}
char szNZBName2[1024];
snprintf(szNZBName2, 1024, "%s.nzb", szNZBName);
Util::MakeValidFilename(szNZBName2, '_', false);
if (strlen(szNZBName) > 0)
{
pUrlInfo->SetNZBFilename(szNZBName2);
}
pUrlInfo->SetCategory(pFeedItemInfo->GetAddCategory());
pUrlInfo->SetPriority(pFeedItemInfo->GetPriority());
pUrlInfo->SetAddPaused(pFeedItemInfo->GetPauseNzb());
pUrlInfo->SetDupeKey(pFeedItemInfo->GetDupeKey());
pUrlInfo->SetDupeScore(pFeedItemInfo->GetDupeScore());
pUrlInfo->SetDupeMode(pFeedItemInfo->GetDupeMode());
pUrlInfo->SetForce(pFeedInfo->GetForce() || g_pOptions->GetUrlForce());
g_pUrlCoordinator->AddUrlToQueue(pUrlInfo, false);
}
bool FeedCoordinator::ViewFeed(int iID, FeedItemInfos** ppFeedItemInfos)
{
if (iID < 1 || iID > (int)m_Feeds.size())
{
return false;
}
FeedInfo* pFeedInfo = m_Feeds.at(iID - 1);
return PreviewFeed(pFeedInfo->GetName(), pFeedInfo->GetUrl(), pFeedInfo->GetFilter(),
pFeedInfo->GetPauseNzb(), pFeedInfo->GetCategory(), pFeedInfo->GetPriority(),
0, NULL, ppFeedItemInfos);
}
bool FeedCoordinator::PreviewFeed(const char* szName, const char* szUrl, const char* szFilter,
bool bPauseNzb, const char* szCategory, int iPriority,
int iCacheTimeSec, const char* szCacheId, FeedItemInfos** ppFeedItemInfos)
{
debug("Preview feed %s", szName);
FeedInfo* pFeedInfo = new FeedInfo(0, szName, szUrl, 0, szFilter, bPauseNzb, szCategory, iPriority);
pFeedInfo->SetPreview(true);
FeedItemInfos* pFeedItemInfos = NULL;
bool bHasCache = false;
if (iCacheTimeSec > 0 && *szCacheId != '\0')
{
m_mutexDownloads.Lock();
for (FeedCache::iterator it = m_FeedCache.begin(); it != m_FeedCache.end(); it++)
{
FeedCacheItem* pFeedCacheItem = *it;
if (!strcmp(pFeedCacheItem->GetCacheId(), szCacheId))
{
pFeedCacheItem->SetLastUsage(time(NULL));
pFeedItemInfos = pFeedCacheItem->GetFeedItemInfos();
pFeedItemInfos->Retain();
bHasCache = true;
break;
}
}
m_mutexDownloads.Unlock();
}
if (!bHasCache)
{
m_mutexDownloads.Lock();
bool bFirstFetch = true;
for (Feeds::iterator it = m_Feeds.begin(); it != m_Feeds.end(); it++)
{
FeedInfo* pFeedInfo2 = *it;
if (!strcmp(pFeedInfo2->GetUrl(), pFeedInfo->GetUrl()) &&
!strcmp(pFeedInfo2->GetFilter(), pFeedInfo->GetFilter()) &&
pFeedInfo2->GetLastUpdate() > 0)
{
bFirstFetch = false;
break;
}
}
StartFeedDownload(pFeedInfo, true);
m_mutexDownloads.Unlock();
// wait until the download in a separate thread completes
while (pFeedInfo->GetStatus() == FeedInfo::fsRunning)
{
usleep(100 * 1000);
}
// now can process the feed
FeedFile* pFeedFile = NULL;
if (pFeedInfo->GetStatus() == FeedInfo::fsFinished)
{
pFeedFile = FeedFile::Create(pFeedInfo->GetOutputFilename());
}
remove(pFeedInfo->GetOutputFilename());
if (!pFeedFile)
{
delete pFeedInfo;
return false;
}
pFeedItemInfos = pFeedFile->GetFeedItemInfos();
pFeedItemInfos->Retain();
delete pFeedFile;
for (FeedItemInfos::iterator it = pFeedItemInfos->begin(); it != pFeedItemInfos->end(); it++)
{
FeedItemInfo* pFeedItemInfo = *it;
pFeedItemInfo->SetStatus(bFirstFetch ? FeedItemInfo::isBacklog : FeedItemInfo::isNew);
FeedHistoryInfo* pFeedHistoryInfo = m_FeedHistory.Find(pFeedItemInfo->GetUrl());
if (pFeedHistoryInfo)
{
pFeedItemInfo->SetStatus((FeedItemInfo::EStatus)pFeedHistoryInfo->GetStatus());
}
}
}
FilterFeed(pFeedInfo, pFeedItemInfos);
delete pFeedInfo;
if (iCacheTimeSec > 0 && *szCacheId != '\0' && !bHasCache)
{
FeedCacheItem* pFeedCacheItem = new FeedCacheItem(szUrl, iCacheTimeSec, szCacheId, time(NULL), pFeedItemInfos);
m_mutexDownloads.Lock();
m_FeedCache.push_back(pFeedCacheItem);
m_mutexDownloads.Unlock();
}
*ppFeedItemInfos = pFeedItemInfos;
return true;
}
void FeedCoordinator::FetchFeed(int iID)
{
debug("FetchFeeds");
m_mutexDownloads.Lock();
for (Feeds::iterator it = m_Feeds.begin(); it != m_Feeds.end(); it++)
{
FeedInfo* pFeedInfo = *it;
if (pFeedInfo->GetID() == iID || iID == 0)
{
pFeedInfo->SetFetch(true);
m_bForce = true;
}
}
m_mutexDownloads.Unlock();
}
void FeedCoordinator::UrlCoordinatorUpdate(Subject* pCaller, void* pAspect)
{
debug("Notification from URL-Coordinator received");
UrlCoordinator::Aspect* pUrlAspect = (UrlCoordinator::Aspect*)pAspect;
if (pUrlAspect->eAction == UrlCoordinator::eaUrlCompleted)
{
m_mutexDownloads.Lock();
FeedHistoryInfo* pFeedHistoryInfo = m_FeedHistory.Find(pUrlAspect->pUrlInfo->GetURL());
if (pFeedHistoryInfo)
{
pFeedHistoryInfo->SetStatus(FeedHistoryInfo::hsFetched);
}
else
{
m_FeedHistory.Add(pUrlAspect->pUrlInfo->GetURL(), FeedHistoryInfo::hsFetched, time(NULL));
}
m_bSave = true;
m_mutexDownloads.Unlock();
}
}
bool FeedCoordinator::HasActiveDownloads()
{
m_mutexDownloads.Lock();
bool bActive = !m_ActiveDownloads.empty();
m_mutexDownloads.Unlock();
return bActive;
}
void FeedCoordinator::CheckSaveFeeds()
{
debug("CheckSaveFeeds");
m_mutexDownloads.Lock();
if (m_bSave)
{
if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode())
{
g_pDiskState->SaveFeeds(&m_Feeds, &m_FeedHistory);
}
m_bSave = false;
}
m_mutexDownloads.Unlock();
}
void FeedCoordinator::CleanupHistory()
{
debug("CleanupHistory");
m_mutexDownloads.Lock();
time_t tOldestUpdate = time(NULL);
for (Feeds::iterator it = m_Feeds.begin(); it != m_Feeds.end(); it++)
{
FeedInfo* pFeedInfo = *it;
if (pFeedInfo->GetLastUpdate() < tOldestUpdate)
{
tOldestUpdate = pFeedInfo->GetLastUpdate();
}
}
time_t tBorderDate = tOldestUpdate - g_pOptions->GetFeedHistory();
int i = 0;
for (FeedHistory::iterator it = m_FeedHistory.begin(); it != m_FeedHistory.end(); )
{
FeedHistoryInfo* pFeedHistoryInfo = *it;
if (pFeedHistoryInfo->GetLastSeen() < tBorderDate)
{
detail("Deleting %s from feed history", pFeedHistoryInfo->GetUrl());
delete pFeedHistoryInfo;
m_FeedHistory.erase(it);
it = m_FeedHistory.begin() + i;
m_bSave = true;
}
else
{
it++;
i++;
}
}
m_mutexDownloads.Unlock();
}
void FeedCoordinator::CleanupCache()
{
debug("CleanupCache");
m_mutexDownloads.Lock();
time_t tCurTime = time(NULL);
int i = 0;
for (FeedCache::iterator it = m_FeedCache.begin(); it != m_FeedCache.end(); )
{
FeedCacheItem* pFeedCacheItem = *it;
if (pFeedCacheItem->GetLastUsage() + pFeedCacheItem->GetCacheTimeSec() < tCurTime ||
pFeedCacheItem->GetLastUsage() > tCurTime)
{
debug("Deleting %s from feed cache", pFeedCacheItem->GetUrl());
delete pFeedCacheItem;
m_FeedCache.erase(it);
it = m_FeedCache.begin() + i;
}
else
{
it++;
i++;
}
}
m_mutexDownloads.Unlock();
}

127
FeedCoordinator.h Normal file
View File

@@ -0,0 +1,127 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
*/
#ifndef FEEDCOORDINATOR_H
#define FEEDCOORDINATOR_H
#include <deque>
#include <list>
#include <time.h>
#include "Thread.h"
#include "WebDownloader.h"
#include "DownloadInfo.h"
#include "FeedInfo.h"
#include "Observer.h"
class FeedDownloader;
class FeedCoordinator : public Thread, public Observer, public Subject
{
public:
typedef std::list<FeedDownloader*> ActiveDownloads;
private:
class UrlCoordinatorObserver: public Observer
{
public:
FeedCoordinator* m_pOwner;
virtual void Update(Subject* pCaller, void* pAspect) { m_pOwner->UrlCoordinatorUpdate(pCaller, pAspect); }
};
class FeedCacheItem
{
private:
char* m_szUrl;
int m_iCacheTimeSec;
char* m_szCacheId;
time_t m_tLastUsage;
FeedItemInfos* m_pFeedItemInfos;
public:
FeedCacheItem(const char* szUrl, int iCacheTimeSec,const char* szCacheId,
time_t tLastUsage, FeedItemInfos* pFeedItemInfos);
~FeedCacheItem();
const char* GetUrl() { return m_szUrl; }
int GetCacheTimeSec() { return m_iCacheTimeSec; }
const char* GetCacheId() { return m_szCacheId; }
time_t GetLastUsage() { return m_tLastUsage; }
void SetLastUsage(time_t tLastUsage) { m_tLastUsage = tLastUsage; }
FeedItemInfos* GetFeedItemInfos() { return m_pFeedItemInfos; }
};
typedef std::deque<FeedCacheItem*> FeedCache;
private:
Feeds m_Feeds;
ActiveDownloads m_ActiveDownloads;
FeedHistory m_FeedHistory;
Mutex m_mutexDownloads;
UrlCoordinatorObserver m_UrlCoordinatorObserver;
bool m_bForce;
bool m_bSave;
FeedCache m_FeedCache;
void StartFeedDownload(FeedInfo* pFeedInfo, bool bForce);
void FeedCompleted(FeedDownloader* pFeedDownloader);
void FilterFeed(FeedInfo* pFeedInfo, FeedItemInfos* pFeedItemInfos);
void ProcessFeed(FeedInfo* pFeedInfo, FeedItemInfos* pFeedItemInfos);
void DownloadItem(FeedInfo* pFeedInfo, FeedItemInfo* pFeedItemInfo);
void ResetHangingDownloads();
void UrlCoordinatorUpdate(Subject* pCaller, void* pAspect);
void CleanupHistory();
void CleanupCache();
void CheckSaveFeeds();
public:
FeedCoordinator();
virtual ~FeedCoordinator();
virtual void Run();
virtual void Stop();
void Update(Subject* pCaller, void* pAspect);
void AddFeed(FeedInfo* pFeedInfo);
bool PreviewFeed(const char* szName, const char* szUrl, const char* szFilter,
bool bPauseNzb, const char* szCategory, int iPriority,
int iCacheTimeSec, const char* szCacheId, FeedItemInfos** ppFeedItemInfos);
bool ViewFeed(int iID, FeedItemInfos** ppFeedItemInfos);
void FetchFeed(int iID);
bool HasActiveDownloads();
Feeds* GetFeeds() { return &m_Feeds; }
void LogDebugInfo();
};
class FeedDownloader : public WebDownloader
{
private:
FeedInfo* m_pFeedInfo;
public:
void SetFeedInfo(FeedInfo* pFeedInfo) { m_pFeedInfo = pFeedInfo; }
FeedInfo* GetFeedInfo() { return m_pFeedInfo; }
};
#endif

609
FeedFile.cpp Normal file
View File

@@ -0,0 +1,609 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <string.h>
#include <list>
#ifdef WIN32
#include <comutil.h>
#import <msxml.tlb> named_guids
using namespace MSXML;
#else
#include <libxml/parser.h>
#include <libxml/xmlreader.h>
#include <libxml/xmlerror.h>
#include <libxml/entities.h>
#endif
#include "nzbget.h"
#include "FeedFile.h"
#include "Log.h"
#include "DownloadInfo.h"
#include "Options.h"
#include "Util.h"
extern Options* g_pOptions;
FeedFile::FeedFile(const char* szFileName)
{
debug("Creating FeedFile");
m_szFileName = strdup(szFileName);
m_pFeedItemInfos = new FeedItemInfos();
m_pFeedItemInfos->Retain();
#ifndef WIN32
m_pFeedItemInfo = NULL;
m_szTagContent = NULL;
m_iTagContentLen = 0;
#endif
}
FeedFile::~FeedFile()
{
debug("Destroying FeedFile");
// Cleanup
free(m_szFileName);
m_pFeedItemInfos->Release();
#ifndef WIN32
delete m_pFeedItemInfo;
free(m_szTagContent);
#endif
}
void FeedFile::LogDebugInfo()
{
debug(" FeedFile %s", m_szFileName);
}
void FeedFile::AddItem(FeedItemInfo* pFeedItemInfo)
{
m_pFeedItemInfos->Add(pFeedItemInfo);
}
void FeedFile::ParseSubject(FeedItemInfo* pFeedItemInfo)
{
// if title has quatation marks we use only part within quatation marks
char* p = (char*)pFeedItemInfo->GetTitle();
char* start = strchr(p, '\"');
if (start)
{
start++;
char* end = strchr(start + 1, '\"');
if (end)
{
int len = (int)(end - start);
char* point = strchr(start + 1, '.');
if (point && point < end)
{
char* filename = (char*)malloc(len + 1);
strncpy(filename, start, len);
filename[len] = '\0';
char* ext = strrchr(filename, '.');
if (ext && !strcasecmp(ext, ".par2"))
{
*ext = '\0';
}
pFeedItemInfo->SetFilename(filename);
free(filename);
return;
}
}
}
pFeedItemInfo->SetFilename(pFeedItemInfo->GetTitle());
}
#ifdef WIN32
FeedFile* FeedFile::Create(const char* szFileName)
{
CoInitialize(NULL);
HRESULT hr;
MSXML::IXMLDOMDocumentPtr doc;
hr = doc.CreateInstance(MSXML::CLSID_DOMDocument);
if (FAILED(hr))
{
return NULL;
}
// Load the XML document file...
doc->put_resolveExternals(VARIANT_FALSE);
doc->put_validateOnParse(VARIANT_FALSE);
doc->put_async(VARIANT_FALSE);
// filename needs to be properly encoded
char* szURL = (char*)malloc(strlen(szFileName)*3 + 1);
EncodeURL(szFileName, szURL);
debug("url=\"%s\"", szURL);
_variant_t v(szURL);
free(szURL);
VARIANT_BOOL success = doc->load(v);
if (success == VARIANT_FALSE)
{
_bstr_t r(doc->GetparseError()->reason);
const char* szErrMsg = r;
error("Error parsing rss feed: %s", szErrMsg);
return NULL;
}
FeedFile* pFile = new FeedFile(szFileName);
if (!pFile->ParseFeed(doc))
{
delete pFile;
pFile = NULL;
}
return pFile;
}
void FeedFile::EncodeURL(const char* szFilename, char* szURL)
{
while (char ch = *szFilename++)
{
if (('0' <= ch && ch <= '9') ||
('a' <= ch && ch <= 'z') ||
('A' <= ch && ch <= 'Z') )
{
*szURL++ = ch;
}
else
{
*szURL++ = '%';
int a = ch >> 4;
*szURL++ = a > 9 ? a - 10 + 'a' : a + '0';
a = ch & 0xF;
*szURL++ = a > 9 ? a - 10 + 'a' : a + '0';
}
}
*szURL = NULL;
}
bool FeedFile::ParseFeed(IUnknown* nzb)
{
MSXML::IXMLDOMDocumentPtr doc = nzb;
MSXML::IXMLDOMNodePtr root = doc->documentElement;
MSXML::IXMLDOMNodeListPtr itemList = root->selectNodes("/rss/channel/item");
for (int i = 0; i < itemList->Getlength(); i++)
{
MSXML::IXMLDOMNodePtr node = itemList->Getitem(i);
FeedItemInfo* pFeedItemInfo = new FeedItemInfo();
AddItem(pFeedItemInfo);
MSXML::IXMLDOMNodePtr tag;
MSXML::IXMLDOMNodePtr attr;
// <title>Debian 6</title>
tag = node->selectSingleNode("title");
if (!tag)
{
// bad rss feed
return false;
}
_bstr_t title(tag->Gettext());
pFeedItemInfo->SetTitle(title);
ParseSubject(pFeedItemInfo);
// <pubDate>Wed, 26 Jun 2013 00:02:54 -0600</pubDate>
tag = node->selectSingleNode("pubDate");
if (tag)
{
_bstr_t time(tag->Gettext());
time_t unixtime = WebUtil::ParseRfc822DateTime(time);
if (unixtime > 0)
{
pFeedItemInfo->SetTime(unixtime);
}
}
// <category>Movies &gt; HD</category>
tag = node->selectSingleNode("category");
if (tag)
{
_bstr_t category(tag->Gettext());
pFeedItemInfo->SetCategory(category);
}
// <description>long text</description>
tag = node->selectSingleNode("description");
if (tag)
{
_bstr_t description(tag->Gettext());
pFeedItemInfo->SetDescription(description);
}
//<enclosure url="http://myindexer.com/fetch/9eeb264aecce961a6e0d" length="150263340" type="application/x-nzb" />
tag = node->selectSingleNode("enclosure");
if (tag)
{
attr = tag->Getattributes()->getNamedItem("url");
if (attr)
{
_bstr_t url(attr->Gettext());
pFeedItemInfo->SetUrl(url);
}
attr = tag->Getattributes()->getNamedItem("length");
if (attr)
{
_bstr_t size(attr->Gettext());
long long lSize = atoll(size);
pFeedItemInfo->SetSize(lSize);
}
}
if (!pFeedItemInfo->GetUrl())
{
// <link>https://nzb.org/fetch/334534ce/4364564564</link>
tag = node->selectSingleNode("link");
if (!tag)
{
// bad rss feed
return false;
}
_bstr_t link(tag->Gettext());
pFeedItemInfo->SetUrl(link);
}
// newznab special
//<newznab:attr name="size" value="5423523453534" />
if (pFeedItemInfo->GetSize() == 0)
{
tag = node->selectSingleNode("newznab:attr[@name='size']");
if (tag)
{
attr = tag->Getattributes()->getNamedItem("value");
if (attr)
{
_bstr_t size(attr->Gettext());
long long lSize = atoll(size);
pFeedItemInfo->SetSize(lSize);
}
}
}
//<newznab:attr name="imdb" value="1588173"/>
tag = node->selectSingleNode("newznab:attr[@name='imdb']");
if (tag)
{
attr = tag->Getattributes()->getNamedItem("value");
if (attr)
{
_bstr_t val(attr->Gettext());
int iVal = atoi(val);
pFeedItemInfo->SetImdbId(iVal);
}
}
//<newznab:attr name="rageid" value="33877"/>
tag = node->selectSingleNode("newznab:attr[@name='rageid']");
if (tag)
{
attr = tag->Getattributes()->getNamedItem("value");
if (attr)
{
_bstr_t val(attr->Gettext());
int iVal = atoi(val);
pFeedItemInfo->SetRageId(iVal);
}
}
//<newznab:attr name="episode" value="E09"/>
//<newznab:attr name="episode" value="9"/>
tag = node->selectSingleNode("newznab:attr[@name='episode']");
if (tag)
{
attr = tag->Getattributes()->getNamedItem("value");
if (attr)
{
_bstr_t val(attr->Gettext());
pFeedItemInfo->SetEpisode(val);
}
}
//<newznab:attr name="season" value="S03"/>
//<newznab:attr name="season" value="3"/>
tag = node->selectSingleNode("newznab:attr[@name='season']");
if (tag)
{
attr = tag->Getattributes()->getNamedItem("value");
if (attr)
{
_bstr_t val(attr->Gettext());
pFeedItemInfo->SetSeason(val);
}
}
MSXML::IXMLDOMNodeListPtr itemList = node->selectNodes("newznab:attr");
for (int i = 0; i < itemList->Getlength(); i++)
{
MSXML::IXMLDOMNodePtr node = itemList->Getitem(i);
MSXML::IXMLDOMNodePtr name = node->Getattributes()->getNamedItem("name");
MSXML::IXMLDOMNodePtr value = node->Getattributes()->getNamedItem("value");
if (name && value)
{
_bstr_t name(name->Gettext());
_bstr_t val(value->Gettext());
pFeedItemInfo->GetAttributes()->Add(name, val);
}
}
}
return true;
}
#else
FeedFile* FeedFile::Create(const char* szFileName)
{
FeedFile* pFile = new FeedFile(szFileName);
xmlSAXHandler SAX_handler = {0};
SAX_handler.startElement = reinterpret_cast<startElementSAXFunc>(SAX_StartElement);
SAX_handler.endElement = reinterpret_cast<endElementSAXFunc>(SAX_EndElement);
SAX_handler.characters = reinterpret_cast<charactersSAXFunc>(SAX_characters);
SAX_handler.error = reinterpret_cast<errorSAXFunc>(SAX_error);
SAX_handler.getEntity = reinterpret_cast<getEntitySAXFunc>(SAX_getEntity);
pFile->m_bIgnoreNextError = false;
int ret = xmlSAXUserParseFile(&SAX_handler, pFile, szFileName);
if (ret != 0)
{
error("Failed to parse rss feed");
delete pFile;
pFile = NULL;
}
return pFile;
}
void FeedFile::Parse_StartElement(const char *name, const char **atts)
{
ResetTagContent();
if (!strcmp("item", name))
{
delete m_pFeedItemInfo;
m_pFeedItemInfo = new FeedItemInfo();
}
else if (!strcmp("enclosure", name) && m_pFeedItemInfo)
{
//<enclosure url="http://myindexer.com/fetch/9eeb264aecce961a6e0d" length="150263340" type="application/x-nzb" />
for (; *atts; atts+=2)
{
if (!strcmp("url", atts[0]))
{
char* szUrl = strdup(atts[1]);
WebUtil::XmlDecode(szUrl);
m_pFeedItemInfo->SetUrl(szUrl);
free(szUrl);
}
else if (!strcmp("length", atts[0]))
{
long long lSize = atoll(atts[1]);
m_pFeedItemInfo->SetSize(lSize);
}
}
}
else if (m_pFeedItemInfo && !strcmp("newznab:attr", name) &&
atts[0] && atts[1] && atts[2] && atts[3] &&
!strcmp("name", atts[0]) && !strcmp("value", atts[2]))
{
m_pFeedItemInfo->GetAttributes()->Add(atts[1], atts[3]);
//<newznab:attr name="size" value="5423523453534" />
if (m_pFeedItemInfo->GetSize() == 0 &&
!strcmp("size", atts[1]))
{
long long lSize = atoll(atts[3]);
m_pFeedItemInfo->SetSize(lSize);
}
//<newznab:attr name="imdb" value="1588173"/>
else if (!strcmp("imdb", atts[1]))
{
m_pFeedItemInfo->SetImdbId(atoi(atts[3]));
}
//<newznab:attr name="rageid" value="33877"/>
else if (!strcmp("rageid", atts[1]))
{
m_pFeedItemInfo->SetRageId(atoi(atts[3]));
}
//<newznab:attr name="episode" value="E09"/>
//<newznab:attr name="episode" value="9"/>
else if (!strcmp("episode", atts[1]))
{
m_pFeedItemInfo->SetEpisode(atts[3]);
}
//<newznab:attr name="season" value="S03"/>
//<newznab:attr name="season" value="3"/>
else if (!strcmp("season", atts[1]))
{
m_pFeedItemInfo->SetSeason(atts[3]);
}
}
}
void FeedFile::Parse_EndElement(const char *name)
{
if (!strcmp("item", name))
{
// Close the file element, add the new file to file-list
AddItem(m_pFeedItemInfo);
m_pFeedItemInfo = NULL;
}
else if (!strcmp("title", name) && m_pFeedItemInfo)
{
m_pFeedItemInfo->SetTitle(m_szTagContent);
ResetTagContent();
ParseSubject(m_pFeedItemInfo);
}
else if (!strcmp("link", name) && m_pFeedItemInfo &&
(!m_pFeedItemInfo->GetUrl() || strlen(m_pFeedItemInfo->GetUrl()) == 0))
{
m_pFeedItemInfo->SetUrl(m_szTagContent);
ResetTagContent();
}
else if (!strcmp("category", name) && m_pFeedItemInfo)
{
m_pFeedItemInfo->SetCategory(m_szTagContent);
ResetTagContent();
}
else if (!strcmp("description", name) && m_pFeedItemInfo)
{
m_pFeedItemInfo->SetDescription(m_szTagContent);
ResetTagContent();
}
else if (!strcmp("pubDate", name) && m_pFeedItemInfo)
{
time_t unixtime = WebUtil::ParseRfc822DateTime(m_szTagContent);
if (unixtime > 0)
{
m_pFeedItemInfo->SetTime(unixtime);
}
ResetTagContent();
}
}
void FeedFile::Parse_Content(const char *buf, int len)
{
m_szTagContent = (char*)realloc(m_szTagContent, m_iTagContentLen + len + 1);
strncpy(m_szTagContent + m_iTagContentLen, buf, len);
m_iTagContentLen += len;
m_szTagContent[m_iTagContentLen] = '\0';
}
void FeedFile::ResetTagContent()
{
free(m_szTagContent);
m_szTagContent = NULL;
m_iTagContentLen = 0;
}
void FeedFile::SAX_StartElement(FeedFile* pFile, const char *name, const char **atts)
{
pFile->Parse_StartElement(name, atts);
}
void FeedFile::SAX_EndElement(FeedFile* pFile, const char *name)
{
pFile->Parse_EndElement(name);
}
void FeedFile::SAX_characters(FeedFile* pFile, const char * xmlstr, int len)
{
char* str = (char*)xmlstr;
// trim starting blanks
int off = 0;
for (int i = 0; i < len; i++)
{
char ch = str[i];
if (ch == ' ' || ch == 10 || ch == 13 || ch == 9)
{
off++;
}
else
{
break;
}
}
int newlen = len - off;
// trim ending blanks
for (int i = len - 1; i >= off; i--)
{
char ch = str[i];
if (ch == ' ' || ch == 10 || ch == 13 || ch == 9)
{
newlen--;
}
else
{
break;
}
}
if (newlen > 0)
{
// interpret tag content
pFile->Parse_Content(str + off, newlen);
}
}
void* FeedFile::SAX_getEntity(FeedFile* pFile, const char * name)
{
xmlEntityPtr e = xmlGetPredefinedEntity((xmlChar* )name);
if (!e)
{
warn("entity not found");
pFile->m_bIgnoreNextError = true;
}
return e;
}
void FeedFile::SAX_error(FeedFile* pFile, const char *msg, ...)
{
if (pFile->m_bIgnoreNextError)
{
pFile->m_bIgnoreNextError = false;
return;
}
va_list argp;
va_start(argp, msg);
char szErrMsg[1024];
vsnprintf(szErrMsg, sizeof(szErrMsg), msg, argp);
szErrMsg[1024-1] = '\0';
va_end(argp);
// remove trailing CRLF
for (char* pend = szErrMsg + strlen(szErrMsg) - 1; pend >= szErrMsg && (*pend == '\n' || *pend == '\r' || *pend == ' '); pend--) *pend = '\0';
error("Error parsing rss feed: %s", szErrMsg);
}
#endif

70
FeedFile.h Normal file
View File

@@ -0,0 +1,70 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
*/
#ifndef FEEDFILE_H
#define FEEDFILE_H
#include <vector>
#include "FeedInfo.h"
class FeedFile
{
private:
FeedItemInfos* m_pFeedItemInfos;
char* m_szFileName;
FeedFile(const char* szFileName);
void AddItem(FeedItemInfo* pFeedItemInfo);
void ParseSubject(FeedItemInfo* pFeedItemInfo);
#ifdef WIN32
bool ParseFeed(IUnknown* nzb);
static void EncodeURL(const char* szFilename, char* szURL);
#else
FeedItemInfo* m_pFeedItemInfo;
char* m_szTagContent;
int m_iTagContentLen;
bool m_bIgnoreNextError;
static void SAX_StartElement(FeedFile* pFile, const char *name, const char **atts);
static void SAX_EndElement(FeedFile* pFile, const char *name);
static void SAX_characters(FeedFile* pFile, const char * xmlstr, int len);
static void* SAX_getEntity(FeedFile* pFile, const char * name);
static void SAX_error(FeedFile* pFile, const char *msg, ...);
void Parse_StartElement(const char *name, const char **atts);
void Parse_EndElement(const char *name);
void Parse_Content(const char *buf, int len);
void ResetTagContent();
#endif
public:
virtual ~FeedFile();
static FeedFile* Create(const char* szFileName);
FeedItemInfos* GetFeedItemInfos() { return m_pFeedItemInfos; }
void LogDebugInfo();
};
#endif

1192
FeedFilter.cpp Normal file
View File

File diff suppressed because it is too large Load Diff

186
FeedFilter.h Normal file
View File

@@ -0,0 +1,186 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
*/
#ifndef FEEDFILTER_H
#define FEEDFILTER_H
#include "DownloadInfo.h"
#include "FeedInfo.h"
#include "Util.h"
class FeedFilter
{
private:
typedef std::deque<char*> RefValues;
enum ETermCommand
{
fcText,
fcRegex,
fcEqual,
fcLess,
fcLessEqual,
fcGreater,
fcGreaterEqual,
fcOpeningBrace,
fcClosingBrace,
fcOrOperator
};
class Term
{
private:
bool m_bPositive;
char* m_szField;
ETermCommand m_eCommand;
char* m_szParam;
long long m_iIntParam;
double m_fFloatParam;
bool m_bFloat;
RegEx* m_pRegEx;
RefValues* m_pRefValues;
bool GetFieldData(const char* szField, FeedItemInfo* pFeedItemInfo,
const char** StrValue, long long* IntValue);
bool ParseParam(const char* szField, const char* szParam);
bool ParseSizeParam(const char* szParam);
bool ParseAgeParam(const char* szParam);
bool ParseNumericParam(const char* szParam);
bool MatchValue(const char* szStrValue, long long iIntValue);
bool MatchText(const char* szStrValue);
bool MatchRegex(const char* szStrValue);
void FillWildMaskRefValues(const char* szStrValue, WildMask* pMask, int iRefOffset);
void FillRegExRefValues(const char* szStrValue, RegEx* pRegEx);
public:
Term();
~Term();
void SetRefValues(RefValues* pRefValues) { m_pRefValues = pRefValues; }
bool Compile(char* szToken);
bool Match(FeedItemInfo* pFeedItemInfo);
ETermCommand GetCommand() { return m_eCommand; }
};
typedef std::deque<Term*> TermList;
enum ERuleCommand
{
frAccept,
frReject,
frRequire,
frOptions,
frComment
};
class Rule
{
private:
bool m_bIsValid;
ERuleCommand m_eCommand;
char* m_szCategory;
int m_iPriority;
int m_iAddPriority;
bool m_bPause;
int m_iDupeScore;
int m_iAddDupeScore;
char* m_szDupeKey;
char* m_szAddDupeKey;
EDupeMode m_eDupeMode;
char* m_szSeries;
char* m_szRageId;
bool m_bHasCategory;
bool m_bHasPriority;
bool m_bHasAddPriority;
bool m_bHasPause;
bool m_bHasDupeScore;
bool m_bHasAddDupeScore;
bool m_bHasDupeKey;
bool m_bHasAddDupeKey;
bool m_bHasDupeMode;
bool m_bPatCategory;
bool m_bPatDupeKey;
bool m_bPatAddDupeKey;
bool m_bHasSeries;
bool m_bHasRageId;
char* m_szPatCategory;
char* m_szPatDupeKey;
char* m_szPatAddDupeKey;
TermList m_Terms;
RefValues m_RefValues;
char* CompileCommand(char* szRule);
char* CompileOptions(char* szRule);
bool CompileTerm(char* szTerm);
bool MatchExpression(FeedItemInfo* pFeedItemInfo);
public:
Rule();
~Rule();
void Compile(char* szRule);
bool IsValid() { return m_bIsValid; }
ERuleCommand GetCommand() { return m_eCommand; }
const char* GetCategory() { return m_szCategory; }
int GetPriority() { return m_iPriority; }
int GetAddPriority() { return m_iAddPriority; }
bool GetPause() { return m_bPause; }
const char* GetDupeKey() { return m_szDupeKey; }
const char* GetAddDupeKey() { return m_szAddDupeKey; }
int GetDupeScore() { return m_iDupeScore; }
int GetAddDupeScore() { return m_iAddDupeScore; }
EDupeMode GetDupeMode() { return m_eDupeMode; }
const char* GetRageId() { return m_szRageId; }
const char* GetSeries() { return m_szSeries; }
bool HasCategory() { return m_bHasCategory; }
bool HasPriority() { return m_bHasPriority; }
bool HasAddPriority() { return m_bHasAddPriority; }
bool HasPause() { return m_bHasPause; }
bool HasDupeScore() { return m_bHasDupeScore; }
bool HasAddDupeScore() { return m_bHasAddDupeScore; }
bool HasDupeKey() { return m_bHasDupeKey; }
bool HasAddDupeKey() { return m_bHasAddDupeKey; }
bool HasDupeMode() { return m_bHasDupeMode; }
bool HasRageId() { return m_bHasRageId; }
bool HasSeries() { return m_bHasSeries; }
bool Match(FeedItemInfo* pFeedItemInfo);
void ExpandRefValues(FeedItemInfo* pFeedItemInfo, char** pDestStr, char* pPatStr);
const char* GetRefValue(FeedItemInfo* pFeedItemInfo, const char* szVarName);
};
typedef std::deque<Rule*> RuleList;
private:
RuleList m_Rules;
void Compile(const char* szFilter);
void CompileRule(char* szRule);
void ApplyOptions(Rule* pRule, FeedItemInfo* pFeedItemInfo);
public:
FeedFilter(const char* szFilter);
~FeedFilter();
void Match(FeedItemInfo* pFeedItemInfo);
};
#endif

440
FeedInfo.cpp Normal file
View File

@@ -0,0 +1,440 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision: 0 $
* $Date$
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <sys/stat.h>
#include "nzbget.h"
#include "FeedInfo.h"
#include "Util.h"
FeedInfo::FeedInfo(int iID, const char* szName, const char* szUrl, int iInterval,
const char* szFilter, bool bPauseNzb, const char* szCategory, int iPriority)
{
m_iID = iID;
m_szName = strdup(szName ? szName : "");
m_szUrl = strdup(szUrl ? szUrl : "");
m_szFilter = strdup(szFilter ? szFilter : "");
m_iFilterHash = Util::HashBJ96(szFilter, strlen(szFilter), 0);
m_szCategory = strdup(szCategory ? szCategory : "");
m_iInterval = iInterval;
m_bPauseNzb = bPauseNzb;
m_iPriority = iPriority;
m_tLastUpdate = 0;
m_bPreview = false;
m_eStatus = fsUndefined;
m_szOutputFilename = NULL;
m_bFetch = false;
m_bForce = false;
}
FeedInfo::~FeedInfo()
{
free(m_szName);
free(m_szUrl);
free(m_szFilter);
free(m_szCategory);
free(m_szOutputFilename);
}
void FeedInfo::SetOutputFilename(const char* szOutputFilename)
{
free(m_szOutputFilename);
m_szOutputFilename = strdup(szOutputFilename);
}
FeedItemInfo::Attr::Attr(const char* szName, const char* szValue)
{
m_szName = strdup(szName ? szName : "");
m_szValue = strdup(szValue ? szValue : "");
}
FeedItemInfo::Attr::~Attr()
{
free(m_szName);
free(m_szValue);
}
FeedItemInfo::Attributes::~Attributes()
{
for (iterator it = begin(); it != end(); it++)
{
delete *it;
}
}
void FeedItemInfo::Attributes::Add(const char* szName, const char* szValue)
{
push_back(new Attr(szName, szValue));
}
FeedItemInfo::Attr* FeedItemInfo::Attributes::Find(const char* szName)
{
for (iterator it = begin(); it != end(); it++)
{
Attr* pAttr = *it;
if (!strcasecmp(pAttr->GetName(), szName))
{
return pAttr;
}
}
return NULL;
}
FeedItemInfo::FeedItemInfo()
{
m_pSharedFeedData = NULL;
m_szTitle = NULL;
m_szFilename = NULL;
m_szUrl = NULL;
m_szCategory = strdup("");
m_lSize = 0;
m_tTime = 0;
m_iImdbId = 0;
m_iRageId = 0;
m_szDescription = strdup("");
m_szSeason = NULL;
m_szEpisode = NULL;
m_iSeasonNum = 0;
m_iEpisodeNum = 0;
m_bSeasonEpisodeParsed = false;
m_szAddCategory = strdup("");
m_bPauseNzb = false;
m_iPriority = 0;
m_eStatus = isUnknown;
m_eMatchStatus = msIgnored;
m_iMatchRule = 0;
m_szDupeKey = NULL;
m_iDupeScore = 0;
m_eDupeMode = dmScore;
}
FeedItemInfo::~FeedItemInfo()
{
free(m_szTitle);
free(m_szFilename);
free(m_szUrl);
free(m_szCategory);
free(m_szDescription);
free(m_szSeason);
free(m_szEpisode);
free(m_szAddCategory);
free(m_szDupeKey);
}
void FeedItemInfo::SetTitle(const char* szTitle)
{
free(m_szTitle);
m_szTitle = szTitle ? strdup(szTitle) : NULL;
}
void FeedItemInfo::SetFilename(const char* szFilename)
{
free(m_szFilename);
m_szFilename = szFilename ? strdup(szFilename) : NULL;
}
void FeedItemInfo::SetUrl(const char* szUrl)
{
free(m_szUrl);
m_szUrl = szUrl ? strdup(szUrl) : NULL;
}
void FeedItemInfo::SetCategory(const char* szCategory)
{
free(m_szCategory);
m_szCategory = strdup(szCategory ? szCategory: "");
}
void FeedItemInfo::SetDescription(const char* szDescription)
{
free(m_szDescription);
m_szDescription = strdup(szDescription ? szDescription: "");
}
void FeedItemInfo::SetSeason(const char* szSeason)
{
free(m_szSeason);
m_szSeason = szSeason ? strdup(szSeason) : NULL;
m_iSeasonNum = szSeason ? ParsePrefixedInt(szSeason) : 0;
}
void FeedItemInfo::SetEpisode(const char* szEpisode)
{
free(m_szEpisode);
m_szEpisode = szEpisode ? strdup(szEpisode) : NULL;
m_iEpisodeNum = szEpisode ? ParsePrefixedInt(szEpisode) : 0;
}
int FeedItemInfo::ParsePrefixedInt(const char *szValue)
{
const char* szVal = szValue;
if (!strchr("0123456789", *szVal))
{
szVal++;
}
return atoi(szVal);
}
void FeedItemInfo::SetAddCategory(const char* szAddCategory)
{
free(m_szAddCategory);
m_szAddCategory = strdup(szAddCategory ? szAddCategory : "");
}
void FeedItemInfo::SetDupeKey(const char* szDupeKey)
{
free(m_szDupeKey);
m_szDupeKey = strdup(szDupeKey ? szDupeKey : "");
}
void FeedItemInfo::AppendDupeKey(const char* szExtraDupeKey)
{
if (!m_szDupeKey || *m_szDupeKey == '\0' || !szExtraDupeKey || *szExtraDupeKey == '\0')
{
return;
}
int iLen = (m_szDupeKey ? strlen(m_szDupeKey) : 0) + 1 + strlen(szExtraDupeKey) + 1;
char* szNewKey = (char*)malloc(iLen);
snprintf(szNewKey, iLen, "%s-%s", m_szDupeKey, szExtraDupeKey);
szNewKey[iLen - 1] = '\0';
free(m_szDupeKey);
m_szDupeKey = szNewKey;
}
void FeedItemInfo::BuildDupeKey(const char* szRageId, const char* szSeries)
{
int iRageId = szRageId && *szRageId ? atoi(szRageId) : m_iRageId;
free(m_szDupeKey);
if (m_iImdbId != 0)
{
m_szDupeKey = (char*)malloc(20);
snprintf(m_szDupeKey, 20, "imdb=%i", m_iImdbId);
}
else if (szSeries && *szSeries && GetSeasonNum() != 0 && GetEpisodeNum() != 0)
{
int iLen = strlen(szSeries) + 50;
m_szDupeKey = (char*)malloc(iLen);
snprintf(m_szDupeKey, iLen, "series=%s-%s-%s", szSeries, m_szSeason, m_szEpisode);
m_szDupeKey[iLen-1] = '\0';
}
else if (iRageId != 0 && GetSeasonNum() != 0 && GetEpisodeNum() != 0)
{
m_szDupeKey = (char*)malloc(100);
snprintf(m_szDupeKey, 100, "rageid=%i-%s-%s", iRageId, m_szSeason, m_szEpisode);
m_szDupeKey[100-1] = '\0';
}
else
{
m_szDupeKey = strdup("");
}
}
int FeedItemInfo::GetSeasonNum()
{
if (!m_szSeason && !m_bSeasonEpisodeParsed)
{
ParseSeasonEpisode();
}
return m_iSeasonNum;
}
int FeedItemInfo::GetEpisodeNum()
{
if (!m_szEpisode && !m_bSeasonEpisodeParsed)
{
ParseSeasonEpisode();
}
return m_iEpisodeNum;
}
void FeedItemInfo::ParseSeasonEpisode()
{
m_bSeasonEpisodeParsed = true;
RegEx* pRegEx = m_pSharedFeedData->GetSeasonEpisodeRegEx();
if (pRegEx->Match(m_szTitle))
{
char szRegValue[100];
char szValue[100];
snprintf(szValue, 100, "S%02d", atoi(m_szTitle + pRegEx->GetMatchStart(1)));
szValue[100-1] = '\0';
SetSeason(szValue);
int iLen = pRegEx->GetMatchLen(2);
iLen = iLen < 99 ? iLen : 99;
strncpy(szRegValue, m_szTitle + pRegEx->GetMatchStart(2), pRegEx->GetMatchLen(2));
szRegValue[iLen] = '\0';
snprintf(szValue, 100, "E%s", szRegValue);
szValue[100-1] = '\0';
Util::ReduceStr(szValue, "-", "");
for (char* p = szValue; *p; p++) *p = toupper(*p); // convert string to uppercase e02 -> E02
SetEpisode(szValue);
}
}
FeedHistoryInfo::FeedHistoryInfo(const char* szUrl, FeedHistoryInfo::EStatus eStatus, time_t tLastSeen)
{
m_szUrl = szUrl ? strdup(szUrl) : NULL;
m_eStatus = eStatus;
m_tLastSeen = tLastSeen;
}
FeedHistoryInfo::~FeedHistoryInfo()
{
free(m_szUrl);
}
FeedHistory::~FeedHistory()
{
Clear();
}
void FeedHistory::Clear()
{
for (iterator it = begin(); it != end(); it++)
{
delete *it;
}
clear();
}
void FeedHistory::Add(const char* szUrl, FeedHistoryInfo::EStatus eStatus, time_t tLastSeen)
{
push_back(new FeedHistoryInfo(szUrl, eStatus, tLastSeen));
}
void FeedHistory::Remove(const char* szUrl)
{
for (iterator it = begin(); it != end(); it++)
{
FeedHistoryInfo* pFeedHistoryInfo = *it;
if (!strcmp(pFeedHistoryInfo->GetUrl(), szUrl))
{
delete pFeedHistoryInfo;
erase(it);
break;
}
}
}
FeedHistoryInfo* FeedHistory::Find(const char* szUrl)
{
for (iterator it = begin(); it != end(); it++)
{
FeedHistoryInfo* pFeedHistoryInfo = *it;
if (!strcmp(pFeedHistoryInfo->GetUrl(), szUrl))
{
return pFeedHistoryInfo;
}
}
return NULL;
}
FeedItemInfos::FeedItemInfos()
{
debug("Creating FeedItemInfos");
m_iRefCount = 0;
}
FeedItemInfos::~FeedItemInfos()
{
debug("Destroing FeedItemInfos");
for (iterator it = begin(); it != end(); it++)
{
delete *it;
}
}
void FeedItemInfos::Retain()
{
m_iRefCount++;
}
void FeedItemInfos::Release()
{
m_iRefCount--;
if (m_iRefCount <= 0)
{
delete this;
}
}
void FeedItemInfos::Add(FeedItemInfo* pFeedItemInfo)
{
push_back(pFeedItemInfo);
pFeedItemInfo->SetSharedFeedData(&m_SharedFeedData);
}
SharedFeedData::SharedFeedData()
{
m_pSeasonEpisodeRegEx = NULL;
}
SharedFeedData::~SharedFeedData()
{
delete m_pSeasonEpisodeRegEx;
}
RegEx* SharedFeedData::GetSeasonEpisodeRegEx()
{
if (!m_pSeasonEpisodeRegEx)
{
m_pSeasonEpisodeRegEx = new RegEx("[^[:alnum:]]s?([0-9]+)[ex]([0-9]+(-?e[0-9]+)?)[^[:alnum:]]", 10);
}
return m_pSeasonEpisodeRegEx;
}

278
FeedInfo.h Normal file
View File

@@ -0,0 +1,278 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision: 0 $
* $Date$
*
*/
#ifndef FEEDINFO_H
#define FEEDINFO_H
#include <deque>
#include <time.h>
#include "Util.h"
#include "DownloadInfo.h"
class FeedInfo
{
public:
enum EStatus
{
fsUndefined,
fsRunning,
fsFinished,
fsFailed
};
private:
int m_iID;
char* m_szName;
char* m_szUrl;
int m_iInterval;
char* m_szFilter;
unsigned int m_iFilterHash;
bool m_bPauseNzb;
char* m_szCategory;
int m_iPriority;
time_t m_tLastUpdate;
bool m_bPreview;
EStatus m_eStatus;
char* m_szOutputFilename;
bool m_bFetch;
bool m_bForce;
public:
FeedInfo(int iID, const char* szName, const char* szUrl, int iInterval,
const char* szFilter, bool bPauseNzb, const char* szCategory, int iPriority);
~FeedInfo();
int GetID() { return m_iID; }
const char* GetName() { return m_szName; }
const char* GetUrl() { return m_szUrl; }
int GetInterval() { return m_iInterval; }
const char* GetFilter() { return m_szFilter; }
unsigned int GetFilterHash() { return m_iFilterHash; }
bool GetPauseNzb() { return m_bPauseNzb; }
const char* GetCategory() { return m_szCategory; }
int GetPriority() { return m_iPriority; }
time_t GetLastUpdate() { return m_tLastUpdate; }
void SetLastUpdate(time_t tLastUpdate) { m_tLastUpdate = tLastUpdate; }
bool GetPreview() { return m_bPreview; }
void SetPreview(bool bPreview) { m_bPreview = bPreview; }
EStatus GetStatus() { return m_eStatus; }
void SetStatus(EStatus Status) { m_eStatus = Status; }
const char* GetOutputFilename() { return m_szOutputFilename; }
void SetOutputFilename(const char* szOutputFilename);
bool GetFetch() { return m_bFetch; }
void SetFetch(bool bFetch) { m_bFetch = bFetch; }
bool GetForce() { return m_bForce; }
void SetForce(bool bForce) { m_bForce = bForce; }
};
typedef std::deque<FeedInfo*> Feeds;
class SharedFeedData
{
private:
RegEx* m_pSeasonEpisodeRegEx;
public:
SharedFeedData();
~SharedFeedData();
RegEx* GetSeasonEpisodeRegEx();
};
class FeedItemInfo
{
public:
enum EStatus
{
isUnknown,
isBacklog,
isFetched,
isNew
};
enum EMatchStatus
{
msIgnored,
msAccepted,
msRejected
};
class Attr
{
private:
char* m_szName;
char* m_szValue;
public:
Attr(const char* szName, const char* szValue);
~Attr();
const char* GetName() { return m_szName; }
const char* GetValue() { return m_szValue; }
};
typedef std::deque<Attr*> AttributesBase;
class Attributes: public AttributesBase
{
public:
~Attributes();
void Add(const char* szName, const char* szValue);
Attr* Find(const char* szName);
};
private:
char* m_szTitle;
char* m_szFilename;
char* m_szUrl;
time_t m_tTime;
long long m_lSize;
char* m_szCategory;
int m_iImdbId;
int m_iRageId;
char* m_szDescription;
char* m_szSeason;
char* m_szEpisode;
int m_iSeasonNum;
int m_iEpisodeNum;
bool m_bSeasonEpisodeParsed;
char* m_szAddCategory;
bool m_bPauseNzb;
int m_iPriority;
EStatus m_eStatus;
EMatchStatus m_eMatchStatus;
int m_iMatchRule;
char* m_szDupeKey;
int m_iDupeScore;
EDupeMode m_eDupeMode;
SharedFeedData* m_pSharedFeedData;
Attributes m_Attributes;
int ParsePrefixedInt(const char *szValue);
void ParseSeasonEpisode();
public:
FeedItemInfo();
~FeedItemInfo();
void SetSharedFeedData(SharedFeedData* pSharedFeedData) { m_pSharedFeedData = pSharedFeedData; }
const char* GetTitle() { return m_szTitle; }
void SetTitle(const char* szTitle);
const char* GetFilename() { return m_szFilename; }
void SetFilename(const char* szFilename);
const char* GetUrl() { return m_szUrl; }
void SetUrl(const char* szUrl);
long long GetSize() { return m_lSize; }
void SetSize(long long lSize) { m_lSize = lSize; }
const char* GetCategory() { return m_szCategory; }
void SetCategory(const char* szCategory);
int GetImdbId() { return m_iImdbId; }
void SetImdbId(int iImdbId) { m_iImdbId = iImdbId; }
int GetRageId() { return m_iRageId; }
void SetRageId(int iRageId) { m_iRageId = iRageId; }
const char* GetDescription() { return m_szDescription; }
void SetDescription(const char* szDescription);
const char* GetSeason() { return m_szSeason; }
void SetSeason(const char* szSeason);
const char* GetEpisode() { return m_szEpisode; }
void SetEpisode(const char* szEpisode);
int GetSeasonNum();
int GetEpisodeNum();
const char* GetAddCategory() { return m_szAddCategory; }
void SetAddCategory(const char* szAddCategory);
bool GetPauseNzb() { return m_bPauseNzb; }
void SetPauseNzb(bool bPauseNzb) { m_bPauseNzb = bPauseNzb; }
int GetPriority() { return m_iPriority; }
void SetPriority(int iPriority) { m_iPriority = iPriority; }
time_t GetTime() { return m_tTime; }
void SetTime(time_t tTime) { m_tTime = tTime; }
EStatus GetStatus() { return m_eStatus; }
void SetStatus(EStatus eStatus) { m_eStatus = eStatus; }
EMatchStatus GetMatchStatus() { return m_eMatchStatus; }
void SetMatchStatus(EMatchStatus eMatchStatus) { m_eMatchStatus = eMatchStatus; }
int GetMatchRule() { return m_iMatchRule; }
void SetMatchRule(int iMatchRule) { m_iMatchRule = iMatchRule; }
const char* GetDupeKey() { return m_szDupeKey; }
void SetDupeKey(const char* szDupeKey);
void AppendDupeKey(const char* szExtraDupeKey);
void BuildDupeKey(const char* szRageId, const char* szSeries);
int GetDupeScore() { return m_iDupeScore; }
void SetDupeScore(int iDupeScore) { m_iDupeScore = iDupeScore; }
EDupeMode GetDupeMode() { return m_eDupeMode; }
void SetDupeMode(EDupeMode eDupeMode) { m_eDupeMode = eDupeMode; }
Attributes* GetAttributes() { return &m_Attributes; }
};
typedef std::deque<FeedItemInfo*> FeedItemInfosBase;
class FeedItemInfos : public FeedItemInfosBase
{
private:
int m_iRefCount;
SharedFeedData m_SharedFeedData;
public:
FeedItemInfos();
~FeedItemInfos();
void Retain();
void Release();
void Add(FeedItemInfo* pFeedItemInfo);
};
class FeedHistoryInfo
{
public:
enum EStatus
{
hsUnknown,
hsBacklog,
hsFetched
};
private:
char* m_szUrl;
EStatus m_eStatus;
time_t m_tLastSeen;
public:
FeedHistoryInfo(const char* szUrl, EStatus eStatus, time_t tLastSeen);
~FeedHistoryInfo();
const char* GetUrl() { return m_szUrl; }
EStatus GetStatus() { return m_eStatus; }
void SetStatus(EStatus Status) { m_eStatus = Status; }
time_t GetLastSeen() { return m_tLastSeen; }
void SetLastSeen(time_t tLastSeen) { m_tLastSeen = tLastSeen; }
};
typedef std::deque<FeedHistoryInfo*> FeedHistoryBase;
class FeedHistory : public FeedHistoryBase
{
public:
~FeedHistory();
void Clear();
void Add(const char* szUrl, FeedHistoryInfo::EStatus eStatus, time_t tLastSeen);
void Remove(const char* szUrl);
FeedHistoryInfo* Find(const char* szUrl);
};
#endif

View File

@@ -2,7 +2,7 @@
* This file is part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2010 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -34,8 +34,7 @@
#include <stdlib.h>
#include <string.h>
#include <cstdio>
#include <fstream>
#include <stdio.h>
#ifndef WIN32
#include <unistd.h>
#include <arpa/inet.h>
@@ -236,6 +235,10 @@ void Frontend::InitMessageBase(SNZBRequestBase* pMessageBase, int iRequest, int
pMessageBase->m_iSignature = htonl(NZBMESSAGE_SIGNATURE);
pMessageBase->m_iType = htonl(iRequest);
pMessageBase->m_iStructSize = htonl(iSize);
strncpy(pMessageBase->m_szUsername, g_pOptions->GetControlUsername(), NZBREQUESTPASSWORDSIZE - 1);
pMessageBase->m_szUsername[NZBREQUESTPASSWORDSIZE - 1] = '\0';
strncpy(pMessageBase->m_szPassword, g_pOptions->GetControlPassword(), NZBREQUESTPASSWORDSIZE);
pMessageBase->m_szPassword[NZBREQUESTPASSWORDSIZE - 1] = '\0';
}

18
Log.cpp
View File

@@ -2,7 +2,7 @@
* This file is part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2009 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -38,7 +38,7 @@
#include <string.h>
#include <sys/stat.h>
#include <stdarg.h>
#include <cstdio>
#include <stdio.h>
#include "nzbget.h"
#include "Options.h"
@@ -60,10 +60,7 @@ Log::Log()
Log::~Log()
{
Clear();
if (m_szLogFilename)
{
free(m_szLogFilename);
}
free(m_szLogFilename);
}
void Log::Filelog(const char* msg, ...)
@@ -80,6 +77,7 @@ void Log::Filelog(const char* msg, ...)
time_t rawtime;
time(&rawtime);
rawtime += g_pOptions->GetTimeCorrection();
char szTime[50];
#ifdef HAVE_CTIME_R_3
@@ -305,10 +303,7 @@ Message::Message(unsigned int iID, EKind eKind, time_t tTime, const char* szText
Message::~ Message()
{
if (m_szText)
{
free(m_szText);
}
free(m_szText);
}
void Log::Clear()
@@ -370,6 +365,9 @@ void Log::InitOptions()
if (g_pOptions->GetCreateLog() && g_pOptions->GetLogFile())
{
m_szLogFilename = strdup(g_pOptions->GetLogFile());
#ifdef WIN32
WebUtil::Utf8ToAnsi(m_szLogFilename, strlen(m_szLogFilename) + 1);
#endif
}
m_iIDGen = 0;

325
Maintenance.cpp Normal file
View File

@@ -0,0 +1,325 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include "win32.h"
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include <errno.h>
#include "nzbget.h"
#include "Log.h"
#include "Util.h"
#include "Maintenance.h"
extern Options* g_pOptions;
extern Maintenance* g_pMaintenance;
Maintenance::Maintenance()
{
m_iIDMessageGen = 0;
m_UpdateScriptController = NULL;
m_szUpdateScript = NULL;
}
Maintenance::~Maintenance()
{
m_mutexController.Lock();
if (m_UpdateScriptController)
{
m_UpdateScriptController->Detach();
m_mutexController.Unlock();
while (m_UpdateScriptController)
{
usleep(20*1000);
}
}
ClearMessages();
free(m_szUpdateScript);
}
void Maintenance::ResetUpdateController()
{
m_mutexController.Lock();
m_UpdateScriptController = NULL;
m_mutexController.Unlock();
}
void Maintenance::ClearMessages()
{
for (Log::Messages::iterator it = m_Messages.begin(); it != m_Messages.end(); it++)
{
delete *it;
}
m_Messages.clear();
}
Log::Messages* Maintenance::LockMessages()
{
m_mutexLog.Lock();
return &m_Messages;
}
void Maintenance::UnlockMessages()
{
m_mutexLog.Unlock();
}
void Maintenance::AppendMessage(Message::EKind eKind, time_t tTime, const char * szText)
{
if (tTime == 0)
{
tTime = time(NULL);
}
m_mutexLog.Lock();
Message* pMessage = new Message(++m_iIDMessageGen, eKind, tTime, szText);
m_Messages.push_back(pMessage);
m_mutexLog.Unlock();
}
bool Maintenance::StartUpdate(EBranch eBranch)
{
m_mutexController.Lock();
bool bAlreadyUpdating = m_UpdateScriptController != NULL;
m_mutexController.Unlock();
if (bAlreadyUpdating)
{
error("Could not start update-script: update-script is already running");
return false;
}
if (m_szUpdateScript)
{
free(m_szUpdateScript);
m_szUpdateScript = NULL;
}
if (!ReadPackageInfoStr("install-script", &m_szUpdateScript))
{
return false;
}
ClearMessages();
m_UpdateScriptController = new UpdateScriptController();
m_UpdateScriptController->SetScript(m_szUpdateScript);
m_UpdateScriptController->SetBranch(eBranch);
m_UpdateScriptController->SetAutoDestroy(true);
m_UpdateScriptController->Start();
return true;
}
bool Maintenance::CheckUpdates(char** pUpdateInfo)
{
char* szUpdateInfoScript;
if (!ReadPackageInfoStr("update-info-script", &szUpdateInfoScript))
{
return false;
}
*pUpdateInfo = NULL;
UpdateInfoScriptController::ExecuteScript(szUpdateInfoScript, pUpdateInfo);
free(szUpdateInfoScript);
return *pUpdateInfo;
}
bool Maintenance::ReadPackageInfoStr(const char* szKey, char** pValue)
{
char szFileName[1024];
snprintf(szFileName, 1024, "%s%cpackage-info.json", g_pOptions->GetWebDir(), PATH_SEPARATOR);
szFileName[1024-1] = '\0';
char* szPackageInfo;
int iPackageInfoLen;
if (!Util::LoadFileIntoBuffer(szFileName, &szPackageInfo, &iPackageInfoLen))
{
error("Could not load file %s", szFileName);
return false;
}
char szKeyStr[100];
snprintf(szKeyStr, 100, "\"%s\"", szKey);
szKeyStr[100-1] = '\0';
char* p = strstr(szPackageInfo, szKeyStr);
if (!p)
{
error("Could not parse file %s", szFileName);
free(szPackageInfo);
return false;
}
p = strchr(p + strlen(szKeyStr), '"');
if (!p)
{
error("Could not parse file %s", szFileName);
free(szPackageInfo);
return false;
}
p++;
char* pend = strchr(p, '"');
if (!pend)
{
error("Could not parse file %s", szFileName);
free(szPackageInfo);
return false;
}
int iLen = pend - p;
if (iLen >= sizeof(szFileName))
{
error("Could not parse file %s", szFileName);
free(szPackageInfo);
return false;
}
*pValue = (char*)malloc(iLen+1);
strncpy(*pValue, p, iLen);
(*pValue)[iLen] = '\0';
WebUtil::JsonDecode(*pValue);
free(szPackageInfo);
return true;
}
void UpdateScriptController::Run()
{
m_iPrefixLen = 0;
PrintMessage(Message::mkInfo, "Executing update-script %s", GetScript());
char szInfoName[1024];
snprintf(szInfoName, 1024, "update-script %s", Util::BaseFileName(GetScript()));
szInfoName[1024-1] = '\0';
SetInfoName(szInfoName);
const char* szBranchName[] = { "STABLE", "TESTING", "DEVEL" };
SetEnvVar("NZBUP_BRANCH", szBranchName[m_eBranch]);
char szProcessID[20];
#ifdef WIN32
int pid = (int)GetCurrentProcessId();
#else
int pid = (int)getppid();
#endif
snprintf(szProcessID, 20, "%i", pid);
szProcessID[20-1] = '\0';
SetEnvVar("NZBUP_PROCESSID", szProcessID);
char szLogPrefix[100];
strncpy(szLogPrefix, Util::BaseFileName(GetScript()), 100);
szLogPrefix[100-1] = '\0';
if (char* ext = strrchr(szLogPrefix, '.')) *ext = '\0'; // strip file extension
SetLogPrefix(szLogPrefix);
m_iPrefixLen = strlen(szLogPrefix) + 2; // 2 = strlen(": ");
Execute();
g_pMaintenance->ResetUpdateController();
}
void UpdateScriptController::AddMessage(Message::EKind eKind, const char* szText)
{
szText = szText + m_iPrefixLen;
g_pMaintenance->AppendMessage(eKind, time(NULL), szText);
ScriptController::AddMessage(eKind, szText);
}
void UpdateInfoScriptController::ExecuteScript(const char* szScript, char** pUpdateInfo)
{
detail("Executing update-info-script %s", Util::BaseFileName(szScript));
UpdateInfoScriptController* pScriptController = new UpdateInfoScriptController();
pScriptController->SetScript(szScript);
char szInfoName[1024];
snprintf(szInfoName, 1024, "update-info-script %s", Util::BaseFileName(szScript));
szInfoName[1024-1] = '\0';
pScriptController->SetInfoName(szInfoName);
char szLogPrefix[1024];
strncpy(szLogPrefix, Util::BaseFileName(szScript), 1024);
szLogPrefix[1024-1] = '\0';
if (char* ext = strrchr(szLogPrefix, '.')) *ext = '\0'; // strip file extension
pScriptController->SetLogPrefix(szLogPrefix);
pScriptController->m_iPrefixLen = strlen(szLogPrefix) + 2; // 2 = strlen(": ");
pScriptController->Execute();
if (pScriptController->m_UpdateInfo.GetBuffer())
{
int iLen = strlen(pScriptController->m_UpdateInfo.GetBuffer());
*pUpdateInfo = (char*)malloc(iLen + 1);
strncpy(*pUpdateInfo, pScriptController->m_UpdateInfo.GetBuffer(), iLen);
(*pUpdateInfo)[iLen] = '\0';
}
delete pScriptController;
}
void UpdateInfoScriptController::AddMessage(Message::EKind eKind, const char* szText)
{
szText = szText + m_iPrefixLen;
if (!strncmp(szText, "[NZB] ", 6))
{
debug("Command %s detected", szText + 6);
if (!strncmp(szText + 6, "[UPDATEINFO]", 12))
{
m_UpdateInfo.Append(szText + 6 + 12);
}
else
{
error("Invalid command \"%s\" received from %s", szText, GetInfoName());
}
}
else
{
ScriptController::AddMessage(eKind, szText);
}
}

94
Maintenance.h Normal file
View File

@@ -0,0 +1,94 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
*/
#ifndef MAINTENANCE_H
#define MAINTENANCE_H
#include "Thread.h"
#include "ScriptController.h"
#include "Log.h"
#include "Util.h"
class UpdateScriptController;
class Maintenance
{
private:
Log::Messages m_Messages;
Mutex m_mutexLog;
Mutex m_mutexController;
int m_iIDMessageGen;
UpdateScriptController* m_UpdateScriptController;
char* m_szUpdateScript;
bool ReadPackageInfoStr(const char* szKey, char** pValue);
public:
enum EBranch
{
brStable,
brTesting,
brDevel
};
Maintenance();
~Maintenance();
void ClearMessages();
void AppendMessage(Message::EKind eKind, time_t tTime, const char* szText);
Log::Messages* LockMessages();
void UnlockMessages();
bool StartUpdate(EBranch eBranch);
void ResetUpdateController();
bool CheckUpdates(char** pUpdateInfo);
};
class UpdateScriptController : public Thread, public ScriptController
{
private:
Maintenance::EBranch m_eBranch;
int m_iPrefixLen;
protected:
virtual void AddMessage(Message::EKind eKind, const char* szText);
public:
virtual void Run();
void SetBranch(Maintenance::EBranch eBranch) { m_eBranch = eBranch; }
};
class UpdateInfoScriptController : public ScriptController
{
private:
int m_iPrefixLen;
StringBuilder m_UpdateInfo;
protected:
virtual void AddMessage(Message::EKind eKind, const char* szText);
public:
static void ExecuteScript(const char* szScript, char** pUpdateInfo);
};
#endif

View File

@@ -24,10 +24,12 @@ bin_PROGRAMS = nzbget
nzbget_SOURCES = \
ArticleDownloader.cpp ArticleDownloader.h BinRpc.cpp BinRpc.h \
ColoredFrontend.cpp ColoredFrontend.h Connection.cpp Connection.h Decoder.cpp Decoder.h \
DiskState.cpp DiskState.h DownloadInfo.cpp DownloadInfo.h Frontend.cpp Frontend.h \
Log.cpp Log.h LoggableFrontend.cpp LoggableFrontend.h MessageBase.h \
NCursesFrontend.cpp NCursesFrontend.h NNTPConnection.cpp NNTPConnection.h NZBFile.cpp \
NZBFile.h NewsServer.cpp NewsServer.h Observer.cpp \
DiskState.cpp DiskState.h DownloadInfo.cpp DownloadInfo.h DupeCoordinator.cpp DupeCoordinator.h \
Frontend.cpp Frontend.h FeedCoordinator.cpp FeedCoordinator.h FeedFile.cpp FeedFile.h \
FeedFilter.cpp FeedFilter.h FeedInfo.cpp FeedInfo.h Log.cpp Log.h LoggableFrontend.cpp \
LoggableFrontend.h Maintenance.cpp Maintenance.h MessageBase.h NCursesFrontend.cpp \
NCursesFrontend.h NNTPConnection.cpp \
NNTPConnection.h NZBFile.cpp NZBFile.h NewsServer.cpp NewsServer.h Observer.cpp \
Observer.h Options.cpp Options.h ParChecker.cpp ParChecker.h ParRenamer.cpp ParRenamer.h \
ParCoordinator.cpp ParCoordinator.h PrePostProcessor.cpp PrePostProcessor.h QueueCoordinator.cpp \
QueueCoordinator.h QueueEditor.cpp QueueEditor.h RemoteClient.cpp RemoteClient.h \
@@ -37,8 +39,8 @@ nzbget_SOURCES = \
UrlCoordinator.cpp UrlCoordinator.h Unpack.cpp Unpack.h nzbget.cpp nzbget.h
EXTRA_DIST = \
Makefile.cvs nzbgetd nzbget-postprocess.sh \
$(patches_FILES) $(windows_FILES)
Makefile.cvs nzbgetd \
$(patches_FILES) $(windows_FILES) $(osx_FILES)
patches_FILES = \
libpar2-0.2-bugfixes.patch libpar2-0.2-cancel.patch \
@@ -47,16 +49,31 @@ patches_FILES = \
windows_FILES = \
win32.h NTService.cpp NTService.h nzbget.sln nzbget.vcproj nzbget-shell.bat
osx_FILES = \
osx/App_Prefix.pch osx/NZBGet-Info.plist \
osx/DaemonController.h osx/DaemonController.m \
osx/MainApp.h osx/MainApp.m osx/MainApp.xib \
osx/PFMoveApplication.h osx/PFMoveApplication.m \
osx/PreferencesDialog.h osx/PreferencesDialog.m osx/PreferencesDialog.xib \
osx/RPC.h osx/RPC.m osx/WebClient.h osx/WebClient.m \
osx/WelcomeDialog.h osx/WelcomeDialog.m osx/WelcomeDialog.xib \
osx/NZBGet.xcodeproj/project.pbxproj \
osx/Resources/Images/mainicon.icns osx/Resources/Images/statusicon.png \
osx/Resources/Images/statusicon@2x.png osx/Resources/Images/statusicon-inv.png \
osx/Resources/Images/statusicon-inv@2x.png osx/Resources/licenses/license-bootstrap.txt \
osx/Resources/licenses/license-jquery-GPL.txt osx/Resources/licenses/license-jquery-MIT.txt \
osx/Resources/Credits.rtf osx/Resources/Localizable.strings osx/Resources/Welcome.rtf
doc_FILES = \
README ChangeLog COPYING
exampleconf_FILES = \
nzbget.conf nzbget-postprocess.conf
nzbget.conf
webui_FILES = \
webui/index.html webui/index.js webui/downloads.js webui/edit.js webui/fasttable.js \
webui/history.js webui/messages.js webui/status.js webui/style.css webui/upload.js \
webui/util.js webui/config.js \
webui/util.js webui/config.js webui/feed.js \
webui/lib/bootstrap.js webui/lib/bootstrap.min.js webui/lib/bootstrap.css \
webui/lib/jquery.js webui/lib/jquery.min.js \
webui/img/icons.png webui/img/icons-2x.png \
@@ -64,16 +81,18 @@ webui_FILES = \
webui/img/download-anim-green-2x.png webui/img/download-anim-orange-2x.png \
webui/img/transmit-reload-2x.gif
ppscripts_FILES = \
ppscripts/EMail.py ppscripts/Logger.py
# Install
sbin_SCRIPTS = nzbgetd
bin_SCRIPTS = nzbget-postprocess.sh
dist_doc_DATA = $(doc_FILES)
exampleconfdir = $(datadir)/nzbget
dist_exampleconf_DATA = $(exampleconf_FILES)
webuiconfdir = $(datadir)/nzbget/webui
dist_webuiconf_DATA = $(exampleconf_FILES)
webuidir = $(datadir)/nzbget
nobase_dist_webui_DATA = $(webui_FILES)
ppscriptsdir = $(datadir)/nzbget
nobase_dist_ppscripts_SCRIPTS = $(ppscripts_FILES)
# Note about "sed":
# We need to make some changes in installed files.
@@ -91,12 +110,15 @@ install-exec-hook:
sed 's?/usr/local/bin?$(bindir)?' < "$(DESTDIR)$(sbindir)/nzbgetd.temp" > "$(DESTDIR)$(sbindir)/nzbgetd"
rm "$(DESTDIR)$(sbindir)/nzbgetd.temp"
# Prepare example configuration files
# Prepare example configuration file
install-data-hook:
rm -f "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp"
cp "$(DESTDIR)$(exampleconfdir)/nzbget.conf" "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp"
sed 's:"nzbget-postprocess.sh":"nzbget-postprocess.sh" (installed into $(bindir)):' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp"
sed 's:^WebDir=:WebDir=$(webuidir)/webui:' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf"
sed 's:^ConfigTemplate=:ConfigTemplate=$(exampleconfdir)/nzbget.conf:' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf"
sed 's:configuration file (typically installed:configuration file (installed:' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp"
sed 's:/usr/local/share/nzbget/nzbget.conf):$(exampleconfdir)/nzbget.conf):' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf"
sed 's:^WebDir=:WebDir=$(webuidir)/webui:' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp"
sed 's:typically installed to /usr/local/share/nzbget/ppscripts:installed to $(ppscriptsdir)/ppscripts:' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf"
rm "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp"
# Install configuration files into /etc
@@ -105,18 +127,9 @@ install-conf:
if test ! -f "$(DESTDIR)$(sysconfdir)/nzbget.conf" ; then \
$(mkinstalldirs) "$(DESTDIR)$(sysconfdir)" ; \
cp "$(DESTDIR)$(exampleconfdir)/nzbget.conf" "$(DESTDIR)$(sysconfdir)/nzbget.conf" ; \
rm -f "$(DESTDIR)$(sysconfdir)/nzbget.conf.temp" ; \
cp "$(DESTDIR)$(sysconfdir)/nzbget.conf" "$(DESTDIR)$(sysconfdir)/nzbget.conf.temp" ; \
sed 's:^PostProcess=:PostProcess=$(bindir)/nzbget-postprocess.sh:' < "$(DESTDIR)$(sysconfdir)/nzbget.conf.temp" > "$(DESTDIR)$(sysconfdir)/nzbget.conf" ; \
rm "$(DESTDIR)$(sysconfdir)/nzbget.conf.temp" ; \
fi
if test ! -f "$(DESTDIR)$(sysconfdir)/nzbget-postprocess.conf" ; then \
$(mkinstalldirs) "$(DESTDIR)$(sysconfdir)" ; \
cp "$(DESTDIR)$(exampleconfdir)/nzbget-postprocess.conf" "$(DESTDIR)$(sysconfdir)/nzbget-postprocess.conf" ; \
fi
uninstall-conf:
rm -f "$(DESTDIR)$(sysconfdir)/nzbget-postprocess.conf"
rm -f "$(DESTDIR)$(sysconfdir)/nzbget.conf"
# Determining subversion revision:

View File

@@ -62,7 +62,7 @@ host_triplet = @host@
target_triplet = @target@
bin_PROGRAMS = nzbget$(EXEEXT)
DIST_COMMON = README $(am__configure_deps) $(dist_doc_DATA) \
$(dist_exampleconf_DATA) $(dist_webuiconf_DATA) \
$(dist_exampleconf_DATA) $(nobase_dist_ppscripts_SCRIPTS) \
$(nobase_dist_webui_DATA) $(srcdir)/Makefile.am \
$(srcdir)/Makefile.in $(srcdir)/config.h.in \
$(top_srcdir)/configure AUTHORS COPYING ChangeLog INSTALL NEWS \
@@ -77,16 +77,18 @@ am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \
mkinstalldirs = $(install_sh) -d
CONFIG_HEADER = config.h
CONFIG_CLEAN_FILES =
am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(bindir)" \
am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(ppscriptsdir)" \
"$(DESTDIR)$(sbindir)" "$(DESTDIR)$(docdir)" \
"$(DESTDIR)$(exampleconfdir)" "$(DESTDIR)$(webuiconfdir)" \
"$(DESTDIR)$(webuidir)"
"$(DESTDIR)$(exampleconfdir)" "$(DESTDIR)$(webuidir)"
binPROGRAMS_INSTALL = $(INSTALL_PROGRAM)
PROGRAMS = $(bin_PROGRAMS)
am_nzbget_OBJECTS = ArticleDownloader.$(OBJEXT) BinRpc.$(OBJEXT) \
ColoredFrontend.$(OBJEXT) Connection.$(OBJEXT) \
Decoder.$(OBJEXT) DiskState.$(OBJEXT) DownloadInfo.$(OBJEXT) \
Frontend.$(OBJEXT) Log.$(OBJEXT) LoggableFrontend.$(OBJEXT) \
DupeCoordinator.$(OBJEXT) Frontend.$(OBJEXT) \
FeedCoordinator.$(OBJEXT) FeedFile.$(OBJEXT) \
FeedFilter.$(OBJEXT) FeedInfo.$(OBJEXT) Log.$(OBJEXT) \
LoggableFrontend.$(OBJEXT) Maintenance.$(OBJEXT) \
NCursesFrontend.$(OBJEXT) NNTPConnection.$(OBJEXT) \
NZBFile.$(OBJEXT) NewsServer.$(OBJEXT) Observer.$(OBJEXT) \
Options.$(OBJEXT) ParChecker.$(OBJEXT) ParRenamer.$(OBJEXT) \
@@ -101,9 +103,15 @@ am_nzbget_OBJECTS = ArticleDownloader.$(OBJEXT) BinRpc.$(OBJEXT) \
nzbget.$(OBJEXT)
nzbget_OBJECTS = $(am_nzbget_OBJECTS)
nzbget_LDADD = $(LDADD)
binSCRIPT_INSTALL = $(INSTALL_SCRIPT)
am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
am__vpath_adj = case $$p in \
$(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
*) f=$$p;; \
esac;
am__strip_dir = `echo $$p | sed -e 's|^.*/||'`;
nobase_dist_ppscriptsSCRIPT_INSTALL = $(install_sh_SCRIPT)
sbinSCRIPT_INSTALL = $(INSTALL_SCRIPT)
SCRIPTS = $(bin_SCRIPTS) $(sbin_SCRIPTS)
SCRIPTS = $(nobase_dist_ppscripts_SCRIPTS) $(sbin_SCRIPTS)
DEFAULT_INCLUDES = -I. -I$(srcdir) -I.
depcomp = $(SHELL) $(top_srcdir)/depcomp
am__depfiles_maybe = depfiles
@@ -118,18 +126,11 @@ CCLD = $(CC)
LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
SOURCES = $(nzbget_SOURCES)
DIST_SOURCES = $(nzbget_SOURCES)
am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
am__vpath_adj = case $$p in \
$(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
*) f=$$p;; \
esac;
am__strip_dir = `echo $$p | sed -e 's|^.*/||'`;
dist_docDATA_INSTALL = $(INSTALL_DATA)
dist_exampleconfDATA_INSTALL = $(INSTALL_DATA)
dist_webuiconfDATA_INSTALL = $(INSTALL_DATA)
nobase_dist_webuiDATA_INSTALL = $(install_sh_DATA)
DATA = $(dist_doc_DATA) $(dist_exampleconf_DATA) \
$(dist_webuiconf_DATA) $(nobase_dist_webui_DATA)
$(nobase_dist_webui_DATA)
ETAGS = etags
CTAGS = ctags
DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
@@ -244,10 +245,12 @@ target_vendor = @target_vendor@
nzbget_SOURCES = \
ArticleDownloader.cpp ArticleDownloader.h BinRpc.cpp BinRpc.h \
ColoredFrontend.cpp ColoredFrontend.h Connection.cpp Connection.h Decoder.cpp Decoder.h \
DiskState.cpp DiskState.h DownloadInfo.cpp DownloadInfo.h Frontend.cpp Frontend.h \
Log.cpp Log.h LoggableFrontend.cpp LoggableFrontend.h MessageBase.h \
NCursesFrontend.cpp NCursesFrontend.h NNTPConnection.cpp NNTPConnection.h NZBFile.cpp \
NZBFile.h NewsServer.cpp NewsServer.h Observer.cpp \
DiskState.cpp DiskState.h DownloadInfo.cpp DownloadInfo.h DupeCoordinator.cpp DupeCoordinator.h \
Frontend.cpp Frontend.h FeedCoordinator.cpp FeedCoordinator.h FeedFile.cpp FeedFile.h \
FeedFilter.cpp FeedFilter.h FeedInfo.cpp FeedInfo.h Log.cpp Log.h LoggableFrontend.cpp \
LoggableFrontend.h Maintenance.cpp Maintenance.h MessageBase.h NCursesFrontend.cpp \
NCursesFrontend.h NNTPConnection.cpp \
NNTPConnection.h NZBFile.cpp NZBFile.h NewsServer.cpp NewsServer.h Observer.cpp \
Observer.h Options.cpp Options.h ParChecker.cpp ParChecker.h ParRenamer.cpp ParRenamer.h \
ParCoordinator.cpp ParCoordinator.h PrePostProcessor.cpp PrePostProcessor.h QueueCoordinator.cpp \
QueueCoordinator.h QueueEditor.cpp QueueEditor.h RemoteClient.cpp RemoteClient.h \
@@ -257,8 +260,8 @@ nzbget_SOURCES = \
UrlCoordinator.cpp UrlCoordinator.h Unpack.cpp Unpack.h nzbget.cpp nzbget.h
EXTRA_DIST = \
Makefile.cvs nzbgetd nzbget-postprocess.sh \
$(patches_FILES) $(windows_FILES)
Makefile.cvs nzbgetd \
$(patches_FILES) $(windows_FILES) $(osx_FILES)
patches_FILES = \
libpar2-0.2-bugfixes.patch libpar2-0.2-cancel.patch \
@@ -267,16 +270,31 @@ patches_FILES = \
windows_FILES = \
win32.h NTService.cpp NTService.h nzbget.sln nzbget.vcproj nzbget-shell.bat
osx_FILES = \
osx/App_Prefix.pch osx/NZBGet-Info.plist \
osx/DaemonController.h osx/DaemonController.m \
osx/MainApp.h osx/MainApp.m osx/MainApp.xib \
osx/PFMoveApplication.h osx/PFMoveApplication.m \
osx/PreferencesDialog.h osx/PreferencesDialog.m osx/PreferencesDialog.xib \
osx/RPC.h osx/RPC.m osx/WebClient.h osx/WebClient.m \
osx/WelcomeDialog.h osx/WelcomeDialog.m osx/WelcomeDialog.xib \
osx/NZBGet.xcodeproj/project.pbxproj \
osx/Resources/Images/mainicon.icns osx/Resources/Images/statusicon.png \
osx/Resources/Images/statusicon@2x.png osx/Resources/Images/statusicon-inv.png \
osx/Resources/Images/statusicon-inv@2x.png osx/Resources/licenses/license-bootstrap.txt \
osx/Resources/licenses/license-jquery-GPL.txt osx/Resources/licenses/license-jquery-MIT.txt \
osx/Resources/Credits.rtf osx/Resources/Localizable.strings osx/Resources/Welcome.rtf
doc_FILES = \
README ChangeLog COPYING
exampleconf_FILES = \
nzbget.conf nzbget-postprocess.conf
nzbget.conf
webui_FILES = \
webui/index.html webui/index.js webui/downloads.js webui/edit.js webui/fasttable.js \
webui/history.js webui/messages.js webui/status.js webui/style.css webui/upload.js \
webui/util.js webui/config.js \
webui/util.js webui/config.js webui/feed.js \
webui/lib/bootstrap.js webui/lib/bootstrap.min.js webui/lib/bootstrap.css \
webui/lib/jquery.js webui/lib/jquery.min.js \
webui/img/icons.png webui/img/icons-2x.png \
@@ -284,17 +302,19 @@ webui_FILES = \
webui/img/download-anim-green-2x.png webui/img/download-anim-orange-2x.png \
webui/img/transmit-reload-2x.gif
ppscripts_FILES = \
ppscripts/EMail.py ppscripts/Logger.py
# Install
sbin_SCRIPTS = nzbgetd
bin_SCRIPTS = nzbget-postprocess.sh
dist_doc_DATA = $(doc_FILES)
exampleconfdir = $(datadir)/nzbget
dist_exampleconf_DATA = $(exampleconf_FILES)
webuiconfdir = $(datadir)/nzbget/webui
dist_webuiconf_DATA = $(exampleconf_FILES)
webuidir = $(datadir)/nzbget
nobase_dist_webui_DATA = $(webui_FILES)
ppscriptsdir = $(datadir)/nzbget
nobase_dist_ppscripts_SCRIPTS = $(ppscripts_FILES)
# Ignore "svn_version.cpp" in distcleancheck
distcleancheck_listfiles = \
@@ -382,24 +402,30 @@ clean-binPROGRAMS:
nzbget$(EXEEXT): $(nzbget_OBJECTS) $(nzbget_DEPENDENCIES)
@rm -f nzbget$(EXEEXT)
$(CXXLINK) $(nzbget_LDFLAGS) $(nzbget_OBJECTS) $(nzbget_LDADD) $(LIBS)
install-binSCRIPTS: $(bin_SCRIPTS)
install-nobase_dist_ppscriptsSCRIPTS: $(nobase_dist_ppscripts_SCRIPTS)
@$(NORMAL_INSTALL)
test -z "$(bindir)" || $(mkdir_p) "$(DESTDIR)$(bindir)"
@list='$(bin_SCRIPTS)'; for p in $$list; do \
test -z "$(ppscriptsdir)" || $(mkdir_p) "$(DESTDIR)$(ppscriptsdir)"
@$(am__vpath_adj_setup) \
list='$(nobase_dist_ppscripts_SCRIPTS)'; for p in $$list; do \
$(am__vpath_adj) p=$$f; \
if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
if test -f $$d$$p; then \
f=`echo "$$p" | sed 's|^.*/||;$(transform)'`; \
echo " $(binSCRIPT_INSTALL) '$$d$$p' '$(DESTDIR)$(bindir)/$$f'"; \
$(binSCRIPT_INSTALL) "$$d$$p" "$(DESTDIR)$(bindir)/$$f"; \
f=`echo "$$p" | sed 's|[^/]*$$||'`"$$f"; \
echo " $(nobase_dist_ppscriptsSCRIPT_INSTALL) '$$d$$p' '$(DESTDIR)$(ppscriptsdir)/$$f'"; \
$(nobase_dist_ppscriptsSCRIPT_INSTALL) "$$d$$p" "$(DESTDIR)$(ppscriptsdir)/$$f"; \
else :; fi; \
done
uninstall-binSCRIPTS:
uninstall-nobase_dist_ppscriptsSCRIPTS:
@$(NORMAL_UNINSTALL)
@list='$(bin_SCRIPTS)'; for p in $$list; do \
@$(am__vpath_adj_setup) \
list='$(nobase_dist_ppscripts_SCRIPTS)'; for p in $$list; do \
$(am__vpath_adj) p=$$f; \
f=`echo "$$p" | sed 's|^.*/||;$(transform)'`; \
echo " rm -f '$(DESTDIR)$(bindir)/$$f'"; \
rm -f "$(DESTDIR)$(bindir)/$$f"; \
f=`echo "$$p" | sed 's|[^/]*$$||'`"$$f"; \
echo " rm -f '$(DESTDIR)$(ppscriptsdir)/$$f'"; \
rm -f "$(DESTDIR)$(ppscriptsdir)/$$f"; \
done
install-sbinSCRIPTS: $(sbin_SCRIPTS)
@$(NORMAL_INSTALL)
@@ -434,9 +460,15 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Decoder.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DiskState.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DownloadInfo.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DupeCoordinator.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/FeedCoordinator.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/FeedFile.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/FeedFilter.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/FeedInfo.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Frontend.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Log.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/LoggableFrontend.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Maintenance.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NCursesFrontend.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NNTPConnection.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NZBFile.Po@am__quote@
@@ -514,23 +546,6 @@ uninstall-dist_exampleconfDATA:
echo " rm -f '$(DESTDIR)$(exampleconfdir)/$$f'"; \
rm -f "$(DESTDIR)$(exampleconfdir)/$$f"; \
done
install-dist_webuiconfDATA: $(dist_webuiconf_DATA)
@$(NORMAL_INSTALL)
test -z "$(webuiconfdir)" || $(mkdir_p) "$(DESTDIR)$(webuiconfdir)"
@list='$(dist_webuiconf_DATA)'; for p in $$list; do \
if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
f=$(am__strip_dir) \
echo " $(dist_webuiconfDATA_INSTALL) '$$d$$p' '$(DESTDIR)$(webuiconfdir)/$$f'"; \
$(dist_webuiconfDATA_INSTALL) "$$d$$p" "$(DESTDIR)$(webuiconfdir)/$$f"; \
done
uninstall-dist_webuiconfDATA:
@$(NORMAL_UNINSTALL)
@list='$(dist_webuiconf_DATA)'; for p in $$list; do \
f=$(am__strip_dir) \
echo " rm -f '$(DESTDIR)$(webuiconfdir)/$$f'"; \
rm -f "$(DESTDIR)$(webuiconfdir)/$$f"; \
done
install-nobase_dist_webuiDATA: $(nobase_dist_webui_DATA)
@$(NORMAL_INSTALL)
test -z "$(webuidir)" || $(mkdir_p) "$(DESTDIR)$(webuidir)"
@@ -602,7 +617,7 @@ distclean-tags:
distdir: $(DISTFILES)
$(am__remove_distdir)
mkdir $(distdir)
$(mkdir_p) $(distdir)/webui $(distdir)/webui/img $(distdir)/webui/lib
$(mkdir_p) $(distdir)/osx $(distdir)/osx/NZBGet.xcodeproj $(distdir)/osx/Resources $(distdir)/osx/Resources/Images $(distdir)/osx/Resources/licenses $(distdir)/ppscripts $(distdir)/webui $(distdir)/webui/img $(distdir)/webui/lib
@srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; \
topsrcdirstrip=`echo "$(top_srcdir)" | sed 's|.|.|g'`; \
list='$(DISTFILES)'; for file in $$list; do \
@@ -733,7 +748,7 @@ check-am: all-am
check: check-am
all-am: Makefile $(PROGRAMS) $(SCRIPTS) $(DATA) config.h
installdirs:
for dir in "$(DESTDIR)$(bindir)" "$(DESTDIR)$(bindir)" "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(docdir)" "$(DESTDIR)$(exampleconfdir)" "$(DESTDIR)$(webuiconfdir)" "$(DESTDIR)$(webuidir)"; do \
for dir in "$(DESTDIR)$(bindir)" "$(DESTDIR)$(ppscriptsdir)" "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(docdir)" "$(DESTDIR)$(exampleconfdir)" "$(DESTDIR)$(webuidir)"; do \
test -z "$$dir" || $(mkdir_p) "$$dir"; \
done
install: install-am
@@ -782,12 +797,12 @@ info: info-am
info-am:
install-data-am: install-dist_docDATA install-dist_exampleconfDATA \
install-dist_webuiconfDATA install-nobase_dist_webuiDATA
install-nobase_dist_ppscriptsSCRIPTS \
install-nobase_dist_webuiDATA
@$(NORMAL_INSTALL)
$(MAKE) $(AM_MAKEFLAGS) install-data-hook
install-exec-am: install-binPROGRAMS install-binSCRIPTS \
install-sbinSCRIPTS
install-exec-am: install-binPROGRAMS install-sbinSCRIPTS
@$(NORMAL_INSTALL)
$(MAKE) $(AM_MAKEFLAGS) install-exec-hook
@@ -816,9 +831,9 @@ ps: ps-am
ps-am:
uninstall-am: uninstall-binPROGRAMS uninstall-binSCRIPTS \
uninstall-dist_docDATA uninstall-dist_exampleconfDATA \
uninstall-dist_webuiconfDATA uninstall-info-am \
uninstall-am: uninstall-binPROGRAMS uninstall-dist_docDATA \
uninstall-dist_exampleconfDATA uninstall-info-am \
uninstall-nobase_dist_ppscriptsSCRIPTS \
uninstall-nobase_dist_webuiDATA uninstall-sbinSCRIPTS
.PHONY: CTAGS GTAGS all all-am am--refresh check check-am clean \
@@ -827,19 +842,19 @@ uninstall-am: uninstall-binPROGRAMS uninstall-binSCRIPTS \
distclean distclean-compile distclean-generic distclean-hdr \
distclean-tags distcleancheck distdir distuninstallcheck dvi \
dvi-am html html-am info info-am install install-am \
install-binPROGRAMS install-binSCRIPTS install-data \
install-data-am install-data-hook install-dist_docDATA \
install-dist_exampleconfDATA install-dist_webuiconfDATA \
install-exec install-exec-am install-exec-hook install-info \
install-info-am install-man install-nobase_dist_webuiDATA \
install-sbinSCRIPTS install-strip installcheck installcheck-am \
installdirs maintainer-clean maintainer-clean-generic \
mostlyclean mostlyclean-compile mostlyclean-generic pdf pdf-am \
ps ps-am tags uninstall uninstall-am uninstall-binPROGRAMS \
uninstall-binSCRIPTS uninstall-dist_docDATA \
uninstall-dist_exampleconfDATA uninstall-dist_webuiconfDATA \
uninstall-info-am uninstall-nobase_dist_webuiDATA \
uninstall-sbinSCRIPTS
install-binPROGRAMS install-data install-data-am \
install-data-hook install-dist_docDATA \
install-dist_exampleconfDATA install-exec install-exec-am \
install-exec-hook install-info install-info-am install-man \
install-nobase_dist_ppscriptsSCRIPTS \
install-nobase_dist_webuiDATA install-sbinSCRIPTS \
install-strip installcheck installcheck-am installdirs \
maintainer-clean maintainer-clean-generic mostlyclean \
mostlyclean-compile mostlyclean-generic pdf pdf-am ps ps-am \
tags uninstall uninstall-am uninstall-binPROGRAMS \
uninstall-dist_docDATA uninstall-dist_exampleconfDATA \
uninstall-info-am uninstall-nobase_dist_ppscriptsSCRIPTS \
uninstall-nobase_dist_webuiDATA uninstall-sbinSCRIPTS
# Note about "sed":
@@ -858,12 +873,15 @@ install-exec-hook:
sed 's?/usr/local/bin?$(bindir)?' < "$(DESTDIR)$(sbindir)/nzbgetd.temp" > "$(DESTDIR)$(sbindir)/nzbgetd"
rm "$(DESTDIR)$(sbindir)/nzbgetd.temp"
# Prepare example configuration files
# Prepare example configuration file
install-data-hook:
rm -f "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp"
cp "$(DESTDIR)$(exampleconfdir)/nzbget.conf" "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp"
sed 's:"nzbget-postprocess.sh":"nzbget-postprocess.sh" (installed into $(bindir)):' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp"
sed 's:^WebDir=:WebDir=$(webuidir)/webui:' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf"
sed 's:^ConfigTemplate=:ConfigTemplate=$(exampleconfdir)/nzbget.conf:' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf"
sed 's:configuration file (typically installed:configuration file (installed:' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp"
sed 's:/usr/local/share/nzbget/nzbget.conf):$(exampleconfdir)/nzbget.conf):' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf"
sed 's:^WebDir=:WebDir=$(webuidir)/webui:' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp"
sed 's:typically installed to /usr/local/share/nzbget/ppscripts:installed to $(ppscriptsdir)/ppscripts:' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf"
rm "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp"
# Install configuration files into /etc
@@ -872,18 +890,9 @@ install-conf:
if test ! -f "$(DESTDIR)$(sysconfdir)/nzbget.conf" ; then \
$(mkinstalldirs) "$(DESTDIR)$(sysconfdir)" ; \
cp "$(DESTDIR)$(exampleconfdir)/nzbget.conf" "$(DESTDIR)$(sysconfdir)/nzbget.conf" ; \
rm -f "$(DESTDIR)$(sysconfdir)/nzbget.conf.temp" ; \
cp "$(DESTDIR)$(sysconfdir)/nzbget.conf" "$(DESTDIR)$(sysconfdir)/nzbget.conf.temp" ; \
sed 's:^PostProcess=:PostProcess=$(bindir)/nzbget-postprocess.sh:' < "$(DESTDIR)$(sysconfdir)/nzbget.conf.temp" > "$(DESTDIR)$(sysconfdir)/nzbget.conf" ; \
rm "$(DESTDIR)$(sysconfdir)/nzbget.conf.temp" ; \
fi
if test ! -f "$(DESTDIR)$(sysconfdir)/nzbget-postprocess.conf" ; then \
$(mkinstalldirs) "$(DESTDIR)$(sysconfdir)" ; \
cp "$(DESTDIR)$(exampleconfdir)/nzbget-postprocess.conf" "$(DESTDIR)$(sysconfdir)/nzbget-postprocess.conf" ; \
fi
uninstall-conf:
rm -f "$(DESTDIR)$(sysconfdir)/nzbget-postprocess.conf"
rm -f "$(DESTDIR)$(sysconfdir)/nzbget.conf"
# Determining subversion revision:

View File

@@ -2,7 +2,7 @@
* This file is part of nzbget
*
* Copyright (C) 2005 Bo Cordes Petersen <placebodk@users.sourceforge.net>
* Copyright (C) 2007-2011 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -27,7 +27,7 @@
#ifndef MESSAGEBASE_H
#define MESSAGEBASE_H
static const int32_t NZBMESSAGE_SIGNATURE = 0x6E7A6212; // = "nzb-XX" (protocol version)
static const int32_t NZBMESSAGE_SIGNATURE = 0x6E7A621B; // = "nzb-XX" (protocol version)
static const int NZBREQUESTFILENAMESIZE = 512;
static const int NZBREQUESTPASSWORDSIZE = 32;
@@ -81,12 +81,15 @@ enum eRemoteEditAction
eRemoteEditActionFilePauseExtraPars, // pause only (almost all) pars, except main par-file (does not affect other files)
eRemoteEditActionFileSetPriority, // set priority for files
eRemoteEditActionFileReorder, // (not supported)
eRemoteEditActionFileSplit, // split - create new group from selected files
eRemoteEditActionGroupMoveOffset, // move group to m_iOffset relative to the current position in download-queue
eRemoteEditActionGroupMoveTop, // move group to the top of download-queue
eRemoteEditActionGroupMoveBottom, // move group to the bottom of download-queue
eRemoteEditActionGroupPause, // pause group
eRemoteEditActionGroupResume, // resume (unpause) group
eRemoteEditActionGroupDelete, // delete group
eRemoteEditActionGroupDupeDelete, // delete group
eRemoteEditActionGroupFinalDelete, // delete group
eRemoteEditActionGroupPauseAllPars, // pause only (all) pars (does not affect other files) in group
eRemoteEditActionGroupPauseExtraPars, // pause only (almost all) pars in group, except main par-file (does not affect other files)
eRemoteEditActionGroupSetPriority, // set priority for groups
@@ -94,13 +97,25 @@ enum eRemoteEditAction
eRemoteEditActionGroupMerge, // merge group
eRemoteEditActionGroupSetParameter, // set post-process parameter for group
eRemoteEditActionGroupSetName, // set group name (rename group)
eRemoteEditActionGroupSetDupeKey, // (reserved)
eRemoteEditActionGroupSetDupeScore, // (reserved)
eRemoteEditActionGroupSetDupeMode, // (reserved)
eRemoteEditActionPostMoveOffset = 51, // move post-job to m_iOffset relative to the current position in post-queue
eRemoteEditActionPostMoveTop, // move post-job to the top of post-queue
eRemoteEditActionPostMoveBottom, // move post-job to the bottom of post-queue
eRemoteEditActionPostDelete, // delete post-job
eRemoteEditActionHistoryDelete, // delete history-item
eRemoteEditActionHistoryDelete, // hide history-item
eRemoteEditActionHistoryFinalDelete, // delete history-item
eRemoteEditActionHistoryReturn, // move history-item back to download queue
eRemoteEditActionHistoryProcess // move history-item back to download queue and start postprocessing
eRemoteEditActionHistoryProcess, // move history-item back to download queue and start postprocessing
eRemoteEditActionHistoryRedownload, // move history-item back to download queue for redownload
eRemoteEditActionHistorySetParameter, // set post-process parameter for history-item
eRemoteEditActionHistorySetDupeKey, // (reserved)
eRemoteEditActionHistorySetDupeScore, // (reserved)
eRemoteEditActionHistorySetDupeMode, // (reserved)
eRemoteEditActionHistorySetDupeBackup, // (reserved)
eRemoteEditActionHistoryMarkBad, // mark history-item as bad (and download other duplicate)
eRemoteEditActionHistoryMarkGood // mark history-item as good (and push it into dup-history)
};
// Possible values for field "m_iAction" of struct "SNZBPauseUnpauseRequest":
@@ -126,7 +141,8 @@ struct SNZBRequestBase
int32_t m_iSignature; // Signature must be NZBMESSAGE_SIGNATURE in integer-value
int32_t m_iStructSize; // Size of the entire struct
int32_t m_iType; // Message type, see enum in NZBMessageRequest-namespace
char m_szPassword[NZBREQUESTPASSWORDSIZE]; // Password needs to be in every request
char m_szUsername[NZBREQUESTPASSWORDSIZE]; // User name
char m_szPassword[NZBREQUESTPASSWORDSIZE]; // Password
};
// The basic SNZBResposneBase struct, used in all responses
@@ -422,12 +438,10 @@ struct SNZBPostQueueResponseEntry
int32_t m_iTotalTimeSec; // Number of seconds this post-job is beeing processed (after it first changed the state from QUEUED).
int32_t m_iStageTimeSec; // Number of seconds the current stage is beeing processed.
int32_t m_iNZBFilenameLen; // Length of NZBFileName-string (m_szNZBFilename), following to this record
int32_t m_iParFilename; // Length of ParFilename-string (m_szParFilename), following to this record
int32_t m_iInfoNameLen; // Length of Filename-string (m_szFilename), following to this record
int32_t m_iDestDirLen; // Length of DestDir-string (m_szDestDir), following to this record
int32_t m_iProgressLabelLen; // Length of ProgressLabel-string (m_szProgressLabel), following to this record
//char m_szNZBFilename[m_iNZBFilenameLen]; // variable sized, may contain full path (local path on client) or only filename
//char m_szParFilename[m_iParFilename]; // variable sized
//char m_szInfoName[m_iInfoNameLen]; // variable sized
//char m_szDestDir[m_iDestDirLen]; // variable sized
//char m_szProgressLabel[m_iProgressLabelLen]; // variable sized

View File

@@ -202,14 +202,9 @@ NCursesFrontend::NCursesFrontend()
NCursesFrontend::~NCursesFrontend()
{
#ifdef WIN32
if (m_pScreenBuffer)
{
free(m_pScreenBuffer);
}
if (m_pOldScreenBuffer)
{
free(m_pOldScreenBuffer);
}
free(m_pScreenBuffer);
free(m_pOldScreenBuffer);
m_ColorAttr.clear();
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
@@ -550,6 +545,8 @@ int NCursesFrontend::PrintMessage(Message* Msg, int iRow, int iMaxLines)
szText = (char*)malloc(iLen);
time_t rawtime = Msg->GetTime();
rawtime += g_pOptions->GetTimeCorrection();
char szTime[50];
#ifdef HAVE_CTIME_R_3
ctime_r(&rawtime, szTime, 50);
@@ -723,11 +720,8 @@ void NCursesFrontend::PrintKeyInputBar()
void NCursesFrontend::SetHint(const char* szHint)
{
if (m_szHint)
{
free(m_szHint);
m_szHint = NULL;
}
free(m_szHint);
m_szHint = NULL;
if (szHint)
{
m_szHint = strdup(szHint);
@@ -792,8 +786,8 @@ void NCursesFrontend::PrintFileQueue()
char szBuffer[MAX_SCREEN_WIDTH];
snprintf(szBuffer, sizeof(szBuffer), " %sFiles for downloading - %i / %i files in queue - %s / %s",
m_bUseColor ? "" : "*** ", pDownloadQueue->GetFileQueue()->size(),
pDownloadQueue->GetFileQueue()->size() - iPausedFiles, szRemaining, szUnpaused);
m_bUseColor ? "" : "*** ", (int)pDownloadQueue->GetFileQueue()->size(),
(int)pDownloadQueue->GetFileQueue()->size() - iPausedFiles, szRemaining, szUnpaused);
szBuffer[MAX_SCREEN_WIDTH - 1] = '\0';
PrintTopHeader(szBuffer, m_iQueueWinTop, true);
}
@@ -977,7 +971,7 @@ void NCursesFrontend::PrintGroupQueue()
char szBuffer[MAX_SCREEN_WIDTH];
snprintf(szBuffer, sizeof(szBuffer), " %sNZBs for downloading - %i NZBs in queue - %s / %s",
m_bUseColor ? "" : "*** ", pGroupQueue->size(), szRemaining, szUnpaused);
m_bUseColor ? "" : "*** ", (int)pGroupQueue->size(), szRemaining, szUnpaused);
szBuffer[MAX_SCREEN_WIDTH - 1] = '\0';
PrintTopHeader(szBuffer, m_iQueueWinTop, false);
}
@@ -1128,11 +1122,7 @@ void NCursesFrontend::PrepareGroupQueue()
void NCursesFrontend::ClearGroupQueue()
{
for (GroupQueue::iterator it = m_groupQueue.begin(); it != m_groupQueue.end(); it++)
{
delete *it;
}
m_groupQueue.clear();
m_groupQueue.Clear();
}
bool NCursesFrontend::EditQueue(QueueEditor::EEditAction eAction, int iOffset)

View File

@@ -2,7 +2,7 @@
* This file is part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2008 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -34,7 +34,8 @@
#include <stdlib.h>
#include <string.h>
#include <cstdio>
#include <stdio.h>
#include <ctype.h>
#include "nzbget.h"
#include "Log.h"
@@ -55,11 +56,7 @@ NNTPConnection::NNTPConnection(NewsServer* pNewsServer) : Connection(pNewsServer
NNTPConnection::~NNTPConnection()
{
if (m_szActiveGroup)
{
free(m_szActiveGroup);
m_szActiveGroup = NULL;
}
free(m_szActiveGroup);
free(m_szLineBuf);
}
@@ -85,17 +82,14 @@ const char* NNTPConnection::Request(const char* req)
{
debug("%s requested authorization", GetHost());
//authentication required!
if (!Authenticate())
{
m_bAuthError = true;
return NULL;
}
//try again
WriteLine(req);
answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL);
return answer;
}
return answer;
@@ -103,13 +97,16 @@ const char* NNTPConnection::Request(const char* req)
bool NNTPConnection::Authenticate()
{
if (!(m_pNewsServer)->GetUser() ||
!(m_pNewsServer)->GetPassword())
if (strlen(m_pNewsServer->GetUser()) == 0 || strlen(m_pNewsServer->GetPassword()) == 0)
{
return true;
error("%c%s (%s) requested authorization but username/password are not set in settings",
toupper(m_pNewsServer->GetName()[0]), m_pNewsServer->GetName() + 1, m_pNewsServer->GetHost());
m_bAuthError = true;
return false;
}
return AuthInfoUser();
m_bAuthError = !AuthInfoUser(0);
return !m_bAuthError;
}
bool NNTPConnection::AuthInfoUser(int iRecur)
@@ -128,7 +125,7 @@ bool NNTPConnection::AuthInfoUser(int iRecur)
char* answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL);
if (!answer)
{
ReportError("Authorization for %s failed: Connection closed by remote host", GetHost(), false, 0);
ReportErrorAnswer("Authorization for server%i (%s) failed: Connection closed by remote host", NULL);
return false;
}
@@ -150,7 +147,7 @@ bool NNTPConnection::AuthInfoUser(int iRecur)
if (GetStatus() != csCancelled)
{
ReportErrorAnswer("Authorization for %s failed (Answer: %s)", answer);
ReportErrorAnswer("Authorization for server%i (%s) failed (Answer: %s)", answer);
}
return false;
}
@@ -171,7 +168,7 @@ bool NNTPConnection::AuthInfoPass(int iRecur)
char* answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL);
if (!answer)
{
ReportError("Authorization for %s failed: Connection closed by remote host", GetHost(), false, 0);
ReportErrorAnswer("Authorization for server%i (%s) failed: Connection closed by remote host", NULL);
return false;
}
else if (!strncmp(answer, "2", 1))
@@ -188,7 +185,7 @@ bool NNTPConnection::AuthInfoPass(int iRecur)
if (GetStatus() != csCancelled)
{
ReportErrorAnswer("Authorization for %s failed (Answer: %s)", answer);
ReportErrorAnswer("Authorization for server%i (%s) failed (Answer: %s)", answer);
}
return false;
}
@@ -207,19 +204,11 @@ const char* NNTPConnection::JoinGroup(const char* grp)
tmp[1024-1] = '\0';
const char* answer = Request(tmp);
if (m_bAuthError)
{
return answer;
}
if (answer && !strncmp(answer, "2", 1))
{
debug("Changed group to %s on %s", grp, GetHost());
if (m_szActiveGroup)
{
free(m_szActiveGroup);
}
free(m_szActiveGroup);
m_szActiveGroup = strdup(grp);
}
else
@@ -230,26 +219,39 @@ const char* NNTPConnection::JoinGroup(const char* grp)
return answer;
}
bool NNTPConnection::DoConnect()
bool NNTPConnection::Connect()
{
debug("Opening connection to %s", GetHost());
bool res = Connection::DoConnect();
if (!res)
if (m_eStatus == csConnected)
{
return res;
return true;
}
char* answer = DoReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL);
if (!Connection::Connect())
{
return false;
}
char* answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL);
if (!answer)
{
ReportError("Connection to %s failed: Connection closed by remote host", GetHost(), false, 0);
ReportErrorAnswer("Connection to server%i (%s) failed: Connection closed by remote host", NULL);
Disconnect();
return false;
}
if (strncmp(answer, "2", 1))
{
ReportErrorAnswer("Connection to %s failed (Answer: %s)", answer);
ReportErrorAnswer("Connection to server%i (%s) failed (Answer: %s)", answer);
Disconnect();
return false;
}
if ((strlen(m_pNewsServer->GetUser()) > 0 && strlen(m_pNewsServer->GetPassword()) > 0) &&
!Authenticate())
{
return false;
}
@@ -258,24 +260,21 @@ bool NNTPConnection::DoConnect()
return true;
}
bool NNTPConnection::DoDisconnect()
bool NNTPConnection::Disconnect()
{
if (m_eStatus == csConnected)
{
Request("quit\r\n");
if (m_szActiveGroup)
{
free(m_szActiveGroup);
m_szActiveGroup = NULL;
}
free(m_szActiveGroup);
m_szActiveGroup = NULL;
}
return Connection::DoDisconnect();
return Connection::Disconnect();
}
void NNTPConnection::ReportErrorAnswer(const char* szMsgPrefix, const char* szAnswer)
{
char szErrStr[1024];
snprintf(szErrStr, 1024, szMsgPrefix, GetHost(), szAnswer);
snprintf(szErrStr, 1024, szMsgPrefix, m_pNewsServer->GetID(), m_pNewsServer->GetHost(), szAnswer);
szErrStr[1024-1] = '\0';
ReportError(szErrStr, NULL, false, 0);

View File

@@ -33,26 +33,26 @@
class NNTPConnection : public Connection
{
private:
NewsServer* m_pNewsServer;
char* m_szActiveGroup;
char* m_szLineBuf;
bool m_bAuthError;
NewsServer* m_pNewsServer;
char* m_szActiveGroup;
char* m_szLineBuf;
bool m_bAuthError;
virtual bool DoConnect();
virtual bool DoDisconnect();
void Clear();
void ReportErrorAnswer(const char* szMsgPrefix, const char* szAnswer);
void Clear();
void ReportErrorAnswer(const char* szMsgPrefix, const char* szAnswer);
bool Authenticate();
bool AuthInfoUser(int iRecur);
bool AuthInfoPass(int iRecur);
public:
NNTPConnection(NewsServer* pNewsServer);
virtual ~NNTPConnection();
NewsServer* GetNewsServer() { return m_pNewsServer; }
const char* Request(const char* req);
bool Authenticate();
bool AuthInfoUser(int iRecur = 0);
bool AuthInfoPass(int iRecur = 0);
const char* JoinGroup(const char* grp);
bool GetAuthError() { return m_bAuthError; }
NNTPConnection(NewsServer* pNewsServer);
virtual ~NNTPConnection();
virtual bool Connect();
virtual bool Disconnect();
NewsServer* GetNewsServer() { return m_pNewsServer; }
const char* Request(const char* req);
const char* JoinGroup(const char* grp);
bool GetAuthError() { return m_bAuthError; }
};
#endif

View File

@@ -2,7 +2,7 @@
* This file is part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2010 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -34,6 +34,7 @@
#include <string.h>
#include <list>
#include <ctype.h>
#ifdef WIN32
#include <comutil.h>
#import <msxml.tlb> named_guids
@@ -61,13 +62,15 @@ NZBFile::NZBFile(const char* szFileName, const char* szCategory)
debug("Creating NZBFile");
m_szFileName = strdup(szFileName);
m_szPassword = NULL;
m_pNZBInfo = new NZBInfo();
m_pNZBInfo->AddReference();
m_pNZBInfo->Retain();
m_pNZBInfo->SetFilename(szFileName);
m_pNZBInfo->SetCategory(szCategory);
m_pNZBInfo->BuildDestDirName();
#ifndef WIN32
m_bPassword = false;
m_pFileInfo = NULL;
m_pArticle = NULL;
m_szTagContent = NULL;
@@ -82,10 +85,8 @@ NZBFile::~NZBFile()
debug("Destroying NZBFile");
// Cleanup
if (m_szFileName)
{
free(m_szFileName);
}
free(m_szFileName);
free(m_szPassword);
for (FileInfos::iterator it = m_FileInfos.begin(); it != m_FileInfos.end(); it++)
{
@@ -99,15 +100,8 @@ NZBFile::~NZBFile()
}
#ifndef WIN32
if (m_pFileInfo)
{
delete m_pFileInfo;
}
if (m_szTagContent)
{
free(m_szTagContent);
}
delete m_pFileInfo;
free(m_szTagContent);
#endif
}
@@ -121,55 +115,76 @@ void NZBFile::DetachFileInfos()
m_FileInfos.clear();
}
NZBFile* NZBFile::CreateFromBuffer(const char* szFileName, const char* szCategory, const char* szBuffer, int iSize)
{
return Create(szFileName, szCategory, szBuffer, iSize, true);
}
NZBFile* NZBFile::CreateFromFile(const char* szFileName, const char* szCategory)
{
return Create(szFileName, szCategory, NULL, 0, false);
}
void NZBFile::AddArticle(FileInfo* pFileInfo, ArticleInfo* pArticleInfo)
{
// make Article-List big enough
while ((int)pFileInfo->GetArticles()->size() < pArticleInfo->GetPartNumber())
pFileInfo->GetArticles()->push_back(NULL);
(*pFileInfo->GetArticles())[pArticleInfo->GetPartNumber() - 1] = pArticleInfo;
int index = pArticleInfo->GetPartNumber() - 1;
if ((*pFileInfo->GetArticles())[index])
{
delete (*pFileInfo->GetArticles())[index];
}
(*pFileInfo->GetArticles())[index] = pArticleInfo;
}
void NZBFile::AddFileInfo(FileInfo* pFileInfo)
{
// deleting empty articles
// calculate file size and delete empty articles
long long lSize = 0;
long long lMissedSize = 0;
long long lOneSize = 0;
int iUncountedArticles = 0;
int iMissedArticles = 0;
FileInfo::Articles* pArticles = pFileInfo->GetArticles();
int iTotalArticles = (int)pArticles->size();
int i = 0;
for (FileInfo::Articles::iterator it = pArticles->begin(); it != pArticles->end();)
for (FileInfo::Articles::iterator it = pArticles->begin(); it != pArticles->end(); )
{
if (*it == NULL)
ArticleInfo* pArticle = *it;
if (!pArticle)
{
pArticles->erase(it);
it = pArticles->begin() + i;
iMissedArticles++;
if (lOneSize > 0)
{
lMissedSize += lOneSize;
}
else
{
iUncountedArticles++;
}
}
else
{
lSize += pArticle->GetSize();
if (lOneSize == 0)
{
lOneSize = pArticle->GetSize();
}
it++;
i++;
}
}
if (!pArticles->empty())
if (pArticles->empty())
{
m_FileInfos.push_back(pFileInfo);
pFileInfo->SetNZBInfo(m_pNZBInfo);
m_pNZBInfo->SetSize(m_pNZBInfo->GetSize() + pFileInfo->GetSize());
m_pNZBInfo->SetFileCount(m_pNZBInfo->GetFileCount() + 1);
}
else
{
delete pFileInfo;
delete pFileInfo;
return;
}
lMissedSize += iUncountedArticles * lOneSize;
lSize += lMissedSize;
m_FileInfos.push_back(pFileInfo);
pFileInfo->SetNZBInfo(m_pNZBInfo);
pFileInfo->SetSize(lSize);
pFileInfo->SetRemainingSize(lSize - lMissedSize);
pFileInfo->SetMissedSize(lMissedSize);
pFileInfo->SetTotalArticles(iTotalArticles);
pFileInfo->SetMissedArticles(iMissedArticles);
}
void NZBFile::ParseSubject(FileInfo* pFileInfo, bool TryQuotes)
@@ -318,7 +333,7 @@ bool NZBFile::HasDuplicateFilenames()
/**
* Generate filenames from subjects and check if the parsing of subject was correct
*/
void NZBFile::ProcessFilenames()
void NZBFile::BuildFilenames()
{
for (FileInfos::iterator it = m_FileInfos.begin(); it != m_FileInfos.end(); it++)
{
@@ -337,27 +352,189 @@ void NZBFile::ProcessFilenames()
if (HasDuplicateFilenames())
{
m_pNZBInfo->SetManyDupeFiles(true);
for (FileInfos::iterator it = m_FileInfos.begin(); it != m_FileInfos.end(); it++)
{
FileInfo* pFileInfo = *it;
pFileInfo->SetFilename(pFileInfo->GetSubject());
}
}
}
bool CompareFileInfo(FileInfo* pFirst, FileInfo* pSecond)
{
return strcmp(pFirst->GetFilename(), pSecond->GetFilename()) > 0;
}
void NZBFile::CalcHashes()
{
FileInfoList fileList;
for (FileInfos::iterator it = m_FileInfos.begin(); it != m_FileInfos.end(); it++)
{
FileInfo* pFileInfo = *it;
pFileInfo->MakeValidFilename();
if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode())
{
fileList.push_back(*it);
}
fileList.sort(CompareFileInfo);
// split ExtCleanupDisk into tokens and create a list
ExtList extList;
char* szExtCleanupDisk = strdup(g_pOptions->GetExtCleanupDisk());
char* saveptr;
char* szExt = strtok_r(szExtCleanupDisk, ",; ", &saveptr);
while (szExt)
{
extList.push_back(szExt);
szExt = strtok_r(NULL, ",; ", &saveptr);
}
unsigned int iFullContentHash = 0;
unsigned int iFilteredContentHash = 0;
int iUseForFilteredCount = 0;
for (FileInfoList::iterator it = fileList.begin(); it != fileList.end(); it++)
{
FileInfo* pFileInfo = *it;
// check file extension
int iFilenameLen = strlen(pFileInfo->GetFilename());
bool bSkip = false;
for (ExtList::iterator it = extList.begin(); it != extList.end(); it++)
{
const char* szExt = *it;
int iExtLen = strlen(szExt);
if (iFilenameLen >= iExtLen && !strcasecmp(szExt, pFileInfo->GetFilename() + iFilenameLen - iExtLen))
{
bSkip = true;
break;
}
}
bSkip = bSkip && !pFileInfo->GetParFile();
for (FileInfo::Articles::iterator it = pFileInfo->GetArticles()->begin(); it != pFileInfo->GetArticles()->end(); it++)
{
ArticleInfo* pArticle = *it;
int iLen = strlen(pArticle->GetMessageID());
iFullContentHash = Util::HashBJ96(pArticle->GetMessageID(), iLen, iFullContentHash);
if (!bSkip)
{
iFilteredContentHash = Util::HashBJ96(pArticle->GetMessageID(), iLen, iFilteredContentHash);
iUseForFilteredCount++;
}
}
}
free(szExtCleanupDisk);
// if filtered hash is based on less than a half of files - do not use filtered hash at all
if (iUseForFilteredCount < (int)fileList.size() / 2)
{
iFilteredContentHash = 0;
}
m_pNZBInfo->SetFullContentHash(iFullContentHash);
m_pNZBInfo->SetFilteredContentHash(iFilteredContentHash);
}
void NZBFile::ProcessFiles()
{
BuildFilenames();
for (FileInfos::iterator it = m_FileInfos.begin(); it != m_FileInfos.end(); it++)
{
FileInfo* pFileInfo = *it;
pFileInfo->MakeValidFilename();
char szLoFileName[1024];
strncpy(szLoFileName, pFileInfo->GetFilename(), 1024);
szLoFileName[1024-1] = '\0';
for (char* p = szLoFileName; *p; p++) *p = tolower(*p); // convert string to lowercase
bool bParFile = strstr(szLoFileName, ".par2");
m_pNZBInfo->SetFileCount(m_pNZBInfo->GetFileCount() + 1);
m_pNZBInfo->SetTotalArticles(m_pNZBInfo->GetTotalArticles() + pFileInfo->GetTotalArticles());
m_pNZBInfo->SetSize(m_pNZBInfo->GetSize() + pFileInfo->GetSize());
m_pNZBInfo->SetFailedSize(m_pNZBInfo->GetFailedSize() + pFileInfo->GetMissedSize());
m_pNZBInfo->SetCurrentFailedSize(m_pNZBInfo->GetFailedSize());
pFileInfo->SetParFile(bParFile);
if (bParFile)
{
m_pNZBInfo->SetParSize(m_pNZBInfo->GetParSize() + pFileInfo->GetSize());
m_pNZBInfo->SetParFailedSize(m_pNZBInfo->GetParFailedSize() + pFileInfo->GetMissedSize());
m_pNZBInfo->SetParCurrentFailedSize(m_pNZBInfo->GetParFailedSize());
}
}
CalcHashes();
if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode())
{
for (FileInfos::iterator it = m_FileInfos.begin(); it != m_FileInfos.end(); it++)
{
FileInfo* pFileInfo = *it;
g_pDiskState->SaveFile(pFileInfo);
pFileInfo->ClearArticles();
}
}
if (m_szPassword)
{
ReadPassword();
}
}
/**
* Password read using XML-parser may have special characters (such as TAB) stripped.
* This function rereads password directly from file to keep all characters intact.
*/
void NZBFile::ReadPassword()
{
FILE* pFile = fopen(m_szFileName, "rb");
if (!pFile)
{
return;
}
// obtain file size.
fseek(pFile , 0 , SEEK_END);
int iSize = ftell(pFile);
rewind(pFile);
// reading first 4KB of the file
// allocate memory to contain the whole file.
char* buf = (char*)malloc(4096);
iSize = iSize < 4096 ? iSize : 4096;
// copy the file into the buffer.
fread(buf, 1, iSize, pFile);
fclose(pFile);
buf[iSize-1] = '\0';
char* szMetaPassword = strstr(buf, "<meta type=\"password\">");
if (szMetaPassword)
{
szMetaPassword += 22; // length of '<meta type="password">'
char* szEnd = strstr(szMetaPassword, "</meta>");
if (szEnd)
{
*szEnd = '\0';
WebUtil::XmlDecode(szMetaPassword);
free(m_szPassword);
m_szPassword = strdup(szMetaPassword);
}
}
free(buf);
}
#ifdef WIN32
NZBFile* NZBFile::Create(const char* szFileName, const char* szCategory, const char* szBuffer, int iSize, bool bFromBuffer)
NZBFile* NZBFile::Create(const char* szFileName, const char* szCategory)
{
CoInitialize(NULL);
@@ -374,21 +551,15 @@ NZBFile* NZBFile::Create(const char* szFileName, const char* szCategory, const c
doc->put_resolveExternals(VARIANT_FALSE);
doc->put_validateOnParse(VARIANT_FALSE);
doc->put_async(VARIANT_FALSE);
VARIANT_BOOL success;
if (bFromBuffer)
{
success = doc->loadXML(szBuffer);
}
else
{
// filename needs to be properly encoded
char* szURL = (char*)malloc(strlen(szFileName)*3 + 1);
EncodeURL(szFileName, szURL);
debug("url=\"%s\"", szURL);
_variant_t v(szURL);
free(szURL);
success = doc->load(v);
}
// filename needs to be properly encoded
char* szURL = (char*)malloc(strlen(szFileName)*3 + 1);
EncodeURL(szFileName, szURL);
debug("url=\"%s\"", szURL);
_variant_t v(szURL);
free(szURL);
VARIANT_BOOL success = doc->load(v);
if (success == VARIANT_FALSE)
{
_bstr_t r(doc->GetparseError()->reason);
@@ -400,7 +571,7 @@ NZBFile* NZBFile::Create(const char* szFileName, const char* szCategory, const c
NZBFile* pFile = new NZBFile(szFileName, szCategory);
if (pFile->ParseNZB(doc))
{
pFile->ProcessFilenames();
pFile->ProcessFiles();
}
else
{
@@ -424,7 +595,7 @@ void NZBFile::EncodeURL(const char* szFilename, char* szURL)
else
{
*szURL++ = '%';
int a = ch >> 4;
int a = (unsigned char)ch >> 4;
*szURL++ = a > 9 ? a - 10 + 'a' : a + '0';
a = ch & 0xF;
*szURL++ = a > 9 ? a - 10 + 'a' : a + '0';
@@ -438,10 +609,17 @@ bool NZBFile::ParseNZB(IUnknown* nzb)
MSXML::IXMLDOMDocumentPtr doc = nzb;
MSXML::IXMLDOMNodePtr root = doc->documentElement;
MSXML::IXMLDOMNodePtr node = root->selectSingleNode("/nzb/head/meta[@type='password']");
if (node)
{
_bstr_t password(node->Gettext());
m_szPassword = strdup(password);
}
MSXML::IXMLDOMNodeListPtr fileList = root->selectNodes("/nzb/file");
for (int i = 0; i < fileList->Getlength(); i++)
{
MSXML::IXMLDOMNodePtr node = fileList->Getitem(i);
node = fileList->Getitem(i);
MSXML::IXMLDOMNodePtr attribute = node->Getattributes()->getNamedItem("subject");
if (!attribute) return false;
_bstr_t subject(attribute->Gettext());
@@ -482,16 +660,14 @@ bool NZBFile::ParseNZB(IUnknown* nzb)
int partNumber = atoi(number);
int lsize = atoi(bytes);
ArticleInfo* pArticle = new ArticleInfo();
pArticle->SetPartNumber(partNumber);
pArticle->SetMessageID(szId);
pArticle->SetSize(lsize);
AddArticle(pFileInfo, pArticle);
if (lsize > 0)
{
pFileInfo->SetSize(pFileInfo->GetSize() + lsize);
}
if (partNumber > 0)
{
ArticleInfo* pArticle = new ArticleInfo();
pArticle->SetPartNumber(partNumber);
pArticle->SetMessageID(szId);
pArticle->SetSize(lsize);
AddArticle(pFileInfo, pArticle);
}
}
AddFileInfo(pFileInfo);
@@ -501,7 +677,7 @@ bool NZBFile::ParseNZB(IUnknown* nzb)
#else
NZBFile* NZBFile::Create(const char* szFileName, const char* szCategory, const char* szBuffer, int iSize, bool bFromBuffer)
NZBFile* NZBFile::Create(const char* szFileName, const char* szCategory)
{
NZBFile* pFile = new NZBFile(szFileName, szCategory);
@@ -512,21 +688,13 @@ NZBFile* NZBFile::Create(const char* szFileName, const char* szCategory, const c
SAX_handler.error = reinterpret_cast<errorSAXFunc>(SAX_error);
SAX_handler.getEntity = reinterpret_cast<getEntitySAXFunc>(SAX_getEntity);
int ret = 0;
pFile->m_bIgnoreNextError = false;
if (bFromBuffer)
{
ret = xmlSAXUserParseMemory(&SAX_handler, pFile, szBuffer, iSize);
}
else
{
ret = xmlSAXUserParseFile(&SAX_handler, pFile, szFileName);
}
int ret = xmlSAXUserParseFile(&SAX_handler, pFile, szFileName);
if (ret == 0)
{
pFile->ProcessFilenames();
pFile->ProcessFiles();
}
else
{
@@ -590,10 +758,6 @@ void NZBFile::Parse_StartElement(const char *name, const char **atts)
partNumber = atol(attrvalue);
}
}
if (lsize > 0)
{
m_pFileInfo->SetSize(m_pFileInfo->GetSize() + lsize);
}
if (partNumber > 0)
{
@@ -604,6 +768,10 @@ void NZBFile::Parse_StartElement(const char *name, const char **atts)
AddArticle(m_pFileInfo, m_pArticle);
}
}
else if (!strcmp("meta", name))
{
m_bPassword = atts[0] && atts[1] && !strcmp("type", atts[0]) && !strcmp("password", atts[1]);
}
}
void NZBFile::Parse_EndElement(const char *name)
@@ -641,6 +809,10 @@ void NZBFile::Parse_EndElement(const char *name)
m_pArticle->SetMessageID(ID);
m_pArticle = NULL;
}
else if (!strcmp("meta", name) && m_bPassword)
{
m_szPassword = strdup(m_szTagContent);
}
}
void NZBFile::Parse_Content(const char *buf, int len)

View File

@@ -2,7 +2,7 @@
* This file is part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2010 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -28,6 +28,7 @@
#define NZBFILE_H
#include <vector>
#include <list>
#include "DownloadInfo.h"
@@ -35,18 +36,24 @@ class NZBFile
{
public:
typedef std::vector<FileInfo*> FileInfos;
typedef std::list<FileInfo*> FileInfoList;
typedef std::list<char*> ExtList;
private:
FileInfos m_FileInfos;
NZBInfo* m_pNZBInfo;
char* m_szFileName;
char* m_szPassword;
NZBFile(const char* szFileName, const char* szCategory);
void AddArticle(FileInfo* pFileInfo, ArticleInfo* pArticleInfo);
void AddFileInfo(FileInfo* pFileInfo);
void ParseSubject(FileInfo* pFileInfo, bool TryQuotes);
void ProcessFilenames();
void BuildFilenames();
void ProcessFiles();
void CalcHashes();
bool HasDuplicateFilenames();
void ReadPassword();
#ifdef WIN32
bool ParseNZB(IUnknown* nzb);
static void EncodeURL(const char* szFilename, char* szURL);
@@ -56,6 +63,7 @@ private:
char* m_szTagContent;
int m_iTagContentLen;
bool m_bIgnoreNextError;
bool m_bPassword;
static void SAX_StartElement(NZBFile* pFile, const char *name, const char **atts);
static void SAX_EndElement(NZBFile* pFile, const char *name);
@@ -66,15 +74,14 @@ private:
void Parse_EndElement(const char *name);
void Parse_Content(const char *buf, int len);
#endif
static NZBFile* Create(const char* szFileName, const char* szCategory, const char* szBuffer, int iSize, bool bFromBuffer);
public:
virtual ~NZBFile();
static NZBFile* CreateFromBuffer(const char* szFileName, const char* szCategory, const char* szBuffer, int iSize);
static NZBFile* CreateFromFile(const char* szFileName, const char* szCategory);
static NZBFile* Create(const char* szFileName, const char* szCategory);
const char* GetFileName() const { return m_szFileName; }
FileInfos* GetFileInfos() { return &m_FileInfos; }
NZBInfo* GetNZBInfo() { return m_pNZBInfo; }
const char* GetPassword() { return m_szPassword; }
void DetachFileInfos();
void LogDebugInfo();

View File

@@ -2,7 +2,7 @@
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2008 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -34,45 +34,47 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "nzbget.h"
#include "NewsServer.h"
NewsServer::NewsServer(int iID, const char* szHost, int iPort, const char* szUser, const char* szPass, bool bJoinGroup,
bool bTLS, const char* szCipher, int iMaxConnections, int iLevel, int iGroup)
NewsServer::NewsServer(int iID, bool bActive, const char* szName, const char* szHost, int iPort,
const char* szUser, const char* szPass, bool bJoinGroup, bool bTLS,
const char* szCipher, int iMaxConnections, int iLevel, int iGroup)
{
m_iID = iID;
m_szHost = NULL;
m_iStateID = 0;
m_bActive = bActive;
m_iPort = iPort;
m_szUser = NULL;
m_szPassword = NULL;
m_iLevel = iLevel;
m_iNormLevel = iLevel;
m_iGroup = iGroup;
m_iMaxConnections = iMaxConnections;
m_bJoinGroup = bJoinGroup;
m_bTLS = bTLS;
m_szHost = szHost ? strdup(szHost) : NULL;
m_szUser = szUser ? strdup(szUser) : NULL;
m_szPassword = szPass ? strdup(szPass) : NULL;
m_szCipher = szCipher ? strdup(szCipher) : NULL;
m_szHost = strdup(szHost ? szHost : "");
m_szUser = strdup(szUser ? szUser : "");
m_szPassword = strdup(szPass ? szPass : "");
m_szCipher = strdup(szCipher ? szCipher : "");
if (szName && strlen(szName) > 0)
{
m_szName = strdup(szName);
}
else
{
m_szName = (char*)malloc(20);
snprintf(m_szName, 20, "server%i", iID);
m_szName[20-1] = '\0';
}
}
NewsServer::~NewsServer()
{
if (m_szHost)
{
free(m_szHost);
}
if (m_szUser)
{
free(m_szUser);
}
if (m_szPassword)
{
free(m_szPassword);
}
if (m_szCipher)
{
free(m_szCipher);
}
free(m_szName);
free(m_szHost);
free(m_szUser);
free(m_szPassword);
free(m_szCipher);
}

View File

@@ -2,7 +2,7 @@
* This file if part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2008 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -27,10 +27,15 @@
#ifndef NEWSSERVER_H
#define NEWSSERVER_H
#include <vector>
class NewsServer
{
private:
int m_iID;
int m_iStateID;
bool m_bActive;
char* m_szName;
int m_iGroup;
char* m_szHost;
int m_iPort;
@@ -38,15 +43,22 @@ private:
char* m_szPassword;
int m_iMaxConnections;
int m_iLevel;
int m_iNormLevel;
bool m_bJoinGroup;
bool m_bTLS;
char* m_szCipher;
public:
NewsServer(int iID, const char* szHost, int iPort, const char* szUser, const char* szPass, bool bJoinGroup,
NewsServer(int iID, bool bActive, const char* szName, const char* szHost, int iPort,
const char* szUser, const char* szPass, bool bJoinGroup,
bool bTLS, const char* szCipher, int iMaxConnections, int iLevel, int iGroup);
~NewsServer();
int GetID() { return m_iID; }
int GetStateID() { return m_iStateID; }
void SetStateID(int iStateID) { m_iStateID = iStateID; }
bool GetActive() { return m_bActive; }
void SetActive(bool bActive) { m_bActive = bActive; }
const char* GetName() { return m_szName; }
int GetGroup() { return m_iGroup; }
const char* GetHost() { return m_szHost; }
int GetPort() { return m_iPort; }
@@ -54,10 +66,13 @@ public:
const char* GetPassword() { return m_szPassword; }
int GetMaxConnections() { return m_iMaxConnections; }
int GetLevel() { return m_iLevel; }
void SetLevel(int iLevel) { m_iLevel = iLevel; }
int GetNormLevel() { return m_iNormLevel; }
void SetNormLevel(int iLevel) { m_iNormLevel = iLevel; }
int GetJoinGroup() { return m_bJoinGroup; }
bool GetTLS() { return m_bTLS; }
const char* GetCipher() { return m_szCipher; }
};
typedef std::vector<NewsServer*> Servers;
#endif

View File

File diff suppressed because it is too large Load Diff

156
Options.h
View File

@@ -28,8 +28,11 @@
#define OPTIONS_H
#include <vector>
#include <list>
#include <time.h>
#include "Thread.h"
#include "Util.h"
class Options
{
@@ -77,11 +80,11 @@ public:
omColored,
omNCurses
};
enum ELoadPars
enum EParCheck
{
lpNone,
lpOne,
lpAll
pcAuto,
pcForce,
pcManual
};
enum EParScan
{
@@ -89,6 +92,12 @@ public:
psFull,
psAuto
};
enum EHealthCheck
{
hcPause,
hcDelete,
hcNone
};
enum EScriptLogKind
{
slNone,
@@ -98,7 +107,6 @@ public:
slError,
slDebug
};
enum EMatchMode
{
mmID = 1,
@@ -106,12 +114,6 @@ public:
mmRegEx
};
enum EDomain
{
dmServer = 1,
dmPostProcess
};
class OptEntry
{
private:
@@ -145,6 +147,31 @@ public:
OptEntry* FindOption(const char* szName);
};
class ConfigTemplate
{
private:
char* m_szName;
char* m_szDisplayName;
char* m_szTemplate;
friend class Options;
public:
ConfigTemplate(const char* szName, const char* szDisplayName, const char* szTemplate);
~ConfigTemplate();
const char* GetName() { return m_szName; }
const char* GetDisplayName() { return m_szDisplayName; }
const char* GetTemplate() { return m_szTemplate; }
};
typedef std::vector<ConfigTemplate*> ConfigTemplatesBase;
class ConfigTemplates: public ConfigTemplatesBase
{
public:
~ConfigTemplates();
};
typedef std::vector<char*> NameList;
class Category
@@ -152,12 +179,18 @@ public:
private:
char* m_szName;
char* m_szDestDir;
bool m_bUnpack;
char* m_szDefScript;
NameList m_Aliases;
public:
Category(const char* szName, const char* szDestDir);
Category(const char* szName, const char* szDestDir, bool bUnpack, const char* szDefScript);
~Category();
const char* GetName() { return m_szName; }
const char* GetDestDir() { return m_szDestDir; }
bool GetUnpack() { return m_bUnpack; }
const char* GetDefScript() { return m_szDefScript; }
NameList* GetAliases() { return &m_Aliases; }
};
typedef std::vector<Category*> CategoriesBase;
@@ -166,7 +199,32 @@ public:
{
public:
~Categories();
Category* FindCategory(const char* szName);
Category* FindCategory(const char* szName, bool bSearchAliases);
};
class Script
{
private:
char* m_szName;
char* m_szLocation;
char* m_szDisplayName;
public:
Script(const char* szName, const char* szLocation);
~Script();
const char* GetName() { return m_szName; }
const char* GetLocation() { return m_szLocation; }
void SetDisplayName(const char* szDisplayName);
const char* GetDisplayName() { return m_szDisplayName; }
};
typedef std::list<Script*> ScriptListBase;
class ScriptList: public ScriptListBase
{
public:
~ScriptList();
Script* Find(const char* szName);
};
private:
@@ -185,6 +243,8 @@ private:
char* m_szQueueDir;
char* m_szNzbDir;
char* m_szWebDir;
char* m_szConfigTemplate;
char* m_szScriptDir;
EMessageTarget m_eInfoTarget;
EMessageTarget m_eWarningTarget;
EMessageTarget m_eErrorTarget;
@@ -195,23 +255,23 @@ private:
bool m_bResetLog;
int m_iConnectionTimeout;
int m_iTerminateTimeout;
bool m_bAppendNZBDir;
bool m_bAppendCategoryDir;
bool m_bContinuePartial;
bool m_bRenameBroken;
int m_iRetries;
int m_iRetryInterval;
bool m_bSaveQueue;
bool m_bDupeCheck;
char* m_szControlIP;
char* m_szControlUsername;
char* m_szControlPassword;
int m_iControlPort;
bool m_bSecureControl;
int m_iSecurePort;
char* m_szSecureCert;
char* m_szSecureKey;
char* m_szAuthorizedIP;
char* m_szLockFile;
char* m_szDaemonUserName;
char* m_szDaemonUsername;
EOutputMode m_eOutputMode;
bool m_bReloadQueue;
bool m_bReloadUrlQueue;
@@ -220,15 +280,15 @@ private:
int m_iLogBufferSize;
bool m_bCreateLog;
char* m_szLogFile;
ELoadPars m_eLoadPars;
bool m_bParCheck;
EParCheck m_eParCheck;
bool m_bParRepair;
EParScan m_eParScan;
char* m_szPostProcess;
char* m_szPostConfigFilename;
bool m_bParRename;
EHealthCheck m_eHealthCheck;
char* m_szDefScript;
char* m_szScriptOrder;
char* m_szNZBProcess;
char* m_szNZBAddedProcess;
bool m_bStrictParName;
bool m_bNoConfig;
int m_iUMask;
int m_iUpdateInterval;
@@ -236,22 +296,18 @@ private:
bool m_bCursesTime;
bool m_bCursesGroup;
bool m_bCrcCheck;
int m_iThreadLimit;
bool m_bDirectWrite;
int m_iWriteBufferSize;
int m_iNzbDirInterval;
int m_iNzbDirFileAge;
bool m_bParCleanupQueue;
int m_iDiskSpace;
EScriptLogKind m_eProcessLogKind;
bool m_bAllowReProcess;
bool m_bTLS;
bool m_bDumpCore;
bool m_bParPauseQueue;
bool m_bPostPauseQueue;
bool m_bScriptPauseQueue;
bool m_bNzbCleanupDisk;
bool m_bDeleteCleanupDisk;
bool m_bMergeNzb;
int m_iParTimeLimit;
int m_iKeepHistory;
bool m_bAccurateRate;
@@ -260,6 +316,10 @@ private:
char* m_szUnrarCmd;
char* m_szSevenZipCmd;
bool m_bUnpackPauseQueue;
char* m_szExtCleanupDisk;
int m_iFeedHistory;
bool m_bUrlForce;
int m_iTimeCorrection;
// Parsed command-line parameters
bool m_bServerMode;
@@ -298,11 +358,11 @@ private:
void InitOptFile();
void InitCommandLine(int argc, char* argv[]);
void InitOptions();
void InitPostConfig();
void InitFileArg(int argc, char* argv[]);
void InitServers();
void InitCategories();
void InitScheduler();
void InitFeeds();
void CheckOptions();
void PrintUsage(char* com);
void Dump();
@@ -313,6 +373,7 @@ private:
const char* GetOption(const char* optname);
void SetOption(const char* optname, const char* value);
bool SetOptionString(const char* option);
bool SplitOptionString(const char* option, char** pOptName, char** pOptValue);
bool ValidateOptionName(const char* optname);
void LoadConfigFile();
void CheckDir(char** dir, const char* szOptionName, bool bAllowEmpty, bool bCreate);
@@ -323,14 +384,19 @@ private:
void ConfigError(const char* msg, ...);
void ConfigWarn(const char* msg, ...);
void LocateOptionSrcPos(const char *szOptionName);
void ConvertOldOptionName(char *szOption, int iBufLen);
void ConvertOldOption(char *szOption, int iOptionBufLen, char *szValue, int iValueBufLen);
static bool CompareScripts(Script* pScript1, Script* pScript2);
void LoadScriptDir(ScriptList* pScriptList, const char* szDirectory, bool bIsSubDir);
void BuildScriptDisplayNames(ScriptList* pScriptList);
public:
Options(int argc, char* argv[]);
~Options();
bool LoadConfig(EDomain eDomain, OptEntries* pOptEntries);
bool SaveConfig(EDomain eDomain, OptEntries* pOptEntries);
bool LoadConfig(OptEntries* pOptEntries);
bool SaveConfig(OptEntries* pOptEntries);
bool LoadConfigTemplates(ConfigTemplates* pConfigTemplates);
void LoadScriptList(ScriptList* pScriptList);
// Options
OptEntries* LockOptEntries();
@@ -342,6 +408,8 @@ public:
const char* GetQueueDir() { return m_szQueueDir; }
const char* GetNzbDir() { return m_szNzbDir; }
const char* GetWebDir() { return m_szWebDir; }
const char* GetConfigTemplate() { return m_szConfigTemplate; }
const char* GetScriptDir() { return m_szScriptDir; }
bool GetCreateBrokenLog() const { return m_bCreateBrokenLog; }
bool GetResetLog() const { return m_bResetLog; }
EMessageTarget GetInfoTarget() const { return m_eInfoTarget; }
@@ -352,23 +420,23 @@ public:
int GetConnectionTimeout() { return m_iConnectionTimeout; }
int GetTerminateTimeout() { return m_iTerminateTimeout; }
bool GetDecode() { return m_bDecode; };
bool GetAppendNZBDir() { return m_bAppendNZBDir; }
bool GetAppendCategoryDir() { return m_bAppendCategoryDir; }
bool GetContinuePartial() { return m_bContinuePartial; }
bool GetRenameBroken() { return m_bRenameBroken; }
int GetRetries() { return m_iRetries; }
int GetRetryInterval() { return m_iRetryInterval; }
bool GetSaveQueue() { return m_bSaveQueue; }
bool GetDupeCheck() { return m_bDupeCheck; }
const char* GetControlIP() { return m_szControlIP; }
const char* GetControlUsername() { return m_szControlUsername; }
const char* GetControlPassword() { return m_szControlPassword; }
int GetControlPort() { return m_iControlPort; }
bool GetSecureControl() { return m_bSecureControl; }
int GetSecurePort() { return m_iSecurePort; }
const char* GetSecureCert() { return m_szSecureCert; }
const char* GetSecureKey() { return m_szSecureKey; }
const char* GetAuthorizedIP() { return m_szAuthorizedIP; }
const char* GetLockFile() { return m_szLockFile; }
const char* GetDaemonUserName() { return m_szDaemonUserName; }
const char* GetDaemonUsername() { return m_szDaemonUsername; }
EOutputMode GetOutputMode() { return m_eOutputMode; }
bool GetReloadQueue() { return m_bReloadQueue; }
bool GetReloadUrlQueue() { return m_bReloadUrlQueue; }
@@ -377,37 +445,33 @@ public:
int GetLogBufferSize() { return m_iLogBufferSize; }
bool GetCreateLog() { return m_bCreateLog; }
const char* GetLogFile() { return m_szLogFile; }
ELoadPars GetLoadPars() { return m_eLoadPars; }
bool GetParCheck() { return m_bParCheck; }
EParCheck GetParCheck() { return m_eParCheck; }
bool GetParRepair() { return m_bParRepair; }
EParScan GetParScan() { return m_eParScan; }
const char* GetPostProcess() { return m_szPostProcess; }
const char* GetPostConfigFilename() { return m_szPostConfigFilename; }
bool GetParRename() { return m_bParRename; }
EHealthCheck GetHealthCheck() { return m_eHealthCheck; }
const char* GetScriptOrder() { return m_szScriptOrder; }
const char* GetDefScript() { return m_szDefScript; }
const char* GetNZBProcess() { return m_szNZBProcess; }
const char* GetNZBAddedProcess() { return m_szNZBAddedProcess; }
bool GetStrictParName() { return m_bStrictParName; }
int GetUMask() { return m_iUMask; }
int GetUpdateInterval() {return m_iUpdateInterval; }
bool GetCursesNZBName() { return m_bCursesNZBName; }
bool GetCursesTime() { return m_bCursesTime; }
bool GetCursesGroup() { return m_bCursesGroup; }
bool GetCrcCheck() { return m_bCrcCheck; }
int GetThreadLimit() { return m_iThreadLimit; }
bool GetDirectWrite() { return m_bDirectWrite; }
int GetWriteBufferSize() { return m_iWriteBufferSize; }
int GetNzbDirInterval() { return m_iNzbDirInterval; }
int GetNzbDirFileAge() { return m_iNzbDirFileAge; }
bool GetParCleanupQueue() { return m_bParCleanupQueue; }
int GetDiskSpace() { return m_iDiskSpace; }
EScriptLogKind GetProcessLogKind() { return m_eProcessLogKind; }
bool GetAllowReProcess() { return m_bAllowReProcess; }
bool GetTLS() { return m_bTLS; }
bool GetDumpCore() { return m_bDumpCore; }
bool GetParPauseQueue() { return m_bParPauseQueue; }
bool GetPostPauseQueue() { return m_bPostPauseQueue; }
bool GetScriptPauseQueue() { return m_bScriptPauseQueue; }
bool GetNzbCleanupDisk() { return m_bNzbCleanupDisk; }
bool GetDeleteCleanupDisk() { return m_bDeleteCleanupDisk; }
bool GetMergeNzb() { return m_bMergeNzb; }
int GetParTimeLimit() { return m_iParTimeLimit; }
int GetKeepHistory() { return m_iKeepHistory; }
bool GetAccurateRate() { return m_bAccurateRate; }
@@ -416,8 +480,12 @@ public:
const char* GetUnrarCmd() { return m_szUnrarCmd; }
const char* GetSevenZipCmd() { return m_szSevenZipCmd; }
bool GetUnpackPauseQueue() { return m_bUnpackPauseQueue; }
const char* GetExtCleanupDisk() { return m_szExtCleanupDisk; }
int GetFeedHistory() { return m_iFeedHistory; }
bool GetUrlForce() { return m_bUrlForce; }
int GetTimeCorrection() { return m_iTimeCorrection; }
Category* FindCategory(const char* szName) { return m_Categories.FindCategory(szName); }
Category* FindCategory(const char* szName, bool bSearchAliases) { return m_Categories.FindCategory(szName, bSearchAliases); }
// Parsed command-line parameters
bool GetServerMode() { return m_bServerMode; }

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2007-2009 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -35,8 +35,8 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <fstream>
#ifdef WIN32
#include <par2cmdline.h>
#include <par2repairer.h>
@@ -45,9 +45,11 @@
#include <libpar2/par2cmdline.h>
#include <libpar2/par2repairer.h>
#endif
#include <algorithm>
#include "nzbget.h"
#include "ParChecker.h"
#include "ParCoordinator.h"
#include "Log.h"
#include "Options.h"
#include "Util.h"
@@ -77,7 +79,6 @@ public:
friend class ParChecker;
};
Result Repairer::PreProcess(const char *szParFilename)
{
#ifdef HAVE_PAR2_BUGFIXES_V2
@@ -155,11 +156,14 @@ ParChecker::ParChecker()
{
debug("Creating ParChecker");
m_eStatus = psUndefined;
m_eStatus = psFailed;
m_szDestDir = NULL;
m_szNZBName = NULL;
m_szParFilename = NULL;
m_szInfoName = NULL;
m_szErrMsg = NULL;
m_szProgressLabel = (char*)malloc(1024);
m_pRepairer = NULL;
m_iFileProgress = 0;
m_iStageProgress = 0;
m_iExtraFiles = 0;
@@ -172,18 +176,9 @@ ParChecker::~ParChecker()
{
debug("Destroying ParChecker");
if (m_szParFilename)
{
free(m_szParFilename);
}
if (m_szInfoName)
{
free(m_szInfoName);
}
if (m_szErrMsg)
{
free(m_szErrMsg);
}
free(m_szDestDir);
free(m_szNZBName);
free(m_szInfoName);
free(m_szProgressLabel);
Cleanup();
@@ -191,6 +186,9 @@ ParChecker::~ParChecker()
void ParChecker::Cleanup()
{
delete (Repairer*)m_pRepairer;
m_pRepairer = NULL;
for (FileList::iterator it = m_QueuedParFiles.begin(); it != m_QueuedParFiles.end() ;it++)
{
free(*it);
@@ -202,93 +200,119 @@ void ParChecker::Cleanup()
free(*it);
}
m_ProcessedFiles.clear();
m_sourceFiles.clear();
free(m_szErrMsg);
m_szErrMsg = NULL;
}
void ParChecker::SetParFilename(const char * szParFilename)
void ParChecker::SetDestDir(const char * szDestDir)
{
if (m_szParFilename)
{
free(m_szParFilename);
}
m_szParFilename = strdup(szParFilename);
free(m_szDestDir);
m_szDestDir = strdup(szDestDir);
}
void ParChecker::SetNZBName(const char * szNZBName)
{
free(m_szNZBName);
m_szNZBName = strdup(szNZBName);
}
void ParChecker::SetInfoName(const char * szInfoName)
{
if (m_szInfoName)
{
free(m_szInfoName);
}
free(m_szInfoName);
m_szInfoName = strdup(szInfoName);
}
void ParChecker::SetStatus(EStatus eStatus)
{
m_eStatus = eStatus;
Notify(NULL);
}
void ParChecker::Run()
{
ParCoordinator::FileList fileList;
if (!ParCoordinator::FindMainPars(m_szDestDir, &fileList))
{
PrintMessage(Message::mkError, "Could not start par-check for %s. Could not find any par-files", m_szNZBName);
m_eStatus = psFailed;
Completed();
return;
}
m_eStatus = psRepairNotNeeded;
m_bCancelled = false;
for (ParCoordinator::FileList::iterator it = fileList.begin(); it != fileList.end(); it++)
{
char* szParFilename = *it;
debug("Found par: %s", szParFilename);
if (!IsStopped() && !m_bCancelled)
{
char szFullParFilename[1024];
snprintf(szFullParFilename, 1024, "%s%c%s", m_szDestDir, (int)PATH_SEPARATOR, szParFilename);
szFullParFilename[1024-1] = '\0';
char szInfoName[1024];
int iBaseLen = 0;
ParCoordinator::ParseParFilename(szParFilename, &iBaseLen, NULL);
int maxlen = iBaseLen < 1024 ? iBaseLen : 1024 - 1;
strncpy(szInfoName, szParFilename, maxlen);
szInfoName[maxlen] = '\0';
char szParInfoName[1024];
snprintf(szParInfoName, 1024, "%s%c%s", m_szNZBName, (int)PATH_SEPARATOR, szInfoName);
szParInfoName[1024-1] = '\0';
SetInfoName(szParInfoName);
EStatus eStatus = RunParCheck(szFullParFilename);
// accumulate total status, the worst status has priority
if (m_eStatus > eStatus)
{
m_eStatus = eStatus;
}
if (g_pOptions->GetCreateBrokenLog())
{
WriteBrokenLog(eStatus);
}
}
free(szParFilename);
}
Completed();
}
ParChecker::EStatus ParChecker::RunParCheck(const char* szParFilename)
{
Cleanup();
m_bRepairNotNeeded = false;
m_szParFilename = szParFilename;
m_eStage = ptLoadingPars;
m_iProcessedFiles = 0;
m_iExtraFiles = 0;
m_bVerifyingExtraFiles = false;
m_bCancelled = false;
EStatus eStatus = psFailed;
info("Verifying %s", m_szInfoName);
SetStatus(psWorking);
PrintMessage(Message::mkInfo, "Verifying %s", m_szInfoName);
debug("par: %s", m_szParFilename);
Result res;
Repairer* pRepairer = new Repairer();
m_pRepairer = pRepairer;
pRepairer->sig_filename.connect(sigc::mem_fun(*this, &ParChecker::signal_filename));
pRepairer->sig_progress.connect(sigc::mem_fun(*this, &ParChecker::signal_progress));
pRepairer->sig_done.connect(sigc::mem_fun(*this, &ParChecker::signal_done));
snprintf(m_szProgressLabel, 1024, "Verifying %s", m_szInfoName);
m_szProgressLabel[1024-1] = '\0';
m_iFileProgress = 0;
m_iStageProgress = 0;
UpdateProgress();
res = pRepairer->PreProcess(m_szParFilename);
debug("ParChecker: PreProcess-result=%i", res);
if (res != eSuccess || IsStopped())
Result res = (Result)PreProcessPar();
if (IsStopped() || res != eSuccess)
{
if (res == eInvalidCommandLineArguments)
{
error("Could not start par-check for %s. Par-file: %s", m_szInfoName, m_szParFilename);
m_szErrMsg = strdup("Command line could not be parsed");
}
else
{
error("Could not verify %s: %s", m_szInfoName, IsStopped() ? "due stopping" : "par2-file could not be processed");
m_szErrMsg = strdup("par2-file could not be processed");
}
SetStatus(psFailed);
delete pRepairer;
Cleanup();
return;
return psFailed;
}
char BufReason[1024];
BufReason[0] = '\0';
if (m_szErrMsg)
{
free(m_szErrMsg);
}
m_szErrMsg = NULL;
m_eStage = ptVerifyingSources;
res = pRepairer->Process(false);
Repairer* pRepairer = (Repairer*)m_pRepairer;
res = pRepairer->Process(false);
debug("ParChecker: Process-result=%i", res);
if (!IsStopped() && pRepairer->missingfilecount > 0 && g_pOptions->GetParScan() == Options::psAuto && AddMissingFiles())
@@ -304,13 +328,204 @@ void ParChecker::Run()
debug("ParChecker: Process-result=%i", res);
}
if (!IsStopped() && res == eRepairNotPossible)
{
res = (Result)ProcessMorePars();
}
if (IsStopped())
{
Cleanup();
return psFailed;
}
eStatus = psFailed;
if (res == eSuccess)
{
PrintMessage(Message::mkInfo, "Repair not needed for %s", m_szInfoName);
eStatus = psRepairNotNeeded;
}
else if (res == eRepairPossible)
{
eStatus = psRepairPossible;
if (g_pOptions->GetParRepair())
{
PrintMessage(Message::mkInfo, "Repairing %s", m_szInfoName);
SaveSourceList();
snprintf(m_szProgressLabel, 1024, "Repairing %s", m_szInfoName);
m_szProgressLabel[1024-1] = '\0';
m_iFileProgress = 0;
m_iStageProgress = 0;
m_iProcessedFiles = 0;
m_eStage = ptRepairing;
m_iFilesToRepair = pRepairer->damagedfilecount + pRepairer->missingfilecount;
UpdateProgress();
res = pRepairer->Process(true);
debug("ParChecker: Process-result=%i", res);
if (res == eSuccess)
{
PrintMessage(Message::mkInfo, "Successfully repaired %s", m_szInfoName);
eStatus = psRepaired;
DeleteLeftovers();
}
}
else
{
PrintMessage(Message::mkInfo, "Repair possible for %s", m_szInfoName);
}
}
if (m_bCancelled)
{
if (m_eStage >= ptRepairing)
{
PrintMessage(Message::mkWarning, "Repair cancelled for %s", m_szInfoName);
m_szErrMsg = strdup("repair cancelled");
eStatus = psRepairPossible;
}
else
{
PrintMessage(Message::mkWarning, "Par-check cancelled for %s", m_szInfoName);
m_szErrMsg = strdup("par-check cancelled");
eStatus = psFailed;
}
}
else if (eStatus == psFailed)
{
if (!m_szErrMsg && (int)res >= 0 && (int)res <= 8)
{
m_szErrMsg = strdup(Par2CmdLineErrStr[res]);
}
PrintMessage(Message::mkError, "Repair failed for %s: %s", m_szInfoName, m_szErrMsg ? m_szErrMsg : "");
}
Cleanup();
return eStatus;
}
int ParChecker::PreProcessPar()
{
Result res = eRepairFailed;
while (!IsStopped() && res != eSuccess)
{
Cleanup();
Repairer* pRepairer = new Repairer();
m_pRepairer = pRepairer;
pRepairer->sig_filename.connect(sigc::mem_fun(*this, &ParChecker::signal_filename));
pRepairer->sig_progress.connect(sigc::mem_fun(*this, &ParChecker::signal_progress));
pRepairer->sig_done.connect(sigc::mem_fun(*this, &ParChecker::signal_done));
res = pRepairer->PreProcess(m_szParFilename);
debug("ParChecker: PreProcess-result=%i", res);
if (IsStopped())
{
PrintMessage(Message::mkError, "Could not verify %s: stopping", m_szInfoName);
m_szErrMsg = strdup("par-check was stopped");
return eRepairFailed;
}
if (res == eInvalidCommandLineArguments)
{
PrintMessage(Message::mkError, "Could not start par-check for %s. Par-file: %s", m_szInfoName, m_szParFilename);
m_szErrMsg = strdup("Command line could not be parsed");
return res;
}
if (res != eSuccess)
{
PrintMessage(Message::mkWarning, "Could not verify %s: par2-file could not be processed", m_szInfoName);
PrintMessage(Message::mkInfo, "Requesting more par2-files for %s", m_szInfoName);
bool bHasMorePars = LoadMainParBak();
if (!bHasMorePars)
{
PrintMessage(Message::mkWarning, "No more par2-files found");
break;
}
}
}
if (res != eSuccess)
{
PrintMessage(Message::mkError, "Could not verify %s: par2-file could not be processed", m_szInfoName);
m_szErrMsg = strdup("par2-file could not be processed");
return res;
}
return res;
}
bool ParChecker::LoadMainParBak()
{
while (!IsStopped())
{
m_mutexQueuedParFiles.Lock();
bool hasMorePars = !m_QueuedParFiles.empty();
for (FileList::iterator it = m_QueuedParFiles.begin(); it != m_QueuedParFiles.end() ;it++)
{
free(*it);
}
m_QueuedParFiles.clear();
m_mutexQueuedParFiles.Unlock();
if (hasMorePars)
{
return true;
}
int iBlockFound = 0;
bool requested = RequestMorePars(1, &iBlockFound);
if (requested)
{
strncpy(m_szProgressLabel, "Awaiting additional par-files", 1024);
m_szProgressLabel[1024-1] = '\0';
m_iFileProgress = 0;
UpdateProgress();
}
m_mutexQueuedParFiles.Lock();
hasMorePars = !m_QueuedParFiles.empty();
m_bQueuedParFilesChanged = false;
m_mutexQueuedParFiles.Unlock();
if (!requested && !hasMorePars)
{
return false;
}
if (!hasMorePars)
{
// wait until new files are added by "AddParFile" or a change is signaled by "QueueChanged"
bool bQueuedParFilesChanged = false;
while (!bQueuedParFilesChanged && !IsStopped())
{
m_mutexQueuedParFiles.Lock();
bQueuedParFilesChanged = m_bQueuedParFilesChanged;
m_mutexQueuedParFiles.Unlock();
usleep(100 * 1000);
}
}
}
return false;
}
int ParChecker::ProcessMorePars()
{
Result res = eRepairNotPossible;
Repairer* pRepairer = (Repairer*)m_pRepairer;
bool bMoreFilesLoaded = true;
while (!IsStopped() && res == eRepairNotPossible)
{
int missingblockcount = pRepairer->missingblockcount - pRepairer->recoverypacketmap.size();
if (bMoreFilesLoaded)
{
info("Need more %i par-block(s) for %s", missingblockcount, m_szInfoName);
PrintMessage(Message::mkInfo, "Need more %i par-block(s) for %s", missingblockcount, m_szInfoName);
}
m_mutexQueuedParFiles.Lock();
@@ -336,9 +551,9 @@ void ParChecker::Run()
if (!requested && !hasMorePars)
{
snprintf(BufReason, 1024, "not enough par-blocks, %i block(s) needed, but %i block(s) available", missingblockcount, iBlockFound);
BufReason[1024-1] = '\0';
m_szErrMsg = strdup(BufReason);
m_szErrMsg = (char*)malloc(1024);
snprintf(m_szErrMsg, 1024, "not enough par-blocks, %i block(s) needed, but %i block(s) available", missingblockcount, iBlockFound);
m_szErrMsg[1024-1] = '\0';
break;
}
@@ -370,70 +585,7 @@ void ParChecker::Run()
}
}
if (IsStopped())
{
SetStatus(psFailed);
delete pRepairer;
Cleanup();
return;
}
if (res == eSuccess)
{
info("Repair not needed for %s", m_szInfoName);
m_bRepairNotNeeded = true;
}
else if (res == eRepairPossible)
{
if (g_pOptions->GetParRepair())
{
info("Repairing %s", m_szInfoName);
snprintf(m_szProgressLabel, 1024, "Repairing %s", m_szInfoName);
m_szProgressLabel[1024-1] = '\0';
m_iFileProgress = 0;
m_iStageProgress = 0;
m_iProcessedFiles = 0;
m_eStage = ptRepairing;
m_iFilesToRepair = pRepairer->damagedfilecount + pRepairer->missingfilecount;
UpdateProgress();
res = pRepairer->Process(true);
debug("ParChecker: Process-result=%i", res);
if (res == eSuccess)
{
info("Successfully repaired %s", m_szInfoName);
}
}
else
{
info("Repair possible for %s", m_szInfoName);
res = eSuccess;
}
}
if (m_bCancelled)
{
warn("Repair cancelled for %s", m_szInfoName);
m_szErrMsg = strdup("repair cancelled");
SetStatus(psFailed);
}
else if (res == eSuccess)
{
SetStatus(psFinished);
}
else
{
if (!m_szErrMsg && (int)res >= 0 && (int)res <= 8)
{
m_szErrMsg = strdup(Par2CmdLineErrStr[res]);
}
error("Repair failed for %s: %s", m_szInfoName, m_szErrMsg ? m_szErrMsg : "");
SetStatus(psFailed);
}
delete pRepairer;
Cleanup();
return res;
}
bool ParChecker::LoadMorePars()
@@ -450,11 +602,11 @@ bool ParChecker::LoadMorePars()
bool loadedOK = ((Repairer*)m_pRepairer)->LoadPacketsFromFile(szParFilename);
if (loadedOK)
{
info("File %s successfully loaded for par-check", Util::BaseFileName(szParFilename), m_szInfoName);
PrintMessage(Message::mkInfo, "File %s successfully loaded for par-check", Util::BaseFileName(szParFilename), m_szInfoName);
}
else
{
info("Could not load file %s for par-check", Util::BaseFileName(szParFilename), m_szInfoName);
PrintMessage(Message::mkInfo, "Could not load file %s for par-check", Util::BaseFileName(szParFilename), m_szInfoName);
}
free(szParFilename);
}
@@ -485,7 +637,7 @@ bool ParChecker::CheckSplittedFragments()
it != ((Repairer*)m_pRepairer)->sourcefiles.end(); it++)
{
Par2RepairerSourceFile *sourcefile = *it;
if (!sourcefile->GetTargetExists() && AddSplittedFragments(sourcefile->TargetFileName().c_str()))
if (AddSplittedFragments(sourcefile->TargetFileName().c_str()))
{
bFragmentsAdded = true;
}
@@ -549,7 +701,7 @@ bool ParChecker::AddSplittedFragments(const char* szFilename)
bool ParChecker::AddMissingFiles()
{
info("Performing extra par-scan for %s", m_szInfoName);
PrintMessage(Message::mkInfo, "Performing extra par-scan for %s", m_szInfoName);
char szDirectory[1024];
strncpy(szDirectory, m_szParFilename, 1024);
@@ -643,7 +795,7 @@ void ParChecker::signal_filename(std::string str)
m_eStage = ptVerifyingRepaired;
}
info("%s %s", szStageMessage[m_eStage], str.c_str());
PrintMessage(Message::mkInfo, "%s %s", szStageMessage[m_eStage], str.c_str());
if (m_eStage == ptLoadingPars || m_eStage == ptVerifyingSources)
{
@@ -727,11 +879,11 @@ void ParChecker::signal_done(std::string str, int available, int total)
if (bFileExists)
{
warn("File %s has %i bad block(s) of total %i block(s)", str.c_str(), total - available, total);
PrintMessage(Message::mkWarning, "File %s has %i bad block(s) of total %i block(s)", str.c_str(), total - available, total);
}
else
{
warn("File %s with %i block(s) is missing", str.c_str(), total);
PrintMessage(Message::mkWarning, "File %s with %i block(s) is missing", str.c_str(), total);
}
}
}
@@ -743,8 +895,103 @@ void ParChecker::Cancel()
((Repairer*)m_pRepairer)->cancelled = true;
m_bCancelled = true;
#else
error("Could not cancel par-repair. The program was compiled using version of libpar2 which doesn't support cancelling of par-repair. Please apply libpar2-patches supplied with NZBGet and recompile libpar2 and NZBGet (see README for details).");
PrintMessage(Message::mkError, "Could not cancel par-repair. The program was compiled using version of libpar2 which doesn't support cancelling of par-repair. Please apply libpar2-patches supplied with NZBGet and recompile libpar2 and NZBGet (see README for details).");
#endif
}
void ParChecker::WriteBrokenLog(EStatus eStatus)
{
char szBrokenLogName[1024];
snprintf(szBrokenLogName, 1024, "%s%c_brokenlog.txt", m_szDestDir, (int)PATH_SEPARATOR);
szBrokenLogName[1024-1] = '\0';
if (eStatus != psRepairNotNeeded || Util::FileExists(szBrokenLogName))
{
FILE* file = fopen(szBrokenLogName, "ab");
if (file)
{
if (eStatus == psFailed)
{
if (m_bCancelled)
{
fprintf(file, "Repair cancelled for %s\n", m_szInfoName);
}
else
{
fprintf(file, "Repair failed for %s: %s\n", m_szInfoName, m_szErrMsg ? m_szErrMsg : "");
}
}
else if (eStatus == psRepairPossible)
{
fprintf(file, "Repair possible for %s\n", m_szInfoName);
}
else if (eStatus == psRepaired)
{
fprintf(file, "Successfully repaired %s\n", m_szInfoName);
}
else if (eStatus == psRepairNotNeeded)
{
fprintf(file, "Repair not needed for %s\n", m_szInfoName);
}
fclose(file);
}
else
{
PrintMessage(Message::mkError, "Could not open file %s", szBrokenLogName);
}
}
}
void ParChecker::SaveSourceList()
{
// Buliding a list of DiskFile-objects, marked as source-files
for (std::vector<Par2RepairerSourceFile*>::iterator it = ((Repairer*)m_pRepairer)->sourcefiles.begin();
it != ((Repairer*)m_pRepairer)->sourcefiles.end(); it++)
{
Par2RepairerSourceFile* sourcefile = (Par2RepairerSourceFile*)*it;
vector<DataBlock>::iterator it2 = sourcefile->SourceBlocks();
for (int i = 0; i < (int)sourcefile->BlockCount(); i++, it2++)
{
DataBlock block = *it2;
DiskFile* pSourceFile = block.GetDiskFile();
if (pSourceFile &&
std::find(m_sourceFiles.begin(), m_sourceFiles.end(), pSourceFile) == m_sourceFiles.end())
{
m_sourceFiles.push_back(pSourceFile);
}
}
}
}
void ParChecker::DeleteLeftovers()
{
// After repairing check if all DiskFile-objects saved by "SaveSourceList()" have
// corresponding target-files. If not - the source file was replaced. In this case
// the DiskFile-object points to the renamed bak-file, which we can delete.
for (SourceList::iterator it = m_sourceFiles.begin(); it != m_sourceFiles.end(); it++)
{
DiskFile* pSourceFile = (DiskFile*)*it;
bool bFound = false;
for (std::vector<Par2RepairerSourceFile*>::iterator it2 = ((Repairer*)m_pRepairer)->sourcefiles.begin();
it2 != ((Repairer*)m_pRepairer)->sourcefiles.end(); it2++)
{
Par2RepairerSourceFile* sourcefile = *it2;
if (sourcefile->GetTargetFile() == pSourceFile)
{
bFound = true;
break;
}
}
if (!bFound)
{
PrintMessage(Message::mkInfo, "Deleting file %s", Util::BaseFileName(pSourceFile->FileName().c_str()));
remove(pSourceFile->FileName().c_str());
}
}
}
#endif

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2007-2009 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -29,19 +29,20 @@
#ifndef DISABLE_PARCHECK
#include <deque>
#include <string>
#include "Thread.h"
#include "Observer.h"
#include "Log.h"
class ParChecker : public Thread, public Subject
class ParChecker : public Thread
{
public:
enum EStatus
{
psUndefined,
psWorking,
psFailed,
psFinished
psRepairPossible,
psRepaired,
psRepairNotNeeded
};
enum EStage
@@ -53,15 +54,17 @@ public:
};
typedef std::deque<char*> FileList;
typedef std::deque<void*> SourceList;
private:
char* m_szInfoName;
char* m_szParFilename;
char* m_szDestDir;
char* m_szNZBName;
const char* m_szParFilename;
EStatus m_eStatus;
EStage m_eStage;
void* m_pRepairer; // declared as void* to prevent the including of libpar2-headers into this header-file
char* m_szErrMsg;
bool m_bRepairNotNeeded;
FileList m_QueuedParFiles;
Mutex m_mutexQueuedParFiles;
bool m_bQueuedParFilesChanged;
@@ -74,12 +77,20 @@ private:
int m_iFileProgress;
int m_iStageProgress;
bool m_bCancelled;
SourceList m_sourceFiles;
void Cleanup();
EStatus RunParCheck(const char* szParFilename);
int PreProcessPar();
bool LoadMainParBak();
int ProcessMorePars();
bool LoadMorePars();
bool CheckSplittedFragments();
bool AddSplittedFragments(const char* szFilename);
bool AddMissingFiles();
void WriteBrokenLog(EStatus eStatus);
void SaveSourceList();
void DeleteLeftovers();
void signal_filename(std::string str);
void signal_progress(double progress);
void signal_done(std::string str, int available, int total);
@@ -92,6 +103,8 @@ protected:
*/
virtual bool RequestMorePars(int iBlockNeeded, int* pBlockFound) = 0;
virtual void UpdateProgress() {}
virtual void Completed() {}
virtual void PrintMessage(Message::EKind eKind, const char* szFormat, ...) {}
EStage GetStage() { return m_eStage; }
const char* GetProgressLabel() { return m_szProgressLabel; }
int GetFileProgress() { return m_iFileProgress; }
@@ -101,14 +114,12 @@ public:
ParChecker();
virtual ~ParChecker();
virtual void Run();
void SetDestDir(const char* szDestDir);
const char* GetParFilename() { return m_szParFilename; }
void SetParFilename(const char* szParFilename);
const char* GetInfoName() { return m_szInfoName; }
void SetInfoName(const char* szInfoName);
void SetStatus(EStatus eStatus);
void SetNZBName(const char* szNZBName);
EStatus GetStatus() { return m_eStatus; }
const char* GetErrMsg() { return m_szErrMsg; }
bool GetRepairNotNeeded() { return m_bRepairNotNeeded; }
void AddParFile(const char* szParFilename);
void QueueChanged();
void Cancel();

View File

@@ -33,7 +33,9 @@
#include <stdlib.h>
#include <string.h>
#include <fstream>
#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>
#ifdef WIN32
#include <direct.h>
#else
@@ -63,10 +65,34 @@ void ParCoordinator::PostParChecker::UpdateProgress()
m_pOwner->UpdateParCheckProgress();
}
void ParCoordinator::PostParChecker::PrintMessage(Message::EKind eKind, const char* szFormat, ...)
{
char szText[1024];
va_list args;
va_start(args, szFormat);
vsnprintf(szText, 1024, szFormat, args);
va_end(args);
szText[1024-1] = '\0';
m_pOwner->PrintMessage(m_pPostInfo, eKind, "%s", szText);
}
void ParCoordinator::PostParRenamer::UpdateProgress()
{
m_pOwner->UpdateParRenameProgress();
}
void ParCoordinator::PostParRenamer::PrintMessage(Message::EKind eKind, const char* szFormat, ...)
{
char szText[1024];
va_list args;
va_start(args, szFormat);
vsnprintf(szText, 1024, szFormat, args);
va_end(args);
szText[1024-1] = '\0';
m_pOwner->PrintMessage(m_pPostInfo, eKind, "%s", szText);
}
#endif
ParCoordinator::ParCoordinator()
@@ -74,18 +100,9 @@ ParCoordinator::ParCoordinator()
debug("Creating ParCoordinator");
#ifndef DISABLE_PARCHECK
m_ParCheckerObserver.m_pOwner = this;
m_ParChecker.Attach(&m_ParCheckerObserver);
m_ParChecker.m_pOwner = this;
m_ParRenamerObserver.m_pOwner = this;
m_ParRenamer.Attach(&m_ParRenamerObserver);
m_ParRenamer.m_pOwner = this;
m_bStopped = false;
const char* szPostScript = g_pOptions->GetPostProcess();
m_bPostScript = szPostScript && strlen(szPostScript) > 0;
m_ParChecker.m_pOwner = this;
m_ParRenamer.m_pOwner = this;
#endif
}
@@ -129,10 +146,7 @@ void ParCoordinator::PausePars(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo)
if (pFileInfo->GetNZBInfo() == pNZBInfo)
{
g_pQueueCoordinator->GetQueueEditor()->LockedEditEntry(pDownloadQueue, pFileInfo->GetID(), false,
(g_pOptions->GetLoadPars() == Options::lpOne ||
(g_pOptions->GetLoadPars() == Options::lpNone && g_pOptions->GetParCheck()))
? QueueEditor::eaGroupPauseExtraPars : QueueEditor::eaGroupPauseAllPars,
0, NULL);
QueueEditor::eaGroupPauseExtraPars, 0, NULL);
break;
}
}
@@ -202,8 +216,13 @@ bool ParCoordinator::ParseParFilename(const char* szParFilename, int* iBaseNameL
char* szEnd = szFilename;
while (char* p = strstr(szEnd, ".par2")) szEnd = p + 5;
*szEnd = '\0';
iLen = strlen(szFilename);
if (iLen < 6)
{
return false;
}
if (strcasecmp(szFilename + iLen - 5, ".par2"))
{
return false;
@@ -245,11 +264,11 @@ bool ParCoordinator::ParseParFilename(const char* szParFilename, int* iBaseNameL
*/
void ParCoordinator::StartParCheckJob(PostInfo* pPostInfo)
{
info("Checking pars for %s", pPostInfo->GetInfoName());
m_eCurrentJob = jkParCheck;
m_ParChecker.SetPostInfo(pPostInfo);
m_ParChecker.SetParFilename(pPostInfo->GetParFilename());
m_ParChecker.SetInfoName(pPostInfo->GetInfoName());
m_ParChecker.SetDestDir(pPostInfo->GetNZBInfo()->GetDestDir());
m_ParChecker.SetNZBName(pPostInfo->GetNZBInfo()->GetName());
m_ParChecker.PrintMessage(Message::mkInfo, "Checking pars for %s", pPostInfo->GetInfoName());
pPostInfo->SetWorking(true);
m_ParChecker.Start();
}
@@ -259,11 +278,21 @@ void ParCoordinator::StartParCheckJob(PostInfo* pPostInfo)
*/
void ParCoordinator::StartParRenameJob(PostInfo* pPostInfo)
{
info("Checking renamed files for %s", pPostInfo->GetNZBInfo()->GetName());
const char* szDestDir = pPostInfo->GetNZBInfo()->GetDestDir();
char szFinalDir[1024];
if (pPostInfo->GetNZBInfo()->GetUnpackStatus() == NZBInfo::usSuccess)
{
pPostInfo->GetNZBInfo()->BuildFinalDirName(szFinalDir, 1024);
szFinalDir[1024-1] = '\0';
szDestDir = szFinalDir;
}
m_eCurrentJob = jkParRename;
m_ParRenamer.SetPostInfo(pPostInfo);
m_ParRenamer.SetDestDir(pPostInfo->GetNZBInfo()->GetDestDir());
m_ParRenamer.SetDestDir(szDestDir);
m_ParRenamer.SetInfoName(pPostInfo->GetNZBInfo()->GetName());
m_ParRenamer.PrintMessage(Message::mkInfo, "Checking renamed files for %s", pPostInfo->GetNZBInfo()->GetName());
pPostInfo->SetWorking(true);
m_ParRenamer.Start();
}
@@ -322,106 +351,38 @@ bool ParCoordinator::AddPar(FileInfo* pFileInfo, bool bDeleted)
return bSameCollection;
}
void ParCoordinator::ParCheckerUpdate(Subject* Caller, void* Aspect)
void ParCoordinator::ParCheckCompleted()
{
if (m_ParChecker.GetStatus() == ParChecker::psFinished ||
m_ParChecker.GetStatus() == ParChecker::psFailed)
DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue();
PostInfo* pPostInfo = m_ParChecker.GetPostInfo();
// Update ParStatus (accumulate result)
if ((m_ParChecker.GetStatus() == ParChecker::psRepaired ||
m_ParChecker.GetStatus() == ParChecker::psRepairNotNeeded) &&
pPostInfo->GetNZBInfo()->GetParStatus() <= NZBInfo::psSkipped)
{
char szPath[1024];
strncpy(szPath, m_ParChecker.GetParFilename(), 1024);
szPath[1024-1] = '\0';
if (char* p = strrchr(szPath, PATH_SEPARATOR)) *p = '\0';
if (g_pOptions->GetCreateBrokenLog())
{
char szBrokenLogName[1024];
snprintf(szBrokenLogName, 1024, "%s%c_brokenlog.txt", szPath, (int)PATH_SEPARATOR);
szBrokenLogName[1024-1] = '\0';
if (!m_ParChecker.GetRepairNotNeeded() || Util::FileExists(szBrokenLogName))
{
FILE* file = fopen(szBrokenLogName, "ab");
if (file)
{
if (m_ParChecker.GetStatus() == ParChecker::psFailed)
{
if (m_ParChecker.GetCancelled())
{
fprintf(file, "Repair cancelled for %s\n", m_ParChecker.GetInfoName());
}
else
{
fprintf(file, "Repair failed for %s: %s\n", m_ParChecker.GetInfoName(), m_ParChecker.GetErrMsg() ? m_ParChecker.GetErrMsg() : "");
}
}
else if (m_ParChecker.GetRepairNotNeeded())
{
fprintf(file, "Repair not needed for %s\n", m_ParChecker.GetInfoName());
}
else
{
if (g_pOptions->GetParRepair())
{
fprintf(file, "Successfully repaired %s\n", m_ParChecker.GetInfoName());
}
else
{
fprintf(file, "Repair possible for %s\n", m_ParChecker.GetInfoName());
}
}
fclose(file);
}
else
{
error("Could not open file %s", szBrokenLogName);
}
}
}
DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue();
PostInfo* pPostInfo = m_ParChecker.GetPostInfo();
pPostInfo->SetWorking(false);
if (pPostInfo->GetDeleted())
{
pPostInfo->SetStage(PostInfo::ptFinished);
}
else
{
pPostInfo->SetStage(PostInfo::ptQueued);
}
// Update ParStatus by NZBInfo (accumulate result)
if (m_ParChecker.GetStatus() == ParChecker::psFailed && !m_ParChecker.GetCancelled())
{
pPostInfo->SetParStatus(PostInfo::psFailure);
pPostInfo->GetNZBInfo()->SetParStatus(NZBInfo::psFailure);
}
else if (m_ParChecker.GetStatus() == ParChecker::psFinished &&
(g_pOptions->GetParRepair() || m_ParChecker.GetRepairNotNeeded()))
{
pPostInfo->SetParStatus(PostInfo::psSuccess);
if (pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psNone)
{
pPostInfo->GetNZBInfo()->SetParStatus(NZBInfo::psSuccess);
}
}
else
{
pPostInfo->SetParStatus(PostInfo::psRepairPossible);
if (pPostInfo->GetNZBInfo()->GetParStatus() != NZBInfo::psFailure)
{
pPostInfo->GetNZBInfo()->SetParStatus(NZBInfo::psRepairPossible);
}
}
if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode())
{
g_pDiskState->SaveDownloadQueue(pDownloadQueue);
}
g_pQueueCoordinator->UnlockQueue();
pPostInfo->GetNZBInfo()->SetParStatus(NZBInfo::psSuccess);
}
else if (m_ParChecker.GetStatus() == ParChecker::psRepairPossible &&
pPostInfo->GetNZBInfo()->GetParStatus() != NZBInfo::psFailure)
{
pPostInfo->GetNZBInfo()->SetParStatus(NZBInfo::psRepairPossible);
}
else
{
pPostInfo->GetNZBInfo()->SetParStatus(NZBInfo::psFailure);
}
pPostInfo->SetWorking(false);
pPostInfo->SetStage(PostInfo::ptQueued);
if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode())
{
g_pDiskState->SaveDownloadQueue(pDownloadQueue);
}
g_pQueueCoordinator->UnlockQueue();
}
/**
@@ -445,7 +406,7 @@ bool ParCoordinator::RequestMorePars(NZBInfo* pNZBInfo, const char* szParFilenam
FindPars(pDownloadQueue, pNZBInfo, szParFilename, &blocks, true, false, &iCurBlockFound);
iBlockFound += iCurBlockFound;
}
if (iBlockFound < iBlockNeeded && !g_pOptions->GetStrictParName())
if (iBlockFound < iBlockNeeded)
{
FindPars(pDownloadQueue, pNZBInfo, szParFilename, &blocks, false, false, &iCurBlockFound);
iBlockFound += iCurBlockFound;
@@ -473,7 +434,7 @@ bool ParCoordinator::RequestMorePars(NZBInfo* pNZBInfo, const char* szParFilenam
{
if (pBestBlockInfo->m_pFileInfo->GetPaused())
{
info("Unpausing %s%c%s for par-recovery", pNZBInfo->GetName(), (int)PATH_SEPARATOR, pBestBlockInfo->m_pFileInfo->GetFilename());
m_ParChecker.PrintMessage(Message::mkInfo, "Unpausing %s%c%s for par-recovery", pNZBInfo->GetName(), (int)PATH_SEPARATOR, pBestBlockInfo->m_pFileInfo->GetFilename());
pBestBlockInfo->m_pFileInfo->SetPaused(false);
pBestBlockInfo->m_pFileInfo->SetExtraPriority(true);
}
@@ -497,7 +458,7 @@ bool ParCoordinator::RequestMorePars(NZBInfo* pNZBInfo, const char* szParFilenam
BlockInfo* pBlockInfo = blocks.front();
if (pBlockInfo->m_pFileInfo->GetPaused())
{
info("Unpausing %s%c%s for par-recovery", pNZBInfo->GetName(), (int)PATH_SEPARATOR, pBlockInfo->m_pFileInfo->GetFilename());
m_ParChecker.PrintMessage(Message::mkInfo, "Unpausing %s%c%s for par-recovery", pNZBInfo->GetName(), (int)PATH_SEPARATOR, pBlockInfo->m_pFileInfo->GetFilename());
pBlockInfo->m_pFileInfo->SetPaused(false);
pBlockInfo->m_pFileInfo->SetExtraPriority(true);
}
@@ -615,9 +576,9 @@ void ParCoordinator::FindPars(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo,
void ParCoordinator::UpdateParCheckProgress()
{
DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue();
g_pQueueCoordinator->LockQueue();
PostInfo* pPostInfo = pDownloadQueue->GetPostQueue()->front();
PostInfo* pPostInfo = m_ParChecker.GetPostInfo();
if (m_ParChecker.GetFileProgress() == 0)
{
pPostInfo->SetProgressLabel(m_ParChecker.GetProgressLabel());
@@ -654,7 +615,7 @@ void ParCoordinator::UpdateParCheckProgress()
if (iEstimatedRepairTime > g_pOptions->GetParTimeLimit() * 60)
{
debug("Estimated repair time %i seconds", iEstimatedRepairTime);
warn("Cancelling par-repair for %s, estimated repair time (%i minutes) exceeds allowed repair time", m_ParChecker.GetInfoName(), iEstimatedRepairTime / 60);
m_ParChecker.PrintMessage(Message::mkWarning, "Cancelling par-repair for %s, estimated repair time (%i minutes) exceeds allowed repair time", m_ParChecker.GetInfoName(), iEstimatedRepairTime / 60);
bParCancel = true;
}
}
@@ -701,50 +662,28 @@ void ParCoordinator::CheckPauseState(PostInfo* pPostInfo)
}
}
void ParCoordinator::ParRenamerUpdate(Subject* Caller, void* Aspect)
void ParCoordinator::ParRenameCompleted()
{
if (m_ParRenamer.GetStatus() == ParRenamer::psFinished ||
m_ParRenamer.GetStatus() == ParRenamer::psFailed)
DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue();
PostInfo* pPostInfo = m_ParRenamer.GetPostInfo();
pPostInfo->GetNZBInfo()->SetRenameStatus(m_ParRenamer.GetStatus() == ParRenamer::psSuccess ? NZBInfo::rsSuccess : NZBInfo::rsFailure);
pPostInfo->SetWorking(false);
pPostInfo->SetStage(PostInfo::ptQueued);
if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode())
{
DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue();
PostInfo* pPostInfo = m_ParRenamer.GetPostInfo();
pPostInfo->SetWorking(false);
if (pPostInfo->GetDeleted())
{
pPostInfo->SetStage(PostInfo::ptFinished);
}
else
{
pPostInfo->SetStage(PostInfo::ptQueued);
}
// Update ParStatus by NZBInfo
if (m_ParRenamer.GetStatus() == ParRenamer::psFailed && !m_ParRenamer.GetCancelled())
{
pPostInfo->SetRenameStatus(PostInfo::rsFailure);
pPostInfo->GetNZBInfo()->SetRenameStatus(NZBInfo::rsFailure);
}
else if (m_ParRenamer.GetStatus() == ParRenamer::psFinished)
{
pPostInfo->SetRenameStatus(PostInfo::rsSuccess);
pPostInfo->GetNZBInfo()->SetRenameStatus(NZBInfo::rsSuccess);
}
if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode())
{
g_pDiskState->SaveDownloadQueue(pDownloadQueue);
}
g_pQueueCoordinator->UnlockQueue();
g_pDiskState->SaveDownloadQueue(pDownloadQueue);
}
g_pQueueCoordinator->UnlockQueue();
}
void ParCoordinator::UpdateParRenameProgress()
{
DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue();
g_pQueueCoordinator->LockQueue();
PostInfo* pPostInfo = pDownloadQueue->GetPostQueue()->front();
PostInfo* pPostInfo = m_ParRenamer.GetPostInfo();
pPostInfo->SetProgressLabel(m_ParRenamer.GetProgressLabel());
pPostInfo->SetStageProgress(m_ParRenamer.GetStageProgress());
time_t tCurrent = time(NULL);
@@ -765,4 +704,39 @@ void ParCoordinator::UpdateParRenameProgress()
CheckPauseState(pPostInfo);
}
void ParCoordinator::PrintMessage(PostInfo* pPostInfo, Message::EKind eKind, const char* szFormat, ...)
{
char szText[1024];
va_list args;
va_start(args, szFormat);
vsnprintf(szText, 1024, szFormat, args);
va_end(args);
szText[1024-1] = '\0';
pPostInfo->AppendMessage(eKind, szText);
switch (eKind)
{
case Message::mkDetail:
detail("%s", szText);
break;
case Message::mkInfo:
info("%s", szText);
break;
case Message::mkWarning:
warn("%s", szText);
break;
case Message::mkError:
error("%s", szText);
break;
case Message::mkDebug:
debug("%s", szText);
break;
}
}
#endif

View File

@@ -29,7 +29,6 @@
#include <list>
#include <deque>
#include "Observer.h"
#include "DownloadInfo.h"
#ifndef DISABLE_PARCHECK
@@ -41,13 +40,6 @@ class ParCoordinator
{
private:
#ifndef DISABLE_PARCHECK
class ParCheckerObserver: public Observer
{
public:
ParCoordinator* m_pOwner;
virtual void Update(Subject* Caller, void* Aspect) { m_pOwner->ParCheckerUpdate(Caller, Aspect); }
};
class PostParChecker: public ParChecker
{
private:
@@ -56,6 +48,8 @@ private:
protected:
virtual bool RequestMorePars(int iBlockNeeded, int* pBlockFound);
virtual void UpdateProgress();
virtual void Completed() { m_pOwner->ParCheckCompleted(); }
virtual void PrintMessage(Message::EKind eKind, const char* szFormat, ...);
public:
PostInfo* GetPostInfo() { return m_pPostInfo; }
void SetPostInfo(PostInfo* pPostInfo) { m_pPostInfo = pPostInfo; }
@@ -63,13 +57,6 @@ private:
friend class ParCoordinator;
};
class ParRenamerObserver: public Observer
{
public:
ParCoordinator* m_pOwner;
virtual void Update(Subject* Caller, void* Aspect) { m_pOwner->ParRenamerUpdate(Caller, Aspect); }
};
class PostParRenamer: public ParRenamer
{
private:
@@ -77,6 +64,8 @@ private:
PostInfo* m_pPostInfo;
protected:
virtual void UpdateProgress();
virtual void Completed() { m_pOwner->ParRenameCompleted(); }
virtual void PrintMessage(Message::EKind eKind, const char* szFormat, ...);
public:
PostInfo* GetPostInfo() { return m_pPostInfo; }
void SetPostInfo(PostInfo* pPostInfo) { m_pPostInfo = pPostInfo; }
@@ -100,16 +89,20 @@ private:
private:
PostParChecker m_ParChecker;
ParCheckerObserver m_ParCheckerObserver;
bool m_bStopped;
bool m_bPostScript;
PostParRenamer m_ParRenamer;
ParRenamerObserver m_ParRenamerObserver;
EJobKind m_eCurrentJob;
protected:
virtual bool PauseDownload() = 0;
virtual bool UnpauseDownload() = 0;
void UpdateParCheckProgress();
void UpdateParRenameProgress();
void ParCheckCompleted();
void ParRenameCompleted();
void CheckPauseState(PostInfo* pPostInfo);
bool RequestMorePars(NZBInfo* pNZBInfo, const char* szParFilename, int iBlockNeeded, int* pBlockFound);
void PrintMessage(PostInfo* pPostInfo, Message::EKind eKind, const char* szFormat, ...);
#endif
public:
@@ -124,15 +117,9 @@ public:
void PausePars(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo);
#ifndef DISABLE_PARCHECK
void ParCheckerUpdate(Subject* Caller, void* Aspect);
void ParRenamerUpdate(Subject* Caller, void* Aspect);
void CheckPauseState(PostInfo* pPostInfo);
bool AddPar(FileInfo* pFileInfo, bool bDeleted);
bool RequestMorePars(NZBInfo* pNZBInfo, const char* szParFilename, int iBlockNeeded, int* pBlockFound);
void FindPars(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, const char* szParFilename,
Blocks* pBlocks, bool bStrictParName, bool bExactParName, int* pBlockFound);
void UpdateParCheckProgress();
void UpdateParRenameProgress();
void StartParCheckJob(PostInfo* pPostInfo);
void StartParRenameJob(PostInfo* pPostInfo);
void Stop();

View File

@@ -35,8 +35,8 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <fstream>
#ifdef WIN32
#include <par2cmdline.h>
#include <par2repairer.h>
@@ -79,7 +79,7 @@ ParRenamer::ParRenamer()
{
debug("Creating ParRenamer");
m_eStatus = psUnknown;
m_eStatus = psFailed;
m_szDestDir = NULL;
m_szInfoName = NULL;
m_szProgressLabel = (char*)malloc(1024);
@@ -91,14 +91,8 @@ ParRenamer::~ParRenamer()
{
debug("Destroying ParRenamer");
if (m_szDestDir)
{
free(m_szDestDir);
}
if (m_szInfoName)
{
free(m_szInfoName);
}
free(m_szDestDir);
free(m_szInfoName);
free(m_szProgressLabel);
Cleanup();
@@ -106,37 +100,36 @@ ParRenamer::~ParRenamer()
void ParRenamer::Cleanup()
{
for (FileHashList::iterator it = m_fileHashList.begin(); it != m_fileHashList.end(); it++)
ClearHashList();
for (DirList::iterator it = m_DirList.begin(); it != m_DirList.end(); it++)
{
free(*it);
}
m_DirList.clear();
}
void ParRenamer::ClearHashList()
{
for (FileHashList::iterator it = m_FileHashList.begin(); it != m_FileHashList.end(); it++)
{
delete *it;
}
m_fileHashList.clear();
m_FileHashList.clear();
}
void ParRenamer::SetDestDir(const char * szDestDir)
{
if (m_szDestDir)
{
free(m_szDestDir);
}
free(m_szDestDir);
m_szDestDir = strdup(szDestDir);
}
void ParRenamer::SetInfoName(const char * szInfoName)
{
if (m_szInfoName)
{
free(m_szInfoName);
}
free(m_szInfoName);
m_szInfoName = strdup(szInfoName);
}
void ParRenamer::SetStatus(EStatus eStatus)
{
m_eStatus = eStatus;
Notify(NULL);
}
void ParRenamer::Cancel()
{
m_bCancelled = true;
@@ -146,48 +139,85 @@ void ParRenamer::Run()
{
Cleanup();
m_bCancelled = false;
m_iFileCount = 0;
m_iCurFile = 0;
m_iRenamedCount = 0;
SetStatus(psUnknown);
m_eStatus = psFailed;
snprintf(m_szProgressLabel, 1024, "Checking renamed files for %s", m_szInfoName);
m_szProgressLabel[1024-1] = '\0';
m_iStageProgress = 0;
UpdateProgress();
LoadParFiles();
CheckFiles();
BuildDirList(m_szDestDir);
for (DirList::iterator it = m_DirList.begin(); it != m_DirList.end(); it++)
{
char* szDestDir = *it;
debug("Checking %s", szDestDir);
ClearHashList();
LoadParFiles(szDestDir);
CheckFiles(szDestDir);
}
if (m_bCancelled)
{
warn("Renaming cancelled for %s", m_szInfoName);
SetStatus(psFailed);
PrintMessage(Message::mkWarning, "Renaming cancelled for %s", m_szInfoName);
}
else if (m_iRenamedCount > 0)
{
info("Successfully renamed %i file(s) for %s", m_iRenamedCount, m_szInfoName);
SetStatus(psFinished);
PrintMessage(Message::mkInfo, "Successfully renamed %i file(s) for %s", m_iRenamedCount, m_szInfoName);
m_eStatus = psSuccess;
}
else
{
info("Could not rename any files for %s", m_szInfoName);
SetStatus(psFailed);
PrintMessage(Message::mkInfo, "No renamed files found for %s", m_szInfoName);
}
Cleanup();
Completed();
}
void ParRenamer::LoadParFiles()
void ParRenamer::BuildDirList(const char* szDestDir)
{
m_DirList.push_back(strdup(szDestDir));
char* szFullFilename = (char*)malloc(1024);
DirBrowser* pDirBrowser = new DirBrowser(szDestDir);
while (const char* filename = pDirBrowser->Next())
{
if (strcmp(filename, ".") && strcmp(filename, "..") && !m_bCancelled)
{
snprintf(szFullFilename, 1024, "%s%c%s", szDestDir, PATH_SEPARATOR, filename);
szFullFilename[1024-1] = '\0';
if (Util::DirectoryExists(szFullFilename))
{
BuildDirList(szFullFilename);
}
else
{
m_iFileCount++;
}
}
}
free(szFullFilename);
delete pDirBrowser;
}
void ParRenamer::LoadParFiles(const char* szDestDir)
{
ParCoordinator::FileList parFileList;
ParCoordinator::FindMainPars(m_szDestDir, &parFileList);
ParCoordinator::FindMainPars(szDestDir, &parFileList);
for (ParCoordinator::FileList::iterator it = parFileList.begin(); it != parFileList.end(); it++)
{
char* szParFilename = *it;
char szFullParFilename[1024];
snprintf(szFullParFilename, 1024, "%s%c%s", m_szDestDir, PATH_SEPARATOR, szParFilename);
snprintf(szFullParFilename, 1024, "%s%c%s", szDestDir, PATH_SEPARATOR, szParFilename);
szFullParFilename[1024-1] = '\0';
LoadParFile(szFullParFilename);
@@ -202,7 +232,7 @@ void ParRenamer::LoadParFile(const char* szParFilename)
if (!pRepairer->LoadPacketsFromFile(szParFilename))
{
warn("Could not load par2-file %s", szParFilename);
PrintMessage(Message::mkWarning, "Could not load par2-file %s", szParFilename);
delete pRepairer;
return;
}
@@ -215,47 +245,39 @@ void ParRenamer::LoadParFile(const char* szParFilename)
}
Par2RepairerSourceFile* sourceFile = (*it).second;
m_fileHashList.push_back(new FileHash(sourceFile->GetDescriptionPacket()->FileName().c_str(),
m_FileHashList.push_back(new FileHash(sourceFile->GetDescriptionPacket()->FileName().c_str(),
sourceFile->GetDescriptionPacket()->Hash16k().print().c_str()));
}
delete pRepairer;
}
void ParRenamer::CheckFiles()
void ParRenamer::CheckFiles(const char* szDestDir)
{
int iFileCount = 0;
DirBrowser dir2(m_szDestDir);
while (const char* filename = dir2.Next())
{
if (strcmp(filename, ".") && strcmp(filename, "..") && !m_bCancelled)
{
iFileCount++;
}
}
int iCurFile = 0;
DirBrowser dir(m_szDestDir);
DirBrowser dir(szDestDir);
while (const char* filename = dir.Next())
{
if (strcmp(filename, ".") && strcmp(filename, "..") && !m_bCancelled)
{
char szFullFilename[1024];
snprintf(szFullFilename, 1024, "%s%c%s", m_szDestDir, PATH_SEPARATOR, filename);
snprintf(szFullFilename, 1024, "%s%c%s", szDestDir, PATH_SEPARATOR, filename);
szFullFilename[1024-1] = '\0';
snprintf(m_szProgressLabel, 1024, "Checking file %s", filename);
m_szProgressLabel[1024-1] = '\0';
m_iStageProgress = iCurFile * 1000 / iFileCount;
UpdateProgress();
iCurFile++;
CheckFile(szFullFilename);
if (!Util::DirectoryExists(szFullFilename))
{
snprintf(m_szProgressLabel, 1024, "Checking file %s", filename);
m_szProgressLabel[1024-1] = '\0';
m_iStageProgress = m_iCurFile * 1000 / m_iFileCount;
UpdateProgress();
m_iCurFile++;
CheckFile(szDestDir, szFullFilename);
}
}
}
}
void ParRenamer::CheckFile(const char* szFilename)
void ParRenamer::CheckFile(const char* szDestDir, const char* szFilename)
{
debug("Computing hash for %s", szFilename);
@@ -264,7 +286,7 @@ void ParRenamer::CheckFile(const char* szFilename)
FILE* pFile = fopen(szFilename, "rb");
if (!pFile)
{
error("Could not open file %s", szFilename);
PrintMessage(Message::mkError, "Could not open file %s", szFilename);
return;
}
@@ -276,7 +298,7 @@ void ParRenamer::CheckFile(const char* szFilename)
int iError = ferror(pFile);
if (iReadBytes != iBlockSize && iError)
{
error("Could not read file %s", szFilename);
PrintMessage(Message::mkError, "Could not read file %s", szFilename);
return;
}
@@ -291,7 +313,7 @@ void ParRenamer::CheckFile(const char* szFilename)
debug("file: %s; hash16k: %s", Util::BaseFileName(szFilename), hash16k.print().c_str());
for (FileHashList::iterator it = m_fileHashList.begin(); it != m_fileHashList.end(); it++)
for (FileHashList::iterator it = m_FileHashList.begin(); it != m_FileHashList.end(); it++)
{
FileHash* pFileHash = *it;
if (!strcmp(pFileHash->GetHash(), hash16k.print().c_str()))
@@ -299,19 +321,19 @@ void ParRenamer::CheckFile(const char* szFilename)
debug("Found correct filename: %s", pFileHash->GetFilename());
char szDstFilename[1024];
snprintf(szDstFilename, 1024, "%s%c%s", m_szDestDir, PATH_SEPARATOR, pFileHash->GetFilename());
snprintf(szDstFilename, 1024, "%s%c%s", szDestDir, PATH_SEPARATOR, pFileHash->GetFilename());
szDstFilename[1024-1] = '\0';
if (!Util::FileExists(szDstFilename))
{
info("Renaming %s to %s", Util::BaseFileName(szFilename), pFileHash->GetFilename());
PrintMessage(Message::mkInfo, "Renaming %s to %s", Util::BaseFileName(szFilename), pFileHash->GetFilename());
if (Util::MoveFile(szFilename, szDstFilename))
{
m_iRenamedCount++;
}
else
{
error("Could not rename %s to %s", szFilename, szDstFilename);
PrintMessage(Message::mkError, "Could not rename %s to %s", szFilename, szDstFilename);
}
}

View File

@@ -31,16 +31,15 @@
#include <deque>
#include "Thread.h"
#include "Observer.h"
#include "Log.h"
class ParRenamer : public Thread, public Subject
class ParRenamer : public Thread
{
public:
enum EStatus
{
psUnknown,
psFailed,
psFinished
psSuccess
};
class FileHash
@@ -57,6 +56,8 @@ public:
};
typedef std::deque<FileHash*> FileHashList;
typedef std::deque<char*> DirList;
private:
char* m_szInfoName;
@@ -65,17 +66,25 @@ private:
char* m_szProgressLabel;
int m_iStageProgress;
bool m_bCancelled;
FileHashList m_fileHashList;
DirList m_DirList;
FileHashList m_FileHashList;
int m_iFileCount;
int m_iCurFile;
int m_iRenamedCount;
void Cleanup();
void LoadParFiles();
void ClearHashList();
void BuildDirList(const char* szDestDir);
void CheckDir(const char* szDestDir);
void LoadParFiles(const char* szDestDir);
void LoadParFile(const char* szParFilename);
void CheckFiles();
void CheckFile(const char* szFilename);
void CheckFiles(const char* szDestDir);
void CheckFile(const char* szDestDir, const char* szFilename);
protected:
virtual void UpdateProgress() {}
virtual void Completed() {}
virtual void PrintMessage(Message::EKind eKind, const char* szFormat, ...) {}
const char* GetProgressLabel() { return m_szProgressLabel; }
int GetStageProgress() { return m_iStageProgress; }

View File

File diff suppressed because it is too large Load Diff

View File

@@ -31,12 +31,13 @@
#include "Thread.h"
#include "Observer.h"
#include "DownloadInfo.h"
#include "Scanner.h"
#include "ParCoordinator.h"
#include "DupeCoordinator.h"
class PrePostProcessor : public Thread
{
public:
// NOTE: changes to this enum must be synced with "eRemoteEditAction" in unit "MessageBase.h"
enum EEditAction
{
eaPostMoveOffset = 51, // move post to m_iOffset relative to the current position in post-queue
@@ -44,8 +45,17 @@ public:
eaPostMoveBottom,
eaPostDelete,
eaHistoryDelete,
eaHistoryFinalDelete,
eaHistoryReturn,
eaHistoryProcess
eaHistoryProcess,
eaHistoryRedownload,
eaHistorySetParameter,
eaHistorySetDupeKey,
eaHistorySetDupeScore,
eaHistorySetDupeMode,
eaHistorySetDupeBackup,
eaHistoryMarkBad,
eaHistoryMarkGood
};
private:
@@ -63,26 +73,34 @@ private:
protected:
virtual bool PauseDownload() { return m_pOwner->PauseDownload(); }
virtual bool UnpauseDownload() { return m_pOwner->UnpauseDownload(); }
friend class PrePostProcessor;
};
class PostDupeCoordinator: public DupeCoordinator
{
private:
PrePostProcessor* m_pOwner;
protected:
virtual void HistoryRedownload(DownloadQueue* pDownloadQueue, HistoryInfo* pHistoryInfo);
virtual void DeleteQueuedFile(const char* szQueuedFile) { m_pOwner->DeleteQueuedFile(szQueuedFile); }
friend class PrePostProcessor;
};
private:
PostParCoordinator m_ParCoordinator;
PostDupeCoordinator m_DupeCoordinator;
QueueCoordinatorObserver m_QueueCoordinatorObserver;
bool m_bHasMoreJobs;
bool m_bPostScript;
bool m_bSchedulerPauseChanged;
bool m_bSchedulerPause;
bool m_bPostPause;
Scanner m_Scanner;
const char* m_szPauseReason;
bool IsNZBFileCompleted(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo,
bool bIgnorePausedPars, bool bCheckPostQueue, bool bAllowOnlyOneDeleted);
bool bIgnorePausedPars, bool bAllowOnlyOneDeleted);
void CheckPostQueue();
void JobCompleted(DownloadQueue* pDownloadQueue, PostInfo* pPostInfo);
void StartProcessJob(DownloadQueue* pDownloadQueue, PostInfo* pPostInfo);
void StartJob(DownloadQueue* pDownloadQueue, PostInfo* pPostInfo);
void SaveQueue(DownloadQueue* pDownloadQueue);
void SanitisePostQueue(PostQueue* pPostQueue);
void CheckDiskSpace();
@@ -91,20 +109,25 @@ private:
void UpdatePauseState(bool bNeedPause, const char* szReason);
bool PauseDownload();
bool UnpauseDownload();
void NZBFound(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo);
void NZBAdded(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo);
void NZBDownloaded(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo);
void NZBDeleted(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo);
void NZBCompleted(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, bool bSaveQueue);
bool CreatePostJobs(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, bool bParCheck, bool bUnpackOrScript, bool bAddTop);
void DeleteQueuedFile(const char* szQueuedFile);
NZBInfo* MergeGroups(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo);
int FindGroupID(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo);
bool PostQueueMove(IDList* pIDList, EEditAction eAction, int iOffset);
bool PostQueueDelete(IDList* pIDList);
bool HistoryDelete(IDList* pIDList);
bool HistoryReturn(IDList* pIDList, bool bReprocess);
bool HistoryEdit(IDList* pIDList, EEditAction eAction, int iOffset, const char* szText);
void HistoryDelete(DownloadQueue* pDownloadQueue, HistoryList::iterator itHistory, HistoryInfo* pHistoryInfo, bool bFinal);
void HistoryReturn(DownloadQueue* pDownloadQueue, HistoryList::iterator itHistory, HistoryInfo* pHistoryInfo, bool bReprocess);
void HistoryRedownload(DownloadQueue* pDownloadQueue, HistoryList::iterator itHistory, HistoryInfo* pHistoryInfo, bool bRestorePauseState);
void HistorySetParameter(HistoryInfo* pHistoryInfo, const char* szText);
void HistorySetDupeParam(HistoryInfo* pHistoryInfo, EEditAction eAction, const char* szText);
void HistoryTransformToDup(DownloadQueue* pDownloadQueue, HistoryInfo* pHistoryInfo, int rindex);
void CheckHistory();
void Cleanup();
FileInfo* GetQueueGroup(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo);
void CheckHistory();
void DeletePostThread(PostInfo* pPostInfo);
public:
@@ -114,8 +137,7 @@ public:
virtual void Stop();
void QueueCoordinatorUpdate(Subject* Caller, void* Aspect);
bool HasMoreJobs() { return m_bHasMoreJobs; }
void ScanNZBDir(bool bSyncMode);
bool QueueEditList(IDList* pIDList, EEditAction eAction, int iOffset);
bool QueueEditList(IDList* pIDList, EEditAction eAction, int iOffset, const char* szText);
};
#endif

View File

@@ -34,12 +34,13 @@
#include <stdlib.h>
#include <string.h>
#include <cstdio>
#include <stdio.h>
#include <sys/stat.h>
#ifndef WIN32
#include <unistd.h>
#include <sys/time.h>
#endif
#include <algorithm>
#include "nzbget.h"
#include "QueueCoordinator.h"
@@ -67,6 +68,7 @@ QueueCoordinator::QueueCoordinator()
m_tStartDownload = 0;
m_tPausedFrom = 0;
m_bStandBy = true;
m_iServerConfigGeneration = 0;
YDecoder::Init();
}
@@ -101,6 +103,13 @@ void QueueCoordinator::Run()
m_mutexDownloadQueue.Lock();
if (g_pOptions->GetServerMode())
{
g_pDiskState->LoadStats(g_pServerPool->GetServers());
// currently there are no any stats but we need to save current server list into diskstate
g_pDiskState->SaveStats(g_pServerPool->GetServers());
}
if (g_pOptions->GetServerMode() && g_pOptions->GetSaveQueue() && g_pDiskState->DownloadQueueExists())
{
if (g_pOptions->GetReloadQueue())
@@ -117,6 +126,7 @@ void QueueCoordinator::Run()
m_mutexDownloadQueue.Unlock();
AdjustDownloadsLimit();
m_tStartServer = time(NULL);
m_tLastCheck = m_tStartServer;
bool bWasStandBy = true;
@@ -125,6 +135,7 @@ void QueueCoordinator::Run()
while (!IsStopped())
{
bool bDownloadsChecked = false;
if (!(g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2()))
{
NNTPConnection* pConnection = g_pServerPool->GetConnection(0, NULL, NULL);
@@ -133,24 +144,32 @@ void QueueCoordinator::Run()
// start download for next article
FileInfo* pFileInfo;
ArticleInfo* pArticleInfo;
bool bFreeConnection = false;
m_mutexDownloadQueue.Lock();
bool bHasMoreArticles = GetNextArticle(pFileInfo, pArticleInfo);
bArticeDownloadsRunning = !m_ActiveDownloads.empty();
bDownloadsChecked = true;
m_bHasMoreJobs = bHasMoreArticles || bArticeDownloadsRunning;
if (bHasMoreArticles && !IsStopped() && Thread::GetThreadCount() < g_pOptions->GetThreadLimit())
if (bHasMoreArticles && !IsStopped() && (int)m_ActiveDownloads.size() < m_iDownloadsLimit)
{
StartArticleDownload(pFileInfo, pArticleInfo, pConnection);
bArticeDownloadsRunning = true;
}
else
{
g_pServerPool->FreeConnection(pConnection, false);
bFreeConnection = true;
}
m_mutexDownloadQueue.Unlock();
if (bFreeConnection)
{
g_pServerPool->FreeConnection(pConnection, false);
}
}
}
else
if (!bDownloadsChecked)
{
m_mutexDownloadQueue.Lock();
bArticeDownloadsRunning = !m_ActiveDownloads.empty();
@@ -181,6 +200,7 @@ void QueueCoordinator::Run()
ResetHangingDownloads();
iResetCounter = 0;
AdjustStartTime();
AdjustDownloadsLimit();
}
}
@@ -200,16 +220,94 @@ void QueueCoordinator::Run()
debug("Exiting QueueCoordinator-loop");
}
/*
* Compute maximum number of allowed download threads
**/
void QueueCoordinator::AdjustDownloadsLimit()
{
if (m_iServerConfigGeneration == g_pServerPool->GetGeneration())
{
return;
}
// two extra threads for completing files (when connections are not needed)
int iDownloadsLimit = 2;
// allow one thread per 0-level (main) and 1-level (backup) server connection
for (Servers::iterator it = g_pServerPool->GetServers()->begin(); it != g_pServerPool->GetServers()->end(); it++)
{
NewsServer* pNewsServer = *it;
if ((pNewsServer->GetNormLevel() == 0 || pNewsServer->GetNormLevel() == 1) && pNewsServer->GetActive())
{
iDownloadsLimit += pNewsServer->GetMaxConnections();
}
}
m_iDownloadsLimit = iDownloadsLimit;
}
void QueueCoordinator::AddNZBFileToQueue(NZBFile* pNZBFile, bool bAddFirst)
{
debug("Adding NZBFile to queue");
m_mutexDownloadQueue.Lock();
Aspect foundAspect = { eaNZBFileFound, &m_DownloadQueue, pNZBFile->GetNZBInfo(), NULL };
Notify(&foundAspect);
if (pNZBFile->GetNZBInfo()->GetDeleteStatus() != NZBInfo::dsNone)
{
bool bAllPaused = !pNZBFile->GetFileInfos()->empty();
for (NZBFile::FileInfos::iterator it = pNZBFile->GetFileInfos()->begin(); it != pNZBFile->GetFileInfos()->end(); it++)
{
FileInfo* pFileInfo = *it;
bAllPaused &= pFileInfo->GetPaused();
if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode())
{
g_pDiskState->DiscardFile(pFileInfo);
}
}
pNZBFile->GetNZBInfo()->SetDeletePaused(bAllPaused);
}
if (pNZBFile->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsManual)
{
m_mutexDownloadQueue.Unlock(); // UNLOCK
return;
}
m_DownloadQueue.GetNZBInfoList()->Add(pNZBFile->GetNZBInfo());
if (pNZBFile->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsNone)
{
AddFileInfosToFileQueue(pNZBFile, m_DownloadQueue.GetFileQueue(), bAddFirst);
}
char szNZBName[1024];
strncpy(szNZBName, pNZBFile->GetNZBInfo()->GetName(), sizeof(szNZBName)-1);
Aspect aspect = { eaNZBFileAdded, &m_DownloadQueue, pNZBFile->GetNZBInfo(), NULL };
Notify(&aspect);
if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode())
{
g_pDiskState->SaveDownloadQueue(&m_DownloadQueue);
}
m_mutexDownloadQueue.Unlock();
if (pNZBFile->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsNone)
{
info("Collection %s added to queue", szNZBName);
}
}
void QueueCoordinator::AddFileInfosToFileQueue(NZBFile* pNZBFile, FileQueue* pFileQueue, bool bAddFirst)
{
debug("Adding NZBFile to queue");
FileQueue tmpFileQueue;
tmpFileQueue.clear();
FileQueue DupeList;
DupeList.clear();
int index1 = 0;
for (NZBFile::FileInfos::iterator it = pNZBFile->GetFileInfos()->begin(); it != pNZBFile->GetFileInfos()->end(); it++)
@@ -217,14 +315,9 @@ void QueueCoordinator::AddNZBFileToQueue(NZBFile* pNZBFile, bool bAddFirst)
index1++;
FileInfo* pFileInfo = *it;
if (g_pOptions->GetDupeCheck())
if (g_pOptions->GetDupeCheck() && !pNZBFile->GetNZBInfo()->GetDupeMode())
{
bool dupe = false;
if (IsDupe(pFileInfo))
{
warn("File \"%s\" seems to be duplicate, skipping", pFileInfo->GetFilename());
dupe = true;
}
int index2 = 0;
for (NZBFile::FileInfos::iterator it2 = pNZBFile->GetFileInfos()->begin(); it2 != pNZBFile->GetFileInfos()->end(); it2++)
{
@@ -235,7 +328,7 @@ void QueueCoordinator::AddNZBFileToQueue(NZBFile* pNZBFile, bool bAddFirst)
(pFileInfo->GetSize() < pFileInfo2->GetSize() ||
(pFileInfo->GetSize() == pFileInfo2->GetSize() && index2 < index1)))
{
warn("File \"%s\" appears twice in nzb-request, adding only the biggest file", pFileInfo->GetFilename());
warn("File \"%s\" appears twice in collection, adding only the biggest file", pFileInfo->GetFilename());
dupe = true;
break;
}
@@ -243,6 +336,7 @@ void QueueCoordinator::AddNZBFileToQueue(NZBFile* pNZBFile, bool bAddFirst)
if (dupe)
{
DupeList.push_back(pFileInfo);
StatFileInfo(pFileInfo, false);
continue;
}
}
@@ -261,11 +355,11 @@ void QueueCoordinator::AddNZBFileToQueue(NZBFile* pNZBFile, bool bAddFirst)
{
if (bAddFirst)
{
m_DownloadQueue.GetFileQueue()->push_front(*it);
pFileQueue->push_front(*it);
}
else
{
m_DownloadQueue.GetFileQueue()->push_back(*it);
pFileQueue->push_back(*it);
}
}
@@ -279,19 +373,7 @@ void QueueCoordinator::AddNZBFileToQueue(NZBFile* pNZBFile, bool bAddFirst)
delete pFileInfo;
}
m_DownloadQueue.GetNZBInfoList()->Add(pNZBFile->GetNZBInfo());
pNZBFile->DetachFileInfos();
Aspect aspect = { eaNZBFileAdded, &m_DownloadQueue, pNZBFile->GetNZBInfo(), NULL };
Notify(&aspect);
if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode())
{
g_pDiskState->SaveDownloadQueue(&m_DownloadQueue);
}
m_mutexDownloadQueue.Unlock();
}
/*
@@ -430,6 +512,8 @@ bool QueueCoordinator::DeleteQueueEntry(FileInfo* pFileInfo)
}
if (!hasDownloads)
{
StatFileInfo(pFileInfo, false);
Aspect aspect = { eaFileDeleted, &m_DownloadQueue, pFileInfo->GetNZBInfo(), pFileInfo };
Notify(&aspect);
@@ -530,10 +614,7 @@ bool QueueCoordinator::GetNextArticle(FileInfo* &pFileInfo, ArticleInfo* &pArtic
}
}
if (pCheckedFiles)
{
free(pCheckedFiles);
}
free(pCheckedFiles);
return bOK;
}
@@ -548,7 +629,11 @@ void QueueCoordinator::StartArticleDownload(FileInfo* pFileInfo, ArticleInfo* pA
pArticleDownloader->SetFileInfo(pFileInfo);
pArticleDownloader->SetArticleInfo(pArticleInfo);
pArticleDownloader->SetConnection(pConnection);
BuildArticleFilename(pArticleDownloader, pFileInfo, pArticleInfo);
char szInfoName[1024];
snprintf(szInfoName, 1024, "%s%c%s [%i/%i]", pFileInfo->GetNZBInfo()->GetName(), (int)PATH_SEPARATOR, pFileInfo->GetFilename(), pArticleInfo->GetPartNumber(), (int)pFileInfo->GetArticles()->size());
szInfoName[1024-1] = '\0';
pArticleDownloader->SetInfoName(szInfoName);
pArticleInfo->SetStatus(ArticleInfo::aiRunning);
pFileInfo->SetActiveDownloads(pFileInfo->GetActiveDownloads() + 1);
@@ -557,45 +642,6 @@ void QueueCoordinator::StartArticleDownload(FileInfo* pFileInfo, ArticleInfo* pA
pArticleDownloader->Start();
}
void QueueCoordinator::BuildArticleFilename(ArticleDownloader* pArticleDownloader, FileInfo* pFileInfo, ArticleInfo* pArticleInfo)
{
char name[1024];
snprintf(name, 1024, "%s%i.%03i", g_pOptions->GetTempDir(), pFileInfo->GetID(), pArticleInfo->GetPartNumber());
name[1024-1] = '\0';
pArticleInfo->SetResultFilename(name);
char tmpname[1024];
snprintf(tmpname, 1024, "%s.tmp", name);
tmpname[1024-1] = '\0';
pArticleDownloader->SetTempFilename(tmpname);
snprintf(name, 1024, "%s%c%s [%i/%i]", pFileInfo->GetNZBInfo()->GetName(), (int)PATH_SEPARATOR, pFileInfo->GetFilename(), pArticleInfo->GetPartNumber(), pFileInfo->GetArticles()->size());
name[1024-1] = '\0';
pArticleDownloader->SetInfoName(name);
if (g_pOptions->GetDirectWrite())
{
pFileInfo->LockOutputFile();
if (pFileInfo->GetOutputFilename())
{
strncpy(name, pFileInfo->GetOutputFilename(), 1024);
name[1024-1] = '\0';
}
else
{
snprintf(name, 1024, "%s%c%i.out.tmp", pFileInfo->GetNZBInfo()->GetDestDir(), (int)PATH_SEPARATOR, pFileInfo->GetID());
name[1024-1] = '\0';
pFileInfo->SetOutputFilename(name);
}
pFileInfo->UnlockOutputFile();
pArticleDownloader->SetOutputFilename(name);
}
}
DownloadQueue* QueueCoordinator::LockQueue()
{
m_mutexDownloadQueue.Lock();
@@ -634,10 +680,18 @@ void QueueCoordinator::ArticleCompleted(ArticleDownloader* pArticleDownloader)
if (pArticleDownloader->GetStatus() == ArticleDownloader::adFinished)
{
pArticleInfo->SetStatus(ArticleInfo::aiFinished);
pFileInfo->SetSuccessSize(pFileInfo->GetSuccessSize() + pArticleInfo->GetSize());
pFileInfo->GetNZBInfo()->SetCurrentSuccessSize(pFileInfo->GetNZBInfo()->GetCurrentSuccessSize() + pArticleInfo->GetSize());
pFileInfo->GetNZBInfo()->SetParCurrentSuccessSize(pFileInfo->GetNZBInfo()->GetParCurrentSuccessSize() + (pFileInfo->GetParFile() ? pArticleInfo->GetSize() : 0));
pFileInfo->SetSuccessArticles(pFileInfo->GetSuccessArticles() + 1);
}
else if (pArticleDownloader->GetStatus() == ArticleDownloader::adFailed)
{
pArticleInfo->SetStatus(ArticleInfo::aiFailed);
pFileInfo->SetFailedSize(pFileInfo->GetFailedSize() + pArticleInfo->GetSize());
pFileInfo->GetNZBInfo()->SetCurrentFailedSize(pFileInfo->GetNZBInfo()->GetCurrentFailedSize() + pArticleInfo->GetSize());
pFileInfo->GetNZBInfo()->SetParCurrentFailedSize(pFileInfo->GetNZBInfo()->GetParCurrentFailedSize() + (pFileInfo->GetParFile() ? pArticleInfo->GetSize() : 0));
pFileInfo->SetFailedArticles(pFileInfo->GetFailedArticles() + 1);
}
else if (pArticleDownloader->GetStatus() == ArticleDownloader::adRetry)
{
@@ -650,6 +704,7 @@ void QueueCoordinator::ArticleCompleted(ArticleDownloader* pArticleDownloader)
pFileInfo->SetRemainingSize(pFileInfo->GetRemainingSize() - pArticleInfo->GetSize());
pFileInfo->SetCompleted(pFileInfo->GetCompleted() + 1);
fileCompleted = (int)pFileInfo->GetArticles()->size() == pFileInfo->GetCompleted();
pFileInfo->GetNZBInfo()->GetServerStats()->Add(pArticleDownloader->GetServerStats());
}
if (!pFileInfo->GetFilenameConfirmed() &&
@@ -658,33 +713,20 @@ void QueueCoordinator::ArticleCompleted(ArticleDownloader* pArticleDownloader)
{
pFileInfo->SetFilename(pArticleDownloader->GetArticleFilename());
pFileInfo->SetFilenameConfirmed(true);
if (g_pOptions->GetDupeCheck() && pFileInfo->IsDupe(pFileInfo->GetFilename()))
if (g_pOptions->GetDupeCheck() &&
pFileInfo->GetNZBInfo()->GetDupeMode() != dmForce &&
!pFileInfo->GetNZBInfo()->GetManyDupeFiles() &&
Util::FileExists(pFileInfo->GetNZBInfo()->GetDestDir(), pFileInfo->GetFilename()))
{
warn("File \"%s\" seems to be duplicate, cancelling download and deleting file from queue", pFileInfo->GetFilename());
fileCompleted = false;
pFileInfo->SetAutoDeleted(true);
DeleteQueueEntry(pFileInfo);
}
}
bool deleteFileObj = false;
if (pFileInfo->GetDeleted())
{
int cnt = 0;
for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++)
{
if ((*it)->GetFileInfo() == pFileInfo)
{
cnt++;
}
}
if (cnt == 1)
{
// this was the last Download for a file deleted from queue
deleteFileObj = true;
}
}
if (fileCompleted && !IsStopped() && !pFileInfo->GetDeleted())
{
// all jobs done
@@ -694,16 +736,22 @@ void QueueCoordinator::ArticleCompleted(ArticleDownloader* pArticleDownloader)
deleteFileObj = true;
}
// delete Download from Queue
CheckHealth(pFileInfo);
bool hasOtherDownloaders = false;
for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++)
{
ArticleDownloader* pa = *it;
if (pa == pArticleDownloader)
ArticleDownloader* pDownloader = *it;
if (pDownloader != pArticleDownloader && pDownloader->GetFileInfo() == pFileInfo)
{
m_ActiveDownloads.erase(it);
hasOtherDownloaders = true;
break;
}
}
deleteFileObj |= pFileInfo->GetDeleted() && !hasOtherDownloaders;
// remove downloader from downloader list
m_ActiveDownloads.erase(std::find(m_ActiveDownloads.begin(), m_ActiveDownloads.end(), pArticleDownloader));
pFileInfo->SetActiveDownloads(pFileInfo->GetActiveDownloads() - 1);
@@ -713,6 +761,8 @@ void QueueCoordinator::ArticleCompleted(ArticleDownloader* pArticleDownloader)
// delete File from Queue
pFileInfo->SetDeleted(true);
StatFileInfo(pFileInfo, fileCompleted);
Aspect aspect = { fileCompleted && !fileDeleted ? eaFileCompleted : eaFileDeleted, &m_DownloadQueue, pFileInfo->GetNZBInfo(), pFileInfo };
Notify(&aspect);
@@ -726,6 +776,40 @@ void QueueCoordinator::ArticleCompleted(ArticleDownloader* pArticleDownloader)
m_mutexDownloadQueue.Unlock();
}
void QueueCoordinator::StatFileInfo(FileInfo* pFileInfo, bool bCompleted)
{
NZBInfo* pNZBInfo = pFileInfo->GetNZBInfo();
if (bCompleted || pNZBInfo->GetDeleting())
{
pNZBInfo->SetSuccessSize(pNZBInfo->GetSuccessSize() + pFileInfo->GetSuccessSize());
pNZBInfo->SetFailedSize(pNZBInfo->GetFailedSize() + pFileInfo->GetFailedSize());
pNZBInfo->SetFailedArticles(pNZBInfo->GetFailedArticles() + pFileInfo->GetFailedArticles() + pFileInfo->GetMissedArticles());
pNZBInfo->SetSuccessArticles(pNZBInfo->GetSuccessArticles() + pFileInfo->GetSuccessArticles());
if (pFileInfo->GetParFile())
{
pNZBInfo->SetParSuccessSize(pNZBInfo->GetParSuccessSize() + pFileInfo->GetSuccessSize());
pNZBInfo->SetParFailedSize(pNZBInfo->GetParFailedSize() + pFileInfo->GetFailedSize());
}
}
else if (!pNZBInfo->GetDeleting() && !pNZBInfo->GetParCleanup())
{
// file deleted but not the whole nzb and not par-cleanup
pNZBInfo->SetFileCount(pNZBInfo->GetFileCount() - 1);
pNZBInfo->SetSize(pNZBInfo->GetSize() - pFileInfo->GetSize());
pNZBInfo->SetCurrentSuccessSize(pNZBInfo->GetCurrentSuccessSize() - pFileInfo->GetSuccessSize());
pNZBInfo->SetFailedSize(pNZBInfo->GetFailedSize() - pFileInfo->GetMissedSize());
pNZBInfo->SetCurrentFailedSize(pNZBInfo->GetCurrentFailedSize() - pFileInfo->GetFailedSize() - pFileInfo->GetMissedSize());
pNZBInfo->SetTotalArticles(pNZBInfo->GetTotalArticles() - pFileInfo->GetTotalArticles());
if (pFileInfo->GetParFile())
{
pNZBInfo->SetParSize(pNZBInfo->GetParSize() - pFileInfo->GetSize());
pNZBInfo->SetParCurrentSuccessSize(pNZBInfo->GetParCurrentSuccessSize() - pFileInfo->GetSuccessSize());
pNZBInfo->SetParFailedSize(pNZBInfo->GetParFailedSize() - pFileInfo->GetMissedSize());
pNZBInfo->SetParCurrentFailedSize(pNZBInfo->GetParCurrentFailedSize() - pFileInfo->GetFailedSize() - pFileInfo->GetMissedSize());
}
}
}
void QueueCoordinator::DeleteFileInfo(FileInfo* pFileInfo, bool bCompleted)
{
for (FileQueue::iterator it = m_DownloadQueue.GetFileQueue()->begin(); it != m_DownloadQueue.GetFileQueue()->end(); it++)
@@ -773,29 +857,30 @@ void QueueCoordinator::DiscardDiskFile(FileInfo* pFileInfo)
}
}
bool QueueCoordinator::IsDupe(FileInfo* pFileInfo)
void QueueCoordinator::CheckHealth(FileInfo* pFileInfo)
{
debug("Checking if the file is already queued");
// checking on disk
if (pFileInfo->IsDupe(pFileInfo->GetFilename()))
if (g_pOptions->GetHealthCheck() == Options::hcNone ||
pFileInfo->GetNZBInfo()->GetHealthPaused() ||
pFileInfo->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsHealth ||
pFileInfo->GetNZBInfo()->CalcHealth() >= pFileInfo->GetNZBInfo()->CalcCriticalHealth())
{
return true;
return;
}
// checking in queue
for (FileQueue::iterator it = m_DownloadQueue.GetFileQueue()->begin(); it != m_DownloadQueue.GetFileQueue()->end(); it++)
if (g_pOptions->GetHealthCheck() == Options::hcPause)
{
FileInfo* pQueueEntry = *it;
if (!strcmp(pFileInfo->GetNZBInfo()->GetDestDir(), pQueueEntry->GetNZBInfo()->GetDestDir()) &&
!strcmp(pFileInfo->GetFilename(), pQueueEntry->GetFilename()) &&
pFileInfo != pQueueEntry)
{
return true;
}
warn("Pausing %s due to health %.1f%% below critical %.1f%%", pFileInfo->GetNZBInfo()->GetName(),
pFileInfo->GetNZBInfo()->CalcHealth() / 10.0, pFileInfo->GetNZBInfo()->CalcCriticalHealth() / 10.0);
pFileInfo->GetNZBInfo()->SetHealthPaused(true);
m_QueueEditor.LockedEditEntry(&m_DownloadQueue, pFileInfo->GetID(), false, QueueEditor::eaGroupPause, 0, NULL);
}
else if (g_pOptions->GetHealthCheck() == Options::hcDelete)
{
warn("Cancelling download and deleting %s due to health %.1f%% below critical %.1f%%", pFileInfo->GetNZBInfo()->GetName(),
pFileInfo->GetNZBInfo()->CalcHealth() / 10.0, pFileInfo->GetNZBInfo()->CalcCriticalHealth() / 10.0);
pFileInfo->GetNZBInfo()->SetDeleteStatus(NZBInfo::dsHealth);
m_QueueEditor.LockedEditEntry(&m_DownloadQueue, pFileInfo->GetID(), false, QueueEditor::eaGroupDelete, 0, NULL);
}
return false;
}
void QueueCoordinator::LogDebugInfo()
@@ -943,7 +1028,7 @@ void QueueCoordinator::AdjustStartTime()
if (tDiff > 60 || tDiff < 0)
{
m_tStartServer += tDiff + 1; // "1" because the method is called once per second
if (m_tStartDownload != 0)
if (m_tStartDownload != 0 && !m_bStandBy)
{
m_tStartDownload += tDiff + 1;
}
@@ -1028,7 +1113,20 @@ bool QueueCoordinator::MergeQueueEntries(NZBInfo* pDestNZBInfo, NZBInfo* pSrcNZB
}
pDestNZBInfo->SetFileCount(pDestNZBInfo->GetFileCount() + pSrcNZBInfo->GetFileCount());
pDestNZBInfo->SetFullContentHash(0);
pDestNZBInfo->SetFilteredContentHash(0);
pDestNZBInfo->SetSize(pDestNZBInfo->GetSize() + pSrcNZBInfo->GetSize());
pDestNZBInfo->SetSuccessSize(pDestNZBInfo->GetSuccessSize() + pSrcNZBInfo->GetSuccessSize());
pDestNZBInfo->SetCurrentSuccessSize(pDestNZBInfo->GetCurrentSuccessSize() + pSrcNZBInfo->GetCurrentSuccessSize());
pDestNZBInfo->SetFailedSize(pDestNZBInfo->GetFailedSize() + pSrcNZBInfo->GetFailedSize());
pDestNZBInfo->SetCurrentFailedSize(pDestNZBInfo->GetCurrentFailedSize() + pSrcNZBInfo->GetCurrentFailedSize());
pDestNZBInfo->SetParSize(pDestNZBInfo->GetParSize() + pSrcNZBInfo->GetParSize());
pDestNZBInfo->SetParSuccessSize(pDestNZBInfo->GetParSuccessSize() + pSrcNZBInfo->GetParSuccessSize());
pDestNZBInfo->SetParCurrentSuccessSize(pDestNZBInfo->GetParCurrentSuccessSize() + pSrcNZBInfo->GetParCurrentSuccessSize());
pDestNZBInfo->SetParFailedSize(pDestNZBInfo->GetParFailedSize() + pSrcNZBInfo->GetParFailedSize());
pDestNZBInfo->SetParCurrentFailedSize(pDestNZBInfo->GetParCurrentFailedSize() + pSrcNZBInfo->GetParCurrentFailedSize());
// reattach completed file items to new NZBInfo-object
for (NZBInfo::Files::iterator it = pSrcNZBInfo->GetCompletedFiles()->begin(); it != pSrcNZBInfo->GetCompletedFiles()->end(); it++)
@@ -1048,3 +1146,99 @@ bool QueueCoordinator::MergeQueueEntries(NZBInfo* pDestNZBInfo, NZBInfo* pSrcNZB
return true;
}
/*
* Creates new nzb-item out of existing files from other nzb-items.
* If any of file-items is being downloaded the command fail.
* For each file-item an event "eaFileDeleted" is fired.
*
* NOTE: DownloadQueue must be locked prior to call of this function
*/
bool QueueCoordinator::SplitQueueEntries(FileQueue* pFileList, const char* szName, NZBInfo** pNewNZBInfo)
{
if (pFileList->empty())
{
return false;
}
NZBInfo* pSrcNZBInfo = NULL;
for (FileQueue::iterator it = pFileList->begin(); it != pFileList->end(); it++)
{
FileInfo* pFileInfo = *it;
if (pFileInfo->GetActiveDownloads() > 0 || pFileInfo->GetCompleted() > 0)
{
error("Could not split %s. File is already (partially) downloaded", pFileInfo->GetFilename());
return false;
}
if (pFileInfo->GetNZBInfo()->GetPostProcess())
{
error("Could not split %s. File in post-process-stage", pFileInfo->GetFilename());
return false;
}
if (!pSrcNZBInfo)
{
pSrcNZBInfo = pFileInfo->GetNZBInfo();
}
}
NZBInfo* pNZBInfo = new NZBInfo();
pNZBInfo->Retain();
m_DownloadQueue.GetNZBInfoList()->Add(pNZBInfo);
pNZBInfo->SetFilename(pSrcNZBInfo->GetFilename());
pNZBInfo->SetName(szName);
pNZBInfo->SetCategory(pSrcNZBInfo->GetCategory());
pNZBInfo->SetFullContentHash(0);
pNZBInfo->SetFilteredContentHash(0);
pNZBInfo->BuildDestDirName();
pNZBInfo->SetQueuedFilename(pSrcNZBInfo->GetQueuedFilename());
pNZBInfo->GetParameters()->CopyFrom(pSrcNZBInfo->GetParameters());
pSrcNZBInfo->SetFullContentHash(0);
pSrcNZBInfo->SetFilteredContentHash(0);
for (FileQueue::iterator it = pFileList->begin(); it != pFileList->end(); it++)
{
FileInfo* pFileInfo = *it;
Aspect aspect = { eaFileDeleted, &m_DownloadQueue, pFileInfo->GetNZBInfo(), pFileInfo };
Notify(&aspect);
pFileInfo->SetNZBInfo(pNZBInfo);
pSrcNZBInfo->SetFileCount(pSrcNZBInfo->GetFileCount() - 1);
pSrcNZBInfo->SetSize(pSrcNZBInfo->GetSize() - pFileInfo->GetSize());
pSrcNZBInfo->SetSuccessSize(pSrcNZBInfo->GetSuccessSize() - pFileInfo->GetSuccessSize());
pSrcNZBInfo->SetCurrentSuccessSize(pSrcNZBInfo->GetCurrentSuccessSize() - pFileInfo->GetSuccessSize());
pSrcNZBInfo->SetFailedSize(pSrcNZBInfo->GetFailedSize() - pFileInfo->GetFailedSize() - pFileInfo->GetMissedSize());
pSrcNZBInfo->SetCurrentFailedSize(pSrcNZBInfo->GetCurrentFailedSize() - pFileInfo->GetFailedSize() - pFileInfo->GetMissedSize());
pNZBInfo->SetFileCount(pNZBInfo->GetFileCount() + 1);
pNZBInfo->SetSize(pNZBInfo->GetSize() + pFileInfo->GetSize());
pNZBInfo->SetSuccessSize(pNZBInfo->GetSuccessSize() + pFileInfo->GetSuccessSize());
pNZBInfo->SetCurrentSuccessSize(pNZBInfo->GetCurrentSuccessSize() + pFileInfo->GetSuccessSize());
pNZBInfo->SetFailedSize(pNZBInfo->GetFailedSize() + pFileInfo->GetFailedSize() + pFileInfo->GetMissedSize());
pNZBInfo->SetCurrentFailedSize(pNZBInfo->GetCurrentFailedSize() + pFileInfo->GetFailedSize() + pFileInfo->GetMissedSize());
if (pFileInfo->GetParFile())
{
pSrcNZBInfo->SetParSize(pSrcNZBInfo->GetParSize() - pFileInfo->GetSize());
pSrcNZBInfo->SetParSuccessSize(pSrcNZBInfo->GetParSuccessSize() - pFileInfo->GetSuccessSize());
pSrcNZBInfo->SetParCurrentSuccessSize(pSrcNZBInfo->GetParCurrentSuccessSize() - pFileInfo->GetSuccessSize());
pSrcNZBInfo->SetParFailedSize(pSrcNZBInfo->GetParFailedSize() - pFileInfo->GetFailedSize() - pFileInfo->GetMissedSize());
pSrcNZBInfo->SetParCurrentFailedSize(pSrcNZBInfo->GetParCurrentFailedSize() - pFileInfo->GetFailedSize() - pFileInfo->GetMissedSize());
pNZBInfo->SetParSize(pNZBInfo->GetParSize() + pFileInfo->GetSize());
pNZBInfo->SetParSuccessSize(pNZBInfo->GetParSuccessSize() + pFileInfo->GetSuccessSize());
pNZBInfo->SetParCurrentSuccessSize(pNZBInfo->GetParCurrentSuccessSize() + pFileInfo->GetSuccessSize());
pNZBInfo->SetParFailedSize(pNZBInfo->GetParFailedSize() + pFileInfo->GetFailedSize() + pFileInfo->GetMissedSize());
pNZBInfo->SetParCurrentFailedSize(pNZBInfo->GetParCurrentFailedSize() + pFileInfo->GetFailedSize() + pFileInfo->GetMissedSize());
}
}
pNZBInfo->Release();
*pNewNZBInfo = pNZBInfo;
return true;
}

View File

@@ -2,7 +2,7 @@
* This file is part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2010 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -43,12 +43,15 @@ class QueueCoordinator : public Thread, public Observer, public Subject, public
{
public:
typedef std::list<ArticleDownloader*> ActiveDownloads;
enum EAspectAction
{
eaNZBFileFound,
eaNZBFileAdded,
eaFileCompleted,
eaFileDeleted
};
struct Aspect
{
EAspectAction eAction;
@@ -63,6 +66,8 @@ private:
QueueEditor m_QueueEditor;
Mutex m_mutexDownloadQueue;
bool m_bHasMoreJobs;
int m_iDownloadsLimit;
int m_iServerConfigGeneration;
// statistics
static const int SPEEDMETER_SLOTS = 30;
@@ -89,14 +94,15 @@ private:
bool GetNextArticle(FileInfo* &pFileInfo, ArticleInfo* &pArticleInfo);
void StartArticleDownload(FileInfo* pFileInfo, ArticleInfo* pArticleInfo, NNTPConnection* pConnection);
void BuildArticleFilename(ArticleDownloader* pArticleDownloader, FileInfo* pFileInfo, ArticleInfo* pArticleInfo);
bool IsDupe(FileInfo* pFileInfo);
void ArticleCompleted(ArticleDownloader* pArticleDownloader);
void DeleteFileInfo(FileInfo* pFileInfo, bool bCompleted);
void StatFileInfo(FileInfo* pFileInfo, bool bCompleted);
void CheckHealth(FileInfo* pFileInfo);
void ResetHangingDownloads();
void ResetSpeedStat();
void EnterLeaveStandBy(bool bEnter);
void AdjustStartTime();
void AdjustDownloadsLimit();
public:
QueueCoordinator();
@@ -115,12 +121,14 @@ public:
DownloadQueue* LockQueue();
void UnlockQueue() ;
void AddNZBFileToQueue(NZBFile* pNZBFile, bool bAddFirst);
void AddFileInfosToFileQueue(NZBFile* pNZBFile, FileQueue* pFileQueue, bool bAddFirst);
bool HasMoreJobs() { return m_bHasMoreJobs; }
bool GetStandBy() { return m_bStandBy; }
bool DeleteQueueEntry(FileInfo* pFileInfo);
bool SetQueueEntryNZBCategory(NZBInfo* pNZBInfo, const char* szCategory);
bool SetQueueEntryNZBName(NZBInfo* pNZBInfo, const char* szName);
bool MergeQueueEntries(NZBInfo* pDestNZBInfo, NZBInfo* pSrcNZBInfo);
bool SplitQueueEntries(FileQueue* pFileList, const char* szName, NZBInfo** pNewNZBInfo);
void DiscardDiskFile(FileInfo* pFileInfo);
QueueEditor* GetQueueEditor() { return &m_QueueEditor; }

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2007-2011 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -33,8 +33,8 @@
#include <stdlib.h>
#include <string.h>
#include <cctype>
#include <cstdio>
#include <stdio.h>
#include <ctype.h>
#include <sys/stat.h>
#include <set>
#ifndef WIN32
@@ -116,7 +116,14 @@ void QueueEditor::PauseUnpauseEntry(FileInfo* pFileInfo, bool bPause)
*/
void QueueEditor::DeleteEntry(FileInfo* pFileInfo)
{
info("Deleting file %s from download queue", pFileInfo->GetFilename());
if (pFileInfo->GetNZBInfo()->GetDeleting())
{
detail("Deleting file %s from download queue", pFileInfo->GetFilename());
}
else
{
info("Deleting file %s from download queue", pFileInfo->GetFilename());
}
g_pQueueCoordinator->DeleteQueueEntry(pFileInfo);
}
@@ -221,80 +228,93 @@ bool QueueEditor::InternEditList(DownloadQueue* pDownloadQueue, IDList* pIDList,
ItemList cItemList;
PrepareList(pDownloadQueue, &cItemList, pIDList, bSmartOrder, eAction, iOffset);
if (eAction == eaFilePauseAllPars || eAction == eaFilePauseExtraPars)
switch (eAction)
{
PauseParsInGroups(&cItemList, eAction == eaFilePauseExtraPars);
}
else if (eAction == eaGroupMerge)
{
MergeGroups(pDownloadQueue, &cItemList);
}
else if (eAction == eaFileReorder)
{
ReorderFiles(pDownloadQueue, &cItemList);
}
else
{
for (ItemList::iterator it = cItemList.begin(); it != cItemList.end(); it++)
{
EditItem* pItem = *it;
switch (eAction)
case eaFilePauseAllPars:
case eaFilePauseExtraPars:
PauseParsInGroups(&cItemList, eAction == eaFilePauseExtraPars);
break;
case eaGroupMerge:
return MergeGroups(pDownloadQueue, &cItemList);
case eaFileSplit:
return SplitGroup(pDownloadQueue, &cItemList, szText);
case eaFileReorder:
ReorderFiles(pDownloadQueue, &cItemList);
break;
default:
for (ItemList::iterator it = cItemList.begin(); it != cItemList.end(); it++)
{
case eaFilePause:
PauseUnpauseEntry(pItem->m_pFileInfo, true);
break;
EditItem* pItem = *it;
switch (eAction)
{
case eaFilePause:
PauseUnpauseEntry(pItem->m_pFileInfo, true);
break;
case eaFileResume:
PauseUnpauseEntry(pItem->m_pFileInfo, false);
break;
case eaFileResume:
PauseUnpauseEntry(pItem->m_pFileInfo, false);
break;
case eaFileMoveOffset:
case eaFileMoveTop:
case eaFileMoveBottom:
MoveEntry(pDownloadQueue, pItem->m_pFileInfo, pItem->m_iOffset);
break;
case eaFileMoveOffset:
case eaFileMoveTop:
case eaFileMoveBottom:
MoveEntry(pDownloadQueue, pItem->m_pFileInfo, pItem->m_iOffset);
break;
case eaFileDelete:
DeleteEntry(pItem->m_pFileInfo);
break;
case eaFileDelete:
DeleteEntry(pItem->m_pFileInfo);
break;
case eaFileSetPriority:
SetPriorityEntry(pItem->m_pFileInfo, szText);
break;
case eaFileSetPriority:
SetPriorityEntry(pItem->m_pFileInfo, szText);
break;
case eaGroupSetCategory:
SetNZBCategory(pItem->m_pFileInfo->GetNZBInfo(), szText);
break;
case eaGroupSetCategory:
SetNZBCategory(pItem->m_pFileInfo->GetNZBInfo(), szText);
break;
case eaGroupSetName:
SetNZBName(pItem->m_pFileInfo->GetNZBInfo(), szText);
break;
case eaGroupSetName:
SetNZBName(pItem->m_pFileInfo->GetNZBInfo(), szText);
break;
case eaGroupSetParameter:
SetNZBParameter(pItem->m_pFileInfo->GetNZBInfo(), szText);
break;
case eaGroupSetDupeKey:
case eaGroupSetDupeScore:
case eaGroupSetDupeMode:
SetNZBDupeParam(pItem->m_pFileInfo->GetNZBInfo(), eAction, szText);
break;
case eaGroupPause:
case eaGroupResume:
case eaGroupDelete:
case eaGroupMoveTop:
case eaGroupMoveBottom:
case eaGroupMoveOffset:
case eaGroupPauseAllPars:
case eaGroupPauseExtraPars:
case eaGroupSetPriority:
EditGroup(pDownloadQueue, pItem->m_pFileInfo, eAction, iOffset, szText);
break;
case eaGroupSetParameter:
SetNZBParameter(pItem->m_pFileInfo->GetNZBInfo(), szText);
break;
case eaFilePauseAllPars:
case eaFilePauseExtraPars:
case eaGroupMerge:
case eaFileReorder:
// remove compiler warning "enumeration not handled in switch"
break;
case eaGroupPause:
case eaGroupResume:
case eaGroupDelete:
case eaGroupDupeDelete:
case eaGroupFinalDelete:
case eaGroupMoveTop:
case eaGroupMoveBottom:
case eaGroupMoveOffset:
case eaGroupPauseAllPars:
case eaGroupPauseExtraPars:
case eaGroupSetPriority:
EditGroup(pDownloadQueue, pItem->m_pFileInfo, eAction, iOffset, szText);
break;
case eaFilePauseAllPars:
case eaFilePauseExtraPars:
case eaGroupMerge:
case eaFileReorder:
case eaFileSplit:
// remove compiler warning "enumeration not handled in switch"
break;
}
delete pItem;
}
delete pItem;
}
}
return cItemList.size() > 0;
@@ -463,10 +483,7 @@ bool QueueEditor::BuildIDListFromNameList(DownloadQueue* pDownloadQueue, IDList*
}
}
if (pRegEx)
{
delete pRegEx;
}
delete pRegEx;
if (!bFound && (eMatchMode == mmName))
{
@@ -481,6 +498,7 @@ bool QueueEditor::EditGroup(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo,
{
IDList cIDList;
cIDList.clear();
bool bAllPaused = true;
// collecting files belonging to group
for (FileQueue::iterator it = pDownloadQueue->GetFileQueue()->begin(); it != pDownloadQueue->GetFileQueue()->end(); it++)
@@ -489,6 +507,7 @@ bool QueueEditor::EditGroup(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo,
if (pFileInfo2->GetNZBInfo() == pFileInfo->GetNZBInfo())
{
cIDList.push_back(pFileInfo2->GetID());
bAllPaused &= pFileInfo2->GetPaused();
}
}
@@ -537,15 +556,21 @@ bool QueueEditor::EditGroup(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo,
}
iOffset = iFileOffset;
}
else if (eAction == eaGroupDelete)
else if (eAction == eaGroupDelete || eAction == eaGroupDupeDelete || eAction == eaGroupFinalDelete)
{
pFileInfo->GetNZBInfo()->SetDeleted(true);
pFileInfo->GetNZBInfo()->SetDeleting(true);
pFileInfo->GetNZBInfo()->SetAvoidHistory(eAction == eaGroupFinalDelete);
pFileInfo->GetNZBInfo()->SetDeletePaused(bAllPaused);
if (eAction == eaGroupDupeDelete)
{
pFileInfo->GetNZBInfo()->SetDeleteStatus(NZBInfo::dsDupe);
}
pFileInfo->GetNZBInfo()->SetCleanupDisk(CanCleanupDisk(pDownloadQueue, pFileInfo->GetNZBInfo()));
}
EEditAction GroupToFileMap[] = { (EEditAction)0, eaFileMoveOffset, eaFileMoveTop, eaFileMoveBottom,
eaFilePause, eaFileResume, eaFileDelete, eaFilePauseAllPars, eaFilePauseExtraPars, eaFileSetPriority, eaFileReorder,
eaFileMoveOffset, eaFileMoveTop, eaFileMoveBottom, eaFilePause, eaFileResume, eaFileDelete,
EEditAction GroupToFileMap[] = { (EEditAction)0, eaFileMoveOffset, eaFileMoveTop, eaFileMoveBottom, eaFilePause,
eaFileResume, eaFileDelete, eaFilePauseAllPars, eaFilePauseExtraPars, eaFileSetPriority, eaFileReorder, eaFileSplit,
eaFileMoveOffset, eaFileMoveTop, eaFileMoveBottom, eaFilePause, eaFileResume, eaFileDelete, eaFileDelete, eaFileDelete,
eaFilePauseAllPars, eaFilePauseExtraPars, eaFileSetPriority,
(EEditAction)0, (EEditAction)0, (EEditAction)0 };
@@ -624,7 +649,7 @@ void QueueEditor::AlignAffectedGroups(DownloadQueue* pDownloadQueue, IDList* pID
}
if (iOffset > 0)
{
for (unsigned int i = iNum + 1; i <= cGroupList.size() - iOffset; i++)
for (int i = iNum + 1; i <= (int)cGroupList.size() - iOffset; i++)
{
if (!ItemExists(&cAffectedGroupList, cGroupList[i]))
{
@@ -810,7 +835,7 @@ void QueueEditor::SetNZBName(NZBInfo* pNZBInfo, const char* szName)
}
/**
* Check if deletion of already downloaded files is possible (when nzb id deleted from queue).
* Check if deletion of already downloaded files is possible (when nzb is deleted from queue).
* The deletion is most always possible, except the case if all remaining files in queue
* (belonging to this nzb-file) are PARS.
*/
@@ -819,28 +844,33 @@ bool QueueEditor::CanCleanupDisk(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInf
for (FileQueue::iterator it = pDownloadQueue->GetFileQueue()->begin(); it != pDownloadQueue->GetFileQueue()->end(); it++)
{
FileInfo* pFileInfo = *it;
char szLoFileName[1024];
strncpy(szLoFileName, pFileInfo->GetFilename(), 1024);
szLoFileName[1024-1] = '\0';
for (char* p = szLoFileName; *p; p++) *p = tolower(*p); // convert string to lowercase
if (!strstr(szLoFileName, ".par2"))
if (pFileInfo->GetNZBInfo() == pNZBInfo)
{
// non-par file found
return true;
char szLoFileName[1024];
strncpy(szLoFileName, pFileInfo->GetFilename(), 1024);
szLoFileName[1024-1] = '\0';
for (char* p = szLoFileName; *p; p++) *p = tolower(*p); // convert string to lowercase
if (!strstr(szLoFileName, ".par2"))
{
// non-par file found
return true;
}
}
}
return false;
}
void QueueEditor::MergeGroups(DownloadQueue* pDownloadQueue, ItemList* pItemList)
bool QueueEditor::MergeGroups(DownloadQueue* pDownloadQueue, ItemList* pItemList)
{
if (pItemList->size() == 0)
{
return;
return false;
}
bool bOK = true;
EditItem* pDestItem = pItemList->front();
for (ItemList::iterator it = pItemList->begin() + 1; it != pItemList->end(); it++)
@@ -849,15 +879,45 @@ void QueueEditor::MergeGroups(DownloadQueue* pDownloadQueue, ItemList* pItemList
if (pItem->m_pFileInfo->GetNZBInfo() != pDestItem->m_pFileInfo->GetNZBInfo())
{
debug("merge %s to %s", pItem->m_pFileInfo->GetNZBInfo()->GetFilename(), pDestItem->m_pFileInfo->GetNZBInfo()->GetFilename());
g_pQueueCoordinator->MergeQueueEntries(pDestItem->m_pFileInfo->GetNZBInfo(), pItem->m_pFileInfo->GetNZBInfo());
if (g_pQueueCoordinator->MergeQueueEntries(pDestItem->m_pFileInfo->GetNZBInfo(), pItem->m_pFileInfo->GetNZBInfo()))
{
bOK = false;
}
}
delete pItem;
}
// align group
AlignGroup(pDownloadQueue, pDestItem->m_pFileInfo->GetNZBInfo());
delete pDestItem;
return bOK;
}
bool QueueEditor::SplitGroup(DownloadQueue* pDownloadQueue, ItemList* pItemList, const char* szName)
{
if (pItemList->size() == 0)
{
return false;
}
FileQueue* pFileList = new FileQueue();
for (ItemList::iterator it = pItemList->begin(); it != pItemList->end(); it++)
{
EditItem* pItem = *it;
pFileList->push_back(pItem->m_pFileInfo);
delete pItem;
}
NZBInfo* pNewNZBInfo = NULL;
bool bOK = g_pQueueCoordinator->SplitQueueEntries(pFileList, szName, &pNewNZBInfo);
if (bOK)
{
AlignGroup(pDownloadQueue, pNewNZBInfo);
}
delete pFileList;
return bOK;
}
void QueueEditor::ReorderFiles(DownloadQueue* pDownloadQueue, ItemList* pItemList)
@@ -916,7 +976,7 @@ void QueueEditor::SetNZBParameter(NZBInfo* pNZBInfo, const char* szParamString)
{
*szValue = '\0';
szValue++;
pNZBInfo->SetParameter(szStr, szValue);
pNZBInfo->GetParameters()->SetParameter(szStr, szValue);
}
else
{
@@ -925,3 +985,47 @@ void QueueEditor::SetNZBParameter(NZBInfo* pNZBInfo, const char* szParamString)
free(szStr);
}
void QueueEditor::SetNZBDupeParam(NZBInfo* pNZBInfo, EEditAction eAction, const char* szText)
{
debug("QueueEditor: setting dupe parameter %i='%s' for '%s'", (int)eAction, szText, pNZBInfo->GetName());
switch (eAction)
{
case eaGroupSetDupeKey:
pNZBInfo->SetDupeKey(szText);
break;
case eaGroupSetDupeScore:
pNZBInfo->SetDupeScore(atoi(szText));
break;
case eaGroupSetDupeMode:
{
EDupeMode eMode = dmScore;
if (!strcasecmp(szText, "SCORE"))
{
eMode = dmScore;
}
else if (!strcasecmp(szText, "ALL"))
{
eMode = dmAll;
}
else if (!strcasecmp(szText, "FORCE"))
{
eMode = dmForce;
}
else
{
error("Could not set duplicate mode for %s: incorrect mode (%s)", pNZBInfo->GetName(), szText);
return;
}
pNZBInfo->SetDupeMode(eMode);
break;
}
default:
// suppress compiler warning
break;
}
}

View File

@@ -1,7 +1,7 @@
/*
* This file if part of nzbget
* This file is part of nzbget
*
* Copyright (C) 2007-2011 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -33,6 +33,7 @@
class QueueEditor
{
public:
// NOTE: changes to this enum must be synced with "eRemoteEditAction" in unit "MessageBase.h"
enum EEditAction
{
eaFileMoveOffset = 1, // move to m_iOffset relative to the current position in queue
@@ -45,19 +46,25 @@ public:
eaFilePauseExtraPars,
eaFileSetPriority,
eaFileReorder,
eaFileSplit,
eaGroupMoveOffset, // move to m_iOffset relative to the current position in queue
eaGroupMoveTop,
eaGroupMoveBottom,
eaGroupPause,
eaGroupResume,
eaGroupDelete,
eaGroupDupeDelete,
eaGroupFinalDelete,
eaGroupPauseAllPars,
eaGroupPauseExtraPars,
eaGroupSetPriority,
eaGroupSetCategory,
eaGroupMerge,
eaGroupSetParameter,
eaGroupSetName
eaGroupSetName,
eaGroupSetDupeKey,
eaGroupSetDupeScore,
eaGroupSetDupeMode
};
enum EMatchMode
@@ -96,9 +103,11 @@ private:
void SetNZBCategory(NZBInfo* pNZBInfo, const char* szCategory);
void SetNZBName(NZBInfo* pNZBInfo, const char* szName);
bool CanCleanupDisk(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo);
void MergeGroups(DownloadQueue* pDownloadQueue, ItemList* pItemList);
bool MergeGroups(DownloadQueue* pDownloadQueue, ItemList* pItemList);
bool SplitGroup(DownloadQueue* pDownloadQueue, ItemList* pItemList, const char* szName);
void ReorderFiles(DownloadQueue* pDownloadQueue, ItemList* pItemList);
void SetNZBParameter(NZBInfo* pNZBInfo, const char* szParamString);
void SetNZBDupeParam(NZBInfo* pNZBInfo, EEditAction eAction, const char* szText);
void PauseUnpauseEntry(FileInfo* pFileInfo, bool bPause);
void DeleteEntry(FileInfo* pFileInfo);

43
README
View File

@@ -429,40 +429,28 @@ same computer)
Security warning
----------------
NZBGet client communicates with NZBGet server via unsecured socket connections.
This makes it vulnerable. Although server checks the password passed by client,
this password is still transmitted in unsecured way. For this reason it is
highly recommended to configure your firewall to not expose the port used by
NZBGet (option <ControlPort>) to WAN.
NZBGet communicates via unsecured socket connections. This makes it vulnerable.
Although server checks the password passed by client, this password is still
transmitted in unsecured way. For this reason it is highly recommended
to configure your Firewall to not expose the port used by NZBGet to WAN.
If you need to control server from WAN it is better to use web-interface via HTTPS
or (if you prefer remote commands) connect to server's terminal via SSH (POSIX)
or remote desktop (Windows) and then run nzbget-client-commands in this terminal.
If you need to control server from WAN it is better to connect to server's
terminal via SSH (POSIX) or remote desktop (Windows) and then run
nzbget-client-commands in this terminal.
Post processing scripts
-----------------------
After the download of nzb-file is completed nzbget can call post-processing
script, defined in configuration file. See example configuration file for
the description of parameters passed to the script (option "PostProcess").
scripts, defined in configuration file.
An example script for unraring of downloaded files is provided in file
"nzbget-postprocess.sh" installed into "<prefix>/bin". The script requires
configuration file "nzbget-postprocess.conf". If you have installed the
program with "make install" this file is copied to "<prefix>/etc",
where the post-processing script finds it automatically. If you install
the program manually from a binary archive you have to copy the file
from "<prefix>/share/nzbget" to the directory where you have put the
nzbget configuration file ("nzbget.conf").
Example post-processing scripts are provided in directory "ppscripts".
Set the option "PostProcess" in "nzbget.conf" to point to the post-
processing script.
To use the scripts copy them into your local directory and set options
<ScriptDir>, <DefScript> and <ScriptOrder>.
Additional usage instructions are included in "nzbget-postprocess.sh",
please open the file in a text editor to read them.
NOTE: The post-processing script "nzbget-postprocess.sh" is for
POSIX systems and will not work on Windows.
For information on writing your own post-processing scripts please
visit NZBGet web site.
Web-interface
-------------
@@ -524,11 +512,8 @@ Binary distribution for Windows contains code from the following libraries:
- libpar2 (http://parchive.sourceforge.net)
- libsigc++ (http://libsigc.sourceforge.net)
- GnuTLS (http://www.gnu.org/software/gnutls)
- zlib (http://www.zlib.net)
- regex (http://gnuwin32.sourceforge.net/packages/regex.htm)
libpar2 is distributed under GPL; libsigc++, GnuTLS and regex - under LGPL;
zlib - under zlib license.
libpar2 is distributed under GPL; libsigc++ and GnuTLS - under LGPL.
=====================================
10. Contact

View File

@@ -2,7 +2,7 @@
* This file is part of nzbget
*
* Copyright (C) 2005 Bo Cordes Petersen <placebodk@sourceforge.net>
* Copyright (C) 2007-2011 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -34,7 +34,7 @@
#include <stdlib.h>
#include <string.h>
#include <cstdio>
#include <stdio.h>
#ifdef WIN32
#include <windows.h>
#else
@@ -76,10 +76,7 @@ RemoteClient::RemoteClient()
RemoteClient::~RemoteClient()
{
if (m_pConnection)
{
delete m_pConnection;
}
delete m_pConnection;
}
void RemoteClient::printf(const char * msg,...)
@@ -126,6 +123,10 @@ void RemoteClient::InitMessageBase(SNZBRequestBase* pMessageBase, int iRequest,
pMessageBase->m_iSignature = htonl(NZBMESSAGE_SIGNATURE);
pMessageBase->m_iType = htonl(iRequest);
pMessageBase->m_iStructSize = htonl(iSize);
strncpy(pMessageBase->m_szUsername, g_pOptions->GetControlUsername(), NZBREQUESTPASSWORDSIZE - 1);
pMessageBase->m_szUsername[NZBREQUESTPASSWORDSIZE - 1] = '\0';
strncpy(pMessageBase->m_szPassword, g_pOptions->GetControlPassword(), NZBREQUESTPASSWORDSIZE - 1);
pMessageBase->m_szPassword[NZBREQUESTPASSWORDSIZE - 1] = '\0';
}
@@ -184,7 +185,7 @@ bool RemoteClient::RequestServerDownload(const char* szFilename, const char* szC
DownloadRequest.m_bAddFirst = htonl(bAddFirst);
DownloadRequest.m_bAddPaused = htonl(bAddPaused);
DownloadRequest.m_iPriority = htonl(iPriority);
DownloadRequest.m_iTrailingDataLength = htonl(iLength);
DownloadRequest.m_iTrailingDataLength = htonl(iLength - 1);
strncpy(DownloadRequest.m_szFilename, szFilename, NZBREQUESTFILENAMESIZE - 1);
DownloadRequest.m_szFilename[NZBREQUESTFILENAMESIZE-1] = '\0';
@@ -209,10 +210,7 @@ bool RemoteClient::RequestServerDownload(const char* szFilename, const char* szC
}
// Cleanup
if (szBuffer)
{
free(szBuffer);
}
free(szBuffer);
return OK;
}
@@ -246,7 +244,7 @@ void RemoteClient::BuildFileList(SNZBListResponse* pListResponse, const char* pT
pNZBInfo->SetQueuedFilename(m_szQueuedFilename);
pNZBInfo->m_bMatch = ntohl(pListAnswer->m_bMatch);
pNZBInfo->AddReference();
pNZBInfo->Retain();
pDownloadQueue->GetNZBInfoList()->Add(pNZBInfo);
pBufPtr += sizeof(SNZBListResponseNZBEntry) + ntohl(pListAnswer->m_iFilenameLen) +
@@ -263,7 +261,7 @@ void RemoteClient::BuildFileList(SNZBListResponse* pListResponse, const char* pT
const char* szValue = pBufPtr + sizeof(SNZBListResponsePPPEntry) + ntohl(pListAnswer->m_iNameLen);
NZBInfo* pNZBInfo = pDownloadQueue->GetNZBInfoList()->at(ntohl(pListAnswer->m_iNZBIndex) - 1);
pNZBInfo->SetParameter(szName, szValue);
pNZBInfo->GetParameters()->SetParameter(szName, szValue);
pBufPtr += sizeof(SNZBListResponsePPPEntry) + ntohl(pListAnswer->m_iNameLen) +
ntohl(pListAnswer->m_iValueLen);
@@ -523,20 +521,20 @@ bool RemoteClient::RequestServerList(bool bFiles, bool bGroups, const char* szPa
{
if (szParameters[0] == '\0')
{
strncat(szParameters, " (", 1024);
strncat(szParameters, " (", sizeof(szParameters) - strlen(szParameters) - 1);
}
else
{
strncat(szParameters, ", ", 1024);
strncat(szParameters, ", ", sizeof(szParameters) - strlen(szParameters) - 1);
}
NZBParameter* pNZBParameter = *it;
strncat(szParameters, pNZBParameter->GetName(), 1024);
strncat(szParameters, "=", 1024);
strncat(szParameters, pNZBParameter->GetValue(), 1024);
strncat(szParameters, pNZBParameter->GetName(), sizeof(szParameters) - strlen(szParameters) - 1);
strncat(szParameters, "=", sizeof(szParameters) - strlen(szParameters) - 1);
strncat(szParameters, pNZBParameter->GetValue(), sizeof(szParameters) - strlen(szParameters) - 1);
}
if (szParameters[0] != '\0')
{
strncat(szParameters, ")", 1024);
strncat(szParameters, ")", sizeof(szParameters) - strlen(szParameters) - 1);
}
if (!szPattern || ((MatchedNZBInfo*)pGroupInfo->GetNZBInfo())->m_bMatch)
@@ -547,8 +545,6 @@ bool RemoteClient::RequestServerList(bool bFiles, bool bGroups, const char* szPa
szPaused, szThreads, szCategory, szParameters);
iMatches++;
}
delete pGroupInfo;
}
for (FileQueue::iterator it = cRemoteQueue.GetFileQueue()->begin(); it != cRemoteQueue.GetFileQueue()->end(); it++)
@@ -654,10 +650,10 @@ bool RemoteClient::RequestServerList(bool bFiles, bool bGroups, const char* szPa
if (ntohl(ListResponse.m_iPostJobCount) > 0 || ntohl(ListResponse.m_bPostPaused))
{
strncat(szServerState, strlen(szServerState) > 0 ? ", Post-Processing" : "Post-Processing", sizeof(szServerState));
strncat(szServerState, strlen(szServerState) > 0 ? ", Post-Processing" : "Post-Processing", sizeof(szServerState) - strlen(szServerState) - 1);
if (ntohl(ListResponse.m_bPostPaused))
{
strncat(szServerState, " paused", sizeof(szServerState));
strncat(szServerState, " paused", sizeof(szServerState) - strlen(szServerState) - 1);
}
}
@@ -1035,13 +1031,13 @@ bool RemoteClient::RequestPostQueue()
}
const char* szPostStageName[] = { "", ", Loading Pars", ", Verifying source files", ", Repairing", ", Verifying repaired files", ", Unpacking", ", Executing postprocess-script", "" };
char* szInfoName = pBufPtr + sizeof(SNZBPostQueueResponseEntry) + ntohl(pPostQueueAnswer->m_iNZBFilenameLen) + ntohl(pPostQueueAnswer->m_iParFilename);
char* szInfoName = pBufPtr + sizeof(SNZBPostQueueResponseEntry) + ntohl(pPostQueueAnswer->m_iNZBFilenameLen);
printf("[%i] %s%s%s\n", ntohl(pPostQueueAnswer->m_iID), szInfoName, szPostStageName[ntohl(pPostQueueAnswer->m_iStage)], szCompleted);
pBufPtr += sizeof(SNZBPostQueueResponseEntry) + ntohl(pPostQueueAnswer->m_iNZBFilenameLen) +
ntohl(pPostQueueAnswer->m_iParFilename) + ntohl(pPostQueueAnswer->m_iInfoNameLen) +
ntohl(pPostQueueAnswer->m_iDestDirLen) + ntohl(pPostQueueAnswer->m_iProgressLabelLen);
ntohl(pPostQueueAnswer->m_iInfoNameLen) + ntohl(pPostQueueAnswer->m_iDestDirLen) +
ntohl(pPostQueueAnswer->m_iProgressLabelLen);
}
free(pBuf);
@@ -1160,7 +1156,7 @@ bool RemoteClient::RequestHistory()
char szSize[20];
Util::FormatFileSize(szSize, sizeof(szSize), lSize);
const char* szParStatusText[] = { "", ", Par failed", ", Par possible", ", Par successful" };
const char* szParStatusText[] = { "", "", ", Par failed", ", Par successful", ", Repair possible", ", Repair needed" };
const char* szScriptStatusText[] = { "", ", Script status unknown", ", Script failed", ", Script successful" };
printf("[%i] %s (%i files, %s%s%s)\n", ntohl(pListAnswer->m_iID), szNicename,

View File

@@ -66,10 +66,7 @@ RemoteServer::~RemoteServer()
{
debug("Destroying RemoteServer");
if (m_pConnection)
{
delete m_pConnection;
}
delete m_pConnection;
}
void RemoteServer::Run()
@@ -104,7 +101,7 @@ void RemoteServer::Run()
m_bTLS);
m_pConnection->SetTimeout(g_pOptions->GetConnectionTimeout());
m_pConnection->SetSuppressErrors(false);
bBind = m_pConnection->Bind() == 0;
bBind = m_pConnection->Bind();
}
// Accept connections and store the new Connection

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2007-2011 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -33,14 +33,13 @@
#include <stdlib.h>
#include <string.h>
#include <fstream>
#include <stdio.h>
#ifdef WIN32
#include <direct.h>
#else
#include <unistd.h>
#endif
#include <sys/stat.h>
#include <errno.h>
#include "nzbget.h"
#include "Scanner.h"
@@ -67,6 +66,45 @@ Scanner::FileData::~FileData()
free(m_szFilename);
}
Scanner::QueueData::QueueData(const char* szFilename, const char* szNZBName, const char* szCategory,
int iPriority, const char* szDupeKey, int iDupeScore, EDupeMode eDupeMode,
NZBParameterList* pParameters, bool bAddTop, bool bAddPaused, EAddStatus* pAddStatus)
{
m_szFilename = strdup(szFilename);
m_szNZBName = strdup(szNZBName);
m_szCategory = strdup(szCategory ? szCategory : "");
m_iPriority = iPriority;
m_szDupeKey = strdup(szDupeKey ? szDupeKey : "");
m_iDupeScore = iDupeScore;
m_eDupeMode = eDupeMode;
m_bAddTop = bAddTop;
m_bAddPaused = bAddPaused;
m_pAddStatus = pAddStatus;
if (pParameters)
{
m_Parameters.CopyFrom(pParameters);
}
}
Scanner::QueueData::~QueueData()
{
free(m_szFilename);
free(m_szNZBName);
free(m_szCategory);
free(m_szDupeKey);
}
void Scanner::QueueData::SetAddStatus(EAddStatus eAddStatus)
{
if (m_pAddStatus)
{
*m_pAddStatus = eAddStatus;
}
}
Scanner::Scanner()
{
debug("Creating Scanner");
@@ -75,7 +113,6 @@ Scanner::Scanner()
m_bScanning = false;
m_iNZBDirInterval = g_pOptions->GetNzbDirInterval() * 1000;
m_iPass = 0;
m_iStepMSec = 0;
const char* szNZBScript = g_pOptions->GetNZBProcess();
m_bNZBScript = szNZBScript && strlen(szNZBScript) > 0;
@@ -90,13 +127,26 @@ Scanner::~Scanner()
delete *it;
}
m_FileList.clear();
ClearQueueList();
}
void Scanner::ClearQueueList()
{
for (QueueList::iterator it = m_QueueList.begin(); it != m_QueueList.end(); it++)
{
delete *it;
}
m_QueueList.clear();
}
void Scanner::Check()
{
if (g_pOptions->GetNzbDir() && (m_bRequestedNZBDirScan ||
m_mutexScan.Lock();
if (m_bRequestedNZBDirScan ||
(!g_pOptions->GetPauseScan() && g_pOptions->GetNzbDirInterval() > 0 &&
m_iNZBDirInterval >= g_pOptions->GetNzbDirInterval() * 1000)))
m_iNZBDirInterval >= g_pOptions->GetNzbDirInterval() * 1000))
{
// check nzbdir every g_pOptions->GetNzbDirInterval() seconds or if requested
bool bCheckStat = !m_bRequestedNZBDirScan;
@@ -105,7 +155,7 @@ void Scanner::Check()
CheckIncomingNZBs(g_pOptions->GetNzbDir(), "", bCheckStat);
if (!bCheckStat && m_bNZBScript)
{
// if immediate scan requesten, we need second scan to process files extracted by NzbProcess-script
// if immediate scan requested, we need second scan to process files extracted by NzbProcess-script
CheckIncomingNZBs(g_pOptions->GetNzbDir(), "", bCheckStat);
}
m_bScanning = false;
@@ -116,7 +166,7 @@ void Scanner::Check()
// - one additional scan is neccessary to check sizes of detected files;
// - another scan is required to check files which were extracted by NzbProcess-script;
// - third scan is needed to check sizes of extracted files.
if (g_pOptions->GetNzbDirFileAge() < g_pOptions->GetNzbDirInterval())
if (g_pOptions->GetNzbDirInterval() > 0 && g_pOptions->GetNzbDirFileAge() < g_pOptions->GetNzbDirInterval())
{
int iMaxPass = m_bNZBScript ? 3 : 1;
if (m_iPass < iMaxPass)
@@ -132,8 +182,11 @@ void Scanner::Check()
}
DropOldFiles();
ClearQueueList();
}
m_iNZBDirInterval += m_iStepMSec;
m_iNZBDirInterval += 200;
m_mutexScan.Unlock();
}
/**
@@ -270,7 +323,8 @@ void Scanner::DropOldFiles()
}
}
void Scanner::ProcessIncomingFile(const char* szDirectory, const char* szBaseFilename, const char* szFullFilename, const char* szCategory)
void Scanner::ProcessIncomingFile(const char* szDirectory, const char* szBaseFilename,
const char* szFullFilename, const char* szCategory)
{
const char* szExtension = strrchr(szBaseFilename, '.');
if (!szExtension)
@@ -278,16 +332,47 @@ void Scanner::ProcessIncomingFile(const char* szDirectory, const char* szBaseFil
return;
}
char* szNZBName = strdup("");
char* szNZBCategory = strdup(szCategory);
NZBParameterList* pParameterList = new NZBParameterList;
NZBParameterList* pParameters = new NZBParameterList();
int iPriority = 0;
bool bAddTop = false;
bool bAddPaused = false;
const char* szDupeKey = NULL;
int iDupeScore = 0;
EDupeMode eDupeMode = dmScore;
EAddStatus eAddStatus = asSkipped;
bool bAdded = false;
QueueData* pQueueData = NULL;
for (QueueList::iterator it = m_QueueList.begin(); it != m_QueueList.end(); it++)
{
QueueData* pQueueData1 = *it;
if (Util::SameFilename(pQueueData1->GetFilename(), szFullFilename))
{
pQueueData = pQueueData1;
free(szNZBName);
szNZBName = strdup(pQueueData->GetNZBName());
free(szNZBCategory);
szNZBCategory = strdup(pQueueData->GetCategory());
iPriority = pQueueData->GetPriority();
szDupeKey = pQueueData->GetDupeKey();
iDupeScore = pQueueData->GetDupeScore();
eDupeMode = pQueueData->GetDupeMode();
bAddTop = pQueueData->GetAddTop();
bAddPaused = pQueueData->GetAddPaused();
pParameters->CopyFrom(pQueueData->GetParameters());
}
}
InitPPParameters(szNZBCategory, pParameters);
bool bExists = true;
if (m_bNZBScript && strcasecmp(szExtension, ".nzb_processed"))
{
NZBScriptController::ExecuteScript(g_pOptions->GetNZBProcess(), szFullFilename, szDirectory,
&szNZBCategory, &iPriority, pParameterList);
&szNZBName, &szNZBCategory, &iPriority, pParameters, &bAddTop, &bAddPaused);
bExists = Util::FileExists(szFullFilename);
if (bExists && strcasecmp(szExtension, ".nzb"))
{
@@ -295,7 +380,8 @@ void Scanner::ProcessIncomingFile(const char* szDirectory, const char* szBaseFil
bool bRenameOK = Util::RenameBak(szFullFilename, "processed", false, bakname2, 1024);
if (!bRenameOK)
{
error("Could not rename file %s to %s! Errcode: %i", szFullFilename, bakname2, errno);
char szSysErrStr[256];
error("Could not rename file %s to %s: %s", szFullFilename, bakname2, Util::GetLastErrorMessage(szSysErrStr, sizeof(szSysErrStr)));
}
}
}
@@ -306,82 +392,260 @@ void Scanner::ProcessIncomingFile(const char* szDirectory, const char* szBaseFil
bool bRenameOK = Util::RenameBak(szFullFilename, "nzb", true, szRenamedName, 1024);
if (bRenameOK)
{
AddFileToQueue(szRenamedName, szNZBCategory, iPriority, pParameterList);
bAdded = AddFileToQueue(szRenamedName, szNZBName, szNZBCategory, iPriority,
szDupeKey, iDupeScore, eDupeMode, pParameters, bAddTop, bAddPaused);
}
else
{
error("Could not rename file %s to %s! Errcode: %i", szFullFilename, szRenamedName, errno);
char szSysErrStr[256];
error("Could not rename file %s to %s: %s", szFullFilename, szRenamedName, Util::GetLastErrorMessage(szSysErrStr, sizeof(szSysErrStr)));
eAddStatus = asFailed;
}
}
else if (bExists && !strcasecmp(szExtension, ".nzb"))
{
AddFileToQueue(szFullFilename, szNZBCategory, iPriority, pParameterList);
bAdded = AddFileToQueue(szFullFilename, szNZBName, szNZBCategory, iPriority,
szDupeKey, iDupeScore, eDupeMode, pParameters, bAddTop, bAddPaused);
}
for (NZBParameterList::iterator it = pParameterList->begin(); it != pParameterList->end(); it++)
{
delete *it;
}
pParameterList->clear();
delete pParameterList;
delete pParameters;
free(szNZBName);
free(szNZBCategory);
if (pQueueData)
{
pQueueData->SetAddStatus(eAddStatus == asFailed ? asFailed : bAdded ? asSuccess : asSkipped);
}
}
void Scanner::AddFileToQueue(const char* szFilename, const char* szCategory, int iPriority, NZBParameterList* pParameterList)
void Scanner::InitPPParameters(const char* szCategory, NZBParameterList* pParameters)
{
bool bUnpack = g_pOptions->GetUnpack();
const char* szDefScript = g_pOptions->GetDefScript();
if (szCategory && *szCategory)
{
Options::Category* pCategory = g_pOptions->FindCategory(szCategory, false);
if (pCategory)
{
bUnpack = pCategory->GetUnpack();
if (pCategory->GetDefScript() && *pCategory->GetDefScript())
{
szDefScript = pCategory->GetDefScript();
}
}
}
pParameters->SetParameter("*Unpack:", bUnpack ? "yes" : "no");
if (szDefScript && *szDefScript)
{
// split szDefScript into tokens and create pp-parameter for each token
char* szDefScript2 = strdup(szDefScript);
char* saveptr;
char* szScriptName = strtok_r(szDefScript2, ",;", &saveptr);
while (szScriptName)
{
szScriptName = Util::Trim(szScriptName);
if (szScriptName[0] != '\0')
{
char szParam[1024];
snprintf(szParam, 1024, "%s:", szScriptName);
szParam[1024-1] = '\0';
pParameters->SetParameter(szParam, "yes");
}
szScriptName = strtok_r(NULL, ",;", &saveptr);
}
free(szDefScript2);
}
}
bool Scanner::AddFileToQueue(const char* szFilename, const char* szNZBName, const char* szCategory,
int iPriority, const char* szDupeKey, int iDupeScore, EDupeMode eDupeMode,
NZBParameterList* pParameters, bool bAddTop, bool bAddPaused)
{
const char* szBasename = Util::BaseFileName(szFilename);
info("Collection %s found", szBasename);
NZBFile* pNZBFile = NZBFile::CreateFromFile(szFilename, szCategory);
if (!pNZBFile)
NZBFile* pNZBFile = NZBFile::Create(szFilename, szCategory);
bool bOK = pNZBFile != NULL;
if (!bOK)
{
error("Could not add collection %s to queue", szBasename);
}
char bakname2[1024];
bool bRenameOK = Util::RenameBak(szFilename, pNZBFile ? "queued" : "error", false, bakname2, 1024);
if (!bRenameOK)
if (!Util::RenameBak(szFilename, pNZBFile ? "queued" : "error", false, bakname2, 1024))
{
error("Could not rename file %s to %s! Errcode: %i", szFilename, bakname2, errno);
bOK = false;
char szSysErrStr[256];
error("Could not rename file %s to %s: %s", szFilename, bakname2, Util::GetLastErrorMessage(szSysErrStr, sizeof(szSysErrStr)));
}
if (pNZBFile && bRenameOK)
if (bOK)
{
pNZBFile->GetNZBInfo()->SetQueuedFilename(bakname2);
for (NZBParameterList::iterator it = pParameterList->begin(); it != pParameterList->end(); it++)
if (szNZBName && strlen(szNZBName) > 0)
{
NZBParameter* pParameter = *it;
pNZBFile->GetNZBInfo()->SetParameter(pParameter->GetName(), pParameter->GetValue());
pNZBFile->GetNZBInfo()->SetName(NULL);
#ifdef WIN32
char* szAnsiFilename = strdup(szNZBName);
WebUtil::Utf8ToAnsi(szAnsiFilename, strlen(szAnsiFilename) + 1);
pNZBFile->GetNZBInfo()->SetFilename(szAnsiFilename);
free(szAnsiFilename);
#else
pNZBFile->GetNZBInfo()->SetFilename(szNZBName);
#endif
pNZBFile->GetNZBInfo()->BuildDestDirName();
}
pNZBFile->GetNZBInfo()->SetDupeKey(szDupeKey);
pNZBFile->GetNZBInfo()->SetDupeScore(iDupeScore);
pNZBFile->GetNZBInfo()->SetDupeMode(eDupeMode);
if (pNZBFile->GetPassword())
{
pNZBFile->GetNZBInfo()->GetParameters()->SetParameter("*Unpack:Password", pNZBFile->GetPassword());
}
pNZBFile->GetNZBInfo()->GetParameters()->CopyFrom(pParameters);
for (NZBFile::FileInfos::iterator it = pNZBFile->GetFileInfos()->begin(); it != pNZBFile->GetFileInfos()->end(); it++)
{
FileInfo* pFileInfo = *it;
pFileInfo->SetPriority(iPriority);
pFileInfo->SetPaused(bAddPaused);
}
g_pQueueCoordinator->AddNZBFileToQueue(pNZBFile, false);
info("Collection %s added to queue", szBasename);
g_pQueueCoordinator->AddNZBFileToQueue(pNZBFile, bAddTop);
}
if (pNZBFile)
{
delete pNZBFile;
}
delete pNZBFile;
return bOK;
}
void Scanner::ScanNZBDir(bool bSyncMode)
{
// ideally we should use mutex to access "m_bRequestedNZBDirScan",
// but it's not critical here.
m_mutexScan.Lock();
m_bScanning = true;
m_bRequestedNZBDirScan = true;
m_mutexScan.Unlock();
while (bSyncMode && (m_bScanning || m_bRequestedNZBDirScan))
{
usleep(100 * 1000);
}
}
Scanner::EAddStatus Scanner::AddExternalFile(const char* szNZBName, const char* szCategory,
int iPriority, const char* szDupeKey, int iDupeScore, EDupeMode eDupeMode,
NZBParameterList* pParameters, bool bAddTop, bool bAddPaused,
const char* szFileName, const char* szBuffer, int iBufSize)
{
bool bNZB = false;
char szTempFileName[1024];
if (szFileName)
{
strncpy(szTempFileName, szFileName, 1024);
szTempFileName[1024-1] = '\0';
}
else
{
int iNum = 1;
while (iNum == 1 || Util::FileExists(szTempFileName))
{
snprintf(szTempFileName, 1024, "%snzb-%i.tmp", g_pOptions->GetTempDir(), iNum);
szTempFileName[1024-1] = '\0';
iNum++;
}
if (!Util::SaveBufferIntoFile(szTempFileName, szBuffer, iBufSize))
{
error("Could not create file %s", szTempFileName);
return asFailed;
}
char buf[1024];
strncpy(buf, szBuffer, 1024);
buf[1024-1] = '\0';
bNZB = !strncmp(buf, "<?xml", 5) && strstr(buf, "<nzb");
}
// move file into NzbDir, make sure the file name is unique
char szValidNZBName[1024];
strncpy(szValidNZBName, Util::BaseFileName(szNZBName), 1024);
szValidNZBName[1024-1] = '\0';
Util::MakeValidFilename(szValidNZBName, '_', false);
#ifdef WIN32
WebUtil::Utf8ToAnsi(szValidNZBName, 1024);
#endif
const char* szExtension = strrchr(szNZBName, '.');
if (bNZB && (!szExtension || strcasecmp(szExtension, ".nzb")))
{
strncat(szValidNZBName, ".nzb", 1024 - strlen(szValidNZBName) - 1);
}
char szScanFileName[1024];
snprintf(szScanFileName, 1024, "%s%s", g_pOptions->GetNzbDir(), szValidNZBName);
char *szExt = strrchr(szValidNZBName, '.');
if (szExt)
{
*szExt = '\0';
szExt++;
}
int iNum = 2;
while (Util::FileExists(szScanFileName))
{
if (szExt)
{
snprintf(szScanFileName, 1024, "%s%s_%i.%s", g_pOptions->GetNzbDir(), szValidNZBName, iNum, szExt);
}
else
{
snprintf(szScanFileName, 1024, "%s%s_%i", g_pOptions->GetNzbDir(), szValidNZBName, iNum);
}
szScanFileName[1024-1] = '\0';
iNum++;
}
m_mutexScan.Lock();
if (!Util::MoveFile(szTempFileName, szScanFileName))
{
char szSysErrStr[256];
error("Could not move file %s to %s: %s", szTempFileName, szScanFileName, Util::GetLastErrorMessage(szSysErrStr, sizeof(szSysErrStr)));
remove(szTempFileName);
m_mutexScan.Unlock(); // UNLOCK
return asFailed;
}
char* szUseCategory = strdup(szCategory ? szCategory : "");
Options::Category *pCategory = g_pOptions->FindCategory(szCategory, true);
if (pCategory && strcmp(szCategory, pCategory->GetName()))
{
free(szUseCategory);
szUseCategory = strdup(pCategory->GetName());
detail("Category %s matched to %s for %s", szCategory, szUseCategory, szNZBName);
}
EAddStatus eAddStatus = asSkipped;
QueueData* pQueueData = new QueueData(szScanFileName, szNZBName, szUseCategory,
iPriority, szDupeKey, iDupeScore, eDupeMode, pParameters, bAddTop, bAddPaused, &eAddStatus);
free(szUseCategory);
m_QueueList.push_back(pQueueData);
m_mutexScan.Unlock();
ScanNZBDir(true);
return eAddStatus;
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2007-2011 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -29,9 +29,18 @@
#include <deque>
#include <time.h>
#include "DownloadInfo.h"
#include "Thread.h"
class Scanner
{
public:
enum EAddStatus
{
asSkipped,
asSuccess,
asFailed
};
private:
class FileData
{
@@ -52,26 +61,70 @@ private:
typedef std::deque<FileData*> FileList;
class QueueData
{
private:
char* m_szFilename;
char* m_szNZBName;
char* m_szCategory;
int m_iPriority;
char* m_szDupeKey;
int m_iDupeScore;
EDupeMode m_eDupeMode;
NZBParameterList m_Parameters;
bool m_bAddTop;
bool m_bAddPaused;
EAddStatus* m_pAddStatus;
public:
QueueData(const char* szFilename, const char* szNZBName, const char* szCategory,
int iPriority, const char* szDupeKey, int iDupeScore, EDupeMode eDupeMode,
NZBParameterList* pParameters, bool bAddTop, bool bAddPaused, EAddStatus* pAddStatus);
~QueueData();
const char* GetFilename() { return m_szFilename; }
const char* GetNZBName() { return m_szNZBName; }
const char* GetCategory() { return m_szCategory; }
int GetPriority() { return m_iPriority; }
const char* GetDupeKey() { return m_szDupeKey; }
int GetDupeScore() { return m_iDupeScore; }
EDupeMode GetDupeMode() { return m_eDupeMode; }
NZBParameterList* GetParameters() { return &m_Parameters; }
bool GetAddTop() { return m_bAddTop; }
bool GetAddPaused() { return m_bAddPaused; }
void SetAddStatus(EAddStatus eAddStatus);
};
typedef std::deque<QueueData*> QueueList;
bool m_bRequestedNZBDirScan;
int m_iNZBDirInterval;
bool m_bNZBScript;
int m_iPass;
int m_iStepMSec;
FileList m_FileList;
QueueList m_QueueList;
bool m_bScanning;
Mutex m_mutexScan;
void CheckIncomingNZBs(const char* szDirectory, const char* szCategory, bool bCheckStat);
void AddFileToQueue(const char* szFilename, const char* szCategory, int iPriority, NZBParameterList* pParameterList);
void ProcessIncomingFile(const char* szDirectory, const char* szBaseFilename, const char* szFullFilename, const char* szCategory);
bool AddFileToQueue(const char* szFilename, const char* szNZBName, const char* szCategory,
int iPriority, const char* szDupeKey, int iDupeScore, EDupeMode eDupeMode,
NZBParameterList* pParameters, bool bAddTop, bool bAddPaused);
void ProcessIncomingFile(const char* szDirectory, const char* szBaseFilename,
const char* szFullFilename, const char* szCategory);
bool CanProcessFile(const char* szFullFilename, bool bCheckStat);
void InitPPParameters(const char* szCategory, NZBParameterList* pParameters);
void DropOldFiles();
void ClearQueueList();
public:
Scanner();
~Scanner();
void SetStepInterval(int iStepMSec) { m_iStepMSec = iStepMSec; }
void ScanNZBDir(bool bSyncMode);
void Check();
EAddStatus AddExternalFile(const char* szNZBName, const char* szCategory, int iPriority,
const char* szDupeKey, int iDupeScore, EDupeMode eDupeMode,
NZBParameterList* pParameters, bool bAddTop, bool bAddPaused,
const char* szFileName, const char* szBuffer, int iBufSize);
};
#endif

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2008-2009 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2008-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -35,37 +35,35 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "nzbget.h"
#include "Scheduler.h"
#include "ScriptController.h"
#include "Options.h"
#include "Log.h"
#include "NewsServer.h"
#include "ServerPool.h"
#include "FeedInfo.h"
#include "FeedCoordinator.h"
extern Options* g_pOptions;
extern ServerPool* g_pServerPool;
extern FeedCoordinator* g_pFeedCoordinator;
Scheduler::Task::Task(int iHours, int iMinutes, int iWeekDaysBits, ECommand eCommand,
int iDownloadRate, const char* szProcess)
Scheduler::Task::Task(int iHours, int iMinutes, int iWeekDaysBits, ECommand eCommand, const char* szParam)
{
m_iHours = iHours;
m_iMinutes = iMinutes;
m_iWeekDaysBits = iWeekDaysBits;
m_eCommand = eCommand;
m_iDownloadRate = iDownloadRate;
m_szProcess = NULL;
if (szProcess)
{
m_szProcess = strdup(szProcess);
}
m_szParam = szParam ? strdup(szParam) : NULL;
m_tLastExecuted = 0;
}
Scheduler::Task::~Task()
{
if (m_szProcess)
{
free(m_szProcess);
}
free(m_szParam);
}
@@ -111,9 +109,6 @@ void Scheduler::FirstCheck()
m_tLastCheck = tCurrent - 60*60*24*7;
m_bDetectClockChanges = false;
m_bExecuteProcess = false;
m_bDownloadRateChanged = false;
m_bPauseDownloadChanged = false;
m_bPauseScanChanged = false;
CheckTasks();
}
@@ -121,21 +116,16 @@ void Scheduler::IntervalCheck()
{
m_bDetectClockChanges = true;
m_bExecuteProcess = true;
m_bDownloadRateChanged = false;
m_bPauseDownloadChanged = false;
m_bPauseScanChanged = false;
CheckTasks();
}
void Scheduler::CheckTasks()
{
PrepareLog();
m_mutexTaskList.Lock();
time_t tCurrent = time(NULL);
struct tm tmCurrent;
localtime_r(&tCurrent, &tmCurrent);
struct tm tmLastCheck;
if (m_bDetectClockChanges)
{
@@ -155,9 +145,12 @@ void Scheduler::CheckTasks()
}
}
tm tmCurrent;
localtime_r(&tCurrent, &tmCurrent);
tm tmLastCheck;
localtime_r(&m_tLastCheck, &tmLastCheck);
struct tm tmLoop;
tm tmLoop;
memcpy(&tmLoop, &tmLastCheck, sizeof(tmLastCheck));
tmLoop.tm_hour = tmCurrent.tm_hour;
tmLoop.tm_min = tmCurrent.tm_min;
@@ -171,13 +164,15 @@ void Scheduler::CheckTasks()
Task* pTask = *it;
if (pTask->m_tLastExecuted != tLoop)
{
struct tm tmAppoint;
tm tmAppoint;
memcpy(&tmAppoint, &tmLoop, sizeof(tmLoop));
tmAppoint.tm_hour = pTask->m_iHours;
tmAppoint.tm_min = pTask->m_iMinutes;
tmAppoint.tm_sec = 0;
time_t tAppoint = mktime(&tmAppoint);
tAppoint -= g_pOptions->GetTimeCorrection();
int iWeekDay = tmAppoint.tm_wday;
if (iWeekDay == 0)
{
@@ -203,25 +198,24 @@ void Scheduler::CheckTasks()
m_tLastCheck = tCurrent;
m_mutexTaskList.Unlock();
PrintLog();
}
void Scheduler::ExecuteTask(Task* pTask)
{
if (pTask->m_eCommand == scDownloadRate)
{
debug("Executing scheduled command: Set download rate to %i", pTask->m_iDownloadRate);
}
else
{
const char* szCommandName[] = { "Pause", "Unpause", "Set download rate", "Execute program", "Pause Scan", "Unpause Scan" };
debug("Executing scheduled command: %s", szCommandName[pTask->m_eCommand]);
}
const char* szCommandName[] = { "Pause", "Unpause", "Set download rate", "Execute program", "Pause Scan", "Unpause Scan",
"Enable Server", "Disable Server", "Fetch Feed" };
debug("Executing scheduled command: %s", szCommandName[pTask->m_eCommand]);
switch (pTask->m_eCommand)
{
case scDownloadRate:
m_iDownloadRate = pTask->m_iDownloadRate;
m_bDownloadRateChanged = true;
if (!Util::EmptyStr(pTask->m_szParam))
{
g_pOptions->SetDownloadRate(atoi(pTask->m_szParam) * 1024);
m_bDownloadRateChanged = true;
}
break;
case scPauseDownload:
@@ -233,14 +227,126 @@ void Scheduler::ExecuteTask(Task* pTask)
case scProcess:
if (m_bExecuteProcess)
{
SchedulerScriptController::StartScript(pTask->m_szProcess);
SchedulerScriptController::StartScript(pTask->m_szParam);
}
break;
case scPauseScan:
case scUnpauseScan:
m_bPauseScan = pTask->m_eCommand == scPauseScan;
g_pOptions->SetPauseScan(pTask->m_eCommand == scPauseScan);
m_bPauseScanChanged = true;
break;
case scActivateServer:
case scDeactivateServer:
EditServer(pTask->m_eCommand == scActivateServer, pTask->m_szParam);
break;
case scFetchFeed:
if (m_bExecuteProcess)
{
FetchFeed(pTask->m_szParam);
break;
}
}
}
void Scheduler::PrepareLog()
{
m_bDownloadRateChanged = false;
m_bPauseDownloadChanged = false;
m_bPauseScanChanged = false;
m_bServerChanged = false;
}
void Scheduler::PrintLog()
{
if (m_bDownloadRateChanged)
{
info("Scheduler: setting download rate to %i KB/s", g_pOptions->GetDownloadRate() / 1024);
}
if (m_bPauseScanChanged)
{
info("Scheduler: %s scan", g_pOptions->GetPauseScan() ? "pausing" : "unpausing");
}
if (m_bServerChanged)
{
int index = 0;
for (Servers::iterator it = g_pServerPool->GetServers()->begin(); it != g_pServerPool->GetServers()->end(); it++, index++)
{
NewsServer* pServer = *it;
if (pServer->GetActive() != m_ServerStatusList[index])
{
info("Scheduler: %s %s", pServer->GetActive() ? "activating" : "deactivating", pServer->GetName());
}
}
g_pServerPool->Changed();
}
}
void Scheduler::EditServer(bool bActive, const char* szServerList)
{
char* szServerList2 = strdup(szServerList);
char* saveptr;
char* szServer = strtok_r(szServerList2, ",;", &saveptr);
while (szServer)
{
szServer = Util::Trim(szServer);
if (!Util::EmptyStr(szServer))
{
int iID = atoi(szServer);
for (Servers::iterator it = g_pServerPool->GetServers()->begin(); it != g_pServerPool->GetServers()->end(); it++)
{
NewsServer* pServer = *it;
if ((iID > 0 && pServer->GetID() == iID) ||
!strcasecmp(pServer->GetName(), szServer))
{
if (!m_bServerChanged)
{
// store old server status for logging
m_ServerStatusList.clear();
m_ServerStatusList.reserve(g_pServerPool->GetServers()->size());
for (Servers::iterator it2 = g_pServerPool->GetServers()->begin(); it2 != g_pServerPool->GetServers()->end(); it2++)
{
NewsServer* pServer2 = *it2;
m_ServerStatusList.push_back(pServer2->GetActive());
}
}
m_bServerChanged = true;
pServer->SetActive(bActive);
break;
}
}
}
szServer = strtok_r(NULL, ",;", &saveptr);
}
free(szServerList2);
}
void Scheduler::FetchFeed(const char* szFeedList)
{
char* szFeedList2 = strdup(szFeedList);
char* saveptr;
char* szFeed = strtok_r(szFeedList2, ",;", &saveptr);
while (szFeed)
{
szFeed = Util::Trim(szFeed);
if (!Util::EmptyStr(szFeed))
{
int iID = atoi(szFeed);
for (Feeds::iterator it = g_pFeedCoordinator->GetFeeds()->begin(); it != g_pFeedCoordinator->GetFeeds()->end(); it++)
{
FeedInfo* pFeed = *it;
if (pFeed->GetID() == iID ||
!strcasecmp(pFeed->GetName(), szFeed) ||
!strcasecmp("0", szFeed))
{
g_pFeedCoordinator->FetchFeed(pFeed->GetID());
break;
}
}
}
szFeed = strtok_r(NULL, ",;", &saveptr);
}
free(szFeedList2);
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2008-2009 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2008-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -27,6 +27,7 @@
#define SCHEDULER_H
#include <list>
#include <vector>
#include <time.h>
#include "Thread.h"
@@ -41,7 +42,10 @@ public:
scDownloadRate,
scProcess,
scPauseScan,
scUnpauseScan
scUnpauseScan,
scActivateServer,
scDeactivateServer,
scFetchFeed
};
class Task
@@ -51,13 +55,12 @@ public:
int m_iMinutes;
int m_iWeekDaysBits;
ECommand m_eCommand;
int m_iDownloadRate;
char* m_szProcess;
char* m_szParam;
time_t m_tLastExecuted;
public:
Task(int iHours, int iMinutes, int iWeekDaysBits, ECommand eCommand,
int iDownloadRate, const char* szProcess);
const char* szParam);
~Task();
friend class Scheduler;
};
@@ -65,6 +68,7 @@ public:
private:
typedef std::list<Task*> TaskList;
typedef std::vector<bool> ServerStatusList;
TaskList m_TaskList;
Mutex m_mutexTaskList;
@@ -72,14 +76,18 @@ private:
bool m_bDetectClockChanges;
bool m_bDownloadRateChanged;
bool m_bExecuteProcess;
int m_iDownloadRate;
bool m_bPauseDownloadChanged;
bool m_bPauseDownload;
bool m_bPauseScanChanged;
bool m_bPauseScan;
bool m_bServerChanged;
ServerStatusList m_ServerStatusList;
void ExecuteTask(Task* pTask);
void CheckTasks();
static bool CompareTasks(Scheduler::Task* pTask1, Scheduler::Task* pTask2);
void PrepareLog();
void PrintLog();
void EditServer(bool bActive, const char* szServerList);
void FetchFeed(const char* szFeedList);
public:
Scheduler();
@@ -87,12 +95,8 @@ public:
void AddTask(Task* pTask);
void FirstCheck();
void IntervalCheck();
bool GetDownloadRateChanged() { return m_bDownloadRateChanged; }
int GetDownloadRate() { return m_iDownloadRate; }
bool GetPauseDownloadChanged() { return m_bPauseDownloadChanged; }
bool GetPauseDownload() { return m_bPauseDownload; }
bool GetPauseScanChanged() { return m_bPauseScanChanged; }
bool GetPauseScan() { return m_bPauseScan; }
};
#endif

View File

File diff suppressed because it is too large Load Diff

View File

@@ -27,7 +27,6 @@
#define SCRIPTCONTROLLER_H
#include <list>
#include <fstream>
#include "Log.h"
#include "Thread.h"
@@ -44,6 +43,7 @@ private:
public:
EnvironmentStrings();
~EnvironmentStrings();
void Clear();
void InitFromCurrentProcess();
void Append(char* szString);
#ifdef WIN32
@@ -60,32 +60,37 @@ private:
const char* m_szWorkingDir;
const char** m_szArgs;
bool m_bFreeArgs;
const char* m_szStdArgs[2];
const char* m_szInfoName;
const char* m_szDefaultKindPrefix;
const char* m_szLogPrefix;
EnvironmentStrings m_environmentStrings;
Options::EScriptLogKind m_eDefaultLogKind;
bool m_bTerminated;
bool m_bDetached;
FILE* m_pReadpipe;
#ifdef WIN32
HANDLE m_hProcess;
char m_szCmdLine[2048];
#else
pid_t m_hProcess;
#endif
void PrepareEnvOptions();
protected:
void ProcessOutput(char* szText);
virtual bool ReadLine(char* szBuf, int iBufSize, FILE* pStream);
void PrintMessage(Message::EKind eKind, const char* szFormat, ...);
virtual void AddMessage(Message::EKind eKind, bool bDefaultKind, const char* szText);
virtual void AddMessage(Message::EKind eKind, const char* szText);
bool GetTerminated() { return m_bTerminated; }
void PrepareEnvParameters(NZBInfo* pNZBInfo);
void ResetEnv();
void PrepareEnvOptions(const char* szStripPrefix);
void PrepareEnvParameters(NZBInfo* pNZBInfo, const char* szStripPrefix);
void PrepareArgs();
public:
ScriptController();
virtual ~ScriptController();
int Execute();
void Terminate();
void Detach();
void SetScript(const char* szScript) { m_szScript = szScript; }
const char* GetScript() { return m_szScript; }
@@ -93,39 +98,53 @@ public:
void SetArgs(const char** szArgs, bool bFreeArgs) { m_szArgs = szArgs; m_bFreeArgs = bFreeArgs; }
void SetInfoName(const char* szInfoName) { m_szInfoName = szInfoName; }
const char* GetInfoName() { return m_szInfoName; }
void SetDefaultKindPrefix(const char* szDefaultKindPrefix) { m_szDefaultKindPrefix = szDefaultKindPrefix; }
void SetDefaultLogKind(Options::EScriptLogKind eDefaultLogKind) { m_eDefaultLogKind = eDefaultLogKind; }
void SetLogPrefix(const char* szLogPrefix) { m_szLogPrefix = szLogPrefix; }
void SetEnvVar(const char* szName, const char* szValue);
void SetEnvVarSpecial(const char* szPrefix, const char* szName, const char* szValue);
void SetIntEnvVar(const char* szName, int iValue);
};
class PostScriptController : public Thread, public ScriptController
{
private:
PostInfo* m_pPostInfo;
bool m_bNZBFileCompleted;
bool m_bHasFailedParJobs;
char m_szNZBName[1024];
int m_iPrefixLen;
void ExecuteScript(const char* szScriptName, const char* szDisplayName, const char* szLocation);
void PrepareParams(const char* szScriptName);
ScriptStatus::EStatus AnalyseExitCode(int iExitCode);
typedef std::deque<char*> FileList;
protected:
virtual void AddMessage(Message::EKind eKind, bool bDefaultKind, const char* szText);
virtual void AddMessage(Message::EKind eKind, const char* szText);
public:
virtual void Run();
virtual void Stop();
static void StartScriptJob(PostInfo* pPostInfo, bool bNZBFileCompleted, bool bHasFailedParJobs);
static void StartJob(PostInfo* pPostInfo);
static void InitParamsForNewNZB(NZBInfo* pNZBInfo);
};
class NZBScriptController : public ScriptController
{
private:
char** m_pNZBName;
char** m_pCategory;
int* m_iPriority;
NZBParameterList* m_pParameterList;
NZBParameterList* m_pParameters;
bool* m_bAddTop;
bool* m_bAddPaused;
int m_iPrefixLen;
protected:
virtual void AddMessage(Message::EKind eKind, bool bDefaultKind, const char* szText);
virtual void AddMessage(Message::EKind eKind, const char* szText);
public:
static void ExecuteScript(const char* szScript, const char* szNZBFilename, const char* szDirectory, char** pCategory, int* iPriority, NZBParameterList* pParameterList);
static void ExecuteScript(const char* szScript, const char* szNZBFilename, const char* szDirectory,
char** pNZBName, char** pCategory, int* iPriority, NZBParameterList* pParameters,
bool* bAddTop, bool* bAddPaused);
};
class NZBAddedScriptController : public Thread, public ScriptController

View File

@@ -2,7 +2,7 @@
* This file is part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2009 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -56,8 +56,9 @@ ServerPool::ServerPool()
{
debug("Creating ServerPool");
m_iMaxLevel = 0;
m_iMaxNormLevel = 0;
m_iTimeout = 60;
m_iGeneration = 0;
}
ServerPool::~ ServerPool()
@@ -71,6 +72,7 @@ ServerPool::~ ServerPool()
delete *it;
}
m_Servers.clear();
m_SortedServers.clear();
for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++)
{
@@ -83,16 +85,16 @@ void ServerPool::AddServer(NewsServer* pNewsServer)
{
debug("Adding server to ServerPool");
if (pNewsServer->GetMaxConnections() > 0)
{
m_Servers.push_back(pNewsServer);
}
else
{
delete pNewsServer;
}
m_Servers.push_back(pNewsServer);
m_SortedServers.push_back(pNewsServer);
}
/*
* Calculate normalized levels for all servers.
* Normalized Level means: starting from 0 with step 1.
* The servers of minimum Level must be always used even if they are not active;
* this is to prevent backup servers to act as main servers.
**/
void ServerPool::NormalizeLevels()
{
if (m_Servers.empty())
@@ -100,21 +102,38 @@ void ServerPool::NormalizeLevels()
return;
}
std::sort(m_Servers.begin(), m_Servers.end(), CompareServers);
std::sort(m_SortedServers.begin(), m_SortedServers.end(), CompareServers);
NewsServer* pNewsServer = m_Servers.front();
m_iMaxLevel = 0;
int iCurLevel = pNewsServer->GetLevel();
for (Servers::iterator it = m_Servers.begin(); it != m_Servers.end(); it++)
// find minimum level
int iMinLevel = m_SortedServers.front()->GetLevel();
for (Servers::iterator it = m_SortedServers.begin(); it != m_SortedServers.end(); it++)
{
NewsServer* pNewsServer = *it;
if (pNewsServer->GetLevel() != iCurLevel)
if (pNewsServer->GetLevel() < iMinLevel)
{
m_iMaxLevel++;
iMinLevel = pNewsServer->GetLevel();
}
}
pNewsServer->SetLevel(m_iMaxLevel);
m_iMaxNormLevel = 0;
int iLastLevel = iMinLevel;
for (Servers::iterator it = m_SortedServers.begin(); it != m_SortedServers.end(); it++)
{
NewsServer* pNewsServer = *it;
if ((pNewsServer->GetActive() && pNewsServer->GetMaxConnections() > 0) ||
(pNewsServer->GetLevel() == iMinLevel))
{
if (pNewsServer->GetLevel() != iLastLevel)
{
m_iMaxNormLevel++;
}
pNewsServer->SetNormLevel(m_iMaxNormLevel);
iLastLevel = pNewsServer->GetLevel();
}
else
{
pNewsServer->SetNormLevel(-1);
}
}
}
@@ -127,28 +146,51 @@ void ServerPool::InitConnections()
{
debug("Initializing connections in ServerPool");
NormalizeLevels();
m_mutexConnections.Lock();
for (Servers::iterator it = m_Servers.begin(); it != m_Servers.end(); it++)
NormalizeLevels();
m_Levels.clear();
for (Servers::iterator it = m_SortedServers.begin(); it != m_SortedServers.end(); it++)
{
NewsServer* pNewsServer = *it;
for (int i = 0; i < pNewsServer->GetMaxConnections(); i++)
int iNormLevel = pNewsServer->GetNormLevel();
if (pNewsServer->GetNormLevel() > -1)
{
PooledConnection* pConnection = new PooledConnection(pNewsServer);
pConnection->SetTimeout(m_iTimeout);
m_Connections.push_back(pConnection);
if ((int)m_Levels.size() <= iNormLevel)
{
m_Levels.push_back(0);
}
if (pNewsServer->GetActive())
{
int iConnections = 0;
for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++)
{
PooledConnection* pConnection = *it;
if (pConnection->GetNewsServer() == pNewsServer)
{
iConnections++;
}
}
for (int i = iConnections; i < pNewsServer->GetMaxConnections(); i++)
{
PooledConnection* pConnection = new PooledConnection(pNewsServer);
pConnection->SetTimeout(m_iTimeout);
m_Connections.push_back(pConnection);
iConnections++;
}
m_Levels[iNormLevel] += iConnections;
}
}
if ((int)m_Levels.size() <= pNewsServer->GetLevel())
{
m_Levels.push_back(0);
}
m_Levels[pNewsServer->GetLevel()] += pNewsServer->GetMaxConnections();
}
if (m_Levels.empty())
{
warn("No news servers defined, download is not possible");
}
m_iGeneration++;
m_mutexConnections.Unlock();
}
NNTPConnection* ServerPool::GetConnection(int iLevel, NewsServer* pWantServer, Servers* pIgnoreServers)
@@ -163,7 +205,8 @@ NNTPConnection* ServerPool::GetConnection(int iLevel, NewsServer* pWantServer, S
{
PooledConnection* pCandidateConnection = *it;
NewsServer* pCandidateServer = pCandidateConnection->GetNewsServer();
if (!pCandidateConnection->GetInUse() && pCandidateServer->GetLevel() == iLevel &&
if (!pCandidateConnection->GetInUse() && pCandidateServer->GetActive() &&
pCandidateServer->GetNormLevel() == iLevel &&
(!pWantServer || pCandidateServer == pWantServer ||
(pWantServer->GetGroup() > 0 && pWantServer->GetGroup() == pCandidateServer->GetGroup())))
{
@@ -176,7 +219,7 @@ NNTPConnection* ServerPool::GetConnection(int iLevel, NewsServer* pWantServer, S
NewsServer* pIgnoreServer = *it;
if (pIgnoreServer == pCandidateServer ||
(pIgnoreServer->GetGroup() > 0 && pIgnoreServer->GetGroup() == pCandidateServer->GetGroup() &&
pIgnoreServer->GetLevel() == pCandidateServer->GetLevel()))
pIgnoreServer->GetNormLevel() == pCandidateServer->GetNormLevel()))
{
bUseConnection = false;
break;
@@ -218,7 +261,11 @@ void ServerPool::FreeConnection(NNTPConnection* pConnection, bool bUsed)
{
((PooledConnection*)pConnection)->SetFreeTimeNow();
}
m_Levels[pConnection->GetNewsServer()->GetLevel()]++;
if (pConnection->GetNewsServer()->GetNormLevel() > -1 && pConnection->GetNewsServer()->GetActive())
{
m_Levels[pConnection->GetNewsServer()->GetNormLevel()]++;
}
m_mutexConnections.Unlock();
}
@@ -229,36 +276,88 @@ void ServerPool::CloseUnusedConnections()
time_t curtime = ::time(NULL);
for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++)
int i = 0;
for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); )
{
PooledConnection* pConnection = *it;
if (!pConnection->GetInUse() && pConnection->GetStatus() == Connection::csConnected)
bool bDeleted = false;
if (!pConnection->GetInUse() &&
(pConnection->GetNewsServer()->GetNormLevel() == -1 ||
!pConnection->GetNewsServer()->GetActive()))
{
debug("Closing (and deleting) unused connection to server%i", pConnection->GetNewsServer()->GetID());
if (pConnection->GetStatus() == Connection::csConnected)
{
pConnection->Disconnect();
}
delete pConnection;
m_Connections.erase(it);
it = m_Connections.begin() + i;
bDeleted = true;
}
if (!bDeleted && !pConnection->GetInUse() && pConnection->GetStatus() == Connection::csConnected)
{
int tdiff = (int)(curtime - pConnection->GetFreeTime());
if (tdiff > CONNECTION_HOLD_SECODNS)
{
debug("Closing unused connection to %s", pConnection->GetHost());
debug("Closing (and keeping) unused connection to server%i", pConnection->GetNewsServer()->GetID());
pConnection->Disconnect();
}
}
if (!bDeleted)
{
it++;
i++;
}
}
m_mutexConnections.Unlock();
}
void ServerPool::Changed()
{
debug("Server config has been changed");
InitConnections();
CloseUnusedConnections();
}
void ServerPool::LogDebugInfo()
{
debug(" ServerPool");
debug(" ----------------");
debug(" Max-Level: %i", m_iMaxLevel);
debug(" Max-Level: %i", m_iMaxNormLevel);
m_mutexConnections.Lock();
debug(" Servers: %i", m_Servers.size());
for (Servers::iterator it = m_Servers.begin(); it != m_Servers.end(); it++)
{
NewsServer* pNewsServer = *it;
debug(" %i) %s (%s): Level=%i, NormLevel=%i", pNewsServer->GetID(), pNewsServer->GetName(),
pNewsServer->GetHost(), pNewsServer->GetLevel(), pNewsServer->GetNormLevel());
}
debug(" Levels: %i", m_Levels.size());
int index = 0;
for (Levels::iterator it = m_Levels.begin(); it != m_Levels.end(); it++, index++)
{
int iSize = *it;
debug(" %i: Size=%i", index, iSize);
}
debug(" Connections: %i", m_Connections.size());
for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++)
{
debug(" %s: Level=%i, InUse:%i", (*it)->GetNewsServer()->GetHost(), (*it)->GetNewsServer()->GetLevel(), (int)(*it)->GetInUse());
PooledConnection* pConnection = *it;
debug(" %i) %s (%s): Level=%i, NormLevel=%i, InUse:%i", pConnection->GetNewsServer()->GetID(),
pConnection->GetNewsServer()->GetName(), pConnection->GetNewsServer()->GetHost(),
pConnection->GetNewsServer()->GetLevel(), pConnection->GetNewsServer()->GetNormLevel(),
(int)pConnection->GetInUse());
}
m_mutexConnections.Unlock();

View File

@@ -2,7 +2,7 @@
* This file is part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2009 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -36,9 +36,6 @@
class ServerPool
{
public:
typedef std::vector<NewsServer*> Servers;
private:
class PooledConnection : public NNTPConnection
{
@@ -57,11 +54,13 @@ private:
typedef std::vector<PooledConnection*> Connections;
Servers m_Servers;
Servers m_SortedServers;
Connections m_Connections;
Levels m_Levels;
int m_iMaxLevel;
int m_iMaxNormLevel;
Mutex m_mutexConnections;
int m_iTimeout;
int m_iGeneration;
void NormalizeLevels();
static bool CompareServers(NewsServer* pServer1, NewsServer* pServer2);
@@ -72,11 +71,13 @@ public:
void SetTimeout(int iTimeout) { m_iTimeout = iTimeout; }
void AddServer(NewsServer* pNewsServer);
void InitConnections();
int GetMaxLevel() { return m_iMaxLevel; }
int GetMaxNormLevel() { return m_iMaxNormLevel; }
Servers* GetServers() { return &m_Servers; } // Only for read access (no lockings)
NNTPConnection* GetConnection(int iLevel, NewsServer* pWantServer, Servers* pIgnoreServers);
void FreeConnection(NNTPConnection* pConnection, bool bUsed);
void CloseUnusedConnections();
void Changed();
int GetGeneration() { return m_iGeneration; }
void LogDebugInfo();
};

21
TLS.cpp
View File

@@ -26,16 +26,15 @@
# include "config.h"
#endif
#ifndef DISABLE_TLS
#ifdef WIN32
#define SKIP_DEFAULT_WINDOWS_HEADERS
#include "win32.h"
#endif
#ifndef DISABLE_TLS
#include <stdlib.h>
#include <string.h>
#include <cstdio>
#ifdef WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
@@ -43,7 +42,6 @@
#include <strings.h>
#endif
#include <ctype.h>
#include <stdarg.h>
#include <limits.h>
#include <time.h>
#include <errno.h>
@@ -277,18 +275,9 @@ TLSSocket::TLSSocket(SOCKET iSocket, bool bIsClient, const char* szCertFile, con
TLSSocket::~TLSSocket()
{
if (m_szCertFile)
{
free(m_szCertFile);
}
if (m_szKeyFile)
{
free(m_szKeyFile);
}
if (m_szCipher)
{
free(m_szCipher);
}
free(m_szCertFile);
free(m_szKeyFile);
free(m_szCipher);
Close();
}

View File

@@ -33,8 +33,8 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <fstream>
#ifndef WIN32
#include <unistd.h>
#endif
@@ -72,12 +72,7 @@ bool UnpackController::FileList::Exists(const char* szFilename)
return false;
}
UnpackController::~UnpackController()
{
m_archiveFiles.Clear();
}
void UnpackController::StartUnpackJob(PostInfo* pPostInfo)
void UnpackController::StartJob(PostInfo* pPostInfo)
{
UnpackController* pUnpackController = new UnpackController();
pUnpackController->m_pPostInfo = pPostInfo;
@@ -100,22 +95,18 @@ void UnpackController::Run()
m_szName[1024-1] = '\0';
m_bCleanedUpDisk = false;
bool bUnpack = true;
m_szPassword[0] = '\0';
m_szFinalDir[0] = '\0';
m_bFinalDirCreated = false;
for (NZBParameterList::iterator it = m_pPostInfo->GetNZBInfo()->GetParameters()->begin(); it != m_pPostInfo->GetNZBInfo()->GetParameters()->end(); it++)
NZBParameter* pParameter = m_pPostInfo->GetNZBInfo()->GetParameters()->Find("*Unpack:", false);
bool bUnpack = !(pParameter && !strcasecmp(pParameter->GetValue(), "no"));
pParameter = m_pPostInfo->GetNZBInfo()->GetParameters()->Find("*Unpack:Password", false);
if (pParameter)
{
NZBParameter* pParameter = *it;
if (!strcasecmp(pParameter->GetName(), "*Unpack:") && !strcasecmp(pParameter->GetValue(), "no"))
{
bUnpack = false;
}
if (!strcasecmp(pParameter->GetName(), "*Unpack:Password"))
{
strncpy(m_szPassword, pParameter->GetValue(), 1024-1);
m_szPassword[1024-1] = '\0';
}
strncpy(m_szPassword, pParameter->GetValue(), 1024-1);
m_szPassword[1024-1] = '\0';
}
g_pDownloadQueueHolder->UnlockQueue();
@@ -125,26 +116,20 @@ void UnpackController::Run()
snprintf(m_szInfoNameUp, 1024, "Unpack for %s", m_szName); // first letter in upper case
m_szInfoNameUp[1024-1] = '\0';
#ifndef DISABLE_PARCHECK
if (bUnpack && HasBrokenFiles() && m_pPostInfo->GetNZBInfo()->GetParStatus() <= NZBInfo::psSkipped && HasParFiles())
{
info("%s has broken files", m_szName);
RequestParCheck(false);
m_pPostInfo->SetWorking(false);
return;
}
#endif
m_bHasParFiles = ParCoordinator::FindMainPars(m_szDestDir, NULL);
if (bUnpack)
{
CheckArchiveFiles();
bool bScanNonStdFiles = m_pPostInfo->GetNZBInfo()->GetRenameStatus() > NZBInfo::rsSkipped ||
m_pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psSuccess ||
!m_bHasParFiles;
CheckArchiveFiles(bScanNonStdFiles);
}
if (bUnpack && (m_bHasRarFiles || m_bHasSevenZipFiles || m_bHasSevenZipMultiFiles))
if (bUnpack && (m_bHasRarFiles || m_bHasNonStdRarFiles || m_bHasSevenZipFiles || m_bHasSevenZipMultiFiles))
{
SetInfoName(m_szInfoName);
SetDefaultLogKind(g_pOptions->GetProcessLogKind());
SetWorkingDir(m_szDestDir);
PrintMessage(Message::mkInfo, "Unpacking %s", m_szName);
@@ -153,22 +138,21 @@ void UnpackController::Run()
m_bUnpackOK = true;
m_bUnpackStartError = false;
m_bUnpackSpaceError = false;
m_bUnpackPasswordError = false;
if (m_bHasRarFiles)
if (m_bHasRarFiles || m_bHasNonStdRarFiles)
{
m_pPostInfo->SetProgressLabel("");
ExecuteUnrar();
}
if (m_bHasSevenZipFiles && m_bUnpackOK)
{
m_pPostInfo->SetProgressLabel("");
ExecuteSevenZip(false);
}
if (m_bHasSevenZipMultiFiles && m_bUnpackOK)
{
m_pPostInfo->SetProgressLabel("");
ExecuteSevenZip(true);
}
@@ -179,14 +163,14 @@ void UnpackController::Run()
PrintMessage(Message::mkInfo, (bUnpack ? "Nothing to unpack for %s" : "Unpack for %s skipped"), m_szName);
#ifndef DISABLE_PARCHECK
if (bUnpack && m_pPostInfo->GetNZBInfo()->GetParStatus() <= NZBInfo::psSkipped && HasParFiles())
if (bUnpack && m_pPostInfo->GetNZBInfo()->GetParStatus() <= NZBInfo::psSkipped &&
m_pPostInfo->GetNZBInfo()->GetRenameStatus() <= NZBInfo::rsSkipped && m_bHasParFiles)
{
RequestParCheck(m_pPostInfo->GetNZBInfo()->GetRenameStatus() <= NZBInfo::rsSkipped);
RequestParCheck();
}
else
#endif
{
m_pPostInfo->SetUnpackStatus(PostInfo::usSkipped);
m_pPostInfo->GetNZBInfo()->SetUnpackStatus(NZBInfo::usSkipped);
m_pPostInfo->SetStage(PostInfo::ptQueued);
}
@@ -212,22 +196,26 @@ void UnpackController::ExecuteUnrar()
szArgs[3] = szPasswordParam;
}
szArgs[4] = "-o+";
szArgs[5] = "*.rar";
szArgs[5] = m_bHasNonStdRarFiles ? "*.*" : "*.rar";
szArgs[6] = m_szUnpackDir;
szArgs[7] = NULL;
SetArgs(szArgs, false);
SetScript(g_pOptions->GetUnrarCmd());
SetDefaultKindPrefix("Unrar: ");
SetLogPrefix("Unrar");
m_bAllOKMessageReceived = false;
m_eUnpacker = upUnrar;
SetProgressLabel("");
int iExitCode = Execute();
m_pPostInfo->SetProgressLabel("");
SetLogPrefix(NULL);
SetProgressLabel("");
m_bUnpackOK = iExitCode == 0 && m_bAllOKMessageReceived && !GetTerminated();
m_bUnpackStartError = iExitCode == -1;
m_bUnpackSpaceError = iExitCode == 5;
m_bUnpackPasswordError = iExitCode == 11; // only for rar5-archives
if (!m_bUnpackOK && iExitCode > 0)
{
@@ -264,14 +252,16 @@ void UnpackController::ExecuteSevenZip(bool bMultiVolumes)
SetArgs(szArgs, false);
SetScript(g_pOptions->GetSevenZipCmd());
SetDefaultKindPrefix("7-Zip: ");
m_bAllOKMessageReceived = false;
m_eUnpacker = upSevenZip;
PrintMessage(Message::mkInfo, "Executing 7-Zip");
SetLogPrefix("7-Zip");
SetProgressLabel("");
int iExitCode = Execute();
m_pPostInfo->SetProgressLabel("");
SetLogPrefix(NULL);
SetProgressLabel("");
m_bUnpackOK = iExitCode == 0 && m_bAllOKMessageReceived && !GetTerminated();
m_bUnpackStartError = iExitCode == -1;
@@ -289,86 +279,83 @@ void UnpackController::Completed()
if (m_bUnpackOK && bCleanupSuccess)
{
PrintMessage(Message::mkInfo, "%s %s", m_szInfoNameUp, "successful");
m_pPostInfo->SetUnpackStatus(PostInfo::usSuccess);
m_pPostInfo->GetNZBInfo()->SetUnpackStatus(NZBInfo::usSuccess);
m_pPostInfo->GetNZBInfo()->SetUnpackCleanedUpDisk(m_bCleanedUpDisk);
if (g_pOptions->GetParRename())
{
//request par-rename check for extracted files
m_pPostInfo->GetNZBInfo()->SetRenameStatus(NZBInfo::rsNone);
}
m_pPostInfo->SetStage(PostInfo::ptQueued);
}
else
{
#ifndef DISABLE_PARCHECK
if (!m_bUnpackOK && m_pPostInfo->GetNZBInfo()->GetParStatus() <= NZBInfo::psSkipped && !m_bUnpackStartError && !GetTerminated() && HasParFiles())
if (!m_bUnpackOK && m_pPostInfo->GetNZBInfo()->GetParStatus() <= NZBInfo::psSkipped &&
!m_bUnpackStartError && !m_bUnpackSpaceError && !m_bUnpackPasswordError &&
!GetTerminated() && m_bHasParFiles)
{
RequestParCheck(false);
RequestParCheck();
}
else
#endif
{
PrintMessage(Message::mkError, "%s failed", m_szInfoNameUp);
m_pPostInfo->SetUnpackStatus(PostInfo::usFailure);
m_pPostInfo->GetNZBInfo()->SetUnpackStatus(NZBInfo::usFailure);
m_pPostInfo->GetNZBInfo()->SetUnpackStatus(
m_bUnpackSpaceError ? NZBInfo::usSpace :
m_bUnpackPasswordError ? NZBInfo::usPassword :
NZBInfo::usFailure);
m_pPostInfo->SetStage(PostInfo::ptQueued);
}
}
}
#ifndef DISABLE_PARCHECK
void UnpackController::RequestParCheck(bool bRename)
void UnpackController::RequestParCheck()
{
PrintMessage(Message::mkInfo, "%s requested %s", m_szInfoNameUp, bRename ? "par-rename": "par-check/repair");
if (bRename)
{
m_pPostInfo->SetRequestParRename(true);
}
else
{
m_pPostInfo->SetRequestParCheck(PostInfo::rpAll);
}
PrintMessage(Message::mkInfo, "%s requested par-check/repair", m_szInfoNameUp);
m_pPostInfo->SetRequestParCheck(true);
m_pPostInfo->SetStage(PostInfo::ptFinished);
}
#endif
bool UnpackController::HasParFiles()
{
return ParCoordinator::FindMainPars(m_szDestDir, NULL);
}
bool UnpackController::HasBrokenFiles()
{
char szBrokenLog[1024];
snprintf(szBrokenLog, 1024, "%s%c%s", m_szDestDir, PATH_SEPARATOR, "_brokenlog.txt");
szBrokenLog[1024-1] = '\0';
return Util::FileExists(szBrokenLog);
}
void UnpackController::CreateUnpackDir()
{
if (strlen(g_pOptions->GetInterDir()) > 0 &&
!strncmp(m_szDestDir, g_pOptions->GetInterDir(), strlen(g_pOptions->GetInterDir())))
m_bInterDir = strlen(g_pOptions->GetInterDir()) > 0 &&
!strncmp(m_szDestDir, g_pOptions->GetInterDir(), strlen(g_pOptions->GetInterDir()));
if (m_bInterDir)
{
m_pPostInfo->GetNZBInfo()->BuildFinalDirName(m_szFinalDir, 1024);
m_szFinalDir[1024-1] = '\0';
Util::ForceDirectories(m_szFinalDir);
snprintf(m_szUnpackDir, 1024, "%s%c%s", m_szFinalDir, PATH_SEPARATOR, "_unpack");
m_bFinalDirCreated = !Util::DirectoryExists(m_szFinalDir);
}
else
{
snprintf(m_szUnpackDir, 1024, "%s%c%s", m_szDestDir, PATH_SEPARATOR, "_unpack");
}
m_szUnpackDir[1024-1] = '\0';
Util::ForceDirectories(m_szUnpackDir);
char szErrBuf[1024];
if (!Util::ForceDirectories(m_szUnpackDir, szErrBuf, sizeof(szErrBuf)))
{
error("Could not create directory %s: %s", m_szUnpackDir, szErrBuf);
}
}
void UnpackController::CheckArchiveFiles()
void UnpackController::CheckArchiveFiles(bool bScanNonStdFiles)
{
m_bHasRarFiles = false;
m_bHasNonStdRarFiles = false;
m_bHasSevenZipFiles = false;
m_bHasSevenZipMultiFiles = false;
RegEx regExRar(".*\\.rar$");
RegEx regExRarMultiSeq(".*\\.(r|s)[0-9][0-9]$");
RegEx regExSevenZip(".*\\.7z$");
RegEx regExSevenZipMulti(".*\\.7z\\.[0-9]*$");
RegEx regExSevenZipMulti(".*\\.7z\\.[0-9]+$");
RegEx regExNumExt(".*\\.[0-9]+$");
DirBrowser dir(m_szDestDir);
while (const char* filename = dir.Next())
@@ -383,18 +370,45 @@ void UnpackController::CheckArchiveFiles()
{
m_bHasRarFiles = true;
}
if (regExSevenZip.Match(filename))
else if (regExSevenZip.Match(filename))
{
m_bHasSevenZipFiles = true;
}
if (regExSevenZipMulti.Match(filename))
else if (regExSevenZipMulti.Match(filename))
{
m_bHasSevenZipMultiFiles = true;
}
else if (bScanNonStdFiles && !m_bHasNonStdRarFiles &&
!regExRarMultiSeq.Match(filename) && regExNumExt.Match(filename) &&
FileHasRarSignature(szFullFilename))
{
m_bHasNonStdRarFiles = true;
}
}
}
}
bool UnpackController::FileHasRarSignature(const char* szFilename)
{
char rar4Signature[] = { 0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00 };
char rar5Signature[] = { 0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x01, 0x00 };
char fileSignature[8];
int cnt = 0;
FILE* infile;
infile = fopen(szFilename, "rb");
if (infile)
{
cnt = (int)fread(fileSignature, 1, sizeof(fileSignature), infile);
fclose(infile);
}
bool bRar = cnt == sizeof(fileSignature) &&
(!strcmp(rar4Signature, fileSignature) || !strcmp(rar5Signature, fileSignature));
return bRar;
}
bool UnpackController::Cleanup()
{
// By success:
@@ -414,7 +428,8 @@ bool UnpackController::Cleanup()
DirBrowser dir(m_szUnpackDir);
while (const char* filename = dir.Next())
{
if (strcmp(filename, ".") && strcmp(filename, ".."))
if (strcmp(filename, ".") && strcmp(filename, "..") &&
strcmp(filename, ".AppleDouble") && strcmp(filename, ".DS_Store"))
{
char szSrcFile[1024];
snprintf(szSrcFile, 1024, "%s%c%s", m_szUnpackDir, PATH_SEPARATOR, filename);
@@ -443,35 +458,19 @@ bool UnpackController::Cleanup()
PrintMessage(Message::mkError, "Could not remove temporary directory %s", m_szUnpackDir);
}
if (!m_bUnpackOK && m_bFinalDirCreated)
{
Util::RemoveDirectory(m_szFinalDir);
}
if (m_bUnpackOK && bOK && g_pOptions->GetUnpackCleanupDisk())
{
PrintMessage(Message::mkInfo, "Deleting archive files");
// Delete rar-files (only files which were used by unrar)
for (FileList::iterator it = m_archiveFiles.begin(); it != m_archiveFiles.end(); it++)
{
char* szFilename = *it;
if (!extractedFiles.Exists(szFilename))
{
char szFullFilename[1024];
snprintf(szFullFilename, 1024, "%s%c%s", m_szDestDir, PATH_SEPARATOR, szFilename);
szFullFilename[1024-1] = '\0';
PrintMessage(Message::mkInfo, "Deleting file %s", szFilename);
if (remove(szFullFilename) != 0)
{
PrintMessage(Message::mkError, "Could not delete file %s", szFullFilename);
}
}
}
// Unfortunately 7-Zip doesn't print the processed archive-files to the output.
// Therefore we don't know for sure which files were extracted.
// We just delete all 7z-files in the directory.
RegEx regExSevenZip(".*\\.7z$|.*\\.7z\\.[0-9]*$");
RegEx regExRar(".*\\.rar$");
RegEx regExRarMultiSeq(".*\\.[r-z][0-9][0-9]$");
RegEx regExSevenZip(".*\\.7z$|.*\\.7z\\.[0-9]+$");
RegEx regExNumExt(".*\\.[0-9]+$");
DirBrowser dir(m_szDestDir);
while (const char* filename = dir.Next())
@@ -480,8 +479,12 @@ bool UnpackController::Cleanup()
snprintf(szFullFilename, 1024, "%s%c%s", m_szDestDir, PATH_SEPARATOR, filename);
szFullFilename[1024-1] = '\0';
if (strcmp(filename, ".") && strcmp(filename, "..") && !Util::DirectoryExists(szFullFilename)
&& regExSevenZip.Match(filename) && !extractedFiles.Exists(filename))
if (strcmp(filename, ".") && strcmp(filename, "..") &&
!Util::DirectoryExists(szFullFilename) &&
(m_bInterDir || !extractedFiles.Exists(filename)) &&
(regExRar.Match(filename) || regExSevenZip.Match(filename) ||
(regExRarMultiSeq.Match(filename) && FileHasRarSignature(szFullFilename)) ||
(m_bHasNonStdRarFiles && regExNumExt.Match(filename) && FileHasRarSignature(szFullFilename))))
{
PrintMessage(Message::mkInfo, "Deleting file %s", filename);
@@ -563,7 +566,7 @@ bool UnpackController::ReadLine(char* szBuf, int iBufSize, FILE* pStream)
return i > 0;
}
void UnpackController::AddMessage(Message::EKind eKind, bool bDefaultKind, const char* szText)
void UnpackController::AddMessage(Message::EKind eKind, const char* szText)
{
char szMsgText[1024];
strncpy(szMsgText, szText, 1024);
@@ -571,31 +574,38 @@ void UnpackController::AddMessage(Message::EKind eKind, bool bDefaultKind, const
// Modify unrar messages for better readability:
// remove the destination path part from message "Extracting file.xxx"
if (m_eUnpacker == upUnrar && !strncmp(szText, "Extracting ", 12) &&
!strncmp(szText + 12, m_szUnpackDir, strlen(m_szUnpackDir)))
if (m_eUnpacker == upUnrar && !strncmp(szText, "Unrar: Extracting ", 19) &&
!strncmp(szText + 19, m_szUnpackDir, strlen(m_szUnpackDir)))
{
snprintf(szMsgText, 1024, "Extracting %s", szText + 12 + strlen(m_szUnpackDir) + 1);
snprintf(szMsgText, 1024, "Unrar: Extracting %s", szText + 19 + strlen(m_szUnpackDir) + 1);
szMsgText[1024-1] = '\0';
}
ScriptController::AddMessage(eKind, bDefaultKind, szMsgText);
ScriptController::AddMessage(eKind, szMsgText);
m_pPostInfo->AppendMessage(eKind, szMsgText);
if (m_eUnpacker == upUnrar && !strncmp(szMsgText, "Extracting ", 11))
if (m_eUnpacker == upUnrar && !strncmp(szMsgText, "Unrar: UNRAR ", 6) &&
strstr(szMsgText, " Copyright ") && strstr(szMsgText, " Alexander Roshal"))
{
m_pPostInfo->SetProgressLabel(szMsgText);
// reset start time for a case if user uses unpack-script to do some things
// (like sending Wake-On-Lan message) before executing unrar
m_pPostInfo->SetStageTime(time(NULL));
}
if (m_eUnpacker == upUnrar && !strncmp(szText, "Extracting from ", 16))
if (m_eUnpacker == upUnrar && !strncmp(szMsgText, "Unrar: Extracting ", 18))
{
const char *szFilename = szText + 16;
SetProgressLabel(szMsgText + 7);
}
if (m_eUnpacker == upUnrar && !strncmp(szText, "Unrar: Extracting from ", 23))
{
const char *szFilename = szText + 23;
debug("Filename: %s", szFilename);
m_archiveFiles.push_back(strdup(szFilename));
m_pPostInfo->SetProgressLabel(szText);
SetProgressLabel(szText + 7);
}
if ((m_eUnpacker == upUnrar && !strncmp(szText, "All OK", 6)) ||
(m_eUnpacker == upSevenZip && !strncmp(szText, "Everything is Ok", 16)))
if ((m_eUnpacker == upUnrar && !strncmp(szText, "Unrar: All OK", 13)) ||
(m_eUnpacker == upSevenZip && !strncmp(szText, "7-Zip: Everything is Ok", 23)))
{
m_bAllOKMessageReceived = true;
}
@@ -608,8 +618,15 @@ void UnpackController::Stop()
Terminate();
}
void UnpackController::SetProgressLabel(const char* szProgressLabel)
{
g_pDownloadQueueHolder->LockQueue();
m_pPostInfo->SetProgressLabel(szProgressLabel);
g_pDownloadQueueHolder->UnlockQueue();
}
void MoveController::StartMoveJob(PostInfo* pPostInfo)
void MoveController::StartJob(PostInfo* pPostInfo)
{
MoveController* pMoveController = new MoveController();
pMoveController->m_pPostInfo = pPostInfo;
@@ -634,9 +651,6 @@ void MoveController::Run()
szInfoName[1024-1] = '\0';
SetInfoName(szInfoName);
SetDefaultKindPrefix("Move: ");
SetDefaultLogKind(g_pOptions->GetProcessLogKind());
strncpy(m_szInterDir, m_pPostInfo->GetNZBInfo()->GetDestDir(), 1024);
m_szInterDir[1024-1] = '\0';
@@ -672,31 +686,26 @@ void MoveController::Run()
bool MoveController::MoveFiles()
{
char szErrBuf[1024];
if (!Util::ForceDirectories(m_szDestDir, szErrBuf, sizeof(szErrBuf)))
{
error("Could not create directory %s: %s", m_szDestDir, szErrBuf);
return false;
}
bool bOK = true;
bOK = Util::ForceDirectories(m_szDestDir);
DirBrowser dir(m_szInterDir);
while (const char* filename = dir.Next())
{
if (strcmp(filename, ".") && strcmp(filename, ".."))
if (strcmp(filename, ".") && strcmp(filename, "..") &&
strcmp(filename, ".AppleDouble") && strcmp(filename, ".DS_Store"))
{
char szSrcFile[1024];
snprintf(szSrcFile, 1024, "%s%c%s", m_szInterDir, PATH_SEPARATOR, filename);
szSrcFile[1024-1] = '\0';
char szDstFile[1024];
snprintf(szDstFile, 1024, "%s%c%s", m_szDestDir, PATH_SEPARATOR, filename);
szDstFile[1024-1] = '\0';
// prevent overwriting of existing files
int dupcount = 0;
while (Util::FileExists(szDstFile))
{
dupcount++;
snprintf(szDstFile, 1024, "%s%c%s_duplicate%d", m_szDestDir, PATH_SEPARATOR, filename, dupcount);
szDstFile[1024-1] = '\0';
}
Util::MakeUniqueFilename(szDstFile, 1024, m_szDestDir, filename);
PrintMessage(Message::mkInfo, "Moving file %s to %s", Util::BaseFileName(szSrcFile), m_szDestDir);
if (!Util::MoveFile(szSrcFile, szDstFile))
@@ -707,7 +716,145 @@ bool MoveController::MoveFiles()
}
}
Util::RemoveDirectory(m_szInterDir);
if (bOK && !Util::DeleteDirectoryWithContent(m_szInterDir))
{
PrintMessage(Message::mkError, "Could not remove intermediate directory %s", m_szInterDir);
}
return bOK;
}
void CleanupController::StartJob(PostInfo* pPostInfo)
{
CleanupController* pCleanupController = new CleanupController();
pCleanupController->m_pPostInfo = pPostInfo;
pCleanupController->SetAutoDestroy(false);
pPostInfo->SetPostThread(pCleanupController);
pCleanupController->Start();
}
void CleanupController::Run()
{
// the locking is needed for accessing the members of NZBInfo
g_pDownloadQueueHolder->LockQueue();
char szNZBName[1024];
strncpy(szNZBName, m_pPostInfo->GetNZBInfo()->GetName(), 1024);
szNZBName[1024-1] = '\0';
char szInfoName[1024];
snprintf(szInfoName, 1024, "cleanup for %s", m_pPostInfo->GetNZBInfo()->GetName());
szInfoName[1024-1] = '\0';
SetInfoName(szInfoName);
strncpy(m_szDestDir, m_pPostInfo->GetNZBInfo()->GetDestDir(), 1024);
m_szDestDir[1024-1] = '\0';
bool bInterDir = strlen(g_pOptions->GetInterDir()) > 0 &&
!strncmp(m_szDestDir, g_pOptions->GetInterDir(), strlen(g_pOptions->GetInterDir()));
if (bInterDir)
{
m_pPostInfo->GetNZBInfo()->BuildFinalDirName(m_szFinalDir, 1024);
m_szFinalDir[1024-1] = '\0';
}
else
{
m_szFinalDir[0] = '\0';
}
g_pDownloadQueueHolder->UnlockQueue();
info("Cleaning up %s", szNZBName);
bool bDeleted = false;
bool bOK = Cleanup(m_szDestDir, &bDeleted);
if (bOK && m_szFinalDir[0] != '\0')
{
bool bDeleted2 = false;
bOK = Cleanup(m_szFinalDir, &bDeleted2);
bDeleted = bDeleted || bDeleted2;
}
szInfoName[0] = 'C'; // uppercase
if (bOK && bDeleted)
{
info("%s successful", szInfoName);
m_pPostInfo->GetNZBInfo()->SetCleanupStatus(NZBInfo::csSuccess);
}
else if (bOK)
{
info("Nothing to cleanup for %s", szNZBName);
m_pPostInfo->GetNZBInfo()->SetCleanupStatus(NZBInfo::csSuccess);
}
else
{
error("%s failed", szInfoName);
m_pPostInfo->GetNZBInfo()->SetCleanupStatus(NZBInfo::csFailure);
}
m_pPostInfo->SetStage(PostInfo::ptQueued);
m_pPostInfo->SetWorking(false);
}
bool CleanupController::Cleanup(const char* szDestDir, bool *bDeleted)
{
*bDeleted = false;
bool bOK = true;
ExtList extList;
// split ExtCleanupDisk into tokens and create a list
char* szExtCleanupDisk = strdup(g_pOptions->GetExtCleanupDisk());
char* saveptr;
char* szExt = strtok_r(szExtCleanupDisk, ",; ", &saveptr);
while (szExt)
{
extList.push_back(szExt);
szExt = strtok_r(NULL, ",; ", &saveptr);
}
DirBrowser dir(szDestDir);
while (const char* filename = dir.Next())
{
// check file extension
int iFilenameLen = strlen(filename);
bool bDeleteIt = false;
for (ExtList::iterator it = extList.begin(); it != extList.end(); it++)
{
const char* szExt = *it;
int iExtLen = strlen(szExt);
if (iFilenameLen >= iExtLen && !strcasecmp(szExt, filename + iFilenameLen - iExtLen))
{
bDeleteIt = true;
break;
}
}
if (bDeleteIt)
{
char szFullFilename[1024];
snprintf(szFullFilename, 1024, "%s%c%s", szDestDir, PATH_SEPARATOR, filename);
szFullFilename[1024-1] = '\0';
PrintMessage(Message::mkInfo, "Deleting file %s", filename);
if (remove(szFullFilename) != 0)
{
PrintMessage(Message::mkError, "Could not delete file %s! Errcode: %i", szFullFilename, errno);
bOK = false;
}
*bDeleted = true;
}
}
free(szExtCleanupDisk);
return bOK;
}

View File

@@ -59,37 +59,41 @@ private:
char m_szFinalDir[1024];
char m_szUnpackDir[1024];
char m_szPassword[1024];
bool m_bInterDir;
bool m_bAllOKMessageReceived;
bool m_bNoFilesMessageReceived;
bool m_bHasParFiles;
bool m_bHasRarFiles;
bool m_bHasNonStdRarFiles;
bool m_bHasSevenZipFiles;
bool m_bHasSevenZipMultiFiles;
bool m_bUnpackOK;
bool m_bUnpackStartError;
bool m_bUnpackSpaceError;
bool m_bUnpackPasswordError;
bool m_bCleanedUpDisk;
EUnpacker m_eUnpacker;
FileList m_archiveFiles;
bool m_bFinalDirCreated;
protected:
virtual bool ReadLine(char* szBuf, int iBufSize, FILE* pStream);
virtual void AddMessage(Message::EKind eKind, bool bDefaultKind, const char* szText);
virtual void AddMessage(Message::EKind eKind, const char* szText);
void ExecuteUnrar();
void ExecuteSevenZip(bool bMultiVolumes);
void Completed();
void CreateUnpackDir();
bool Cleanup();
bool HasParFiles();
bool HasBrokenFiles();
void CheckArchiveFiles();
void CheckArchiveFiles(bool bScanNonStdFiles);
void SetProgressLabel(const char* szProgressLabel);
#ifndef DISABLE_PARCHECK
void RequestParCheck(bool bRename);
void RequestParCheck();
#endif
bool FileHasRarSignature(const char* szFilename);
public:
virtual ~UnpackController();
virtual void Run();
virtual void Stop();
static void StartUnpackJob(PostInfo* pPostInfo);
static void StartJob(PostInfo* pPostInfo);
};
class MoveController : public Thread, public ScriptController
@@ -103,7 +107,23 @@ private:
public:
virtual void Run();
static void StartMoveJob(PostInfo* pPostInfo);
static void StartJob(PostInfo* pPostInfo);
};
class CleanupController : public Thread, public ScriptController
{
private:
PostInfo* m_pPostInfo;
char m_szDestDir[1024];
char m_szFinalDir[1024];
bool Cleanup(const char* szDestDir, bool *bDeleted);
typedef std::deque<char*> ExtList;
public:
virtual void Run();
static void StartJob(PostInfo* pPostInfo);
};
#endif

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2012 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2012-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -33,7 +33,7 @@
#include <stdlib.h>
#include <string.h>
#include <cstdio>
#include <stdio.h>
#include <sys/stat.h>
#ifndef WIN32
#include <unistd.h>
@@ -49,10 +49,13 @@
#include "Util.h"
#include "NZBFile.h"
#include "QueueCoordinator.h"
#include "Scanner.h"
extern Options* g_pOptions;
extern DiskState* g_pDiskState;
extern QueueCoordinator* g_pQueueCoordinator;
extern Scanner* g_pScanner;
UrlDownloader::UrlDownloader() : WebDownloader()
{
@@ -61,36 +64,46 @@ UrlDownloader::UrlDownloader() : WebDownloader()
UrlDownloader::~UrlDownloader()
{
if (m_szCategory)
{
free(m_szCategory);
}
free(m_szCategory);
}
void UrlDownloader::ProcessHeader(const char* szLine)
{
WebDownloader::ProcessHeader(szLine);
if (!strncmp(szLine, "X-DNZB-Category: ", 17))
if (!strncmp(szLine, "X-DNZB-Category:", 16))
{
if (m_szCategory)
{
free(m_szCategory);
}
const char *szCat = szLine + 17;
int iCatLen = strlen(szCat);
// trim trailing CR/LF/spaces
while (iCatLen > 0 && (szCat[iCatLen-1] == '\n' || szCat[iCatLen-1] == '\r' || szCat[iCatLen-1] == ' ')) iCatLen--;
m_szCategory = (char*)malloc(iCatLen + 1);
strncpy(m_szCategory, szCat, iCatLen);
m_szCategory[iCatLen] = '\0';
free(m_szCategory);
char* szCategory = strdup(szLine + 16);
m_szCategory = strdup(Util::Trim(szCategory));
free(szCategory);
debug("Category: %s", m_szCategory);
}
else if (!strncmp(szLine, "X-DNZB-", 7))
{
char* szModLine = strdup(szLine);
char* szValue = strchr(szModLine, ':');
if (szValue)
{
*szValue = NULL;
szValue++;
while (*szValue == ' ') szValue++;
Util::Trim(szValue);
debug("X-DNZB: %s", szModLine);
debug("Value: %s", szValue);
char szParamName[100];
snprintf(szParamName, 100, "*DNZB:%s", szModLine + 7);
szParamName[100-1] = '\0';
char* szVal = WebUtil::Latin1ToUtf8(szValue);
m_ppParameters.SetParameter(szParamName, szVal);
free(szVal);
}
free(szModLine);
}
}
UrlCoordinator::UrlCoordinator()
@@ -98,6 +111,7 @@ UrlCoordinator::UrlCoordinator()
debug("Creating UrlCoordinator");
m_bHasMoreJobs = true;
m_bForce = false;
}
UrlCoordinator::~UrlCoordinator()
@@ -131,7 +145,7 @@ void UrlCoordinator::Run()
while (!IsStopped())
{
if (!(g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2()))
if (!(g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2()) || m_bForce || g_pOptions->GetUrlForce())
{
// start download for next URL
DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue();
@@ -142,10 +156,14 @@ void UrlCoordinator::Run()
bool bHasMoreUrls = GetNextUrl(pDownloadQueue, pUrlInfo);
bool bUrlDownloadsRunning = !m_ActiveDownloads.empty();
m_bHasMoreJobs = bHasMoreUrls || bUrlDownloadsRunning;
if (bHasMoreUrls && !IsStopped() && Thread::GetThreadCount() < g_pOptions->GetThreadLimit())
if (bHasMoreUrls && !IsStopped())
{
StartUrlDownload(pUrlInfo);
}
if (!bHasMoreUrls)
{
m_bForce = false;
}
}
g_pQueueCoordinator->UnlockQueue();
}
@@ -260,6 +278,11 @@ void UrlCoordinator::AddUrlToQueue(UrlInfo* pUrlInfo, bool AddFirst)
g_pDiskState->SaveDownloadQueue(pDownloadQueue);
}
if (pUrlInfo->GetForce())
{
m_bForce = true;
}
g_pQueueCoordinator->UnlockQueue();
}
@@ -268,19 +291,19 @@ void UrlCoordinator::AddUrlToQueue(UrlInfo* pUrlInfo, bool AddFirst)
*/
bool UrlCoordinator::GetNextUrl(DownloadQueue* pDownloadQueue, UrlInfo* &pUrlInfo)
{
bool bOK = false;
bool bPauseDownload = g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2();
for (UrlQueue::iterator at = pDownloadQueue->GetUrlQueue()->begin(); at != pDownloadQueue->GetUrlQueue()->end(); at++)
{
pUrlInfo = *at;
if (pUrlInfo->GetStatus() == 0)
if (pUrlInfo->GetStatus() == 0 && (!bPauseDownload || pUrlInfo->GetForce() || g_pOptions->GetUrlForce()))
{
bOK = true;
return true;
break;
}
}
return bOK;
return false;
}
void UrlCoordinator::StartUrlDownload(UrlInfo* pUrlInfo)
@@ -292,6 +315,7 @@ void UrlCoordinator::StartUrlDownload(UrlInfo* pUrlInfo)
pUrlDownloader->Attach(this);
pUrlDownloader->SetUrlInfo(pUrlInfo);
pUrlDownloader->SetURL(pUrlInfo->GetURL());
pUrlDownloader->SetForce(pUrlInfo->GetForce() || g_pOptions->GetUrlForce());
char tmp[1024];
@@ -308,11 +332,11 @@ void UrlCoordinator::StartUrlDownload(UrlInfo* pUrlInfo)
pUrlDownloader->Start();
}
void UrlCoordinator::Update(Subject* Caller, void* Aspect)
void UrlCoordinator::Update(Subject* pCaller, void* pAspect)
{
debug("Notification from UrlDownloader received");
UrlDownloader* pUrlDownloader = (UrlDownloader*) Caller;
UrlDownloader* pUrlDownloader = (UrlDownloader*) pCaller;
if ((pUrlDownloader->GetStatus() == WebDownloader::adFinished) ||
(pUrlDownloader->GetStatus() == WebDownloader::adFailed) ||
(pUrlDownloader->GetStatus() == WebDownloader::adRetry))
@@ -358,9 +382,8 @@ void UrlCoordinator::UrlCompleted(UrlDownloader* pUrlDownloader)
debug("Filename: [%s]", filename);
DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue();
// delete Download from Queue
// delete Download from active jobs
g_pQueueCoordinator->LockQueue();
for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++)
{
UrlDownloader* pa = *it;
@@ -370,12 +393,32 @@ void UrlCoordinator::UrlCompleted(UrlDownloader* pUrlDownloader)
break;
}
}
g_pQueueCoordinator->UnlockQueue();
bool bDeleteObj = false;
Aspect aspect = { eaUrlCompleted, pUrlInfo };
Notify(&aspect);
if (pUrlInfo->GetStatus() == UrlInfo::aiFinished || pUrlInfo->GetStatus() == UrlInfo::aiFailed)
if (pUrlInfo->GetStatus() == UrlInfo::aiFinished)
{
// delete UrlInfo from Queue
// add nzb-file to download queue
Scanner::EAddStatus eAddStatus = g_pScanner->AddExternalFile(
pUrlInfo->GetNZBFilename() && strlen(pUrlInfo->GetNZBFilename()) > 0 ? pUrlInfo->GetNZBFilename() : filename,
strlen(pUrlInfo->GetCategory()) > 0 ? pUrlInfo->GetCategory() : pUrlDownloader->GetCategory(),
pUrlInfo->GetPriority(), pUrlInfo->GetDupeKey(), pUrlInfo->GetDupeScore(), pUrlInfo->GetDupeMode(),
pUrlDownloader->GetParameters(), pUrlInfo->GetAddTop(), pUrlInfo->GetAddPaused(),
pUrlDownloader->GetOutputFilename(), NULL, 0);
if (eAddStatus != Scanner::asSuccess)
{
pUrlInfo->SetStatus(eAddStatus == Scanner::asFailed ? UrlInfo::aiScanFailed : UrlInfo::aiScanSkipped);
}
}
// delete Download from Url Queue
if (pUrlInfo->GetStatus() != UrlInfo::aiRetry)
{
DownloadQueue* pDownloadQueue = g_pQueueCoordinator->LockQueue();
for (UrlQueue::iterator it = pDownloadQueue->GetUrlQueue()->begin(); it != pDownloadQueue->GetUrlQueue()->end(); it++)
{
UrlInfo* pa = *it;
@@ -386,9 +429,9 @@ void UrlCoordinator::UrlCompleted(UrlDownloader* pUrlDownloader)
}
}
bDeleteObj = true;
bool bDeleteObj = true;
if (g_pOptions->GetKeepHistory() > 0 && pUrlInfo->GetStatus() == UrlInfo::aiFailed)
if (g_pOptions->GetKeepHistory() > 0 && pUrlInfo->GetStatus() != UrlInfo::aiFinished)
{
HistoryInfo* pHistoryInfo = new HistoryInfo(pUrlInfo);
pHistoryInfo->SetTime(time(NULL));
@@ -400,56 +443,12 @@ void UrlCoordinator::UrlCompleted(UrlDownloader* pUrlDownloader)
{
g_pDiskState->SaveDownloadQueue(pDownloadQueue);
}
}
g_pQueueCoordinator->UnlockQueue();
g_pQueueCoordinator->UnlockQueue();
if (pUrlInfo->GetStatus() == UrlInfo::aiFinished)
{
// add nzb-file to download queue
AddToNZBQueue(pUrlInfo, pUrlDownloader->GetOutputFilename(), filename, pUrlDownloader->GetCategory());
}
if (bDeleteObj)
{
delete pUrlInfo;
if (bDeleteObj)
{
delete pUrlInfo;
}
}
}
void UrlCoordinator::AddToNZBQueue(UrlInfo* pUrlInfo, const char* szTempFilename, const char* szOriginalFilename, const char* szOriginalCategory)
{
info("Queue downloaded collection %s", szOriginalFilename);
NZBFile* pNZBFile = NZBFile::CreateFromFile(szTempFilename, pUrlInfo->GetCategory());
if (pNZBFile)
{
pNZBFile->GetNZBInfo()->SetName(NULL);
pNZBFile->GetNZBInfo()->SetFilename(pUrlInfo->GetNZBFilename() && strlen(pUrlInfo->GetNZBFilename()) > 0 ? pUrlInfo->GetNZBFilename() : szOriginalFilename);
if (strlen(pUrlInfo->GetCategory()) > 0)
{
pNZBFile->GetNZBInfo()->SetCategory(pUrlInfo->GetCategory());
}
else if (szOriginalCategory)
{
pNZBFile->GetNZBInfo()->SetCategory(szOriginalCategory);
}
pNZBFile->GetNZBInfo()->BuildDestDirName();
for (NZBFile::FileInfos::iterator it = pNZBFile->GetFileInfos()->begin(); it != pNZBFile->GetFileInfos()->end(); it++)
{
FileInfo* pFileInfo = *it;
pFileInfo->SetPriority(pUrlInfo->GetPriority());
pFileInfo->SetPaused(pUrlInfo->GetAddPaused());
}
g_pQueueCoordinator->AddNZBFileToQueue(pNZBFile, pUrlInfo->GetAddTop());
delete pNZBFile;
info("Collection %s added to queue", szOriginalFilename);
}
else
{
error("Could not add downloaded collection %s to queue", szOriginalFilename);
}
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2012 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2012-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -41,23 +41,33 @@ class UrlCoordinator : public Thread, public Observer, public Subject
{
public:
typedef std::list<UrlDownloader*> ActiveDownloads;
enum EAspectAction
{
eaUrlAdded,
eaUrlCompleted
};
struct Aspect
{
EAspectAction eAction;
UrlInfo* pUrlInfo;
};
private:
ActiveDownloads m_ActiveDownloads;
bool m_bHasMoreJobs;
bool m_bForce;
bool GetNextUrl(DownloadQueue* pDownloadQueue, UrlInfo* &pUrlInfo);
void StartUrlDownload(UrlInfo* pUrlInfo);
void UrlCompleted(UrlDownloader* pUrlDownloader);
void ResetHangingDownloads();
void AddToNZBQueue(UrlInfo* pUrlInfo, const char* szTempFilename, const char* szOriginalFilename, const char* szOriginalCategory);
public:
UrlCoordinator();
virtual ~UrlCoordinator();
virtual void Run();
virtual void Stop();
void Update(Subject* Caller, void* Aspect);
void Update(Subject* pCaller, void* pAspect);
// Editing the queue
void AddUrlToQueue(UrlInfo* pUrlInfo, bool AddFirst);
@@ -71,6 +81,7 @@ class UrlDownloader : public WebDownloader
private:
UrlInfo* m_pUrlInfo;
char* m_szCategory;
NZBParameterList m_ppParameters;
protected:
virtual void ProcessHeader(const char* szLine);
@@ -81,6 +92,7 @@ public:
void SetUrlInfo(UrlInfo* pUrlInfo) { m_pUrlInfo = pUrlInfo; }
UrlInfo* GetUrlInfo() { return m_pUrlInfo; }
const char* GetCategory() { return m_szCategory; }
NZBParameterList* GetParameters() { return &m_ppParameters; }
};
#endif

765
Util.cpp
View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2007-2010 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -36,6 +36,7 @@
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#ifdef WIN32
#include <direct.h>
#include <WinIoCtl.h>
@@ -50,6 +51,7 @@
#ifndef DISABLE_GZIP
#include <zlib.h>
#endif
#include <time.h>
#include "nzbget.h"
#include "Util.h"
@@ -140,15 +142,7 @@ int getopt(int argc, char *argv[], char *optstring)
DirBrowser::DirBrowser(const char* szPath)
{
char szMask[MAX_PATH + 1];
int len = strlen(szPath);
if (szPath[len] == '\\' || szPath[len] == '/')
{
snprintf(szMask, MAX_PATH + 1, "%s*.*", szPath);
}
else
{
snprintf(szMask, MAX_PATH + 1, "%s%c*.*", szPath, (int)PATH_SEPARATOR);
}
snprintf(szMask, MAX_PATH + 1, "%s%c*.*", szPath, (int)PATH_SEPARATOR);
szMask[MAX_PATH] = '\0';
m_hFile = _findfirst(szMask, &m_FindData);
m_bFirst = true;
@@ -211,6 +205,33 @@ const char* DirBrowser::Next()
#endif
StringBuilder::StringBuilder()
{
m_szBuffer = NULL;
m_iBufferSize = 0;
m_iUsedSize = 0;
}
StringBuilder::~StringBuilder()
{
free(m_szBuffer);
}
void StringBuilder::Append(const char* szStr)
{
int iPartLen = strlen(szStr);
if (m_iUsedSize + iPartLen + 1 > m_iBufferSize)
{
m_iBufferSize += iPartLen + 10240;
m_szBuffer = (char*)realloc(m_szBuffer, m_iBufferSize);
}
strcpy(m_szBuffer + m_iUsedSize, szStr);
m_iUsedSize += iPartLen;
m_szBuffer[m_iUsedSize] = '\0';
}
char Util::VersionRevisionBuf[40];
char* Util::BaseFileName(const char* filename)
@@ -245,9 +266,13 @@ void Util::NormalizePathSeparators(char* szPath)
}
}
bool Util::ForceDirectories(const char* szPath)
bool Util::ForceDirectories(const char* szPath, char* szErrBuf, int iBufSize)
{
char* szNormPath = strdup(szPath);
*szErrBuf = '\0';
char szSysErrStr[256];
char szNormPath[1024];
strncpy(szNormPath, szPath, 1024);
szNormPath[1024-1] = '\0';
NormalizePathSeparators(szNormPath);
int iLen = strlen(szNormPath);
if ((iLen > 0) && szNormPath[iLen-1] == PATH_SEPARATOR
@@ -260,15 +285,30 @@ bool Util::ForceDirectories(const char* szPath)
}
struct stat buffer;
bool bOK = !stat(szNormPath, &buffer) && S_ISDIR(buffer.st_mode);
bool bOK = !stat(szNormPath, &buffer);
if (!bOK && errno != ENOENT)
{
snprintf(szErrBuf, iBufSize, "could not read information for directory %s: errno %i, %s", szNormPath, errno, GetLastErrorMessage(szSysErrStr, sizeof(szSysErrStr)));
szErrBuf[iBufSize-1] = 0;
return false;
}
if (bOK && !S_ISDIR(buffer.st_mode))
{
snprintf(szErrBuf, iBufSize, "path %s is not a directory", szNormPath);
szErrBuf[iBufSize-1] = 0;
return false;
}
if (!bOK
#ifdef WIN32
&& strlen(szNormPath) > 2
#endif
)
{
char* szParentPath = strdup(szNormPath);
bOK = true;
char szParentPath[1024];
strncpy(szParentPath, szNormPath, 1024);
szParentPath[1024-1] = '\0';
char* p = (char*)strrchr(szParentPath, PATH_SEPARATOR);
if (p)
{
@@ -282,20 +322,35 @@ bool Util::ForceDirectories(const char* szPath)
{
*p = '\0';
}
if (strlen(szParentPath) != strlen(szPath))
if (strlen(szParentPath) != strlen(szPath) && !ForceDirectories(szParentPath, szErrBuf, iBufSize))
{
bOK = ForceDirectories(szParentPath);
return false;
}
}
if (bOK)
if (mkdir(szNormPath, S_DIRMODE) != 0 && errno != EEXIST)
{
mkdir(szNormPath, S_DIRMODE);
bOK = !stat(szNormPath, &buffer) && S_ISDIR(buffer.st_mode);
snprintf(szErrBuf, iBufSize, "could not create directory %s: %s", szNormPath, GetLastErrorMessage(szSysErrStr, sizeof(szSysErrStr)));
szErrBuf[iBufSize-1] = 0;
return false;
}
if (stat(szNormPath, &buffer) != 0)
{
snprintf(szErrBuf, iBufSize, "could not read information for directory %s: %s", szNormPath, GetLastErrorMessage(szSysErrStr, sizeof(szSysErrStr)));
szErrBuf[iBufSize-1] = 0;
return false;
}
if (!S_ISDIR(buffer.st_mode))
{
snprintf(szErrBuf, iBufSize, "path %s is not a directory", szNormPath);
szErrBuf[iBufSize-1] = 0;
return false;
}
free(szParentPath);
}
free(szNormPath);
return bOK;
return true;
}
bool Util::GetCurrentDirectory(char* szBuffer, int iBufSize)
@@ -361,6 +416,20 @@ bool Util::LoadFileIntoBuffer(const char* szFileName, char** pBuffer, int* pBuff
return true;
}
bool Util::SaveBufferIntoFile(const char* szFileName, const char* szBuffer, int iBufLen)
{
FILE* pFile = fopen(szFileName, "wb");
if (!pFile)
{
return false;
}
int iWrittenBytes = fwrite(szBuffer, 1, iBufLen, pFile);
fclose(pFile);
return iWrittenBytes == iBufLen;
}
bool Util::CreateSparseFile(const char* szFilename, int iSize)
{
bool bOK = false;
@@ -451,6 +520,48 @@ void Util::MakeValidFilename(char* szFilename, char cReplaceChar, bool bAllowSla
}
}
// returns TRUE if the name was changed by adding duplicate-suffix
bool Util::MakeUniqueFilename(char* szDestBufFilename, int iDestBufSize, const char* szDestDir, const char* szBasename)
{
snprintf(szDestBufFilename, iDestBufSize, "%s%c%s", szDestDir, (int)PATH_SEPARATOR, szBasename);
szDestBufFilename[iDestBufSize-1] = '\0';
int iDupeNumber = 0;
while (FileExists(szDestBufFilename))
{
iDupeNumber++;
const char* szExtension = strrchr(szBasename, '.');
if (szExtension && szExtension != szBasename)
{
char szFilenameWithoutExt[1024];
strncpy(szFilenameWithoutExt, szBasename, 1024);
int iEnd = szExtension - szBasename;
szFilenameWithoutExt[iEnd < 1024 ? iEnd : 1024-1] = '\0';
if (!strcasecmp(szExtension, ".par2"))
{
char* szVolExtension = strrchr(szFilenameWithoutExt, '.');
if (szVolExtension && szVolExtension != szFilenameWithoutExt && !strncasecmp(szVolExtension, ".vol", 4))
{
*szVolExtension = '\0';
szExtension = szBasename + (szVolExtension - szFilenameWithoutExt);
}
}
snprintf(szDestBufFilename, iDestBufSize, "%s%c%s.duplicate%d%s", szDestDir, (int)PATH_SEPARATOR, szFilenameWithoutExt, iDupeNumber, szExtension);
}
else
{
snprintf(szDestBufFilename, iDestBufSize, "%s%c%s.duplicate%d", szDestDir, (int)PATH_SEPARATOR, szBasename, iDupeNumber);
}
szDestBufFilename[iDestBufSize-1] = '\0';
}
return iDupeNumber > 0;
}
long long Util::JoinInt64(unsigned long Hi, unsigned long Lo)
{
return (((long long)Hi) << 32) + Lo;
@@ -586,6 +697,15 @@ bool Util::FileExists(const char* szFilename)
return bExists;
}
bool Util::FileExists(const char* szPath, const char* szFilenameWithoutPath)
{
char fullFilename[1024];
snprintf(fullFilename, 1024, "%s%c%s", szPath, (int)PATH_SEPARATOR, szFilenameWithoutPath);
fullFilename[1024-1] = '\0';
bool bExists = Util::FileExists(fullFilename);
return bExists;
}
bool Util::DirectoryExists(const char* szDirFilename)
{
struct stat buffer;
@@ -640,14 +760,9 @@ long long Util::FileSize(const char* szFilename)
#ifdef WIN32
struct _stat32i64 buffer;
_stat32i64(szFilename, &buffer);
#else
#ifdef HAVE_STAT64
struct stat64 buffer;
stat64(szFilename, &buffer);
#else
struct stat buffer;
stat(szFilename, &buffer);
#endif
#endif
return buffer.st_size;
}
@@ -793,6 +908,36 @@ void Util::FormatFileSize(char * szBuffer, int iBufLen, long long lFileSize)
szBuffer[iBufLen - 1] = '\0';
}
bool Util::SameFilename(const char* szFilename1, const char* szFilename2)
{
#ifdef WIN32
return strcasecmp(szFilename1, szFilename2) == 0;
#else
return strcmp(szFilename1, szFilename2) == 0;
#endif
}
#ifndef WIN32
void Util::FixExecPermission(const char* szFilename)
{
struct stat buffer;
bool bOK = !stat(szFilename, &buffer);
if (bOK)
{
buffer.st_mode = buffer.st_mode | S_IXUSR | S_IXGRP | S_IXOTH;
chmod(szFilename, buffer.st_mode);
}
}
#endif
char* Util::GetLastErrorMessage(char* szBuffer, int iBufLen)
{
szBuffer[0] = '\0';
strerror_r(errno, szBuffer, iBufLen);
szBuffer[iBufLen-1] = '\0';
return szBuffer;
}
void Util::InitVersionRevision()
{
#ifndef WIN32
@@ -889,16 +1034,130 @@ bool Util::SplitCommandLine(const char* szCommandLine, char*** argv)
void Util::TrimRight(char* szStr)
{
int iLen = strlen(szStr);
char ch = szStr[iLen-1];
while (*szStr && (ch == '\n' || ch == '\r' || ch == ' ' || ch == '\t'))
char* szEnd = szStr + strlen(szStr) - 1;
while (szEnd >= szStr && (*szEnd == '\n' || *szEnd == '\r' || *szEnd == ' ' || *szEnd == '\t'))
{
szStr[iLen-1] = 0;
iLen--;
ch = szStr[iLen-1];
*szEnd = '\0';
szEnd--;
}
}
char* Util::Trim(char* szStr)
{
TrimRight(szStr);
while (*szStr == '\n' || *szStr == '\r' || *szStr == ' ' || *szStr == '\t')
{
szStr++;
}
return szStr;
}
char* Util::ReduceStr(char* szStr, const char* szFrom, const char* szTo)
{
int iLenFrom = strlen(szFrom);
int iLenTo = strlen(szTo);
// assert(iLenTo < iLenFrom);
while (char* p = strstr(szStr, szFrom))
{
const char* src = szTo;
while ((*p++ = *src++)) ;
src = --p - iLenTo + iLenFrom;
while ((*p++ = *src++)) ;
}
return szStr;
}
/* Calculate Hash using Bob Jenkins (1996) algorithm
* http://burtleburtle.net/bob/c/lookup2.c
*/
typedef unsigned int ub4; /* unsigned 4-byte quantities */
typedef unsigned char ub1;
#define hashsize(n) ((ub4)1<<(n))
#define hashmask(n) (hashsize(n)-1)
#define mix(a,b,c) \
{ \
a -= b; a -= c; a ^= (c>>13); \
b -= c; b -= a; b ^= (a<<8); \
c -= a; c -= b; c ^= (b>>13); \
a -= b; a -= c; a ^= (c>>12); \
b -= c; b -= a; b ^= (a<<16); \
c -= a; c -= b; c ^= (b>>5); \
a -= b; a -= c; a ^= (c>>3); \
b -= c; b -= a; b ^= (a<<10); \
c -= a; c -= b; c ^= (b>>15); \
}
ub4 hash(register ub1 *k, register ub4 length, register ub4 initval)
// register ub1 *k; /* the key */
// register ub4 length; /* the length of the key */
// register ub4 initval; /* the previous hash, or an arbitrary value */
{
register ub4 a,b,c,len;
/* Set up the internal state */
len = length;
a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */
c = initval; /* the previous hash value */
/*---------------------------------------- handle most of the key */
while (len >= 12)
{
a += (k[0] +((ub4)k[1]<<8) +((ub4)k[2]<<16) +((ub4)k[3]<<24));
b += (k[4] +((ub4)k[5]<<8) +((ub4)k[6]<<16) +((ub4)k[7]<<24));
c += (k[8] +((ub4)k[9]<<8) +((ub4)k[10]<<16)+((ub4)k[11]<<24));
mix(a,b,c);
k += 12; len -= 12;
}
/*------------------------------------- handle the last 11 bytes */
c += length;
switch(len) /* all the case statements fall through */
{
case 11: c+=((ub4)k[10]<<24);
case 10: c+=((ub4)k[9]<<16);
case 9 : c+=((ub4)k[8]<<8);
/* the first byte of c is reserved for the length */
case 8 : b+=((ub4)k[7]<<24);
case 7 : b+=((ub4)k[6]<<16);
case 6 : b+=((ub4)k[5]<<8);
case 5 : b+=k[4];
case 4 : a+=((ub4)k[3]<<24);
case 3 : a+=((ub4)k[2]<<16);
case 2 : a+=((ub4)k[1]<<8);
case 1 : a+=k[0];
/* case 0: nothing left to add */
}
mix(a,b,c);
/*-------------------------------------------- report the result */
return c;
}
unsigned int Util::HashBJ96(const char* szBuffer, int iBufSize, unsigned int iInitValue)
{
return (unsigned int)hash((ub1*)szBuffer, (ub4)iBufSize, (ub4)iInitValue);
}
#ifdef WIN32
bool Util::RegReadStr(HKEY hKey, const char* szKeyName, const char* szValueName, char* szBuffer, int* iBufLen)
{
HKEY hSubKey;
if (!RegOpenKeyEx(hKey, szKeyName, 0, KEY_READ, &hSubKey))
{
DWORD iRetBytes = *iBufLen;
LONG iRet = RegQueryValueEx(hSubKey, szValueName, NULL, NULL, (LPBYTE)szBuffer, &iRetBytes);
*iBufLen = iRetBytes;
RegCloseKey(hSubKey);
return iRet == 0;
}
return false;
}
#endif
unsigned int WebUtil::DecodeBase64(char* szInputBuffer, int iInputBufferLength, char* szOutputBuffer)
{
@@ -960,9 +1219,9 @@ char* WebUtil::XmlEncode(const char* raw)
iReqSize += 6;
break;
default:
if (ch >= 0x80)
if (ch < 0x20 || ch >= 0x80)
{
iReqSize += 6;
iReqSize += 10;
break;
}
}
@@ -1000,10 +1259,51 @@ char* WebUtil::XmlEncode(const char* raw)
output += 6;
break;
default:
if (ch >= 0x80)
if (ch < 0x20 || ch > 0x80)
{
sprintf(output, "&#%i;", ch);
output += 6;
unsigned int cp = ch;
// decode utf8
if ((cp >> 5) == 0x6 && (p[1] & 0xc0) == 0x80)
{
// 2 bytes
if (!(ch = *++p)) goto BreakLoop; // read next char
cp = ((cp << 6) & 0x7ff) + (ch & 0x3f);
}
else if ((cp >> 4) == 0xe && (p[1] & 0xc0) == 0x80)
{
// 3 bytes
if (!(ch = *++p)) goto BreakLoop; // read next char
cp = ((cp << 12) & 0xffff) + ((ch << 6) & 0xfff);
if (!(ch = *++p)) goto BreakLoop; // read next char
cp += ch & 0x3f;
}
else if ((cp >> 3) == 0x1e && (p[1] & 0xc0) == 0x80)
{
// 4 bytes
if (!(ch = *++p)) goto BreakLoop; // read next char
cp = ((cp << 18) & 0x1fffff) + ((ch << 12) & 0x3ffff);
if (!(ch = *++p)) goto BreakLoop; // read next char
cp += (ch << 6) & 0xfff;
if (!(ch = *++p)) goto BreakLoop; // read next char
cp += ch & 0x3f;
}
// accept only valid XML 1.0 characters
if (cp == 0x9 || cp == 0xA || cp == 0xD ||
(0x20 <= cp && cp <= 0xD7FF) ||
(0xE000 <= cp && cp <= 0xFFFD) ||
(0x10000 <= cp && cp <= 0x10FFFF))
{
sprintf(output, "&#x%06x;", cp);
output += 10;
}
else
{
// replace invalid characters with dots
sprintf(output, ".");
output += 1;
}
}
else
{
@@ -1056,6 +1356,13 @@ void WebUtil::XmlDecode(char* raw)
*output++ = '\"';
p += 5;
}
else if (*p == '#')
{
int code = atoi(p+1);
p = strchr(p+1, ';');
if (p) p++;
*output++ = (char)code;
}
else
{
// unknown entity
@@ -1197,9 +1504,38 @@ char* WebUtil::JsonEncode(const char* raw)
output += 2;
break;
default:
if (ch < 0x20 || ch >= 0x80)
if (ch < 0x20 || ch > 0x80)
{
sprintf(output, "\\u%04x", ch);
unsigned int cp = ch;
// decode utf8
if ((cp >> 5) == 0x6 && (p[1] & 0xc0) == 0x80)
{
// 2 bytes
if (!(ch = *++p)) goto BreakLoop; // read next char
cp = ((cp << 6) & 0x7ff) + (ch & 0x3f);
}
else if ((cp >> 4) == 0xe && (p[1] & 0xc0) == 0x80)
{
// 3 bytes
if (!(ch = *++p)) goto BreakLoop; // read next char
cp = ((cp << 12) & 0xffff) + ((ch << 6) & 0xfff);
if (!(ch = *++p)) goto BreakLoop; // read next char
cp += ch & 0x3f;
}
else if ((cp >> 3) == 0x1e && (p[1] & 0xc0) == 0x80)
{
// 4 bytes
if (!(ch = *++p)) goto BreakLoop; // read next char
cp = ((cp << 18) & 0x1fffff) + ((ch << 12) & 0x3ffff);
if (!(ch = *++p)) goto BreakLoop; // read next char
cp += (ch << 6) & 0xfff;
if (!(ch = *++p)) goto BreakLoop; // read next char
cp += ch & 0x3f;
}
// we support only Unicode range U+0000-U+FFFF
sprintf(output, "\\u%04x", cp <= 0xFFFF ? cp : '.');
output += 6;
}
else
@@ -1357,6 +1693,103 @@ BreakLoop:
*output = '\0';
}
#ifdef WIN32
bool WebUtil::Utf8ToAnsi(char* szBuffer, int iBufLen)
{
WCHAR* wstr = (WCHAR*)malloc(iBufLen * 2);
int errcode = MultiByteToWideChar(CP_UTF8, 0, szBuffer, -1, wstr, iBufLen);
if (errcode > 0)
{
errcode = WideCharToMultiByte(CP_ACP, 0, wstr, -1, szBuffer, iBufLen, "_", NULL);
}
free(wstr);
return errcode > 0;
}
bool WebUtil::AnsiToUtf8(char* szBuffer, int iBufLen)
{
WCHAR* wstr = (WCHAR*)malloc(iBufLen * 2);
int errcode = MultiByteToWideChar(CP_ACP, 0, szBuffer, -1, wstr, iBufLen);
if (errcode > 0)
{
errcode = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, szBuffer, iBufLen, NULL, NULL);
}
free(wstr);
return errcode > 0;
}
#endif
char* WebUtil::Latin1ToUtf8(const char* szStr)
{
char *res = (char*)malloc(strlen(szStr) * 2 + 1);
const unsigned char *in = (const unsigned char*)szStr;
unsigned char *out = (unsigned char*)res;
while (*in)
{
if (*in < 128)
{
*out++ = *in++;
}
else
{
*out++ = 0xc2 + (*in > 0xbf);
*out++ = (*in++ & 0x3f) + 0x80;
}
}
*out = '\0';
return res;
}
/*
The date/time can be formatted according to RFC822 in different ways. Examples:
Wed, 26 Jun 2013 01:02:54 -0600
Wed, 26 Jun 2013 01:02:54 GMT
26 Jun 2013 01:02:54 -0600
26 Jun 2013 01:02 -0600
26 Jun 2013 01:02 A
This function however supports only the first format!
*/
time_t WebUtil::ParseRfc822DateTime(const char* szDateTimeStr)
{
char month[4];
int day, year, hours, minutes, seconds, zonehours, zoneminutes;
int r = sscanf(szDateTimeStr, "%*s %d %3s %d %d:%d:%d %3d %2d", &day, &month[0], &year, &hours, &minutes, &seconds, &zonehours, &zoneminutes);
if (r != 8)
{
return 0;
}
int mon = 0;
if (!strcasecmp(month, "Jan")) mon = 0;
else if (!strcasecmp(month, "Feb")) mon = 1;
else if (!strcasecmp(month, "Mar")) mon = 2;
else if (!strcasecmp(month, "Apr")) mon = 3;
else if (!strcasecmp(month, "May")) mon = 4;
else if (!strcasecmp(month, "Jun")) mon = 5;
else if (!strcasecmp(month, "Jul")) mon = 6;
else if (!strcasecmp(month, "Aug")) mon = 7;
else if (!strcasecmp(month, "Sep")) mon = 8;
else if (!strcasecmp(month, "Oct")) mon = 9;
else if (!strcasecmp(month, "Nov")) mon = 10;
else if (!strcasecmp(month, "Dec")) mon = 11;
struct tm rawtime;
memset(&rawtime, 0, sizeof(rawtime));
rawtime.tm_year = year - 1900;
rawtime.tm_mon = mon;
rawtime.tm_mday = day;
rawtime.tm_hour = hours;
rawtime.tm_min = minutes;
rawtime.tm_sec = seconds;
time_t enctime = mktime(&rawtime);
enctime = enctime - (zonehours * 60 + (zonehours > 0 ? zoneminutes : -zoneminutes)) * 60;
return enctime;
}
URL::URL(const char* szAddress)
{
@@ -1379,30 +1812,12 @@ URL::URL(const char* szAddress)
URL::~URL()
{
if (m_szAddress)
{
free(m_szAddress);
}
if (m_szProtocol)
{
free(m_szProtocol);
}
if (m_szUser)
{
free(m_szUser);
}
if (m_szPassword)
{
free(m_szPassword);
}
if (m_szHost)
{
free(m_szHost);
}
if (m_szResource)
{
free(m_szResource);
}
free(m_szAddress);
free(m_szProtocol);
free(m_szUser);
free(m_szPassword);
free(m_szHost);
free(m_szResource);
}
void URL::ParseURL()
@@ -1488,11 +1903,20 @@ void URL::ParseURL()
m_bValid = true;
}
RegEx::RegEx(const char *szPattern)
RegEx::RegEx(const char *szPattern, int iMatchBufSize)
{
#ifdef HAVE_REGEX_H
m_pContext = malloc(sizeof(regex_t));
m_bValid = regcomp((regex_t*)m_pContext, szPattern, REG_EXTENDED | REG_ICASE | REG_NOSUB) == 0;
m_bValid = regcomp((regex_t*)m_pContext, szPattern, REG_EXTENDED | REG_ICASE | (iMatchBufSize > 0 ? 0 : REG_NOSUB)) == 0;
m_iMatchBufSize = iMatchBufSize;
if (iMatchBufSize > 0)
{
m_pMatches = malloc(sizeof(regmatch_t) * iMatchBufSize);
}
else
{
m_pMatches = NULL;
}
#else
m_bValid = false;
#endif
@@ -1503,18 +1927,223 @@ RegEx::~RegEx()
#ifdef HAVE_REGEX_H
regfree((regex_t*)m_pContext);
free(m_pContext);
free(m_pMatches);
#endif
}
bool RegEx::Match(const char *szStr)
{
#ifdef HAVE_REGEX_H
return m_bValid ? regexec((regex_t*)m_pContext, szStr, 0, NULL, 0) == 0 : false;
return m_bValid ? regexec((regex_t*)m_pContext, szStr, m_iMatchBufSize, (regmatch_t*)m_pMatches, 0) == 0 : false;
#else
return false;
#endif
}
int RegEx::GetMatchCount()
{
#ifdef HAVE_REGEX_H
int iCount = 0;
if (m_pMatches)
{
regmatch_t* pMatches = (regmatch_t*)m_pMatches;
while (iCount < m_iMatchBufSize && pMatches[iCount].rm_so > -1)
{
iCount++;
}
}
return iCount;
#else
return 0;
#endif
}
int RegEx::GetMatchStart(int index)
{
#ifdef HAVE_REGEX_H
regmatch_t* pMatches = (regmatch_t*)m_pMatches;
return pMatches[index].rm_so;
#else
return NULL;
#endif
}
int RegEx::GetMatchLen(int index)
{
#ifdef HAVE_REGEX_H
regmatch_t* pMatches = (regmatch_t*)m_pMatches;
return pMatches[index].rm_eo - pMatches[index].rm_so;
#else
return 0;
#endif
}
WildMask::WildMask(const char *szPattern, bool bWantsPositions)
{
m_szPattern = strdup(szPattern);
m_bWantsPositions = bWantsPositions;
m_WildStart = NULL;
m_WildLen = NULL;
m_iArrLen = 0;
}
WildMask::~WildMask()
{
free(m_szPattern);
free(m_WildStart);
free(m_WildLen);
}
void WildMask::ExpandArray()
{
m_iWildCount++;
if (m_iWildCount > m_iArrLen)
{
m_iArrLen += 100;
m_WildStart = (int*)realloc(m_WildStart, sizeof(*m_WildStart) * m_iArrLen);
m_WildLen = (int*)realloc(m_WildLen, sizeof(*m_WildLen) * m_iArrLen);
}
}
// Based on code from http://bytes.com/topic/c/answers/212179-string-matching
// Extended to save positions of matches.
bool WildMask::Match(const char *szStr)
{
const char* pat = m_szPattern;
const char* str = szStr;
const char *spos, *wpos;
m_iWildCount = 0;
bool qmark = false;
bool star = false;
spos = wpos = str;
while (*str && *pat != '*')
{
if (m_bWantsPositions && (*pat == '?' || *pat == '#'))
{
if (!qmark)
{
ExpandArray();
m_WildStart[m_iWildCount-1] = str - szStr;
m_WildLen[m_iWildCount-1] = 0;
qmark = true;
}
}
else if (m_bWantsPositions && qmark)
{
m_WildLen[m_iWildCount-1] = str - (szStr + m_WildStart[m_iWildCount-1]);
qmark = false;
}
if (!(tolower(*pat) == tolower(*str) || *pat == '?' ||
(*pat == '#' && strchr("0123456789", *str))))
{
return false;
}
str++;
pat++;
}
if (m_bWantsPositions && qmark)
{
m_WildLen[m_iWildCount-1] = str - (szStr + m_WildStart[m_iWildCount-1]);
qmark = false;
}
while (*str)
{
if (*pat == '*')
{
if (m_bWantsPositions && qmark)
{
m_WildLen[m_iWildCount-1] = str - (szStr + m_WildStart[m_iWildCount-1]);
qmark = false;
}
if (m_bWantsPositions && !star)
{
ExpandArray();
m_WildStart[m_iWildCount-1] = str - szStr;
m_WildLen[m_iWildCount-1] = 0;
star = true;
}
if (*++pat == '\0')
{
if (m_bWantsPositions && star)
{
m_WildLen[m_iWildCount-1] = strlen(str);
}
return true;
}
wpos = pat;
spos = str + 1;
}
else if (*pat == '?' || (*pat == '#' && strchr("0123456789", *str)))
{
if (m_bWantsPositions && !qmark)
{
ExpandArray();
m_WildStart[m_iWildCount-1] = str - szStr;
m_WildLen[m_iWildCount-1] = 0;
qmark = true;
}
pat++;
str++;
}
else if (tolower(*pat) == tolower(*str))
{
if (m_bWantsPositions && qmark)
{
m_WildLen[m_iWildCount-1] = str - (szStr + m_WildStart[m_iWildCount-1]);
qmark = false;
}
else if (m_bWantsPositions && star)
{
m_WildLen[m_iWildCount-1] = str - (szStr + m_WildStart[m_iWildCount-1]);
star = false;
}
pat++;
str++;
}
else
{
if (m_bWantsPositions && qmark)
{
m_iWildCount--;
qmark = false;
}
pat = wpos;
str = spos++;
star = true;
}
}
if (m_bWantsPositions && qmark)
{
m_WildLen[m_iWildCount-1] = str - (szStr + m_WildStart[m_iWildCount-1]);
}
if (*pat == '*' && m_bWantsPositions && !star)
{
ExpandArray();
m_WildStart[m_iWildCount-1] = str - szStr;
m_WildLen[m_iWildCount-1] = strlen(str);
}
while (*pat == '*')
{
pat++;
}
return *pat == '\0';
}
#ifndef DISABLE_GZIP
unsigned int ZLib::GZipLen(int iInputBufferLength)
{

76
Util.h
View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2007-2009 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -57,6 +57,19 @@ public:
const char* Next();
};
class StringBuilder
{
private:
char* m_szBuffer;
int m_iBufferSize;
int m_iUsedSize;
public:
StringBuilder();
~StringBuilder();
void Append(const char* szStr);
const char* GetBuffer() { return m_szBuffer; }
};
class Util
{
public:
@@ -64,16 +77,19 @@ public:
static char* BaseFileName(const char* filename);
static void NormalizePathSeparators(char* szPath);
static bool LoadFileIntoBuffer(const char* szFileName, char** pBuffer, int* pBufferLength);
static bool SaveBufferIntoFile(const char* szFileName, const char* szBuffer, int iBufLen);
static bool CreateSparseFile(const char* szFilename, int iSize);
static bool TruncateFile(const char* szFilename, int iSize);
static void MakeValidFilename(char* szFilename, char cReplaceChar, bool bAllowSlashes);
static bool MakeUniqueFilename(char* szDestBufFilename, int iDestBufSize, const char* szDestDir, const char* szBasename);
static bool MoveFile(const char* szSrcFilename, const char* szDstFilename);
static bool FileExists(const char* szFilename);
static bool FileExists(const char* szPath, const char* szFilenameWithoutPath);
static bool DirectoryExists(const char* szDirFilename);
static bool CreateDirectory(const char* szDirFilename);
static bool RemoveDirectory(const char* szDirFilename);
static bool DeleteDirectoryWithContent(const char* szDirFilename);
static bool ForceDirectories(const char* szPath);
static bool ForceDirectories(const char* szPath, char* szErrBuf, int iBufSize);
static bool GetCurrentDirectory(char* szBuffer, int iBufSize);
static bool SetCurrentDirectory(const char* szDirFilename);
static long long FileSize(const char* szFilename);
@@ -82,9 +98,12 @@ public:
static bool RenameBak(const char* szFilename, const char* szBakPart, bool bRemoveOldExtension, char* szNewNameBuf, int iNewNameBufSize);
#ifndef WIN32
static bool ExpandHomePath(const char* szFilename, char* szBuffer, int iBufSize);
static void FixExecPermission(const char* szFilename);
#endif
static void ExpandFileName(const char* szFilename, char* szBuffer, int iBufSize);
static void FormatFileSize(char* szBuffer, int iBufLen, long long lFileSize);
static bool SameFilename(const char* szFilename1, const char* szFilename2);
static char* GetLastErrorMessage(char* szBuffer, int iBufLen);
/*
* Split command line int arguments.
@@ -111,6 +130,18 @@ public:
static float Int64ToFloat(long long Int64);
static void TrimRight(char* szStr);
static char* Trim(char* szStr);
static bool EmptyStr(const char* szStr) { return !szStr || !*szStr; }
/* replace all occurences of szFrom to szTo in string szStr with a limitation that szTo must be shorter than szFrom */
static char* ReduceStr(char* szStr, const char* szFrom, const char* szTo);
/* Calculate Hash using Bob Jenkins (1996) algorithm */
static unsigned int HashBJ96(const char* szBuffer, int iBufSize, unsigned int iInitValue);
#ifdef WIN32
static bool RegReadStr(HKEY hKey, const char* szKeyName, const char* szValueName, char* szBuffer, int* iBufLen);
#endif
/*
* Returns program version and revision number as string formatted like "0.7.0-r295".
@@ -185,6 +216,19 @@ public:
* The string is decoded on the place overwriting the content of raw-data.
*/
static void HttpUnquote(char* raw);
#ifdef WIN32
static bool Utf8ToAnsi(char* szBuffer, int iBufLen);
static bool AnsiToUtf8(char* szBuffer, int iBufLen);
#endif
/*
* Converts ISO-8859-1 (aka Latin-1) into UTF-8.
* Returns new string allocated with malloc, it needs to be freed by caller.
*/
static char* Latin1ToUtf8(const char* szStr);
static time_t ParseRfc822DateTime(const char* szDateTimeStr);
};
class URL
@@ -220,12 +264,38 @@ class RegEx
private:
void* m_pContext;
bool m_bValid;
void* m_pMatches;
int m_iMatchBufSize;
public:
RegEx(const char *szPattern);
RegEx(const char *szPattern, int iMatchBufSize = 100);
~RegEx();
bool IsValid() { return m_bValid; }
bool Match(const char *szStr);
int GetMatchCount();
int GetMatchStart(int index);
int GetMatchLen(int index);
};
class WildMask
{
private:
char* m_szPattern;
bool m_bWantsPositions;
int m_iWildCount;
int* m_WildStart;
int* m_WildLen;
int m_iArrLen;
void ExpandArray();
public:
WildMask(const char *szPattern, bool bWantsPositions = false);
~WildMask();
bool Match(const char *szStr);
int GetMatchCount() { return m_iWildCount; }
int GetMatchStart(int index) { return m_WildStart[index]; }
int GetMatchLen(int index) { return m_WildLen[index]; }
};
#ifndef DISABLE_GZIP

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2012 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2012-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -33,7 +33,7 @@
#include <stdlib.h>
#include <string.h>
#include <cstdio>
#include <stdio.h>
#ifdef WIN32
#include <direct.h>
#else
@@ -62,6 +62,8 @@ WebDownloader::WebDownloader()
m_bConfirmedLength = false;
m_eStatus = adUndefined;
m_szOriginalFilename = NULL;
m_bForce = false;
m_bRetry = true;
SetLastUpdateTimeNow();
}
@@ -69,22 +71,10 @@ WebDownloader::~WebDownloader()
{
debug("Destroying WebDownloader");
if (m_szURL)
{
free(m_szURL);
}
if (m_szInfoName)
{
free(m_szInfoName);
}
if (m_szOutputFilename)
{
free(m_szOutputFilename);
}
if (m_szOriginalFilename)
{
free(m_szOriginalFilename);
}
free(m_szURL);
free(m_szInfoName);
free(m_szOutputFilename);
free(m_szOriginalFilename);
}
void WebDownloader::SetOutputFilename(const char* v)
@@ -99,6 +89,7 @@ void WebDownloader::SetInfoName(const char* v)
void WebDownloader::SetURL(const char * szURL)
{
free(m_szURL);
m_szURL = strdup(szURL);
}
@@ -116,7 +107,13 @@ void WebDownloader::Run()
int iRemainedDownloadRetries = g_pOptions->GetRetries() > 0 ? g_pOptions->GetRetries() : 1;
int iRemainedConnectRetries = iRemainedDownloadRetries > 10 ? iRemainedDownloadRetries : 10;
if (!m_bRetry)
{
iRemainedDownloadRetries = 1;
iRemainedConnectRetries = 1;
}
m_iRedirects = 0;
EStatus Status = adFailed;
while (!IsStopped() && iRemainedDownloadRetries > 0 && iRemainedConnectRetries > 0)
@@ -127,19 +124,19 @@ void WebDownloader::Run()
if ((((Status == adFailed) && (iRemainedDownloadRetries > 1)) ||
((Status == adConnectError) && (iRemainedConnectRetries > 1)))
&& !IsStopped() && !(g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2()))
&& !IsStopped() && !(!m_bForce && (g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2())))
{
detail("Waiting %i sec to retry", g_pOptions->GetRetryInterval());
int msec = 0;
while (!IsStopped() && (msec < g_pOptions->GetRetryInterval() * 1000) &&
!(g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2()))
!(!m_bForce && (g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2())))
{
usleep(100 * 1000);
msec += 100;
}
}
if (IsStopped() || g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2())
if (IsStopped() || (!m_bForce && (g_pOptions->GetPauseDownload() || g_pOptions->GetPauseDownload2())))
{
Status = adRetry;
break;
@@ -150,6 +147,17 @@ void WebDownloader::Run()
break;
}
if (Status == adRedirect)
{
m_iRedirects++;
if (m_iRedirects > 5)
{
warn("Too many redirects for %s", m_szInfoName);
Status = adFailed;
break;
}
}
if (Status != adConnectError)
{
iRemainedDownloadRetries--;
@@ -312,6 +320,8 @@ WebDownloader::EStatus WebDownloader::DownloadHeaders()
m_iContentLen = -1;
bool bFirstLine = true;
m_bGZip = false;
m_bRedirecting = false;
m_bRedirected = false;
// Headers
while (!IsStopped())
@@ -354,7 +364,14 @@ WebDownloader::EStatus WebDownloader::DownloadHeaders()
break;
}
Util::TrimRight(line);
ProcessHeader(line);
if (m_bRedirected)
{
Status = adRedirect;
break;
}
}
free(szLineBuf);
@@ -397,7 +414,7 @@ WebDownloader::EStatus WebDownloader::DownloadBody()
// Have we encountered a timeout?
if (iLen <= 0)
{
if (m_iContentLen == -1)
if (m_iContentLen == -1 && iWrittenLen > 0)
{
bEnd = true;
break;
@@ -430,10 +447,7 @@ WebDownloader::EStatus WebDownloader::DownloadBody()
free(szLineBuf);
#ifndef DISABLE_GZIP
if (m_pGUnzipStream)
{
delete m_pGUnzipStream;
}
delete m_pGUnzipStream;
#endif
if (m_pOutFile)
@@ -485,6 +499,11 @@ WebDownloader::EStatus WebDownloader::CheckResponse(const char* szResponse)
warn("URL %s failed: %s", m_szInfoName, szHTTPResponse);
return adNotFound;
}
else if (!strncmp(szHTTPResponse, "301", 3) || !strncmp(szHTTPResponse, "302", 3))
{
m_bRedirecting = true;
return adRunning;
}
else if (!strncmp(szHTTPResponse, "200", 3))
{
// OK
@@ -500,21 +519,24 @@ WebDownloader::EStatus WebDownloader::CheckResponse(const char* szResponse)
void WebDownloader::ProcessHeader(const char* szLine)
{
if (!strncmp(szLine, "Content-Length: ", 16))
if (!strncasecmp(szLine, "Content-Length: ", 16))
{
m_iContentLen = atoi(szLine + 16);
m_bConfirmedLength = true;
}
if (!strncmp(szLine, "Content-Encoding: gzip", 22))
else if (!strncasecmp(szLine, "Content-Encoding: gzip", 22))
{
m_bGZip = true;
}
if (!strncmp(szLine, "Content-Disposition: ", 21))
else if (!strncasecmp(szLine, "Content-Disposition: ", 21))
{
ParseFilename(szLine);
}
else if (m_bRedirecting && !strncasecmp(szLine, "Location: ", 10))
{
ParseRedirect(szLine + 10);
m_bRedirected = true;
}
}
void WebDownloader::ParseFilename(const char* szContentDisposition)
@@ -551,15 +573,36 @@ void WebDownloader::ParseFilename(const char* szContentDisposition)
WebUtil::HttpUnquote(fname);
if (m_szOriginalFilename)
{
free(m_szOriginalFilename);
}
free(m_szOriginalFilename);
m_szOriginalFilename = strdup(Util::BaseFileName(fname));
debug("OriginalFilename: %s", m_szOriginalFilename);
}
void WebDownloader::ParseRedirect(const char* szLocation)
{
const char* szNewURL = szLocation;
char szUrlBuf[1024];
URL newUrl(szNewURL);
if (!newUrl.IsValid())
{
// relative address
URL oldUrl(m_szURL);
if (oldUrl.GetPort() > 0)
{
snprintf(szUrlBuf, 1024, "%s://%s:%i%s", oldUrl.GetProtocol(), oldUrl.GetHost(), oldUrl.GetPort(), szNewURL);
}
else
{
snprintf(szUrlBuf, 1024, "%s://%s%s", oldUrl.GetProtocol(), oldUrl.GetHost(), szNewURL);
}
szUrlBuf[1024-1] = '\0';
szNewURL = szUrlBuf;
}
detail("URL %s redirected to %s", m_szURL, szNewURL);
SetURL(szNewURL);
}
bool WebDownloader::Write(void* pBuffer, int iLen)
{
if (!m_pOutFile && !PrepareFile())

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2012 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2012-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -44,6 +44,7 @@ public:
adFailed,
adRetry,
adNotFound,
adRedirect,
adConnectError,
adFatalError
};
@@ -60,7 +61,12 @@ private:
int m_iContentLen;
bool m_bConfirmedLength;
char* m_szOriginalFilename;
bool m_bForce;
bool m_bRedirecting;
bool m_bRedirected;
int m_iRedirects;
bool m_bGZip;
bool m_bRetry;
#ifndef DISABLE_GZIP
GUnzipStream* m_pGUnzipStream;
#endif
@@ -70,12 +76,12 @@ private:
bool PrepareFile();
void FreeConnection();
EStatus CheckResponse(const char* szResponse);
EStatus Download();
EStatus CreateConnection(URL *pUrl);
void ParseFilename(const char* szContentDisposition);
void SendHeaders(URL *pUrl);
EStatus DownloadHeaders();
EStatus DownloadBody();
void ParseRedirect(const char* szLocation);
protected:
virtual void ProcessHeader(const char* szLine);
@@ -86,6 +92,7 @@ public:
EStatus GetStatus() { return m_eStatus; }
virtual void Run();
virtual void Stop();
EStatus Download();
bool Terminate();
void SetInfoName(const char* v);
const char* GetInfoName() { return m_szInfoName; }
@@ -96,6 +103,8 @@ public:
void SetLastUpdateTimeNow() { m_tLastUpdateTime = ::time(NULL); }
bool GetConfirmedLength() { return m_bConfirmedLength; }
const char* GetOriginalFilename() { return m_szOriginalFilename; }
void SetForce(bool bForce) { m_bForce = bForce; }
void SetRetry(bool bRetry) { m_bRetry = bRetry; }
void LogDebugInfo();
};

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2012 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2012-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -33,7 +33,7 @@
#include <stdlib.h>
#include <string.h>
#include <cstdio>
#include <stdio.h>
#ifndef WIN32
#include <unistd.h>
#endif
@@ -66,18 +66,9 @@ WebProcessor::WebProcessor()
WebProcessor::~WebProcessor()
{
if (m_szRequest)
{
free(m_szRequest);
}
if (m_szUrl)
{
free(m_szUrl);
}
if (m_szOrigin)
{
free(m_szOrigin);
}
free(m_szRequest);
free(m_szUrl);
free(m_szOrigin);
}
void WebProcessor::SetUrl(const char* szUrl)
@@ -107,7 +98,7 @@ void WebProcessor::Execute()
char* szAuthInfo64 = p + 21;
if (strlen(szAuthInfo64) > sizeof(szAuthInfo))
{
error("invalid-request: auth-info too big");
error("Invalid-request: auth-info too big");
return;
}
szAuthInfo[WebUtil::DecodeBase64(szAuthInfo64, 0, szAuthInfo)] = '\0';
@@ -131,7 +122,7 @@ void WebProcessor::Execute()
if (m_eHttpMethod == hmPost && iContentLen <= 0)
{
error("invalid-request: content length is 0");
error("Invalid-request: content length is 0");
return;
}
@@ -185,20 +176,26 @@ void WebProcessor::Execute()
debug("Final URL=%s", m_szUrl);
if (strlen(szAuthInfo) == 0)
if (strlen(g_pOptions->GetControlPassword()) > 0 &&
!(strlen(g_pOptions->GetAuthorizedIP()) > 0 && IsAuthorizedIP(m_pConnection->GetRemoteAddr())))
{
SendAuthResponse();
return;
}
if (strlen(szAuthInfo) == 0)
{
SendAuthResponse();
return;
}
// Authorization
char* pw = strchr(szAuthInfo, ':');
if (pw) *pw++ = '\0';
if (strcmp(szAuthInfo, "nzbget") || strcmp(pw, g_pOptions->GetControlPassword()))
{
warn("request received on port %i from %s, but password invalid", g_pOptions->GetControlPort(), m_pConnection->GetRemoteAddr());
SendAuthResponse();
return;
// Authorization
char* pw = strchr(szAuthInfo, ':');
if (pw) *pw++ = '\0';
if ((strlen(g_pOptions->GetControlUsername()) > 0 && strcmp(szAuthInfo, g_pOptions->GetControlUsername())) ||
strcmp(pw, g_pOptions->GetControlPassword()))
{
warn("Request received on port %i from %s, but username or password invalid (%s:%s)",
g_pOptions->GetControlPort(), m_pConnection->GetRemoteAddr(), szAuthInfo, pw);
SendAuthResponse();
return;
}
}
if (m_eHttpMethod == hmPost)
@@ -209,8 +206,7 @@ void WebProcessor::Execute()
if (!m_pConnection->Recv(m_szRequest, iContentLen))
{
free(m_szRequest);
error("invalid-request: could not read data");
error("Invalid-request: could not read data");
return;
}
debug("Request=%s", m_szRequest);
@@ -221,6 +217,30 @@ void WebProcessor::Execute()
Dispatch();
}
bool WebProcessor::IsAuthorizedIP(const char* szRemoteAddr)
{
const char* szRemoteIP = m_pConnection->GetRemoteAddr();
// split option AuthorizedIP into tokens and check each token
bool bAuthorized = false;
char* szAuthorizedIP = strdup(g_pOptions->GetAuthorizedIP());
char* saveptr;
char* szIP = strtok_r(szAuthorizedIP, ",;", &saveptr);
while (szIP)
{
szIP = Util::Trim(szIP);
if (szIP[0] != '\0' && !strcmp(szIP, szRemoteIP))
{
bAuthorized = true;
break;
}
szIP = strtok_r(NULL, ",;", &saveptr);
}
free(szAuthorizedIP);
return bAuthorized;
}
void WebProcessor::Dispatch()
{
if (*m_szUrl != '/')
@@ -421,10 +441,7 @@ void WebProcessor::SendBodyResponse(const char* szBody, int iBodyLen, const char
m_pConnection->Send(szBody, iBodyLen);
#ifndef DISABLE_GZIP
if (szGBuf)
{
free(szGBuf);
}
free(szGBuf);
#endif
}

View File

@@ -54,6 +54,7 @@ private:
void SendBodyResponse(const char* szBody, int iBodyLen, const char* szContentType);
void SendRedirectResponse(const char* szURL);
const char* DetectContentType(const char* szFilename);
bool IsAuthorizedIP(const char* szRemoteAddr);
public:
WebProcessor();

1750
XmlRpc.cpp
View File

File diff suppressed because it is too large Load Diff

180
XmlRpc.h
View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2007-2010 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -27,19 +27,7 @@
#define XMLRPC_H
#include "Connection.h"
class StringBuilder
{
private:
char* m_szBuffer;
int m_iBufferSize;
int m_iUsedSize;
public:
StringBuilder();
~StringBuilder();
void Append(const char* szStr);
const char* GetBuffer() { return m_szBuffer; }
};
#include "Util.h"
class XmlCommand;
@@ -121,168 +109,4 @@ public:
bool GetFault() { return m_bFault; }
};
class ErrorXmlCommand: public XmlCommand
{
private:
int m_iErrCode;
const char* m_szErrText;
public:
ErrorXmlCommand(int iErrCode, const char* szErrText);
virtual void Execute();
};
class PauseUnpauseXmlCommand: public XmlCommand
{
public:
enum EPauseAction
{
paDownload,
paDownload2,
paPostProcess,
paScan
};
private:
bool m_bPause;
EPauseAction m_eEPauseAction;
public:
PauseUnpauseXmlCommand(bool bPause, EPauseAction eEPauseAction);
virtual void Execute();
};
class ScheduleResumeXmlCommand: public XmlCommand
{
public:
virtual void Execute();
};
class ShutdownXmlCommand: public XmlCommand
{
public:
virtual void Execute();
};
class ReloadXmlCommand: public XmlCommand
{
public:
virtual void Execute();
};
class VersionXmlCommand: public XmlCommand
{
public:
virtual void Execute();
};
class DumpDebugXmlCommand: public XmlCommand
{
public:
virtual void Execute();
};
class SetDownloadRateXmlCommand: public XmlCommand
{
public:
virtual void Execute();
};
class StatusXmlCommand: public XmlCommand
{
public:
virtual void Execute();
};
class LogXmlCommand: public XmlCommand
{
public:
virtual void Execute();
};
class ListFilesXmlCommand: public XmlCommand
{
public:
virtual void Execute();
};
class ListGroupsXmlCommand: public XmlCommand
{
public:
virtual void Execute();
};
class EditQueueXmlCommand: public XmlCommand
{
public:
virtual void Execute();
};
class DownloadXmlCommand: public XmlCommand
{
public:
virtual void Execute();
};
class PostQueueXmlCommand: public XmlCommand
{
public:
virtual void Execute();
};
class WriteLogXmlCommand: public XmlCommand
{
public:
virtual void Execute();
};
class ClearLogXmlCommand: public XmlCommand
{
public:
virtual void Execute();
};
class ScanXmlCommand: public XmlCommand
{
public:
virtual void Execute();
};
class HistoryXmlCommand: public XmlCommand
{
public:
virtual void Execute();
};
class DownloadUrlXmlCommand: public XmlCommand
{
public:
virtual void Execute();
};
class UrlQueueXmlCommand: public XmlCommand
{
public:
virtual void Execute();
};
class ConfigXmlCommand: public XmlCommand
{
public:
virtual void Execute();
};
class LoadConfigXmlCommand: public XmlCommand
{
public:
virtual void Execute();
};
class SaveConfigXmlCommand: public XmlCommand
{
public:
virtual void Execute();
void Save(const char *szFilename);
};
#endif

View File

@@ -79,9 +79,6 @@
/* Define to 1 if spinlocks are supported */
#undef HAVE_SPINLOCK
/* Define to 1 if stat64 is supported */
#undef HAVE_STAT64
/* Define to 1 if you have the <stdint.h> header file. */
#undef HAVE_STDINT_H
@@ -138,3 +135,9 @@
/* Version number of package */
#undef VERSION
/* Number of bits in a file offset, on hosts where this is settable. */
#undef _FILE_OFFSET_BITS
/* Define for large files, on AIX-style hosts. */
#undef _LARGE_FILES

398
configure vendored
View File

@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.61 for nzbget 10.0.
# Generated by GNU Autoconf 2.61 for nzbget 12.0.
#
# Report bugs to <hugbug@users.sourceforge.net>.
#
@@ -574,8 +574,8 @@ SHELL=${CONFIG_SHELL-/bin/sh}
# Identity of this package.
PACKAGE_NAME='nzbget'
PACKAGE_TARNAME='nzbget'
PACKAGE_VERSION='10.0'
PACKAGE_STRING='nzbget 10.0'
PACKAGE_VERSION='12.0'
PACKAGE_STRING='nzbget 12.0'
PACKAGE_BUGREPORT='hugbug@users.sourceforge.net'
ac_unique_file="nzbget.cpp"
@@ -1235,7 +1235,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
\`configure' configures nzbget 10.0 to adapt to many kinds of systems.
\`configure' configures nzbget 12.0 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1306,7 +1306,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
short | recursive ) echo "Configuration of nzbget 10.0:";;
short | recursive ) echo "Configuration of nzbget 12.0:";;
esac
cat <<\_ACEOF
@@ -1315,6 +1315,7 @@ Optional Features:
--enable-FEATURE[=ARG] include FEATURE [ARG=yes]
--disable-dependency-tracking speeds up one-time build
--enable-dependency-tracking do not reject slow dependency extractors
--disable-largefile omit support for large files
--disable-curses do not use curses (removes dependency from
curses-library)
--disable-parcheck do not include par-check/-repair-support (removes
@@ -1327,8 +1328,8 @@ Optional Features:
--disable-gzip disable gzip-compression/decompression (removes
dependency from zlib-library)
--disable-sigchld-handler
do not use sigchld-handler (the disabling is
recommended for BSD)
do not use sigchld-handler (the disabling may be
neccessary on 32-Bit BSD)
--enable-debug enable debugging
Optional Packages:
@@ -1452,7 +1453,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
nzbget configure 10.0
nzbget configure 12.0
generated by GNU Autoconf 2.61
Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
@@ -1466,7 +1467,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
It was created by nzbget $as_me 10.0, which was
It was created by nzbget $as_me 12.0, which was
generated by GNU Autoconf 2.61. Invocation command line was
$ $0 $@
@@ -2262,7 +2263,7 @@ fi
# Define the identity of the package.
PACKAGE=nzbget
VERSION=10.0
VERSION=12.0
cat >>confdefs.h <<_ACEOF
@@ -4721,64 +4722,55 @@ fi
{ echo "$as_me:$LINENO: checking for stat64" >&5
echo $ECHO_N "checking for stat64... $ECHO_C" >&6; }
if test "${ac_cv_func_stat64+set}" = set; then
# Check whether --enable-largefile was given.
if test "${enable_largefile+set}" = set; then
enableval=$enable_largefile;
fi
if test "$enable_largefile" != no; then
{ echo "$as_me:$LINENO: checking for special C compiler options needed for large files" >&5
echo $ECHO_N "checking for special C compiler options needed for large files... $ECHO_C" >&6; }
if test "${ac_cv_sys_largefile_CC+set}" = set; then
echo $ECHO_N "(cached) $ECHO_C" >&6
else
cat >conftest.$ac_ext <<_ACEOF
ac_cv_sys_largefile_CC=no
if test "$GCC" != yes; then
ac_save_CC=$CC
while :; do
# IRIX 6.2 and later do not support large files by default,
# so use the C compiler's -n32 option if that helps.
cat >conftest.$ac_ext <<_ACEOF
/* confdefs.h. */
_ACEOF
cat confdefs.h >>conftest.$ac_ext
cat >>conftest.$ac_ext <<_ACEOF
/* end confdefs.h. */
/* Define stat64 to an innocuous variant, in case <limits.h> declares stat64.
For example, HP-UX 11i <limits.h> declares gettimeofday. */
#define stat64 innocuous_stat64
/* System header to define __stub macros and hopefully few prototypes,
which can conflict with char stat64 (); below.
Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
<limits.h> exists even on freestanding compilers. */
#ifdef __STDC__
# include <limits.h>
#else
# include <assert.h>
#endif
#undef stat64
/* Override any GCC internal prototype to avoid an error.
Use char because int might match the return type of a GCC
builtin and then its argument prototype would still apply. */
#ifdef __cplusplus
extern "C"
#endif
char stat64 ();
/* The GNU C library defines this for functions which it implements
to always fail with ENOSYS. Some functions are actually named
something starting with __ and the normal name is an alias. */
#if defined __stub_stat64 || defined __stub___stat64
choke me
#endif
#include <sys/types.h>
/* Check that off_t can represent 2**63 - 1 correctly.
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
int
main ()
{
return stat64 ();
;
return 0;
}
_ACEOF
rm -f conftest.$ac_objext conftest$ac_exeext
if { (ac_try="$ac_link"
rm -f conftest.$ac_objext
if { (ac_try="$ac_compile"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
(eval "$ac_link") 2>conftest.er1
(eval "$ac_compile") 2>conftest.er1
ac_status=$?
grep -v '^ *+' conftest.er1 >conftest.err
rm -f conftest.er1
@@ -4787,27 +4779,297 @@ eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
(exit $ac_status); } && {
test -z "$ac_cxx_werror_flag" ||
test ! -s conftest.err
} && test -s conftest$ac_exeext &&
$as_test_x conftest$ac_exeext; then
ac_cv_func_stat64=yes
} && test -s conftest.$ac_objext; then
break
else
echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
ac_cv_func_stat64=no
fi
rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \
conftest$ac_exeext conftest.$ac_ext
fi
{ echo "$as_me:$LINENO: result: $ac_cv_func_stat64" >&5
echo "${ECHO_T}$ac_cv_func_stat64" >&6; }
if test $ac_cv_func_stat64 = yes; then
rm -f core conftest.err conftest.$ac_objext
CC="$CC -n32"
rm -f conftest.$ac_objext
if { (ac_try="$ac_compile"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
(eval "$ac_compile") 2>conftest.er1
ac_status=$?
grep -v '^ *+' conftest.er1 >conftest.err
rm -f conftest.er1
cat conftest.err >&5
echo "$as_me:$LINENO: \$? = $ac_status" >&5
(exit $ac_status); } && {
test -z "$ac_cxx_werror_flag" ||
test ! -s conftest.err
} && test -s conftest.$ac_objext; then
ac_cv_sys_largefile_CC=' -n32'; break
else
echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
cat >>confdefs.h <<\_ACEOF
#define HAVE_STAT64 1
fi
rm -f core conftest.err conftest.$ac_objext
break
done
CC=$ac_save_CC
rm -f conftest.$ac_ext
fi
fi
{ echo "$as_me:$LINENO: result: $ac_cv_sys_largefile_CC" >&5
echo "${ECHO_T}$ac_cv_sys_largefile_CC" >&6; }
if test "$ac_cv_sys_largefile_CC" != no; then
CC=$CC$ac_cv_sys_largefile_CC
fi
{ echo "$as_me:$LINENO: checking for _FILE_OFFSET_BITS value needed for large files" >&5
echo $ECHO_N "checking for _FILE_OFFSET_BITS value needed for large files... $ECHO_C" >&6; }
if test "${ac_cv_sys_file_offset_bits+set}" = set; then
echo $ECHO_N "(cached) $ECHO_C" >&6
else
while :; do
cat >conftest.$ac_ext <<_ACEOF
/* confdefs.h. */
_ACEOF
cat confdefs.h >>conftest.$ac_ext
cat >>conftest.$ac_ext <<_ACEOF
/* end confdefs.h. */
#include <sys/types.h>
/* Check that off_t can represent 2**63 - 1 correctly.
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
int
main ()
{
;
return 0;
}
_ACEOF
rm -f conftest.$ac_objext
if { (ac_try="$ac_compile"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
(eval "$ac_compile") 2>conftest.er1
ac_status=$?
grep -v '^ *+' conftest.er1 >conftest.err
rm -f conftest.er1
cat conftest.err >&5
echo "$as_me:$LINENO: \$? = $ac_status" >&5
(exit $ac_status); } && {
test -z "$ac_cxx_werror_flag" ||
test ! -s conftest.err
} && test -s conftest.$ac_objext; then
ac_cv_sys_file_offset_bits=no; break
else
echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
cat >conftest.$ac_ext <<_ACEOF
/* confdefs.h. */
_ACEOF
cat confdefs.h >>conftest.$ac_ext
cat >>conftest.$ac_ext <<_ACEOF
/* end confdefs.h. */
#define _FILE_OFFSET_BITS 64
#include <sys/types.h>
/* Check that off_t can represent 2**63 - 1 correctly.
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
int
main ()
{
;
return 0;
}
_ACEOF
rm -f conftest.$ac_objext
if { (ac_try="$ac_compile"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
(eval "$ac_compile") 2>conftest.er1
ac_status=$?
grep -v '^ *+' conftest.er1 >conftest.err
rm -f conftest.er1
cat conftest.err >&5
echo "$as_me:$LINENO: \$? = $ac_status" >&5
(exit $ac_status); } && {
test -z "$ac_cxx_werror_flag" ||
test ! -s conftest.err
} && test -s conftest.$ac_objext; then
ac_cv_sys_file_offset_bits=64; break
else
echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
ac_cv_sys_file_offset_bits=unknown
break
done
fi
{ echo "$as_me:$LINENO: result: $ac_cv_sys_file_offset_bits" >&5
echo "${ECHO_T}$ac_cv_sys_file_offset_bits" >&6; }
case $ac_cv_sys_file_offset_bits in #(
no | unknown) ;;
*)
cat >>confdefs.h <<_ACEOF
#define _FILE_OFFSET_BITS $ac_cv_sys_file_offset_bits
_ACEOF
;;
esac
rm -f conftest*
if test $ac_cv_sys_file_offset_bits = unknown; then
{ echo "$as_me:$LINENO: checking for _LARGE_FILES value needed for large files" >&5
echo $ECHO_N "checking for _LARGE_FILES value needed for large files... $ECHO_C" >&6; }
if test "${ac_cv_sys_large_files+set}" = set; then
echo $ECHO_N "(cached) $ECHO_C" >&6
else
while :; do
cat >conftest.$ac_ext <<_ACEOF
/* confdefs.h. */
_ACEOF
cat confdefs.h >>conftest.$ac_ext
cat >>conftest.$ac_ext <<_ACEOF
/* end confdefs.h. */
#include <sys/types.h>
/* Check that off_t can represent 2**63 - 1 correctly.
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
int
main ()
{
;
return 0;
}
_ACEOF
rm -f conftest.$ac_objext
if { (ac_try="$ac_compile"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
(eval "$ac_compile") 2>conftest.er1
ac_status=$?
grep -v '^ *+' conftest.er1 >conftest.err
rm -f conftest.er1
cat conftest.err >&5
echo "$as_me:$LINENO: \$? = $ac_status" >&5
(exit $ac_status); } && {
test -z "$ac_cxx_werror_flag" ||
test ! -s conftest.err
} && test -s conftest.$ac_objext; then
ac_cv_sys_large_files=no; break
else
echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
cat >conftest.$ac_ext <<_ACEOF
/* confdefs.h. */
_ACEOF
cat confdefs.h >>conftest.$ac_ext
cat >>conftest.$ac_ext <<_ACEOF
/* end confdefs.h. */
#define _LARGE_FILES 1
#include <sys/types.h>
/* Check that off_t can represent 2**63 - 1 correctly.
We can't simply define LARGE_OFF_T to be 9223372036854775807,
since some C++ compilers masquerading as C compilers
incorrectly reject 9223372036854775807. */
#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62))
int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721
&& LARGE_OFF_T % 2147483647 == 1)
? 1 : -1];
int
main ()
{
;
return 0;
}
_ACEOF
rm -f conftest.$ac_objext
if { (ac_try="$ac_compile"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
(eval "$ac_compile") 2>conftest.er1
ac_status=$?
grep -v '^ *+' conftest.er1 >conftest.err
rm -f conftest.er1
cat conftest.err >&5
echo "$as_me:$LINENO: \$? = $ac_status" >&5
(exit $ac_status); } && {
test -z "$ac_cxx_werror_flag" ||
test ! -s conftest.err
} && test -s conftest.$ac_objext; then
ac_cv_sys_large_files=1; break
else
echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
ac_cv_sys_large_files=unknown
break
done
fi
{ echo "$as_me:$LINENO: result: $ac_cv_sys_large_files" >&5
echo "${ECHO_T}$ac_cv_sys_large_files" >&6; }
case $ac_cv_sys_large_files in #(
no | unknown) ;;
*)
cat >>confdefs.h <<_ACEOF
#define _LARGE_FILES $ac_cv_sys_large_files
_ACEOF
;;
esac
rm -f conftest*
fi
fi
@@ -8566,17 +8828,9 @@ echo $ECHO_N "checking whether to use an empty SIGCHLD handler... $ECHO_C" >&6;
if test "${enable_sigchld_handler+set}" = set; then
enableval=$enable_sigchld_handler; SIGCHLDHANDLER=$enableval
else
SIGCHLDHANDLER=auto
SIGCHLDHANDLER=yes
fi
if test "$SIGCHLDHANDLER" = "auto"; then
SIGCHLDHANDLER=yes
case "$target" in
*bsd*)
SIGCHLDHANDLER=no
;;
esac
fi
{ echo "$as_me:$LINENO: result: $SIGCHLDHANDLER" >&5
echo "${ECHO_T}$SIGCHLDHANDLER" >&6; }
if test "$SIGCHLDHANDLER" = "yes"; then
@@ -9283,7 +9537,7 @@ exec 6>&1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
This file was extended by nzbget $as_me 10.0, which was
This file was extended by nzbget $as_me 12.0, which was
generated by GNU Autoconf 2.61. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -9336,7 +9590,7 @@ Report bugs to <bug-autoconf@gnu.org>."
_ACEOF
cat >>$CONFIG_STATUS <<_ACEOF
ac_cs_version="\\
nzbget config.status 10.0
nzbget config.status 12.0
configured by $0, generated by GNU Autoconf 2.61,
with options \\"`echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`\\"

View File

@@ -2,9 +2,9 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.59)
AC_INIT(nzbget, 10.0, hugbug@users.sourceforge.net)
AC_INIT(nzbget, 12.0, hugbug@users.sourceforge.net)
AC_CANONICAL_SYSTEM
AM_INIT_AUTOMAKE(nzbget, 10.0)
AM_INIT_AUTOMAKE(nzbget, 12.0)
AC_CONFIG_SRCDIR([nzbget.cpp])
AC_CONFIG_HEADERS([config.h])
@@ -56,10 +56,9 @@ AC_CHECK_FUNC(getopt_long,
dnl
dnl stat64
dnl use 64-Bits for file sizes
dnl
AC_CHECK_FUNC(stat64,
[AC_DEFINE([HAVE_STAT64], 1, [Define to 1 if stat64 is supported])],)
AC_SYS_LARGEFILE
dnl
@@ -503,22 +502,14 @@ fi
dnl
dnl Some Linux systems require an empty signal handler for SIGCHLD
dnl in order for exit codes to be correctly delivered to parent process.
dnl Some BSD systems however may not function properly if the handler is installed.
dnl The default behavior is to check the target and disable the handler on BSD but keep it enabled on other systems.
dnl Some 32-Bit BSD systems however may not function properly if the handler is installed.
dnl The default behavior is to install the handler.
dnl
AC_MSG_CHECKING(whether to use an empty SIGCHLD handler)
AC_ARG_ENABLE(sigchld-handler,
[AS_HELP_STRING([--disable-sigchld-handler], [do not use sigchld-handler (the disabling is recommended for BSD)])],
[AS_HELP_STRING([--disable-sigchld-handler], [do not use sigchld-handler (the disabling may be neccessary on 32-Bit BSD)])],
[SIGCHLDHANDLER=$enableval],
[SIGCHLDHANDLER=auto])
if test "$SIGCHLDHANDLER" = "auto"; then
SIGCHLDHANDLER=yes
case "$target" in
*bsd*)
SIGCHLDHANDLER=no
;;
esac
fi
[SIGCHLDHANDLER=yes])
AC_MSG_RESULT($SIGCHLDHANDLER)
if test "$SIGCHLDHANDLER" = "yes"; then
AC_DEFINE([SIGCHLD_HANDLER], 1, [Define to 1 to install an empty signal handler for SIGCHLD])

View File

@@ -1,50 +0,0 @@
#
# This file if part of nzbget
#
# Template configuration file for post-processing script "nzbget-postprocess.sh".
# Please refer to "nzbget-postprocess.sh" for usage instructions.
#
# Copyright (C) 2008-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
#
##############################################################################
### OPTIONS ###
# Rename img-files to iso (yes, no).
RenameIMG=yes
# Joint TS-files (yes, no).
JoinTS=yes
##############################################################################
### POSTPROCESSING-PARAMETERS ###
# This section defines parameters, which can be set for each nzb-file
# individually using either web-interface or command line.
# Example command line for setting parameter "PostProcess" to value "no" for
# nzb-file with id=2:
# nzbget -E G O PostProcess=no 2
# Perform postprocessing (yes, no).
#
# Set to "no" to skip postprocessing for this nzb-file.
PostProcess=yes
# Destination directory.
DestDir=

View File

@@ -1,231 +0,0 @@
#!/bin/sh
#
# This file if part of nzbget
#
# Example postprocessing script for NZBGet
#
# Copyright (C) 2008 Peter Roubos <peterroubos@hotmail.com>
# Copyright (C) 2008 Otmar Werner
# Copyright (C) 2008-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
#
####################### Usage instructions #######################
# o Script will cleanup, join ts-files and rename img-files to iso.
#
# o To use this script with nzbget set the option "PostProcess" in
# nzbget configuration file to point to this script file. E.g.:
# PostProcess=/home/user/nzbget/nzbget-postprocess.sh
#
# o The script needs a configuration file. An example configuration file
# is provided in file "nzbget-postprocess.conf". Put the configuration file
# into the directory where nzbget's configuration file (nzbget.conf) is located.
# Then edit the configuration file in any text editor to adjust the settings.
#
# o You can also edit the script's configuration via web-interface.
#
# o There are few options, which can be ajdusted for each nzb-file individually.
#
####################### End of Usage instructions #######################
# NZBGet passes following arguments to postprocess-programm as environment
# variables:
# NZBPP_DIRECTORY - path to destination dir for downloaded files;
# NZBPP_NZBNAME - user-friendly name of processed nzb-file as it is displayed
# by the program. The file path and extension are removed.
# If download was renamed, this parameter reflects the new name;
# NZBPP_NZBFILENAME - name of processed nzb-file. It includes file extension and also
# may include full path;
# NZBPP_CATEGORY - category assigned to nzb-file (can be empty string);
# NZBPP_PARSTATUS - result of par-check:
# 0 = not checked: par-check is disabled or nzb-file does
# not contain any par-files;
# 1 = checked and failed to repair;
# 2 = checked and successfully repaired;
# 3 = checked and can be repaired but repair is disabled.
# NZBPP_UNPACKSTATUS - result of unpack:
# 0 = unpack is disabled or was skipped due to nzb-file
# properties or due to errors during par-check;
# 1 = unpack failed;
# 2 = unpack successful.
# Name of script's configuration file
SCRIPT_CONFIG_FILE="nzbget-postprocess.conf"
# Exit codes
POSTPROCESS_PARCHECK_CURRENT=91
POSTPROCESS_PARCHECK_ALL=92
POSTPROCESS_SUCCESS=93
POSTPROCESS_ERROR=94
POSTPROCESS_NONE=95
# Check if the script is called from nzbget 10.0 or later
if [ "$NZBPP_DIRECTORY" = "" -o "$NZBOP_CONFIGFILE" = "" ]; then
echo "*** NZBGet post-processing script ***"
echo "This script is supposed to be called from nzbget (10.0 or later)."
exit $POSTPROCESS_ERROR
fi
if [ "$NZBOP_UNPACK" = "" ]; then
echo "[ERROR] This script requires nzbget version at least 10.0-testing-r555 or 10.0-stable."
exit $POSTPROCESS_ERROR
fi
# Check if postprocessing was disabled in postprocessing parameters
# (for current nzb-file) via web-interface or via command line with
# "nzbget -E G O PostProcess=no <ID>"
if [ "$NZBPR_PostProcess" = "no" ]; then
echo "[WARNING] Post-Process: Post-processing disabled for this nzb-file, exiting"
exit $POSTPROCESS_NONE
fi
echo "[INFO] Post-Process: Post-processing script successfully started"
cd "$NZBPP_DIRECTORY"
# Determine the location of configuration file (it must be stored in
# the directory with nzbget.conf).
ConfigDir="${NZBOP_CONFIGFILE%/*}"
ScriptConfigFile="$ConfigDir/$SCRIPT_CONFIG_FILE"
if [ ! -f "$ScriptConfigFile" ]; then
echo "[ERROR] Post-Process: Configuration file $ScriptConfigFile not found, exiting"
exit $POSTPROCESS_ERROR
fi
# Readg configuration file
while read line; do eval "$line"; done < $ScriptConfigFile
# Check nzbget.conf options
BadConfig=0
if [ "$NZBOP_ALLOWREPROCESS" = "yes" ]; then
echo "[ERROR] Post-Process: Please disable option \"AllowReProcess\" in nzbget configuration file"
BadConfig=1
fi
if [ "$NZBOP_UNPACK" != "yes" ]; then
echo "[ERROR] Post-Process: Please enable option \"Unpack\" in nzbget configuration file"
BadConfig=1
fi
if [ "$BadConfig" -eq 1 ]; then
echo "[ERROR] Post-Process: Exiting due to incompatible nzbget configuration"
exit $POSTPROCESS_ERROR
fi
# Check par status
if [ "$NZBPP_PARSTATUS" -eq 3 ]; then
echo "[WARNING] Post-Process: Par-check successful, but Par-repair disabled, exiting"
exit $POSTPROCESS_NONE
fi
if [ "$NZBPP_PARSTATUS" -eq 1 ]; then
echo "[WARNING] Post-Process: Par-check failed, exiting"
exit $POSTPROCESS_NONE
fi
# Check unpack status
if [ "$NZBPP_UNPACKSTATUS" -eq 1 ]; then
echo "[WARNING] Post-Process: Unpack failed, exiting"
exit $POSTPROCESS_NONE
fi
if [ "$NZBPP_UNPACKSTATUS" -eq 0 -a "$NZBPP_PARSTATUS" -ne 2 ]; then
# Unpack is disabled or was skipped due to nzb-file properties or due to errors during par-check
if (ls *.rar *.7z *.7z.??? >/dev/null 2>&1); then
echo "[WARNING] Post-Process: Archive files exist but unpack skipped, exiting"
exit $POSTPROCESS_NONE
fi
if (ls *.par2 >/dev/null 2>&1); then
echo "[WARNING] Post-Process: Unpack skipped and par-check skipped (although par2-files exist), exiting"
exit $POSTPROCESS_NONE
fi
if [ -f "_brokenlog.txt" ]; then
echo "[WARNING] Post-Process: _brokenlog.txt exists, download is probably damaged, exiting"
exit $POSTPROCESS_NONE
fi
echo "[INFO] Post-Process: Neither archive- nor par2-files found, _brokenlog.txt doesn't exist, considering download successful"
fi
# Check if destination directory exists (important for reprocessing of history items)
if [ ! -d "$NZBPP_DIRECTORY" ]; then
echo "[ERROR] Post-Process: Nothing to post-process: destination directory $NZBPP_DIRECTORY doesn't exist"
exit $POSTPROCESS_ERROR
fi
# All checks done, now processing the files
# If download contains only nzb-files move them into nzb-directory
# for further download
# Check if command "wc" exists
wc -l . >/dev/null 2>&1
if [ "$?" -ne 127 ]; then
AllFilesCount=`ls -1 2>/dev/null | wc -l`
NZBFilesCount=`ls -1 *.nzb 2>/dev/null | wc -l`
if [ "$AllFilesCount" -eq "$NZBFilesCount" ]; then
echo "[INFO] Moving downloaded nzb-files into incoming nzb-directory for further download"
mv *.nzb $NZBOP_NZBDIR
fi
fi
# Clean up
echo "[INFO] Post-Process: Cleaning up"
chmod -R a+rw .
rm *.nzb >/dev/null 2>&1
rm *.sfv >/dev/null 2>&1
rm *.1 >/dev/null 2>&1
rm _brokenlog.txt >/dev/null 2>&1
rm *.[pP][aA][rR]2 >/dev/null 2>&1
if [ "$JoinTS" = "yes" ]; then
# Join any split .ts files if they are named xxxx.0000.ts xxxx.0001.ts
# They will be joined together to a file called xxxx.0001.ts
if (ls *.ts >/dev/null 2>&1); then
echo "[INFO] Post-Process: Joining ts-files"
tsname=`find . -name "*0001.ts" |awk -F/ '{print $NF}'`
cat *0???.ts > ./$tsname
# Remove all the split .ts files
echo "[INFO] Post-Process: Deleting source ts-files"
rm *0???.ts >/dev/null 2>&1
fi
fi
if [ "$RenameIMG" = "yes" ]; then
# Rename img file to iso
# It will be renamed to .img.iso so you can see that it has been renamed
if (ls *.img >/dev/null 2>&1); then
echo "[INFO] Post-Process: Renaming img-files to iso"
imgname=`find . -name "*.img" |awk -F/ '{print $NF}'`
mv $imgname $imgname.iso
fi
fi
# Check if destination directory was set in postprocessing parameters
# (for current nzb-file) via web-interface or via command line with
# "nzbget -E G O DestDir=/new/path <ID>"
if [ "$NZBPR_DestDir" != "" ]; then
mkdir $NZBPR_DestDir
mv * $NZBPR_DestDir >/dev/null 2>&1
cd ..
rmdir $NZBPP_DIRECTORY
fi
# All OK, requesting cleaning up of download queue
exit $POSTPROCESS_SUCCESS

View File

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
* This file is part of nzbget
*
* Copyright (C) 2004 Sven Henkel <sidddy@users.sourceforge.net>
* Copyright (C) 2007-2012 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -74,6 +74,9 @@
#include "PrePostProcessor.h"
#include "ParChecker.h"
#include "Scheduler.h"
#include "Scanner.h"
#include "FeedCoordinator.h"
#include "Maintenance.h"
#include "Util.h"
#ifdef WIN32
#include "NTService.h"
@@ -113,6 +116,9 @@ Log* g_pLog = NULL;
PrePostProcessor* g_pPrePostProcessor = NULL;
DiskState* g_pDiskState = NULL;
Scheduler* g_pScheduler = NULL;
Scanner* g_pScanner = NULL;
FeedCoordinator* g_pFeedCoordinator = NULL;
Maintenance* g_pMaintenance = NULL;
int g_iArgumentCount;
char* (*g_szEnvironmentVariables)[] = NULL;
char* (*g_szArguments)[] = NULL;
@@ -145,6 +151,8 @@ int main(int argc, char *argv[], char *argp[])
DisableCout();
#endif
srand (time(NULL));
g_iArgumentCount = argc;
g_szArguments = (char*(*)[])argv;
g_szEnvironmentVariables = (char*(*)[])argp;
@@ -203,6 +211,12 @@ void Run(bool bReload)
g_pServerPool = new ServerPool();
g_pScheduler = new Scheduler();
g_pQueueCoordinator = new QueueCoordinator();
g_pDownloadSpeedMeter = g_pQueueCoordinator;
g_pDownloadQueueHolder = g_pQueueCoordinator;
g_pUrlCoordinator = new UrlCoordinator();
g_pFeedCoordinator = new FeedCoordinator();
g_pMaintenance = new Maintenance();
debug("Reading options");
g_pOptions = new Options(g_iArgumentCount, *g_szArguments);
@@ -223,16 +237,19 @@ void Run(bool bReload)
g_pLog->InitOptions();
if (g_pOptions->GetDaemonMode() && !bReload)
if (g_pOptions->GetDaemonMode())
{
#ifdef WIN32
info("nzbget %s service-mode", Util::VersionRevision());
#else
Daemonize();
if (!bReload)
{
Daemonize();
}
info("nzbget %s daemon-mode", Util::VersionRevision());
#endif
}
else if (g_pOptions->GetServerMode() && !bReload)
else if (g_pOptions->GetServerMode())
{
info("nzbget %s server-mode", Util::VersionRevision());
}
@@ -249,9 +266,6 @@ void Run(bool bReload)
if (!g_pOptions->GetRemoteClientMode())
{
g_pServerPool->InitConnections();
#ifdef DEBUG
g_pServerPool->LogDebugInfo();
#endif
}
#ifndef WIN32
@@ -281,16 +295,6 @@ void Run(bool bReload)
return;
}
// Create the queue coordinator
if (!g_pOptions->GetRemoteClientMode())
{
g_pQueueCoordinator = new QueueCoordinator();
g_pDownloadSpeedMeter = g_pQueueCoordinator;
g_pDownloadQueueHolder = g_pQueueCoordinator;
g_pUrlCoordinator = new UrlCoordinator();
}
// Setup the network-server
if (g_pOptions->GetServerMode())
{
@@ -307,6 +311,7 @@ void Run(bool bReload)
// Creating PrePostProcessor
if (!g_pOptions->GetRemoteClientMode())
{
g_pScanner = new Scanner();
g_pPrePostProcessor = new PrePostProcessor();
}
@@ -341,7 +346,7 @@ void Run(bool bReload)
// Standalone-mode
if (!g_pOptions->GetServerMode())
{
NZBFile* pNZBFile = NZBFile::CreateFromFile(g_pOptions->GetArgFilename(), g_pOptions->GetAddCategory() ? g_pOptions->GetAddCategory() : "");
NZBFile* pNZBFile = NZBFile::Create(g_pOptions->GetArgFilename(), g_pOptions->GetAddCategory() ? g_pOptions->GetAddCategory() : "");
if (!pNZBFile)
{
abort("FATAL ERROR: Parsing NZB-document %s failed\n\n", g_pOptions->GetArgFilename() ? g_pOptions->GetArgFilename() : "N/A");
@@ -359,11 +364,13 @@ void Run(bool bReload)
g_pQueueCoordinator->Start();
g_pUrlCoordinator->Start();
g_pPrePostProcessor->Start();
g_pFeedCoordinator->Start();
// enter main program-loop
while (g_pQueueCoordinator->IsRunning() ||
g_pUrlCoordinator->IsRunning() ||
g_pPrePostProcessor->IsRunning())
g_pPrePostProcessor->IsRunning() ||
g_pFeedCoordinator->IsRunning())
{
if (!g_pOptions->GetServerMode() &&
!g_pQueueCoordinator->HasMoreJobs() &&
@@ -383,6 +390,10 @@ void Run(bool bReload)
{
g_pPrePostProcessor->Stop();
}
if (!g_pFeedCoordinator->IsStopped())
{
g_pFeedCoordinator->Stop();
}
}
usleep(100 * 1000);
}
@@ -391,6 +402,7 @@ void Run(bool bReload)
debug("QueueCoordinator stopped");
debug("UrlCoordinator stopped");
debug("PrePostProcessor stopped");
debug("FeedCoordinator stopped");
}
// Stop network-server
@@ -589,6 +601,7 @@ void ExitProc()
g_pQueueCoordinator->Stop();
g_pUrlCoordinator->Stop();
g_pPrePostProcessor->Stop();
g_pFeedCoordinator->Stop();
}
}
}
@@ -713,59 +726,40 @@ void Cleanup()
debug("Cleaning up global objects");
debug("Deleting UrlCoordinator");
if (g_pUrlCoordinator)
{
delete g_pUrlCoordinator;
g_pUrlCoordinator = NULL;
}
delete g_pUrlCoordinator;
g_pUrlCoordinator = NULL;
debug("UrlCoordinator deleted");
debug("Deleting RemoteServer");
if (g_pRemoteServer)
{
delete g_pRemoteServer;
g_pRemoteServer = NULL;
}
delete g_pRemoteServer;
g_pRemoteServer = NULL;
debug("RemoteServer deleted");
debug("Deleting RemoteSecureServer");
if (g_pRemoteSecureServer)
{
delete g_pRemoteSecureServer;
g_pRemoteSecureServer = NULL;
}
delete g_pRemoteSecureServer;
g_pRemoteSecureServer = NULL;
debug("RemoteSecureServer deleted");
debug("Deleting PrePostProcessor");
if (g_pPrePostProcessor)
{
delete g_pPrePostProcessor;
g_pPrePostProcessor = NULL;
}
delete g_pPrePostProcessor;
g_pPrePostProcessor = NULL;
delete g_pScanner;
g_pScanner = NULL;
debug("PrePostProcessor deleted");
debug("Deleting Frontend");
if (g_pFrontend)
{
delete g_pFrontend;
g_pFrontend = NULL;
}
delete g_pFrontend;
g_pFrontend = NULL;
debug("Frontend deleted");
debug("Deleting QueueCoordinator");
if (g_pQueueCoordinator)
{
delete g_pQueueCoordinator;
g_pQueueCoordinator = NULL;
}
delete g_pQueueCoordinator;
g_pQueueCoordinator = NULL;
debug("QueueCoordinator deleted");
debug("Deleting DiskState");
if (g_pDiskState)
{
delete g_pDiskState;
g_pDiskState = NULL;
}
delete g_pDiskState;
g_pDiskState = NULL;
debug("DiskState deleted");
debug("Deleting Options");
@@ -782,21 +776,25 @@ void Cleanup()
debug("Options deleted");
debug("Deleting ServerPool");
if (g_pServerPool)
{
delete g_pServerPool;
g_pServerPool = NULL;
}
delete g_pServerPool;
g_pServerPool = NULL;
debug("ServerPool deleted");
debug("Deleting Scheduler");
if (g_pScheduler)
{
delete g_pScheduler;
g_pScheduler = NULL;
}
delete g_pScheduler;
g_pScheduler = NULL;
debug("Scheduler deleted");
debug("Deleting FeedCoordinator");
delete g_pFeedCoordinator;
g_pFeedCoordinator = NULL;
debug("FeedCoordinator deleted");
debug("Deleting Maintenance");
delete g_pMaintenance;
g_pMaintenance = NULL;
debug("Maintenance deleted");
if (!g_bReloading)
{
Connection::Final();
@@ -805,11 +803,8 @@ void Cleanup()
debug("Global objects cleaned up");
if (g_pLog)
{
delete g_pLog;
g_pLog = NULL;
}
delete g_pLog;
g_pLog = NULL;
}
#ifndef WIN32
@@ -833,14 +828,14 @@ void Daemonize()
/* Drop user if there is one, and we were run as root */
if ( getuid() == 0 || geteuid() == 0 )
{
struct passwd *pw = getpwnam(g_pOptions->GetDaemonUserName());
struct passwd *pw = getpwnam(g_pOptions->GetDaemonUsername());
if (pw)
{
fchown(lfp, pw->pw_uid, pw->pw_gid); /* change owner of lock file */
setgroups( 0, (const gid_t*) 0 ); /* Set aux groups to null. */
setgid(pw->pw_gid); /* Set primary group. */
/* Try setting aux groups correctly - not critical if this fails. */
initgroups( g_pOptions->GetDaemonUserName(),pw->pw_gid);
initgroups( g_pOptions->GetDaemonUsername(),pw->pw_gid);
/* Finally, set uid. */
setuid(pw->pw_uid);
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2007-2010 Andrey Prygunkov <hugbug@users.sourceforge.net>
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -37,6 +37,8 @@
#define fdopen _fdopen
#define ctime_r(timep, buf, bufsize) ctime_s(buf, bufsize, timep)
#define localtime_r(time, tm) localtime_s(tm, time)
#define strtok_r(str, delim, saveptr) strtok_s(str, delim, saveptr)
#define strerror_r(errnum, buffer, size) strerror_s(buffer, size, errnum)
#define int32_t __int32
#define mkdir(dir, flags) _mkdir(dir)
#define rmdir _rmdir
@@ -56,6 +58,7 @@
#define ALT_PATH_SEPARATOR '/'
#define LINE_ENDING "\r\n"
#define pid_t int
#define atoll _atoi64
#ifndef FSCTL_SET_SPARSE
#define FSCTL_SET_SPARSE 590020
#endif

View File

@@ -1,274 +0,0 @@
<?xml version = '1.0'?>
<kdevelop>
<general>
<author>gnu</author>
<email/>
<version>0.3.0</version>
<projectmanagement>KDevAutoProject</projectmanagement>
<primarylanguage>C++</primarylanguage>
<keywords>
<keyword>C++</keyword>
<keyword>Code</keyword>
</keywords>
<ignoreparts>
<part>kdevabbrev</part>
<part>kdevbookmarks</part>
<part>kdevsnippet</part>
<part>kdevctags2</part>
<part>kdevdoxygen</part>
<part>kdevkonsoleview</part>
<part>kdevfilegroups</part>
<part>kdevfilelist</part>
<part>kdevfileview</part>
<part>kdevdistpart</part>
<part>kdevopenwith</part>
<part>kdevregexptest</part>
<part>kdevscripting</part>
<part>kdevfilter</part>
<part>kdevtexttools</part>
</ignoreparts>
<projectdirectory>.</projectdirectory>
<absoluteprojectpath>false</absoluteprojectpath>
<description/>
<projectname>nzbget</projectname>
<defaultencoding/>
<versioncontrol>kdevsubversion</versioncontrol>
</general>
<kdevautoproject>
<general>
<activetarget>src/nzbget</activetarget>
<useconfiguration>default</useconfiguration>
</general>
<run>
<mainprogram>/home/user/nzbget/nzbget</mainprogram>
<terminal>false</terminal>
<directoryradio>executable</directoryradio>
<customdirectory>/</customdirectory>
<programargs/>
<autocompile>true</autocompile>
<envvars/>
<globaldebugarguments></globaldebugarguments>
<globalcwd>/home/user/nzbget</globalcwd>
<useglobalprogram>true</useglobalprogram>
<autoinstall>false</autoinstall>
<autokdesu>false</autokdesu>
</run>
<configurations>
<default>
<envvars/>
<configargs>--enable-debug</configargs>
<builddir/>
<topsourcedir/>
<cppflags/>
<ldflags/>
<ccompiler>kdevgccoptions</ccompiler>
<cxxcompiler>kdevgppoptions</cxxcompiler>
<f77compiler>kdevpgf77options</f77compiler>
<ccompilerbinary/>
<cxxcompilerbinary/>
<f77compilerbinary/>
<cflags/>
<cxxflags/>
<f77flags/>
</default>
</configurations>
<make>
<envvars>
<envvar value="1" name="WANT_AUTOCONF_2_5" />
<envvar value="1" name="WANT_AUTOMAKE_1_6" />
</envvars>
<abortonerror>true</abortonerror>
<runmultiplejobs>false</runmultiplejobs>
<numberofjobs>1</numberofjobs>
<dontact>false</dontact>
<makebin/>
<prio>0</prio>
</make>
</kdevautoproject>
<kdevdoctreeview>
<ignoretocs>
<toc>ada</toc>
<toc>ada_bugs_gcc</toc>
<toc>bash</toc>
<toc>bash_bugs</toc>
<toc>clanlib</toc>
<toc>w3c-dom-level2-html</toc>
<toc>fortran_bugs_gcc</toc>
<toc>gnome1</toc>
<toc>gnustep</toc>
<toc>gtk</toc>
<toc>gtk_bugs</toc>
<toc>haskell</toc>
<toc>haskell_bugs_ghc</toc>
<toc>java_bugs_gcc</toc>
<toc>java_bugs_sun</toc>
<toc>kde2book</toc>
<toc>opengl</toc>
<toc>pascal_bugs_fp</toc>
<toc>php</toc>
<toc>php_bugs</toc>
<toc>perl</toc>
<toc>perl_bugs</toc>
<toc>python</toc>
<toc>python_bugs</toc>
<toc>qt-kdev3</toc>
<toc>ruby</toc>
<toc>ruby_bugs</toc>
<toc>sdl</toc>
<toc>w3c-svg</toc>
<toc>sw</toc>
<toc>w3c-uaag10</toc>
<toc>wxwidgets_bugs</toc>
</ignoretocs>
<ignoreqt_xml>
<toc>Guide to the Qt Translation Tools</toc>
<toc>Qt Assistant Manual</toc>
<toc>Qt Designer Manual</toc>
<toc>Qt Reference Documentation</toc>
<toc>qmake User Guide</toc>
</ignoreqt_xml>
<ignoredoxygen>
<toc>KDE Libraries (Doxygen)</toc>
</ignoredoxygen>
</kdevdoctreeview>
<kdevfilecreate>
<filetypes/>
<useglobaltypes>
<type ext="cpp" />
<type ext="h" />
</useglobaltypes>
</kdevfilecreate>
<kdevfileview>
<groups>
<group pattern="*.h" name="Header files" />
<group pattern="*.cpp" name="Source files" />
<hidenonprojectfiles>false</hidenonprojectfiles>
<hidenonlocation>false</hidenonlocation>
</groups>
<tree>
<hidepatterns>*.o,*.lo,CVS</hidepatterns>
<hidenonprojectfiles>false</hidenonprojectfiles>
</tree>
</kdevfileview>
<kdevdocumentation>
<projectdoc>
<docsystem>Doxygen Documentation Collection</docsystem>
<docurl>nzbget.tag</docurl>
<usermanualurl/>
</projectdoc>
</kdevdocumentation>
<substmap>
<APPNAME>nzbget</APPNAME>
<APPNAMELC>nzbget</APPNAMELC>
<APPNAMESC>Nzbget</APPNAMESC>
<APPNAMEUC>NZBGET</APPNAMEUC>
<AUTHOR>gnu</AUTHOR>
<EMAIL/>
<LICENSE>GPL</LICENSE>
<LICENSEFILE>COPYING</LICENSEFILE>
<VERSION>0.3.0</VERSION>
<YEAR>2007</YEAR>
<dest>/home/user/nzbget-0.3.0/nzbget</dest>
</substmap>
<cppsupportpart>
<filetemplates>
<interfacesuffix>.h</interfacesuffix>
<implementationsuffix>.cpp</implementationsuffix>
</filetemplates>
</cppsupportpart>
<kdevcppsupport>
<qt>
<used>false</used>
<version>3</version>
<root></root>
<includestyle>3</includestyle>
<designerintegration>EmbeddedKDevDesigner</designerintegration>
<qmake></qmake>
<designer></designer>
<designerpluginpaths/>
</qt>
<codecompletion>
<includeGlobalFunctions>true</includeGlobalFunctions>
<includeTypes>true</includeTypes>
<includeEnums>true</includeEnums>
<includeTypedefs>false</includeTypedefs>
<automaticCodeCompletion>false</automaticCodeCompletion>
<automaticArgumentsHint>false</automaticArgumentsHint>
<automaticHeaderCompletion>true</automaticHeaderCompletion>
<codeCompletionDelay>250</codeCompletionDelay>
<argumentsHintDelay>400</argumentsHintDelay>
<headerCompletionDelay>250</headerCompletionDelay>
<showOnlyAccessibleItems>false</showOnlyAccessibleItems>
<completionBoxItemOrder>0</completionBoxItemOrder>
<howEvaluationContextMenu>true</howEvaluationContextMenu>
<showCommentWithArgumentHint>false</showCommentWithArgumentHint>
<statusBarTypeEvaluation>false</statusBarTypeEvaluation>
<namespaceAliases>std=_GLIBCXX_STD;__gnu_cxx=std</namespaceAliases>
<processPrimaryTypes>true</processPrimaryTypes>
<processFunctionArguments>false</processFunctionArguments>
<preProcessAllHeaders>false</preProcessAllHeaders>
<parseMissingHeaders>false</parseMissingHeaders>
<resolveIncludePaths>true</resolveIncludePaths>
<alwaysParseInBackground>true</alwaysParseInBackground>
<usePermanentCaching>true</usePermanentCaching>
<alwaysIncludeNamespaces>false</alwaysIncludeNamespaces>
<includePaths>.;</includePaths>
</codecompletion>
<creategettersetter>
<prefixGet/>
<prefixSet>set</prefixSet>
<prefixVariable>m_,_</prefixVariable>
<parameterName>theValue</parameterName>
<inlineGet>true</inlineGet>
<inlineSet>true</inlineSet>
</creategettersetter>
<references/>
<splitheadersource>
<enabled>false</enabled>
<synchronize>true</synchronize>
<orientation>Vertical</orientation>
</splitheadersource>
</kdevcppsupport>
<kdevdebugger>
<general>
<programargs>--standalone /home/user/.nzbget/nzb/t1.nzb</programargs>
<gdbpath/>
<dbgshell/>
<configGdbScript/>
<runShellScript/>
<runGdbScript/>
<breakonloadinglibs>true</breakonloadinglibs>
<separatetty>true</separatetty>
<floatingtoolbar>false</floatingtoolbar>
</general>
<display>
<staticmembers>false</staticmembers>
<demanglenames>true</demanglenames>
<outputradix>10</outputradix>
</display>
</kdevdebugger>
<dist>
<custom>false</custom>
<bzip>false</bzip>
<archname/>
<appname>nzbget</appname>
<version>0.2.4</version>
<release/>
<vendor/>
<licence/>
<summary/>
<group/>
<packager/>
<description/>
<changelog/>
<devpackage>false</devpackage>
<docspackage>false</docspackage>
<appicon>false</appicon>
<arch>0</arch>
<genHTML>false</genHTML>
<useRPM>false</useRPM>
<ftpkde>false</ftpkde>
<appskde>false</appskde>
<url/>
</dist>
</kdevelop>

View File

@@ -299,6 +299,46 @@
RelativePath=".\DownloadInfo.h"
>
</File>
<File
RelativePath=".\DupeCoordinator.cpp"
>
</File>
<File
RelativePath=".\DupeCoordinator.h"
>
</File>
<File
RelativePath=".\FeedCoordinator.cpp"
>
</File>
<File
RelativePath=".\FeedCoordinator.h"
>
</File>
<File
RelativePath=".\FeedFile.cpp"
>
</File>
<File
RelativePath=".\FeedFile.h"
>
</File>
<File
RelativePath=".\FeedFilter.cpp"
>
</File>
<File
RelativePath=".\FeedFilter.h"
>
</File>
<File
RelativePath=".\FeedInfo.cpp"
>
</File>
<File
RelativePath=".\FeedInfo.h"
>
</File>
<File
RelativePath=".\Frontend.cpp"
>
@@ -323,6 +363,14 @@
RelativePath=".\LoggableFrontend.h"
>
</File>
<File
RelativePath=".\Maintenance.h"
>
</File>
<File
RelativePath=".\Maintenance.cpp"
>
</File>
<File
RelativePath=".\MessageBase.h"
>

41
osx/App_Prefix.pch Normal file
View File

@@ -0,0 +1,41 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
*/
#ifdef __OBJC__
#import <Cocoa/Cocoa.h>
#endif
#ifdef DEBUG
# define DLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
#else
# define DLog(...) do {} while (0)
#endif
#define SuppressPerformSelectorLeakWarning(Stuff) \
do { \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
Stuff; \
_Pragma("clang diagnostic pop") \
} while (0)

73
osx/DaemonController.h Normal file
View File

@@ -0,0 +1,73 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
*/
#import <Cocoa/Cocoa.h>
@protocol DaemonControllerDelegate
- (void)daemonConfigLoaded;
- (void)daemonStatusUpdated;
@end
@interface DaemonController : NSObject {
NSString* lockFilePath;
NSDictionary* config;
int restartCounter;
int restartPid;
NSTimer* updateTimer;
int lastUptime;
BOOL factoryReset;
}
@property (nonatomic, assign) NSTimeInterval updateInterval;
@property (nonatomic, readonly) BOOL connected;
@property (nonatomic, readonly) BOOL restarting;
@property (nonatomic, readonly) BOOL recoveryMode;
@property (nonatomic, readonly) NSString* configFilePath;
@property (nonatomic, readonly) NSString* browserUrl;
@property (nonatomic, readonly) NSDate* lastUpdate;
@property (nonatomic, assign) id<DaemonControllerDelegate> delegate;
@property (nonatomic, readonly) NSDictionary* status;
- (id)init;
- (NSString*)valueForOption:(NSString*)option;
- (void)start;
- (void)stop;
- (void)restartInRecoveryMode:(BOOL)recovery withFactoryReset:(BOOL)reset;
- (NSString *)browserUrl;
- (void)updateStatus;
- (void)rpc:(NSString*)method success:(SEL)successCallback failure:(SEL)failureCallback;
- (void)setUpdateInterval:(NSTimeInterval)updateInterval;
@end

339
osx/DaemonController.m Normal file
View File

@@ -0,0 +1,339 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
*/
#include <libproc.h>
#import <Cocoa/Cocoa.h>
#import "DaemonController.h"
#import "RPC.h"
NSString* MAIN_DIR = @"${AppSupDir}";
NSString* LOCK_FILE = @"${AppSupDir}/nzbget.lock";
NSString* CONFIG_FILE = @"${AppSupDir}/nzbget.conf";
NSString* PPSCRIPTS_DIR = @"${AppSupDir}/ppscripts";
NSString* NZB_DIR = @"${AppSupDir}/nzb";
NSString* QUEUE_DIR = @"${AppSupDir}/queue";
NSString* TMP_DIR = @"${AppSupDir}/tmp";
@implementation DaemonController
- (id)init {
self = [super init];
_configFilePath = [self resolveAppSupDir:CONFIG_FILE];
lockFilePath = [self resolveAppSupDir:LOCK_FILE];
return self;
}
- (NSString *) bundlePath {
return [[NSBundle mainBundle] pathForResource:@"daemon" ofType:nil];
}
- (NSString *) resolveAppSupDir:(NSString *)dir {
NSString *appSupPath = [@"${AppSupDir}/Application Support/NZBGet" stringByExpandingTildeInPath];
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
if (paths.count > 0)
{
appSupPath = [paths objectAtIndex:0];
appSupPath = [appSupPath stringByAppendingPathComponent:@"NZBGet"];
}
dir = [dir stringByReplacingOccurrencesOfString:@"${AppSupDir}"
withString:appSupPath
options:NSCaseInsensitiveSearch
range:NSMakeRange(0, dir.length)];
return dir;
}
- (void) checkDefaults {
NSString* mainDir = [self resolveAppSupDir:MAIN_DIR];
if (![[NSFileManager defaultManager] fileExistsAtPath:mainDir]) {
[[NSFileManager defaultManager] createDirectoryAtPath:mainDir withIntermediateDirectories:YES attributes:nil error:nil];
}
NSString* bundlePath = [self bundlePath];
if (![[NSFileManager defaultManager] fileExistsAtPath:_configFilePath]) {
NSString* configTemplate = [NSString stringWithFormat:@"%@/usr/local/share/nzbget/nzbget.conf", bundlePath];
[[NSFileManager defaultManager] copyItemAtPath:configTemplate toPath:_configFilePath error:nil];
}
NSString* ppscriptsDir = [self resolveAppSupDir:PPSCRIPTS_DIR];
if (![[NSFileManager defaultManager] fileExistsAtPath:ppscriptsDir]) {
NSString* ppscriptsTemplate = [NSString stringWithFormat:@"%@/usr/local/share/nzbget/ppscripts", bundlePath];
[[NSFileManager defaultManager] copyItemAtPath:ppscriptsTemplate toPath:ppscriptsDir error:nil];
}
}
- (int)readLockFilePid {
if ([[NSFileManager defaultManager] fileExistsAtPath:lockFilePath]) {
// Lock file exists
// read pid from lock file
int pid = [[NSString stringWithContentsOfFile:lockFilePath encoding:NSUTF8StringEncoding error:nil] intValue];
DLog(@"pid: %i", pid);
// check if the process name is "nzbget" to avoid killing of other proceses
char pathbuf[PROC_PIDPATHINFO_MAXSIZE];
int ret = proc_pidpath(pid, pathbuf, sizeof(pathbuf));
if (ret <= 0) {
// error
return 0;
}
DLog(@"proc %d: %s\n", pid, pathbuf);
NSString* instancePath = [NSString stringWithUTF8String:pathbuf];
if ([instancePath hasSuffix:@".app/Contents/Resources/daemon/usr/local/bin/nzbget"]) {
return pid;
}
}
return 0;
}
- (void)killDaemonWithSignal:(int)signal {
int pid = [self readLockFilePid];
if (pid > 0) {
kill(pid, signal);
[[NSFileManager defaultManager] removeItemAtPath:lockFilePath error:nil];
}
}
- (void)start {
DLog(@"DaemonController->start");
[self checkDefaults];
[self killDaemonWithSignal:SIGKILL];
[self readConfigFile];
[self initRpcUrl];
[self initBrowserUrl];
NSString* bundlePath = [self bundlePath];
NSString* daemonPath = [NSString stringWithFormat:@"%@/usr/local/bin/nzbget", bundlePath];
NSString* optionWebDir = [NSString stringWithFormat:@"WebDir=%@/usr/local/share/nzbget/webui", bundlePath];
NSString* optionConfigTemplate = [NSString stringWithFormat:@"ConfigTemplate=%@/usr/local/share/nzbget/nzbget.conf", bundlePath];
NSString* optionLockFile = [NSString stringWithFormat:@"LockFile=%@", lockFilePath];
NSMutableArray* arguments = [NSMutableArray arrayWithObjects:
@"-c", _configFilePath,
@"-D",
@"-o", optionWebDir,
@"-o", optionConfigTemplate,
@"-o", optionLockFile,
nil];
if (_recoveryMode) {
[arguments addObjectsFromArray: [NSArray arrayWithObjects:
@"-o", @"ControlIP=127.0.0.1",
@"-o", @"ControlPort=6789",
@"-o", @"ControlPassword=",
@"-o", @"SecureControl=no",
nil
]];
}
NSTask* task = [[NSTask alloc] init];
[task setLaunchPath: daemonPath];
[task setArguments: arguments];
[task launch];
_restarting = NO;
[self scheduleNextUpdate];
}
- (void)stop {
DLog(@"DaemonController->stop");
[self killDaemonWithSignal:SIGTERM];
}
- (void)restartInRecoveryMode:(BOOL)recovery withFactoryReset:(BOOL)reset {
_recoveryMode = recovery;
factoryReset = reset;
_restarting = YES;
restartPid = [self readLockFilePid];
[self stop];
// in timer wait for deletion of lockfile for 10 seconds,
// after that call "start" which will kill the old process.
restartCounter = 0;
[self restartWait];
}
- (void)restartWait {
DLog(@"DaemonController->restartWait");
restartCounter++;
char pathbuf[PROC_PIDPATHINFO_MAXSIZE];
int ret = proc_pidpath(restartPid, pathbuf, sizeof(pathbuf));
if (ret > 0 && restartCounter < 100) {
DLog(@"restartWait: scheduleNextRestartWait");
[self scheduleNextRestartWait];
} else {
DLog(@"restartWait: start");
if (factoryReset) {
[self resetToFactoryDefaults];
}
[self start];
}
}
- (void)resetToFactoryDefaults {
DLog(@"DaemonController->resetToFactoryDefaults");
[[NSFileManager defaultManager] removeItemAtPath:_configFilePath error:nil];
[[NSFileManager defaultManager] removeItemAtPath:[self resolveAppSupDir:QUEUE_DIR] error:nil];
[[NSFileManager defaultManager] removeItemAtPath:[self resolveAppSupDir:PPSCRIPTS_DIR] error:nil];
[[NSFileManager defaultManager] removeItemAtPath:[self resolveAppSupDir:NZB_DIR] error:nil];
[[NSFileManager defaultManager] removeItemAtPath:[self resolveAppSupDir:TMP_DIR] error:nil];
}
- (void)scheduleNextRestartWait {
NSTimer* timer = [NSTimer timerWithTimeInterval:0.100 target:self selector:@selector(restartWait) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
- (void)readConfigFile {
DLog(@"DaemonController->readConfigFile");
NSString* str = [NSString stringWithContentsOfFile:_configFilePath encoding:NSUTF8StringEncoding error:nil];
NSArray* conf = [str componentsSeparatedByString: @"\n"];
config = [[NSMutableDictionary alloc] init];
for (NSString* opt in conf) {
if ([opt hasPrefix:@"#"]) {
continue;
}
NSRange pos = [opt rangeOfString:@"="];
if (pos.location != NSNotFound) {
NSString* name = [opt substringToIndex:pos.location];
name = [name stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]];
NSString* value = [opt substringFromIndex:pos.location + 1];
value = [value stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]];
[config setValue:value forKey:[name uppercaseString]];
}
}
if (_recoveryMode) {
[config setValue:@"localhost" forKey:@"CONTROLIP"];
[config setValue:@"6789" forKey:@"CONTROLPORT"];
[config setValue:@"" forKey:@"CONTROLPASSWORD"];
}
[_delegate daemonConfigLoaded];
}
- (NSString*)valueForOption:(NSString*)option {
return [config valueForKey:[option uppercaseString]];
}
- (void)initBrowserUrl {
NSString* ip = [self valueForOption:@"ControlIP"];
if ([ip isEqualToString:@"0.0.0.0"] || [ip isEqualToString:@"127.0.0.1"]) {
ip = @"localhost";
}
NSString* port = [self valueForOption:@"ControlPort"];
_browserUrl = [NSString stringWithFormat:@"http://@%@:%@", ip, port];
}
- (void)initRpcUrl {
NSString* ip = [self valueForOption:@"ControlIP"];
if ([ip isEqualToString:@"0.0.0.0"]) {
ip = @"127.0.0.1";
}
NSString* port = [self valueForOption:@"ControlPort"];
NSString* username = [self valueForOption:@"ControlUsername"];
NSString* password = [self valueForOption:@"ControlPassword"];
NSString* RpcUrl = [NSString stringWithFormat:@"http://%@:%@/%@:%@/jsonrpc/", ip, port, username, password];
[RPC setRpcUrl:RpcUrl];
}
- (void)setUpdateInterval:(NSTimeInterval)updateInterval {
_updateInterval = updateInterval;
if (_connected) {
[updateTimer invalidate];
[self updateStatus];
}
}
- (void)scheduleNextUpdate {
updateTimer = [NSTimer timerWithTimeInterval:_updateInterval target:self selector:@selector(updateStatus) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:updateTimer forMode:NSRunLoopCommonModes];
}
- (void)rpc:(NSString*)method success:(SEL)successCallback failure:(SEL)failureCallback {
RPC* rpc = [[RPC alloc] initWithMethod:method receiver:self success:successCallback failure:failureCallback];
[rpc start];
}
- (void)receiveStatus:(NSDictionary*)status {
//DLog(@"receiveStatus");
if (_restarting) {
return;
}
_connected = YES;
_lastUpdate = [NSDate date];
_status = status;
//DLog(@"response: %@", status);
int uptime = [(NSNumber*)[status objectForKey:@"UpTimeSec"] integerValue];
if (lastUptime == 0) {
lastUptime = uptime;
} else if (lastUptime > uptime) {
// daemon was reloaded (soft-restart)
[self readConfigFile];
}
[_delegate daemonStatusUpdated];
[self scheduleNextUpdate];
}
- (void)failureStatus {
DLog(@"failureStatus");
if (_restarting) {
return;
}
_connected = NO;
int pid = [self readLockFilePid];
if (pid == 0) {
// Daemon is not running. Crashed?
_restarting = YES;
[_delegate daemonStatusUpdated];
[self start];
} else {
[_delegate daemonStatusUpdated];
[self scheduleNextUpdate];
}
}
- (void)updateStatus {
if (_restarting) {
return;
}
[self rpc:@"status" success:@selector(receiveStatus:) failure:@selector(failureStatus)];
}
@end

81
osx/MainApp.h Normal file
View File

@@ -0,0 +1,81 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
*/
#import <Cocoa/Cocoa.h>
#import "DaemonController.h"
@interface MainApp : NSObject <NSMenuDelegate, DaemonControllerDelegate> {
IBOutlet NSMenu *statusMenu;
NSStatusItem *statusItem;
IBOutlet NSMenuItem *webuiItem;
IBOutlet NSMenuItem *homePageItem;
IBOutlet NSMenuItem *downloadsItem;
IBOutlet NSMenuItem *forumItem;
IBOutlet NSMenuItem *info1Item;
IBOutlet NSMenuItem *info2Item;
IBOutlet NSMenuItem *restartRecoveryItem;
IBOutlet NSMenuItem *factoryResetItem;
IBOutlet NSMenuItem *destDirItem;
IBOutlet NSMenuItem *interDirItem;
IBOutlet NSMenuItem *nzbDirItem;
IBOutlet NSMenuItem *scriptDirItem;
IBOutlet NSMenuItem *configFileItem;
IBOutlet NSMenuItem *logFileItem;
IBOutlet NSMenuItem *destDirSeparator;
NSWindowController *welcomeDialog;
NSWindowController *preferencesDialog;
DaemonController *daemonController;
int connectionAttempts;
BOOL restarting;
BOOL resetting;
NSTimer* restartTimer;
NSMutableArray* categoryItems;
NSMutableArray* categoryDirs;
}
+ (void)setupAppDefaults;
- (void)setupDefaultsObserver;
- (IBAction)quitClicked:(id)sender;
- (IBAction)preferencesClicked:(id)sender;
- (void)userDefaultsDidChange:(id)sender;
- (IBAction)aboutClicked:(id)sender;
+ (BOOL)wasLaunchedAsLoginItem;
- (IBAction)webuiClicked:(id)sender;
- (IBAction)infoLinkClicked:(id)sender;
- (IBAction)openConfigInTextEditClicked:(id)sender;
- (IBAction)restartClicked:(id)sender;
- (IBAction)showInFinderClicked:(id)sender;
@end

579
osx/MainApp.m Normal file
View File

@@ -0,0 +1,579 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
*/
#import "MainApp.h"
#import "PreferencesDialog.h"
#import "WelcomeDialog.h"
#import "PFMoveApplication.h"
NSString *PreferencesContext = @"PreferencesContext";
const NSTimeInterval NORMAL_UPDATE_INTERVAL = 10.000;
const NSTimeInterval MENUOPEN_UPDATE_INTERVAL = 1.000;
const NSTimeInterval START_UPDATE_INTERVAL = 0.500;
int main(int argc, char *argv[]) {
return NSApplicationMain(argc, (const char **)argv);
}
/*
* Signal handler
*/
void SignalProc(int iSignal)
{
switch (iSignal)
{
case SIGINT:
case SIGTERM:
signal(iSignal, SIG_DFL); // Reset the signal handler
[NSApp terminate:nil];
break;
}
}
// we install seignal handler in order to properly terminat app from Activity Mo1nitor
void InstallSignalHandlers()
{
signal(SIGINT, SignalProc);
signal(SIGTERM, SignalProc);
signal(SIGPIPE, SIG_IGN);
}
@implementation MainApp
- (void)applicationWillFinishLaunching:(NSNotification *)aNotification {
[self checkOtherRunningInstances];
#ifndef DEBUG
PFMoveToApplicationsFolderIfNecessary();
#endif
}
- (void)checkOtherRunningInstances {
for (NSRunningApplication *runningApplication in [NSRunningApplication runningApplicationsWithBundleIdentifier:[[NSBundle mainBundle] bundleIdentifier]]) {
if (![[runningApplication executableURL] isEqualTo:[[NSRunningApplication currentApplication] executableURL]]) {
NSString *executablePath = [[runningApplication executableURL] path];
executablePath = [[[executablePath stringByDeletingLastPathComponent] stringByDeletingLastPathComponent] stringByDeletingLastPathComponent];
DLog(@"Switching to an already running instance: %@", executablePath);
[[NSTask launchedTaskWithLaunchPath:@"/usr/bin/open"
arguments:[NSArray arrayWithObjects:executablePath, @"--args", @"--second-instance", nil]] waitUntilExit];
exit(0);
}
}
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
BOOL autoStartWebUI = [[NSUserDefaults standardUserDefaults] boolForKey:@"AutoStartWebUI"];
daemonController = [[DaemonController alloc] init];
daemonController.updateInterval = autoStartWebUI ? START_UPDATE_INTERVAL : NORMAL_UPDATE_INTERVAL;
daemonController.delegate = self;
[self setupDefaultsObserver];
[self userDefaultsDidChange:nil];
if (![MainApp wasLaunchedAsLoginItem]) {
[self showWelcomeScreen];
}
InstallSignalHandlers();
DLog(@"Start Daemon");
[daemonController start];
}
- (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag {
DLog(@"applicationShouldHandleReopen");
[self showAlreadyRunning];
return YES;
}
+ (void)initialize {
[self setupAppDefaults];
}
- (void)awakeFromNib {
DLog(@"awakeFromNib");
[statusMenu setDelegate:self];
}
+ (void)setupAppDefaults {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSDictionary *appDefaults = [NSDictionary dictionaryWithObjectsAndKeys:
@"YES", @"ShowInMenubar",
@"NO", @"Autostart",
@"YES", @"AutoStartWebUI",
nil];
[userDefaults registerDefaults:appDefaults];
}
- (void)setupDefaultsObserver {
NSUserDefaultsController *sdc = [NSUserDefaultsController sharedUserDefaultsController];
[sdc addObserver:self forKeyPath:@"values.ShowInMenubar" options:0 context:(__bridge void *)(PreferencesContext)];
[sdc addObserver:self forKeyPath:@"values.AutoStartWebUI" options:0 context:(__bridge void *)(PreferencesContext)];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == (__bridge void *)(PreferencesContext)) {
[self userDefaultsDidChange:nil];
}
else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void)userDefaultsDidChange:(id)sender {
DLog(@"userDefaultsDidChange: %@", sender);
BOOL showInMenubar = [[NSUserDefaults standardUserDefaults] boolForKey:@"ShowInMenubar"];
if (showInMenubar != (statusItem != nil)) {
if (showInMenubar) {
statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
[statusItem setHighlightMode:YES];
[statusItem setMenu:statusMenu];
[statusItem setImage:[NSImage imageNamed:@"statusicon.png"]];
[statusItem setAlternateImage:[NSImage imageNamed:@"statusicon-inv.png"]];
}
else {
statusItem = nil;
}
}
}
- (IBAction)preferencesClicked:(id)sender {
[self showPreferences];
}
- (void)showPreferences {
[NSApp activateIgnoringOtherApps:TRUE];
if (preferencesDialog) {
return;
}
preferencesDialog = [[PreferencesDialog alloc] init];
[[preferencesDialog window] center];
[preferencesDialog showWindow:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(preferencesWillClose:)
name:NSWindowWillCloseNotification
object:[preferencesDialog window]];
}
- (void)preferencesWillClose:(NSNotification *)notification {
DLog(@"Pref Closed");
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSWindowWillCloseNotification
object:[preferencesDialog window]];
preferencesDialog = nil;
[self userDefaultsDidChange:nil];
}
- (IBAction)aboutClicked:(id)sender {
[NSApp activateIgnoringOtherApps:TRUE];
[NSApp orderFrontStandardAboutPanel:nil];
}
- (IBAction)quitClicked:(id)sender {
DLog(@"Quit");
[NSApp terminate:nil];
}
- (void)showWelcomeScreen {
welcomeDialog = [[WelcomeDialog alloc] init];
[(WelcomeDialog*)welcomeDialog setMainDelegate:self];
[(WelcomeDialog*)welcomeDialog showDialog];
}
- (void)showAlreadyRunning {
BOOL showInMenubar = [[NSUserDefaults standardUserDefaults] boolForKey:@"ShowInMenubar"];
[NSApp activateIgnoringOtherApps:TRUE];
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:NSLocalizedString(@"AlreadyRunning.MessageText", nil)];
[alert setInformativeText:NSLocalizedString(showInMenubar ? @"AlreadyRunning.InformativeTextWithIcon" : @"AlreadyRunning.InformativeTextWithoutIcon", nil)];
NSButton* webUIButton = [alert addButtonWithTitle:NSLocalizedString(@"AlreadyRunning.WebUI", nil)];
[alert addButtonWithTitle:NSLocalizedString(@"AlreadyRunning.Preferences", nil)];
[alert addButtonWithTitle:NSLocalizedString(@"AlreadyRunning.Quit", nil)];
[alert.window makeFirstResponder:webUIButton];
[webUIButton setKeyEquivalent:@"\r"];
switch ([alert runModal]) {
case NSAlertFirstButtonReturn:
[self showWebUI];
break;
case NSAlertSecondButtonReturn:
[self showPreferences];
break;
case NSAlertThirdButtonReturn:
[self quitClicked:nil];
break;
}
}
+ (BOOL)wasLaunchedByProcess:(NSString*)creator {
BOOL wasLaunchedByProcess = NO;
// Get our PSN
OSStatus err;
ProcessSerialNumber currPSN;
err = GetCurrentProcess (&currPSN);
if (!err) {
// We don't use ProcessInformationCopyDictionary() because the 'ParentPSN' item in the dictionary
// has endianness problems in 10.4, fixed in 10.5 however.
ProcessInfoRec procInfo;
bzero (&procInfo, sizeof (procInfo));
procInfo.processInfoLength = (UInt32)sizeof (ProcessInfoRec);
err = GetProcessInformation (&currPSN, &procInfo);
if (!err) {
ProcessSerialNumber parentPSN = procInfo.processLauncher;
// Get info on the launching process
NSDictionary* parentDict = (__bridge NSDictionary*)ProcessInformationCopyDictionary (&parentPSN, kProcessDictionaryIncludeAllInformationMask);
// Test the creator code of the launching app
if (parentDict) {
wasLaunchedByProcess = [[parentDict objectForKey:@"FileCreator"] isEqualToString:creator];
}
}
}
return wasLaunchedByProcess;
}
+ (BOOL)wasLaunchedAsLoginItem {
// If the launching process was 'loginwindow', we were launched as a login item
return [self wasLaunchedByProcess:@"lgnw"];
}
- (IBAction)webuiClicked:(id)sender {
if (daemonController.connected) {
[self showWebUI];
} else {
[NSApp activateIgnoringOtherApps:TRUE];
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:NSLocalizedString(@"ShowWebUINoConnection.MessageText", nil)];
[alert setInformativeText:NSLocalizedString(@"ShowWebUINoConnection.InformativeText", nil)];
[alert setAlertStyle:NSWarningAlertStyle];
[alert runModal];
}
}
- (void)showWebUI {
DLog(@"showWebUI");
[[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString:daemonController.browserUrl]];
}
- (IBAction)openConfigInTextEditClicked:(id)sender {
NSString *configFile = [daemonController configFilePath];
[[NSWorkspace sharedWorkspace] openFile:configFile withApplication:@"TextEdit"];
}
- (IBAction)restartClicked:(id)sender {
if (sender == factoryResetItem) {
[NSApp activateIgnoringOtherApps:TRUE];
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:NSLocalizedString(@"FactoryReset.MessageText", nil)];
[alert setInformativeText:NSLocalizedString(@"FactoryReset.InformativeText", nil)];
[alert setAlertStyle:NSCriticalAlertStyle];
NSButton* cancelButton = [alert addButtonWithTitle:NSLocalizedString(@"FactoryReset.Cancel", nil)];
// we use middle invisible button to align the third RESET-button at left side
[[alert addButtonWithTitle:@"Cancel"] setHidden:YES];
[alert addButtonWithTitle:NSLocalizedString(@"FactoryReset.Reset", nil)];
[alert.window makeFirstResponder:cancelButton];
[cancelButton setKeyEquivalent:@"\E"];
if ([alert runModal] != NSAlertThirdButtonReturn) {
return;
}
}
restarting = YES;
resetting = sender == factoryResetItem;
[self updateStatus];
[daemonController restartInRecoveryMode: sender == restartRecoveryItem withFactoryReset: sender == factoryResetItem];
daemonController.updateInterval = START_UPDATE_INTERVAL;
restartTimer = [NSTimer timerWithTimeInterval:10.000 target:self selector:@selector(restartFailed) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:restartTimer forMode:NSRunLoopCommonModes];
}
- (void)welcomeContinue {
DLog(@"welcomeContinue");
BOOL autoStartWebUI = [[NSUserDefaults standardUserDefaults] boolForKey:@"AutoStartWebUI"];
if (autoStartWebUI) {
if (daemonController.connected) {
if (daemonController.updateInterval == START_UPDATE_INTERVAL)
{
daemonController.updateInterval = NORMAL_UPDATE_INTERVAL;
}
[self showWebUI];
} else {
// try again in 100 msec for max. 25 seconds, then give up
connectionAttempts++;
if (connectionAttempts < 250) {
[self performSelector:@selector(welcomeContinue) withObject:nil afterDelay: 0.100];
} else {
// show error message
[self webuiClicked:nil];
}
}
}
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
DLog(@"Stop Daemon");
[daemonController stop];
}
- (void)menuWillOpen:(NSMenu *)menu {
DLog(@"menuWillOpen");
daemonController.updateInterval = MENUOPEN_UPDATE_INTERVAL;
}
- (void)menuDidClose:(NSMenu *)menu {
DLog(@"menuDidClose");
daemonController.updateInterval = NORMAL_UPDATE_INTERVAL;
}
- (IBAction)infoLinkClicked:(id)sender {
NSString *url;
if (sender == homePageItem) {
url = NSLocalizedString(@"Menu.LinkHomePage", nil);
}
else if (sender == downloadsItem) {
url = NSLocalizedString(@"Menu.LinkDownloads", nil);
}
else if (sender == forumItem) {
url = NSLocalizedString(@"Menu.LinkForum", nil);
}
[[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString:url]];
}
- (void)restartFailed {
if (restarting) {
restarting = NO;
resetting = NO;
daemonController.updateInterval = NORMAL_UPDATE_INTERVAL;
[NSApp activateIgnoringOtherApps:TRUE];
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:NSLocalizedString(@"RestartNoConnection.MessageText", nil)];
[alert setInformativeText:NSLocalizedString(@"RestartNoConnection.InformativeText", nil)];
[alert setAlertStyle:NSWarningAlertStyle];
[alert runModal];
}
}
- (IBAction)showInFinderClicked:(id)sender {
NSString* dir = nil;
NSString* option = nil;
if (sender == destDirItem) {
option = @"DestDir";
} else if (sender == interDirItem) {
option = @"InterDir";
} else if (sender == nzbDirItem) {
option = @"NzbDir";
} else if (sender == scriptDirItem) {
option = @"ScriptDir";
} else if (sender == logFileItem) {
option = @"LogFile";
} else if (sender == configFileItem) {
dir = [daemonController configFilePath];
} else if ([categoryItems containsObject:sender]) {
int index = [categoryItems indexOfObject:sender];
dir = [categoryDirs objectAtIndex:index];
} else {
return;
}
if (dir == nil) {
NSString* mainDir = [[daemonController valueForOption:@"MainDir"] stringByExpandingTildeInPath];
dir = [[daemonController valueForOption:option] stringByExpandingTildeInPath];
dir = [dir stringByReplacingOccurrencesOfString:@"${MainDir}"
withString:mainDir
options:NSCaseInsensitiveSearch
range:NSMakeRange(0, [dir length])];
}
if (dir.length == 0 || ![[NSFileManager defaultManager] fileExistsAtPath:dir]) {
[NSApp activateIgnoringOtherApps:TRUE];
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:[NSString stringWithFormat:NSLocalizedString(@"CantShowInFinder.MessageText", nil), dir]];
[alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(option == nil ? @"CantShowInFinder.InformativeTextForCategory" : @"CantShowInFinder.InformativeTextWithOption", nil), option]];
[alert runModal];
return;
}
[[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:@[[NSURL fileURLWithPath:dir isDirectory:YES]]];
}
- (void)daemonStatusUpdated {
if (restarting && daemonController.connected) {
restarting = NO;
[restartTimer invalidate];
[NSApp activateIgnoringOtherApps:TRUE];
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:NSLocalizedString(resetting ? @"FactoryResetted.MessageText" : daemonController.recoveryMode ? @"RestartedRecoveryMode.MessageText" : @"Restarted.MessageText", nil)];
[alert setInformativeText:NSLocalizedString(resetting ? @"FactoryResetted.InformativeText" : daemonController.recoveryMode ? @"RestartedRecoveryMode.InformativeText" : @"Restarted.InformativeText", nil)];
[alert setAlertStyle:NSInformationalAlertStyle];
[alert addButtonWithTitle:NSLocalizedString(@"Restarted.OK", nil)];
[alert addButtonWithTitle:NSLocalizedString(@"Restarted.WebUI", nil)];
if ([alert runModal] == NSAlertSecondButtonReturn) {
[self showWebUI];
}
resetting = NO;
} else {
[self updateStatus];
}
}
- (void)updateStatus {
//DLog(@"updateStatus");
NSString* info1 = @"";
NSString* info2 = nil;
NSDictionary* status = [daemonController status];
if (restarting || daemonController.restarting) {
info1 = NSLocalizedString(@"Status.Restarting", nil);
} else if (!daemonController.connected) {
info1 = NSLocalizedString(@"Status.NoConnection", nil);
} else if ([(NSNumber*)[status objectForKey:@"ServerStandBy"] integerValue] == 1) {
if ([(NSNumber*)[status objectForKey:@"PostJobCount"] integerValue] > 0) {
info1 = NSLocalizedString(@"Status.Post-Processing", nil);
}
else if ([(NSNumber*)[status objectForKey:@"UrlCount"] integerValue] > 0) {
info1 = NSLocalizedString(@"Status.Fetching NZBs", nil);
}
else if ([(NSNumber*)[status objectForKey:@"FeedActive"] integerValue] == 1) {
info1 = NSLocalizedString(@"Status.Fetching Feeds", nil);
}
else if ([(NSNumber*)[status objectForKey:@"DownloadPaused"] integerValue] == 1 ||
[(NSNumber*)[status objectForKey:@"Download2Paused"] integerValue] == 1) {
info1 = NSLocalizedString(@"Status.Paused", nil);
} else {
info1 = NSLocalizedString(@"Status.Idle", nil);
}
} else {
int speed = [(NSNumber*)[status objectForKey:@"DownloadRate"] integerValue];
info1 = [NSString stringWithFormat:NSLocalizedString(@"Status.Downloading", nil), speed / 1024];
if (speed > 0) {
long long remaining = ([(NSNumber*)[status objectForKey:@"RemainingSizeHi"] integerValue] << 32) + [(NSNumber*)[status objectForKey:@"RemainingSizeLo"] integerValue];
int secondsLeft = remaining / speed;
info2 = [NSString stringWithFormat:NSLocalizedString(@"Status.Left", nil), [self formatTimeLeft:secondsLeft]];
}
}
[info1Item setTitle:info1];
[info2Item setHidden:info2 == nil];
if (info2 != nil) {
[info2Item setTitle:info2];
}
}
- (NSString*)formatTimeLeft:(int)sec {
int days = floor(sec / 86400);
int hours = floor((sec % 86400) / 3600);
int minutes = floor((sec / 60) % 60);
int seconds = floor(sec % 60);
if (days > 10)
{
return [NSString stringWithFormat:NSLocalizedString(@"Left.Days", nil), days];
}
if (days > 0)
{
return [NSString stringWithFormat:NSLocalizedString(@"Left.Days.Hours", nil), days, hours];
}
if (hours > 10)
{
return [NSString stringWithFormat:NSLocalizedString(@"Left.Hours", nil), hours];
}
if (hours > 0)
{
return [NSString stringWithFormat:NSLocalizedString(@"Left.Hours.Minutes", nil), hours, minutes];
}
if (minutes > 10)
{
return [NSString stringWithFormat:NSLocalizedString(@"Left.Minutes", nil), minutes];
}
if (minutes > 0)
{
return [NSString stringWithFormat:NSLocalizedString(@"Left.Minutes.Seconds", nil), minutes, seconds];
}
return [NSString stringWithFormat:NSLocalizedString(@"Left.Seconds", nil), seconds];
}
- (void)daemonConfigLoaded {
DLog(@"config loaded");
[self updateCategoriesMenu];
}
- (void)updateCategoriesMenu {
NSMenu *submenu = destDirItem.parentItem.submenu;
for (NSMenuItem* item in categoryItems) {
[submenu removeItem:item];
}
categoryItems = [NSMutableArray array];
categoryDirs = [NSMutableArray array];
NSString* mainDir = [[daemonController valueForOption:@"MainDir"] stringByExpandingTildeInPath];
NSString* destDir = [[daemonController valueForOption:@"DestDir"] stringByExpandingTildeInPath];
for (int i=1; ; i++) {
NSString* catName = [daemonController valueForOption:[NSString stringWithFormat:@"Category%i.Name", i]];
NSString* catDir = [daemonController valueForOption:[NSString stringWithFormat:@"Category%i.DestDir", i]];
if (catName.length == 0) {
break;
}
if (catDir.length == 0) {
catDir = [destDir stringByAppendingPathComponent:catName];
}
NSString* dir = [catDir stringByExpandingTildeInPath];
dir = [dir stringByReplacingOccurrencesOfString:@"${MainDir}"
withString:mainDir
options:NSCaseInsensitiveSearch
range:NSMakeRange(0, dir.length)];
dir = [dir stringByReplacingOccurrencesOfString:@"${DestDir}"
withString:destDir
options:NSCaseInsensitiveSearch
range:NSMakeRange(0, dir.length)];
NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[NSString stringWithFormat:@"Category%i: %@", i, catName]
action:@selector(showInFinderClicked:) keyEquivalent:@""];
[item setTarget:self];
[submenu insertItem:item atIndex:[submenu indexOfItem:destDirSeparator]];
[categoryItems addObject:item];
[categoryDirs addObject:dir];
}
}
@end

989
osx/MainApp.xib Normal file
View File

@@ -0,0 +1,989 @@
<?xml version="1.0" encoding="UTF-8"?>
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.10">
<data>
<int key="IBDocument.SystemTarget">1070</int>
<string key="IBDocument.SystemVersion">11G63</string>
<string key="IBDocument.InterfaceBuilderVersion">3084</string>
<string key="IBDocument.AppKitVersion">1138.51</string>
<string key="IBDocument.HIToolboxVersion">569.00</string>
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="NS.object.0">3084</string>
</object>
<object class="NSArray" key="IBDocument.IntegratedClassDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>NSCustomObject</string>
<string>NSMenu</string>
<string>NSMenuItem</string>
<string>NSUserDefaultsController</string>
</object>
<object class="NSArray" key="IBDocument.PluginDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
</object>
<object class="NSMutableDictionary" key="IBDocument.Metadata">
<string key="NS.key.0">PluginDependencyRecalculationVersion</string>
<integer value="1" key="NS.object.0"/>
</object>
<object class="NSMutableArray" key="IBDocument.RootObjects" id="1048">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSCustomObject" id="1021">
<string key="NSClassName">NSApplication</string>
</object>
<object class="NSCustomObject" id="1014">
<string key="NSClassName">FirstResponder</string>
</object>
<object class="NSCustomObject" id="1050">
<string key="NSClassName">NSApplication</string>
</object>
<object class="NSCustomObject" id="163992474">
<string key="NSClassName">NSFontManager</string>
</object>
<object class="NSCustomObject" id="584004514">
<string key="NSClassName">MainApp</string>
</object>
<object class="NSMenu" id="1071488544">
<string key="NSTitle"/>
<object class="NSMutableArray" key="NSMenuItems">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSMenuItem" id="913321237">
<reference key="NSMenu" ref="1071488544"/>
<bool key="NSIsDisabled">YES</bool>
<string key="NSTitle">Starting</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<object class="NSCustomResource" key="NSOnImage" id="723905397">
<string key="NSClassName">NSImage</string>
<string key="NSResourceName">NSMenuCheckmark</string>
</object>
<object class="NSCustomResource" key="NSMixedImage" id="488763290">
<string key="NSClassName">NSImage</string>
<string key="NSResourceName">NSMenuMixedState</string>
</object>
</object>
<object class="NSMenuItem" id="687450617">
<reference key="NSMenu" ref="1071488544"/>
<bool key="NSIsDisabled">YES</bool>
<bool key="NSIsHidden">YES</bool>
<string key="NSTitle">Left</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="723905397"/>
<reference key="NSMixedImage" ref="488763290"/>
</object>
<object class="NSMenuItem" id="893657456">
<reference key="NSMenu" ref="1071488544"/>
<bool key="NSIsDisabled">YES</bool>
<bool key="NSIsSeparator">YES</bool>
<string key="NSTitle"/>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="723905397"/>
<reference key="NSMixedImage" ref="488763290"/>
</object>
<object class="NSMenuItem" id="332065098">
<reference key="NSMenu" ref="1071488544"/>
<string key="NSTitle">Show Web-Interface</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="723905397"/>
<reference key="NSMixedImage" ref="488763290"/>
</object>
<object class="NSMenuItem" id="984846223">
<reference key="NSMenu" ref="1071488544"/>
<string key="NSTitle">Show in Finder</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="723905397"/>
<reference key="NSMixedImage" ref="488763290"/>
<string key="NSAction">submenuAction:</string>
<object class="NSMenu" key="NSSubmenu" id="309075451">
<string key="NSTitle">Show in Finder</string>
<object class="NSMutableArray" key="NSMenuItems">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSMenuItem" id="19318460">
<reference key="NSMenu" ref="309075451"/>
<string key="NSTitle">Default Destination</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="723905397"/>
<reference key="NSMixedImage" ref="488763290"/>
</object>
<object class="NSMenuItem" id="945655763">
<reference key="NSMenu" ref="309075451"/>
<bool key="NSIsDisabled">YES</bool>
<bool key="NSIsSeparator">YES</bool>
<string key="NSTitle"/>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="723905397"/>
<reference key="NSMixedImage" ref="488763290"/>
</object>
<object class="NSMenuItem" id="833448292">
<reference key="NSMenu" ref="309075451"/>
<string key="NSTitle">Intermediate Files</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="723905397"/>
<reference key="NSMixedImage" ref="488763290"/>
</object>
<object class="NSMenuItem" id="374625387">
<reference key="NSMenu" ref="309075451"/>
<string key="NSTitle">Incoming NZBs</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="723905397"/>
<reference key="NSMixedImage" ref="488763290"/>
</object>
<object class="NSMenuItem" id="942754376">
<reference key="NSMenu" ref="309075451"/>
<string key="NSTitle">Post-Processing Scripts</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="723905397"/>
<reference key="NSMixedImage" ref="488763290"/>
</object>
<object class="NSMenuItem" id="831099071">
<reference key="NSMenu" ref="309075451"/>
<bool key="NSIsDisabled">YES</bool>
<bool key="NSIsSeparator">YES</bool>
<string key="NSTitle"/>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="723905397"/>
<reference key="NSMixedImage" ref="488763290"/>
</object>
<object class="NSMenuItem" id="163691210">
<reference key="NSMenu" ref="309075451"/>
<string key="NSTitle">Config-File</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="723905397"/>
<reference key="NSMixedImage" ref="488763290"/>
</object>
<object class="NSMenuItem" id="297452147">
<reference key="NSMenu" ref="309075451"/>
<string key="NSTitle">Log-File</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="723905397"/>
<reference key="NSMixedImage" ref="488763290"/>
</object>
</object>
</object>
</object>
<object class="NSMenuItem" id="750438089">
<reference key="NSMenu" ref="1071488544"/>
<string key="NSTitle">Info-Links</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="723905397"/>
<reference key="NSMixedImage" ref="488763290"/>
<string key="NSAction">submenuAction:</string>
<object class="NSMenu" key="NSSubmenu" id="322188981">
<string key="NSTitle">Info-Links</string>
<object class="NSMutableArray" key="NSMenuItems">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSMenuItem" id="812981948">
<reference key="NSMenu" ref="322188981"/>
<string key="NSTitle">Home Page</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="723905397"/>
<reference key="NSMixedImage" ref="488763290"/>
</object>
<object class="NSMenuItem" id="179994147">
<reference key="NSMenu" ref="322188981"/>
<string key="NSTitle">Downloads</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="723905397"/>
<reference key="NSMixedImage" ref="488763290"/>
</object>
<object class="NSMenuItem" id="100406801">
<reference key="NSMenu" ref="322188981"/>
<string key="NSTitle">Forum</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="723905397"/>
<reference key="NSMixedImage" ref="488763290"/>
</object>
</object>
</object>
</object>
<object class="NSMenuItem" id="22060917">
<reference key="NSMenu" ref="1071488544"/>
<string key="NSTitle">Troubleshooting</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="723905397"/>
<reference key="NSMixedImage" ref="488763290"/>
<string key="NSAction">submenuAction:</string>
<object class="NSMenu" key="NSSubmenu" id="987038733">
<string key="NSTitle">Troubleshooting</string>
<object class="NSMutableArray" key="NSMenuItems">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSMenuItem" id="305652885">
<reference key="NSMenu" ref="987038733"/>
<string key="NSTitle">Restart</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="723905397"/>
<reference key="NSMixedImage" ref="488763290"/>
</object>
<object class="NSMenuItem" id="298404849">
<reference key="NSMenu" ref="987038733"/>
<string key="NSTitle">Restart in Recovery Mode</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="723905397"/>
<reference key="NSMixedImage" ref="488763290"/>
</object>
<object class="NSMenuItem" id="846476020">
<reference key="NSMenu" ref="987038733"/>
<string key="NSTitle">Open Config in TextEdit</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="723905397"/>
<reference key="NSMixedImage" ref="488763290"/>
</object>
<object class="NSMenuItem" id="631055199">
<reference key="NSMenu" ref="987038733"/>
<bool key="NSIsDisabled">YES</bool>
<bool key="NSIsSeparator">YES</bool>
<string key="NSTitle"/>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="723905397"/>
<reference key="NSMixedImage" ref="488763290"/>
</object>
<object class="NSMenuItem" id="408663019">
<reference key="NSMenu" ref="987038733"/>
<string key="NSTitle">Reset to Factory Defaults</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="723905397"/>
<reference key="NSMixedImage" ref="488763290"/>
</object>
</object>
</object>
</object>
<object class="NSMenuItem" id="239974611">
<reference key="NSMenu" ref="1071488544"/>
<bool key="NSIsDisabled">YES</bool>
<bool key="NSIsSeparator">YES</bool>
<string key="NSTitle"/>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="723905397"/>
<reference key="NSMixedImage" ref="488763290"/>
</object>
<object class="NSMenuItem" id="7270580">
<reference key="NSMenu" ref="1071488544"/>
<string key="NSTitle">About NZBGet</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="723905397"/>
<reference key="NSMixedImage" ref="488763290"/>
</object>
<object class="NSMenuItem" id="845819171">
<reference key="NSMenu" ref="1071488544"/>
<string key="NSTitle">Preferences...</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="723905397"/>
<reference key="NSMixedImage" ref="488763290"/>
</object>
<object class="NSMenuItem" id="4615620">
<reference key="NSMenu" ref="1071488544"/>
<bool key="NSIsDisabled">YES</bool>
<bool key="NSIsSeparator">YES</bool>
<string key="NSTitle"/>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="723905397"/>
<reference key="NSMixedImage" ref="488763290"/>
</object>
<object class="NSMenuItem" id="667042401">
<reference key="NSMenu" ref="1071488544"/>
<string key="NSTitle">Quit NZBGet</string>
<string key="NSKeyEquiv"/>
<int key="NSMnemonicLoc">2147483647</int>
<reference key="NSOnImage" ref="723905397"/>
<reference key="NSMixedImage" ref="488763290"/>
</object>
</object>
</object>
<object class="NSUserDefaultsController" id="821481866">
<bool key="NSSharedInstance">YES</bool>
</object>
</object>
<object class="IBObjectContainer" key="IBDocument.Objects">
<object class="NSMutableArray" key="connectionRecords">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">delegate</string>
<reference key="source" ref="1021"/>
<reference key="destination" ref="584004514"/>
</object>
<int key="connectionID">824</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">statusMenu</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="1071488544"/>
</object>
<int key="connectionID">831</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">quitClicked:</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="667042401"/>
</object>
<int key="connectionID">832</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">preferencesClicked:</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="845819171"/>
</object>
<int key="connectionID">850</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">aboutClicked:</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="7270580"/>
</object>
<int key="connectionID">856</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">webuiClicked:</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="332065098"/>
</object>
<int key="connectionID">865</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">webuiItem</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="332065098"/>
</object>
<int key="connectionID">866</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">homePageItem</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="812981948"/>
</object>
<int key="connectionID">874</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">downloadsItem</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="179994147"/>
</object>
<int key="connectionID">875</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">forumItem</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="100406801"/>
</object>
<int key="connectionID">876</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">infoLinkClicked:</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="812981948"/>
</object>
<int key="connectionID">877</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">infoLinkClicked:</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="179994147"/>
</object>
<int key="connectionID">878</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">infoLinkClicked:</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="100406801"/>
</object>
<int key="connectionID">879</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">infoItem</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="913321237"/>
</object>
<int key="connectionID">893</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">restartRecoveryItem</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="298404849"/>
</object>
<int key="connectionID">896</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">destDirItem</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="19318460"/>
</object>
<int key="connectionID">911</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">interDirItem</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="833448292"/>
</object>
<int key="connectionID">912</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">nzbDirItem</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="374625387"/>
</object>
<int key="connectionID">913</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">scriptDirItem</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="942754376"/>
</object>
<int key="connectionID">915</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">showInFinderClicked:</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="19318460"/>
</object>
<int key="connectionID">916</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">showInFinderClicked:</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="833448292"/>
</object>
<int key="connectionID">917</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">showInFinderClicked:</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="374625387"/>
</object>
<int key="connectionID">918</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">showInFinderClicked:</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="942754376"/>
</object>
<int key="connectionID">919</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">showInFinderClicked:</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="163691210"/>
</object>
<int key="connectionID">922</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">info2Item</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="687450617"/>
</object>
<int key="connectionID">925</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">info1Item</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="913321237"/>
</object>
<int key="connectionID">926</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">destDirSeparator</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="945655763"/>
</object>
<int key="connectionID">927</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">showInFinderClicked:</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="297452147"/>
</object>
<int key="connectionID">929</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">configFileItem</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="163691210"/>
</object>
<int key="connectionID">930</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">logFileItem</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="297452147"/>
</object>
<int key="connectionID">931</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">restartClicked:</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="298404849"/>
</object>
<int key="connectionID">940</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">openConfigInTextEditClicked:</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="846476020"/>
</object>
<int key="connectionID">941</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">restartClicked:</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="305652885"/>
</object>
<int key="connectionID">943</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">restartClicked:</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="408663019"/>
</object>
<int key="connectionID">944</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">factoryResetItem</string>
<reference key="source" ref="584004514"/>
<reference key="destination" ref="408663019"/>
</object>
<int key="connectionID">945</int>
</object>
</object>
<object class="IBMutableOrderedSet" key="objectRecords">
<object class="NSArray" key="orderedObjects">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBObjectRecord">
<int key="objectID">0</int>
<object class="NSArray" key="object" id="0">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
<reference key="children" ref="1048"/>
<nil key="parent"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">-2</int>
<reference key="object" ref="1021"/>
<reference key="parent" ref="0"/>
<string key="objectName">File's Owner</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">-1</int>
<reference key="object" ref="1014"/>
<reference key="parent" ref="0"/>
<string key="objectName">First Responder</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">-3</int>
<reference key="object" ref="1050"/>
<reference key="parent" ref="0"/>
<string key="objectName">Application</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">373</int>
<reference key="object" ref="163992474"/>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">823</int>
<reference key="object" ref="584004514"/>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">827</int>
<reference key="object" ref="1071488544"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="667042401"/>
<reference ref="332065098"/>
<reference ref="893657456"/>
<reference ref="22060917"/>
<reference ref="239974611"/>
<reference ref="913321237"/>
<reference ref="7270580"/>
<reference ref="845819171"/>
<reference ref="4615620"/>
<reference ref="750438089"/>
<reference ref="984846223"/>
<reference ref="687450617"/>
</object>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">828</int>
<reference key="object" ref="667042401"/>
<reference key="parent" ref="1071488544"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">840</int>
<reference key="object" ref="821481866"/>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">849</int>
<reference key="object" ref="845819171"/>
<reference key="parent" ref="1071488544"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">851</int>
<reference key="object" ref="7270580"/>
<reference key="parent" ref="1071488544"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">860</int>
<reference key="object" ref="893657456"/>
<reference key="parent" ref="1071488544"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">861</int>
<reference key="object" ref="332065098"/>
<reference key="parent" ref="1071488544"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">867</int>
<reference key="object" ref="750438089"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="322188981"/>
</object>
<reference key="parent" ref="1071488544"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">868</int>
<reference key="object" ref="322188981"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="812981948"/>
<reference ref="100406801"/>
<reference ref="179994147"/>
</object>
<reference key="parent" ref="750438089"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">869</int>
<reference key="object" ref="812981948"/>
<reference key="parent" ref="322188981"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">870</int>
<reference key="object" ref="100406801"/>
<reference key="parent" ref="322188981"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">872</int>
<reference key="object" ref="179994147"/>
<reference key="parent" ref="322188981"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">880</int>
<reference key="object" ref="22060917"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="987038733"/>
</object>
<reference key="parent" ref="1071488544"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">881</int>
<reference key="object" ref="987038733"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="305652885"/>
<reference ref="846476020"/>
<reference ref="298404849"/>
<reference ref="631055199"/>
<reference ref="408663019"/>
</object>
<reference key="parent" ref="22060917"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">882</int>
<reference key="object" ref="305652885"/>
<reference key="parent" ref="987038733"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">885</int>
<reference key="object" ref="846476020"/>
<reference key="parent" ref="987038733"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">887</int>
<reference key="object" ref="298404849"/>
<reference key="parent" ref="987038733"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">889</int>
<reference key="object" ref="239974611"/>
<reference key="parent" ref="1071488544"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">890</int>
<reference key="object" ref="913321237"/>
<reference key="parent" ref="1071488544"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">897</int>
<reference key="object" ref="4615620"/>
<reference key="parent" ref="1071488544"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">898</int>
<reference key="object" ref="984846223"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="309075451"/>
</object>
<reference key="parent" ref="1071488544"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">899</int>
<reference key="object" ref="309075451"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="19318460"/>
<reference ref="942754376"/>
<reference ref="831099071"/>
<reference ref="945655763"/>
<reference ref="374625387"/>
<reference ref="833448292"/>
<reference ref="163691210"/>
<reference ref="297452147"/>
</object>
<reference key="parent" ref="984846223"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">900</int>
<reference key="object" ref="19318460"/>
<reference key="parent" ref="309075451"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">902</int>
<reference key="object" ref="833448292"/>
<reference key="parent" ref="309075451"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">906</int>
<reference key="object" ref="374625387"/>
<reference key="parent" ref="309075451"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">908</int>
<reference key="object" ref="942754376"/>
<reference key="parent" ref="309075451"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">910</int>
<reference key="object" ref="831099071"/>
<reference key="parent" ref="309075451"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">920</int>
<reference key="object" ref="945655763"/>
<reference key="parent" ref="309075451"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">921</int>
<reference key="object" ref="163691210"/>
<reference key="parent" ref="309075451"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">924</int>
<reference key="object" ref="687450617"/>
<reference key="parent" ref="1071488544"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">928</int>
<reference key="object" ref="297452147"/>
<reference key="parent" ref="309075451"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">934</int>
<reference key="object" ref="631055199"/>
<reference key="parent" ref="987038733"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">935</int>
<reference key="object" ref="408663019"/>
<reference key="parent" ref="987038733"/>
</object>
</object>
</object>
<object class="NSMutableDictionary" key="flattenedProperties">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>-1.IBPluginDependency</string>
<string>-2.IBPluginDependency</string>
<string>-3.IBPluginDependency</string>
<string>373.IBPluginDependency</string>
<string>823.IBPluginDependency</string>
<string>827.IBPluginDependency</string>
<string>828.IBPluginDependency</string>
<string>840.IBPluginDependency</string>
<string>849.IBPluginDependency</string>
<string>851.IBPluginDependency</string>
<string>860.IBPluginDependency</string>
<string>861.IBPluginDependency</string>
<string>867.IBPluginDependency</string>
<string>868.IBPluginDependency</string>
<string>869.IBPluginDependency</string>
<string>870.IBPluginDependency</string>
<string>872.IBPluginDependency</string>
<string>880.IBPluginDependency</string>
<string>881.IBPluginDependency</string>
<string>882.IBPluginDependency</string>
<string>885.IBPluginDependency</string>
<string>887.IBPluginDependency</string>
<string>889.IBPluginDependency</string>
<string>890.IBPluginDependency</string>
<string>897.IBPluginDependency</string>
<string>898.IBPluginDependency</string>
<string>899.IBPluginDependency</string>
<string>900.IBPluginDependency</string>
<string>902.IBPluginDependency</string>
<string>906.IBPluginDependency</string>
<string>908.IBPluginDependency</string>
<string>910.IBPluginDependency</string>
<string>920.IBPluginDependency</string>
<string>921.IBPluginDependency</string>
<string>924.IBPluginDependency</string>
<string>928.IBPluginDependency</string>
<string>934.IBPluginDependency</string>
<string>935.IBPluginDependency</string>
</object>
<object class="NSArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
</object>
</object>
<object class="NSMutableDictionary" key="unlocalizedProperties">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference key="dict.sortedKeys" ref="0"/>
<reference key="dict.values" ref="0"/>
</object>
<nil key="activeLocalization"/>
<object class="NSMutableDictionary" key="localizations">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference key="dict.sortedKeys" ref="0"/>
<reference key="dict.values" ref="0"/>
</object>
<nil key="sourceID"/>
<int key="maxID">945</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes"/>
<int key="IBDocument.localizationMode">0</int>
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencyDefaults">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string>
<real value="1070" key="NS.object.0"/>
</object>
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDevelopmentDependencies">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3</string>
<integer value="3000" key="NS.object.0"/>
</object>
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
<int key="IBDocument.defaultPropertyAccessControl">3</int>
<object class="NSMutableDictionary" key="IBDocument.LastKnownImageSizes">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>NSMenuCheckmark</string>
<string>NSMenuMixedState</string>
</object>
<object class="NSArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>{11, 11}</string>
<string>{10, 3}</string>
</object>
</object>
</data>
</archive>

34
osx/NZBGet-Info.plist Normal file
View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string>mainicon.icns</string>
<key>CFBundleIdentifier</key>
<string>net.sourceforge.nzbget</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>12.0-testing-r831M</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
<string>10.7</string>
<key>LSUIElement</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2007-2013 Andrey Prygunkov</string>
<key>NSMainNibFile</key>
<string>MainApp</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

View File

@@ -0,0 +1,409 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
F20FC6E217C6BAAE00C392AC /* PFMoveApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = F20FC6DF17C6B9FC00C392AC /* PFMoveApplication.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
F20FC6E417C6BC8D00C392AC /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F20FC6E317C6BC8D00C392AC /* Security.framework */; };
F21D369B13BF387F00E6D821 /* PreferencesDialog.m in Sources */ = {isa = PBXBuildFile; fileRef = F21D369A13BF387F00E6D821 /* PreferencesDialog.m */; };
F26CBA8B136DE86A00DCB596 /* MainApp.m in Sources */ = {isa = PBXBuildFile; fileRef = F26CBA8A136DE86A00DCB596 /* MainApp.m */; };
F26D959317C0E81800E58E5D /* Welcome.rtf in Resources */ = {isa = PBXBuildFile; fileRef = F26D959217C0E81800E58E5D /* Welcome.rtf */; };
F26D959517C0E86300E58E5D /* MainApp.xib in Resources */ = {isa = PBXBuildFile; fileRef = F26D959417C0E86300E58E5D /* MainApp.xib */; };
F26D959717C0E87E00E58E5D /* PreferencesDialog.xib in Resources */ = {isa = PBXBuildFile; fileRef = F26D959617C0E87E00E58E5D /* PreferencesDialog.xib */; };
F26D959917C0E88700E58E5D /* WelcomeDialog.xib in Resources */ = {isa = PBXBuildFile; fileRef = F26D959817C0E88700E58E5D /* WelcomeDialog.xib */; };
F26D959B17C0E89D00E58E5D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F26D959A17C0E89D00E58E5D /* Localizable.strings */; };
F29ABB2617BFC61B0023A423 /* DaemonController.m in Sources */ = {isa = PBXBuildFile; fileRef = F29ABB2417BFC03D0023A423 /* DaemonController.m */; };
F29ABB2B17C00E150023A423 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29B97324FDCFA39411CA2CEA /* AppKit.framework */; };
F29ABB2C17C00E190023A423 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 29B97325FDCFA39411CA2CEA /* Foundation.framework */; };
F2A55D3717C4CA9000D6FFE1 /* daemon in Resources */ = {isa = PBXBuildFile; fileRef = F2A55D3617C4CA9000D6FFE1 /* daemon */; };
F2A55D3B17C4CAF800D6FFE1 /* tools in Resources */ = {isa = PBXBuildFile; fileRef = F2A55D3A17C4CAF800D6FFE1 /* tools */; };
F2A6E11017C8E42300D910CB /* statusicon-inv@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F2A6E10E17C8E42300D910CB /* statusicon-inv@2x.png */; };
F2A6E11117C8E42300D910CB /* statusicon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F2A6E10F17C8E42300D910CB /* statusicon@2x.png */; };
F2BBD9C613E083160037473A /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = F2FC0F8B13BF595700D834E3 /* Credits.rtf */; };
F2CD856817C282080019D2CA /* RPC.m in Sources */ = {isa = PBXBuildFile; fileRef = F2CD856517C254A90019D2CA /* RPC.m */; };
F2CD856B17C282820019D2CA /* WebClient.m in Sources */ = {isa = PBXBuildFile; fileRef = F2CD856A17C282800019D2CA /* WebClient.m */; };
F2D2A2F113CBA680000824B4 /* WelcomeDialog.m in Sources */ = {isa = PBXBuildFile; fileRef = F2D2A2EF13CBA680000824B4 /* WelcomeDialog.m */; };
F2F9804A17C9081D004623D6 /* licenses in Resources */ = {isa = PBXBuildFile; fileRef = F2F9804917C9081D004623D6 /* licenses */; };
F2FCD73B13BB9CE900FC81F5 /* mainicon.icns in Resources */ = {isa = PBXBuildFile; fileRef = F2FCD73A13BB9CE900FC81F5 /* mainicon.icns */; };
F2FCD7D913BBDAED00FC81F5 /* statusicon.png in Resources */ = {isa = PBXBuildFile; fileRef = F2FCD7D813BBDAED00FC81F5 /* statusicon.png */; };
F2FCD84D13BCFD0900FC81F5 /* statusicon-inv.png in Resources */ = {isa = PBXBuildFile; fileRef = F2FCD84C13BCFD0900FC81F5 /* statusicon-inv.png */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; };
256AC3F00F4B6AF500CF3369 /* App_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = App_Prefix.pch; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; };
29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
8D1107310486CEB800E47090 /* NZBGet-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "NZBGet-Info.plist"; sourceTree = "<group>"; };
8D1107320486CEB800E47090 /* NZBGet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NZBGet.app; sourceTree = BUILT_PRODUCTS_DIR; };
F20FC6DE17C6B9FC00C392AC /* PFMoveApplication.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PFMoveApplication.h; sourceTree = "<group>"; };
F20FC6DF17C6B9FC00C392AC /* PFMoveApplication.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PFMoveApplication.m; sourceTree = "<group>"; };
F20FC6E317C6BC8D00C392AC /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/System/Library/Frameworks/Security.framework; sourceTree = DEVELOPER_DIR; };
F21D369913BF387F00E6D821 /* PreferencesDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PreferencesDialog.h; sourceTree = "<group>"; };
F21D369A13BF387F00E6D821 /* PreferencesDialog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PreferencesDialog.m; sourceTree = "<group>"; };
F26CBA89136DE86A00DCB596 /* MainApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MainApp.h; sourceTree = "<group>"; };
F26CBA8A136DE86A00DCB596 /* MainApp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MainApp.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
F26D959217C0E81800E58E5D /* Welcome.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; name = Welcome.rtf; path = Resources/Welcome.rtf; sourceTree = "<group>"; };
F26D959417C0E86300E58E5D /* MainApp.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainApp.xib; sourceTree = "<group>"; };
F26D959617C0E87E00E58E5D /* PreferencesDialog.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PreferencesDialog.xib; sourceTree = "<group>"; };
F26D959817C0E88700E58E5D /* WelcomeDialog.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = WelcomeDialog.xib; sourceTree = "<group>"; };
F26D959A17C0E89D00E58E5D /* Localizable.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = Localizable.strings; path = Resources/Localizable.strings; sourceTree = "<group>"; };
F29ABB2317BFC03D0023A423 /* DaemonController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DaemonController.h; sourceTree = "<group>"; };
F29ABB2417BFC03D0023A423 /* DaemonController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DaemonController.m; sourceTree = "<group>"; };
F2A55D3617C4CA9000D6FFE1 /* daemon */ = {isa = PBXFileReference; lastKnownFileType = folder; name = daemon; path = Resources/daemon; sourceTree = "<group>"; };
F2A55D3A17C4CAF800D6FFE1 /* tools */ = {isa = PBXFileReference; lastKnownFileType = folder; name = tools; path = Resources/tools; sourceTree = "<group>"; };
F2A6E10E17C8E42300D910CB /* statusicon-inv@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "statusicon-inv@2x.png"; path = "Images/statusicon-inv@2x.png"; sourceTree = "<group>"; };
F2A6E10F17C8E42300D910CB /* statusicon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "statusicon@2x.png"; path = "Images/statusicon@2x.png"; sourceTree = "<group>"; };
F2CD856417C254A90019D2CA /* RPC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RPC.h; sourceTree = "<group>"; };
F2CD856517C254A90019D2CA /* RPC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RPC.m; sourceTree = "<group>"; };
F2CD856917C282800019D2CA /* WebClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WebClient.h; sourceTree = "<group>"; };
F2CD856A17C282800019D2CA /* WebClient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WebClient.m; sourceTree = "<group>"; };
F2D2A2EE13CBA680000824B4 /* WelcomeDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WelcomeDialog.h; sourceTree = "<group>"; };
F2D2A2EF13CBA680000824B4 /* WelcomeDialog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WelcomeDialog.m; sourceTree = "<group>"; };
F2F9804917C9081D004623D6 /* licenses */ = {isa = PBXFileReference; lastKnownFileType = folder; name = licenses; path = Resources/licenses; sourceTree = "<group>"; };
F2FC0F8B13BF595700D834E3 /* Credits.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = Credits.rtf; path = Resources/Credits.rtf; sourceTree = "<group>"; };
F2FCD73A13BB9CE900FC81F5 /* mainicon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = mainicon.icns; path = Images/mainicon.icns; sourceTree = "<group>"; };
F2FCD7D813BBDAED00FC81F5 /* statusicon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = statusicon.png; path = Images/statusicon.png; sourceTree = "<group>"; };
F2FCD84C13BCFD0900FC81F5 /* statusicon-inv.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "statusicon-inv.png"; path = "Images/statusicon-inv.png"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
8D11072E0486CEB800E47090 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
F20FC6E417C6BC8D00C392AC /* Security.framework in Frameworks */,
8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */,
F29ABB2B17C00E150023A423 /* AppKit.framework in Frameworks */,
F29ABB2C17C00E190023A423 /* Foundation.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
19C28FACFE9D520D11CA2CBB /* Products */ = {
isa = PBXGroup;
children = (
8D1107320486CEB800E47090 /* NZBGet.app */,
);
name = Products;
sourceTree = "<group>";
};
29B97314FDCFA39411CA2CEA /* NZBGet */ = {
isa = PBXGroup;
children = (
F26CBA8C136DE87500DCB596 /* Main */,
F29ABB2517BFC0450023A423 /* DaemonController */,
F2D2A2F213CBA7C8000824B4 /* WelcomeDialog */,
F21D343013BE339300E6D821 /* Preferences */,
F20FC6E017C6BA0100C392AC /* LetsMove */,
29B97315FDCFA39411CA2CEA /* Other Sources */,
29B97317FDCFA39411CA2CEA /* Resources */,
29B97323FDCFA39411CA2CEA /* Frameworks */,
19C28FACFE9D520D11CA2CBB /* Products */,
);
name = "NZBGet";
sourceTree = "<group>";
};
29B97315FDCFA39411CA2CEA /* Other Sources */ = {
isa = PBXGroup;
children = (
256AC3F00F4B6AF500CF3369 /* App_Prefix.pch */,
);
name = "Other Sources";
sourceTree = "<group>";
};
29B97317FDCFA39411CA2CEA /* Resources */ = {
isa = PBXGroup;
children = (
F2F9804917C9081D004623D6 /* licenses */,
F2A55D3617C4CA9000D6FFE1 /* daemon */,
F2A55D3A17C4CAF800D6FFE1 /* tools */,
F2D370AE13C0859A002C0573 /* Images */,
F26D959A17C0E89D00E58E5D /* Localizable.strings */,
F26D959217C0E81800E58E5D /* Welcome.rtf */,
F2FC0F8B13BF595700D834E3 /* Credits.rtf */,
8D1107310486CEB800E47090 /* NZBGet-Info.plist */,
);
name = Resources;
sourceTree = "<group>";
};
29B97323FDCFA39411CA2CEA /* Frameworks */ = {
isa = PBXGroup;
children = (
29B97324FDCFA39411CA2CEA /* AppKit.framework */,
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */,
29B97325FDCFA39411CA2CEA /* Foundation.framework */,
F20FC6E317C6BC8D00C392AC /* Security.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
F20FC6E017C6BA0100C392AC /* LetsMove */ = {
isa = PBXGroup;
children = (
F20FC6DE17C6B9FC00C392AC /* PFMoveApplication.h */,
F20FC6DF17C6B9FC00C392AC /* PFMoveApplication.m */,
);
name = LetsMove;
sourceTree = "<group>";
};
F21D343013BE339300E6D821 /* Preferences */ = {
isa = PBXGroup;
children = (
F26D959617C0E87E00E58E5D /* PreferencesDialog.xib */,
F21D369913BF387F00E6D821 /* PreferencesDialog.h */,
F21D369A13BF387F00E6D821 /* PreferencesDialog.m */,
);
name = Preferences;
sourceTree = "<group>";
};
F26CBA8C136DE87500DCB596 /* Main */ = {
isa = PBXGroup;
children = (
F26D959417C0E86300E58E5D /* MainApp.xib */,
F26CBA89136DE86A00DCB596 /* MainApp.h */,
F26CBA8A136DE86A00DCB596 /* MainApp.m */,
);
name = Main;
sourceTree = "<group>";
};
F29ABB2517BFC0450023A423 /* DaemonController */ = {
isa = PBXGroup;
children = (
F2CD856917C282800019D2CA /* WebClient.h */,
F2CD856A17C282800019D2CA /* WebClient.m */,
F2CD856417C254A90019D2CA /* RPC.h */,
F2CD856517C254A90019D2CA /* RPC.m */,
F29ABB2317BFC03D0023A423 /* DaemonController.h */,
F29ABB2417BFC03D0023A423 /* DaemonController.m */,
);
name = DaemonController;
sourceTree = "<group>";
};
F2D2A2F213CBA7C8000824B4 /* WelcomeDialog */ = {
isa = PBXGroup;
children = (
F26D959817C0E88700E58E5D /* WelcomeDialog.xib */,
F2D2A2EE13CBA680000824B4 /* WelcomeDialog.h */,
F2D2A2EF13CBA680000824B4 /* WelcomeDialog.m */,
);
name = WelcomeDialog;
sourceTree = "<group>";
};
F2D370AE13C0859A002C0573 /* Images */ = {
isa = PBXGroup;
children = (
F2FCD73A13BB9CE900FC81F5 /* mainicon.icns */,
F2A6E10E17C8E42300D910CB /* statusicon-inv@2x.png */,
F2A6E10F17C8E42300D910CB /* statusicon@2x.png */,
F2FCD84C13BCFD0900FC81F5 /* statusicon-inv.png */,
F2FCD7D813BBDAED00FC81F5 /* statusicon.png */,
);
name = Images;
path = Resources;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
8D1107260486CEB800E47090 /* NZBGet */ = {
isa = PBXNativeTarget;
buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "NZBGet" */;
buildPhases = (
8D1107290486CEB800E47090 /* Resources */,
8D11072C0486CEB800E47090 /* Sources */,
8D11072E0486CEB800E47090 /* Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = NZBGet;
productInstallPath = "$(HOME)/Applications";
productName = "NZBGet";
productReference = 8D1107320486CEB800E47090 /* NZBGet.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
29B97313FDCFA39411CA2CEA /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0460;
ORGANIZATIONNAME = "Andrey Prygunkov";
};
buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "NZBGet" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 1;
knownRegions = (
English,
Japanese,
French,
German,
Russian,
de,
ru,
);
mainGroup = 29B97314FDCFA39411CA2CEA /* NZBGet */;
projectDirPath = "";
projectRoot = "";
targets = (
8D1107260486CEB800E47090 /* NZBGet */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
8D1107290486CEB800E47090 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
F2FCD73B13BB9CE900FC81F5 /* mainicon.icns in Resources */,
F2FCD7D913BBDAED00FC81F5 /* statusicon.png in Resources */,
F2FCD84D13BCFD0900FC81F5 /* statusicon-inv.png in Resources */,
F2BBD9C613E083160037473A /* Credits.rtf in Resources */,
F26D959317C0E81800E58E5D /* Welcome.rtf in Resources */,
F26D959517C0E86300E58E5D /* MainApp.xib in Resources */,
F26D959717C0E87E00E58E5D /* PreferencesDialog.xib in Resources */,
F26D959917C0E88700E58E5D /* WelcomeDialog.xib in Resources */,
F26D959B17C0E89D00E58E5D /* Localizable.strings in Resources */,
F2A55D3717C4CA9000D6FFE1 /* daemon in Resources */,
F2A55D3B17C4CAF800D6FFE1 /* tools in Resources */,
F2A6E11017C8E42300D910CB /* statusicon-inv@2x.png in Resources */,
F2A6E11117C8E42300D910CB /* statusicon@2x.png in Resources */,
F2F9804A17C9081D004623D6 /* licenses in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
8D11072C0486CEB800E47090 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
F26CBA8B136DE86A00DCB596 /* MainApp.m in Sources */,
F21D369B13BF387F00E6D821 /* PreferencesDialog.m in Sources */,
F2D2A2F113CBA680000824B4 /* WelcomeDialog.m in Sources */,
F29ABB2617BFC61B0023A423 /* DaemonController.m in Sources */,
F2CD856817C282080019D2CA /* RPC.m in Sources */,
F2CD856B17C282820019D2CA /* WebClient.m in Sources */,
F20FC6E217C6BAAE00C392AC /* PFMoveApplication.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
C01FCF4B08A954540054247B /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ENABLE_OBJC_ARC = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"\"$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/Frameworks/ApplicationServices.framework/Frameworks\"",
);
GCC_DYNAMIC_NO_PIC = NO;
GCC_MODEL_TUNING = G5;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = App_Prefix.pch;
"GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = DEBUG;
HEADER_SEARCH_PATHS = "";
INFOPLIST_FILE = "NZBGet-Info.plist";
INSTALL_PATH = "$(HOME)/Applications";
MACOSX_DEPLOYMENT_TARGET = 10.7;
OTHER_LDFLAGS = "";
PRODUCT_NAME = NZBGet;
SDKROOT = macosx10.7;
VALID_ARCHS = x86_64;
};
name = Debug;
};
C01FCF4C08A954540054247B /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ENABLE_OBJC_ARC = YES;
CODE_SIGN_IDENTITY = "";
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"\"$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/Frameworks/ApplicationServices.framework/Frameworks\"",
);
GCC_MODEL_TUNING = G5;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = App_Prefix.pch;
INFOPLIST_FILE = "NZBGet-Info.plist";
INSTALL_PATH = "$(HOME)/Applications";
MACOSX_DEPLOYMENT_TARGET = 10.7;
PRODUCT_NAME = NZBGet;
SDKROOT = macosx10.7;
VALID_ARCHS = x86_64;
};
name = Release;
};
C01FCF4F08A954540054247B /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_NAME = "";
SDKROOT = macosx10.6;
};
name = Debug;
};
C01FCF5008A954540054247B /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
CODE_SIGN_IDENTITY = "";
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
PRODUCT_NAME = "";
SDKROOT = macosx10.6;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "NZBGet" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C01FCF4B08A954540054247B /* Debug */,
C01FCF4C08A954540054247B /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
C01FCF4E08A954540054247B /* Build configuration list for PBXProject "NZBGet" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C01FCF4F08A954540054247B /* Debug */,
C01FCF5008A954540054247B /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 29B97313FDCFA39411CA2CEA /* Project object */;
}

11
osx/PFMoveApplication.h Normal file
View File

@@ -0,0 +1,11 @@
//
// PFMoveApplication.h, version 1.8
// LetsMove
//
// Created by Andy Kim at Potion Factory LLC on 9/17/09
//
// The contents of this file are dedicated to the public domain.
#import <Foundation/Foundation.h>
void PFMoveToApplicationsFolderIfNecessary(void);

460
osx/PFMoveApplication.m Normal file
View File

@@ -0,0 +1,460 @@
//
// PFMoveApplication.m, version 1.8
// LetsMove
//
// Created by Andy Kim at Potion Factory LLC on 9/17/09
//
// The contents of this file are dedicated to the public domain.
#import "PFMoveApplication.h"
// Andrey Prygunkov (NZBGet):
// all references to "NSString+SymlinksAndAliases.h" removed because
// it has a BSD like license which might not be compatible with GPLv2.
// This makes the function a little bit less robust not properly handle some rare cases.
//#import "NSString+SymlinksAndAliases.h"
#import <Security/Security.h>
#import <dlfcn.h>
// Strings
// These are macros to be able to use custom i18n tools
#define _I10NS(nsstr) NSLocalizedStringFromTable(nsstr, @"MoveApplication", nil)
#define kStrMoveApplicationCouldNotMove _I10NS(@"Could not move to Applications folder")
#define kStrMoveApplicationQuestionTitle _I10NS(@"Move to Applications folder?")
#define kStrMoveApplicationQuestionTitleHome _I10NS(@"Move to Applications folder in your Home folder?")
#define kStrMoveApplicationQuestionMessage _I10NS(@"NZBGet can move itself to the Applications folder if you'd like.")
#define kStrMoveApplicationButtonMove _I10NS(@"Move to Applications Folder")
#define kStrMoveApplicationButtonDoNotMove _I10NS(@"Do Not Move")
#define kStrMoveApplicationQuestionInfoWillRequirePasswd _I10NS(@"Note that this will require an administrator password.")
#define kStrMoveApplicationQuestionInfoInDownloadsFolder _I10NS(@"This will keep your Downloads folder uncluttered.")
// Needs to be defined for compiling under 10.4 SDK
#ifndef NSAppKitVersionNumber10_4
#define NSAppKitVersionNumber10_4 824
#endif
// Needs to be defined for compiling under 10.5 SDK
#ifndef NSAppKitVersionNumber10_5
#define NSAppKitVersionNumber10_5 949
#endif
// By default, we use a small control/font for the suppression button.
// If you prefer to use the system default (to match your other alerts),
// set this to 0.
#define PFUseSmallAlertSuppressCheckbox 1
static NSString *AlertSuppressKey = @"moveToApplicationsFolderAlertSuppress";
// Helper functions
static NSString *PreferredInstallLocation(BOOL *isUserDirectory);
static BOOL IsInApplicationsFolder(NSString *path);
static BOOL IsInDownloadsFolder(NSString *path);
static BOOL IsLaunchedFromDMG();
static BOOL Trash(NSString *path);
static BOOL AuthorizedInstall(NSString *srcPath, NSString *dstPath, BOOL *canceled);
static BOOL CopyBundle(NSString *srcPath, NSString *dstPath);
static void Relaunch();
// Main worker function
void PFMoveToApplicationsFolderIfNecessary(void) {
// Skip if user suppressed the alert before
if ([[NSUserDefaults standardUserDefaults] boolForKey:AlertSuppressKey]) return;
// Path of the bundle
NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
// Skip if the application is already in some Applications folder
if (IsInApplicationsFolder(bundlePath)) return;
// File Manager
NSFileManager *fm = [NSFileManager defaultManager];
BOOL isLaunchedFromDMG = IsLaunchedFromDMG();
// Since we are good to go, get the preferred installation directory.
BOOL installToUserApplications = NO;
NSString *applicationsDirectory = PreferredInstallLocation(&installToUserApplications);
NSString *bundleName = [bundlePath lastPathComponent];
NSString *destinationPath = [applicationsDirectory stringByAppendingPathComponent:bundleName];
// Check if we need admin password to write to the Applications directory
BOOL needAuthorization = ([fm isWritableFileAtPath:applicationsDirectory] == NO);
// Check if the destination bundle is already there but not writable
needAuthorization |= ([fm fileExistsAtPath:destinationPath] && ![fm isWritableFileAtPath:destinationPath]);
// Setup the alert
NSAlert *alert = [[[NSAlert alloc] init] autorelease];
{
NSString *informativeText = nil;
[alert setMessageText:(installToUserApplications ? kStrMoveApplicationQuestionTitleHome : kStrMoveApplicationQuestionTitle)];
informativeText = kStrMoveApplicationQuestionMessage;
if (needAuthorization) {
informativeText = [informativeText stringByAppendingString:@" "];
informativeText = [informativeText stringByAppendingString:kStrMoveApplicationQuestionInfoWillRequirePasswd];
}
else if (IsInDownloadsFolder(bundlePath)) {
// Don't mention this stuff if we need authentication. The informative text is long enough as it is in that case.
informativeText = [informativeText stringByAppendingString:@" "];
informativeText = [informativeText stringByAppendingString:kStrMoveApplicationQuestionInfoInDownloadsFolder];
}
[alert setInformativeText:informativeText];
// Add accept button
[alert addButtonWithTitle:kStrMoveApplicationButtonMove];
// Add deny button
NSButton *cancelButton = [alert addButtonWithTitle:kStrMoveApplicationButtonDoNotMove];
[cancelButton setKeyEquivalent:@"\e"];
#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4
if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_4) {
// Setup suppression button
[alert setShowsSuppressionButton:YES];
if (PFUseSmallAlertSuppressCheckbox) {
[[[alert suppressionButton] cell] setControlSize:NSSmallControlSize];
[[[alert suppressionButton] cell] setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
}
}
#endif
}
// Activate app -- work-around for focus issues related to "scary file from internet" OS dialog.
if (![NSApp isActive]) {
[NSApp activateIgnoringOtherApps:YES];
}
if ([alert runModal] == NSAlertFirstButtonReturn) {
DLog(@"INFO -- Moving myself to the Applications folder");
// Move
if (needAuthorization) {
BOOL authorizationCanceled;
if (!AuthorizedInstall(bundlePath, destinationPath, &authorizationCanceled)) {
if (authorizationCanceled) {
DLog(@"INFO -- Not moving because user canceled authorization");
return;
}
else {
DLog(@"ERROR -- Could not copy myself to /Applications with authorization");
goto fail;
}
}
}
else {
// If a copy already exists in the Applications folder, put it in the Trash
if ([fm fileExistsAtPath:destinationPath]) {
// But first, make sure that it's not running
BOOL destinationIsRunning = NO;
// Use the shell to determine if the app is already running on systems 10.5 or lower
if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_5) {
NSString *script = [NSString stringWithFormat:@"ps ax -o comm | grep '%@/' | grep -v grep >/dev/null", destinationPath];
NSTask *task = [NSTask launchedTaskWithLaunchPath:@"/bin/sh" arguments:[NSArray arrayWithObjects:@"-c", script, nil]];
[task waitUntilExit];
// If the task terminated with status 0, it means that the final grep produced 1 or more lines of output.
// Which means that the app is already running
destinationIsRunning = ([task terminationStatus] == 0);
}
// Use the new API on 10.6 or higher
else {
for (NSRunningApplication *runningApplication in [[NSWorkspace sharedWorkspace] runningApplications]) {
NSString *executablePath = [[runningApplication executableURL] path];
if ([executablePath hasPrefix:destinationPath]) {
destinationIsRunning = YES;
break;
}
}
}
if (destinationIsRunning) {
// Give the running app focus and terminate myself
DLog(@"INFO -- Switching to an already running version");
[[NSTask launchedTaskWithLaunchPath:@"/usr/bin/open" arguments:[NSArray arrayWithObject:destinationPath]] waitUntilExit];
exit(0);
}
else {
if (!Trash([applicationsDirectory stringByAppendingPathComponent:bundleName]))
goto fail;
}
}
if (!CopyBundle(bundlePath, destinationPath)) {
DLog(@"ERROR -- Could not copy myself to %@", destinationPath);
goto fail;
}
}
// Trash the original app. It's okay if this fails.
// NOTE: This final delete does not work if the source bundle is in a network mounted volume.
// Calling rm or file manager's delete method doesn't work either. It's unlikely to happen
// but it'd be great if someone could fix this.
if (!isLaunchedFromDMG && !Trash(bundlePath)) {
DLog(@"WARNING -- Could not delete application after moving it to Applications folder");
}
// Relaunch.
Relaunch(destinationPath);
}
else {
if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_4) {
// Save the alert suppress preference if checked
#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4
if ([[alert suppressionButton] state] == NSOnState) {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:AlertSuppressKey];
}
#endif
}
else {
// Always suppress after the first decline on 10.4 since there is no suppression checkbox
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:AlertSuppressKey];
}
}
return;
fail:
{
// Show failure message
alert = [[[NSAlert alloc] init] autorelease];
[alert setMessageText:kStrMoveApplicationCouldNotMove];
[alert runModal];
}
}
#pragma mark -
#pragma mark Helper Functions
static NSString *PreferredInstallLocation(BOOL *isUserDirectory) {
// Return the preferred install location.
// Assume that if the user has a ~/Applications folder, they'd prefer their
// applications to go there.
NSFileManager *fm = [NSFileManager defaultManager];
NSArray *userApplicationsDirs = NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSUserDomainMask, YES);
if ([userApplicationsDirs count] > 0) {
NSString *userApplicationsDir = [userApplicationsDirs objectAtIndex:0];
BOOL isDirectory;
if ([fm fileExistsAtPath:userApplicationsDir isDirectory:&isDirectory] && isDirectory) {
// User Applications directory exists. Get the directory contents.
NSArray *contents = [fm contentsOfDirectoryAtPath:userApplicationsDir error:NULL];
// Check if there is at least one ".app" inside the directory.
for (NSString *contentsPath in contents) {
if ([[contentsPath pathExtension] isEqualToString:@"app"]) {
if (isUserDirectory) *isUserDirectory = YES;
//return [userApplicationsDir stringByResolvingSymlinksAndAliases];
return userApplicationsDir;
}
}
}
}
// No user Applications directory in use. Return the machine local Applications directory
if (isUserDirectory) *isUserDirectory = NO;
//return [[NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSLocalDomainMask, YES) lastObject] stringByResolvingSymlinksAndAliases];
return [NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSLocalDomainMask, YES) lastObject];
}
static BOOL IsInApplicationsFolder(NSString *path) {
// Check all the normal Application directories
NSEnumerator *e = [NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSAllDomainsMask, YES) objectEnumerator];
NSString *appDirPath = nil;
while ((appDirPath = [e nextObject])) {
if ([path hasPrefix:appDirPath]) return YES;
}
// Also, handle the case that the user has some other Application directory (perhaps on a separate data partition).
if ([[path pathComponents] containsObject:@"Applications"]) {
return YES;
}
return NO;
}
static BOOL IsInDownloadsFolder(NSString *path) {
#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4
// 10.5 or higher has NSDownloadsDirectory
if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_4) {
NSEnumerator *e = [NSSearchPathForDirectoriesInDomains(NSDownloadsDirectory, NSAllDomainsMask, YES) objectEnumerator];
NSString *downloadsDirPath = nil;
while ((downloadsDirPath = [e nextObject])) {
if ([path hasPrefix:downloadsDirPath]) return YES;
}
return NO;
}
#endif
// 10.4
return [[[path stringByDeletingLastPathComponent] lastPathComponent] isEqualToString:@"Downloads"];
}
static BOOL IsLaunchedFromDMG() {
// Guess if we have launched from a disk image
NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
NSFileManager *fm = [NSFileManager defaultManager];
BOOL bundlePathIsWritable = [fm isWritableFileAtPath:bundlePath];
return [bundlePath hasPrefix:@"/Volumes/"] && !bundlePathIsWritable;
}
static BOOL Trash(NSString *path) {
if ([[NSWorkspace sharedWorkspace] performFileOperation:NSWorkspaceRecycleOperation
source:[path stringByDeletingLastPathComponent]
destination:@""
files:[NSArray arrayWithObject:[path lastPathComponent]]
tag:NULL]) {
return YES;
}
else {
DLog(@"ERROR -- Could not trash '%@'", path);
return NO;
}
}
static BOOL AuthorizedInstall(NSString *srcPath, NSString *dstPath, BOOL *canceled) {
if (canceled) *canceled = NO;
// Make sure that the destination path is an app bundle. We're essentially running 'sudo rm -rf'
// so we really don't want to fuck this up.
if (![dstPath hasSuffix:@".app"]) return NO;
// Do some more checks
if ([[dstPath stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] length] == 0) return NO;
if ([[srcPath stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] length] == 0) return NO;
int pid, status;
AuthorizationRef myAuthorizationRef;
// Get the authorization
OSStatus err = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &myAuthorizationRef);
if (err != errAuthorizationSuccess) return NO;
AuthorizationItem myItems = {kAuthorizationRightExecute, 0, NULL, 0};
AuthorizationRights myRights = {1, &myItems};
AuthorizationFlags myFlags = kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights;
err = AuthorizationCopyRights(myAuthorizationRef, &myRights, NULL, myFlags, NULL);
if (err != errAuthorizationSuccess) {
if (err == errAuthorizationCanceled && canceled)
*canceled = YES;
goto fail;
}
static OSStatus (*security_AuthorizationExecuteWithPrivileges)(AuthorizationRef authorization, const char *pathToTool,
AuthorizationFlags options, char * const *arguments,
FILE **communicationsPipe) = NULL;
if (!security_AuthorizationExecuteWithPrivileges) {
// On 10.7, AuthorizationExecuteWithPrivileges is deprecated. We want to still use it since there's no
// good alternative (without requiring code signing). We'll look up the function through dyld and fail
// if it is no longer accessible. If Apple removes the function entirely this will fail gracefully. If
// they keep the function and throw some sort of exception, this won't fail gracefully, but that's a
// risk we'll have to take for now.
security_AuthorizationExecuteWithPrivileges = dlsym(RTLD_DEFAULT, "AuthorizationExecuteWithPrivileges");
}
if (!security_AuthorizationExecuteWithPrivileges) {
goto fail;
}
// Delete the destination
{
char *args[] = {"-rf", (char *)[dstPath fileSystemRepresentation], NULL};
err = security_AuthorizationExecuteWithPrivileges(myAuthorizationRef, "/bin/rm", kAuthorizationFlagDefaults, args, NULL);
if (err != errAuthorizationSuccess) goto fail;
// Wait until it's done
pid = wait(&status);
if (pid == -1 || !WIFEXITED(status)) goto fail; // We don't care about exit status as the destination most likely does not exist
}
// Copy
{
char *args[] = {"-pR", (char *)[srcPath fileSystemRepresentation], (char *)[dstPath fileSystemRepresentation], NULL};
err = security_AuthorizationExecuteWithPrivileges(myAuthorizationRef, "/bin/cp", kAuthorizationFlagDefaults, args, NULL);
if (err != errAuthorizationSuccess) goto fail;
// Wait until it's done
pid = wait(&status);
if (pid == -1 || !WIFEXITED(status) || WEXITSTATUS(status)) goto fail;
}
AuthorizationFree(myAuthorizationRef, kAuthorizationFlagDefaults);
return YES;
fail:
AuthorizationFree(myAuthorizationRef, kAuthorizationFlagDefaults);
return NO;
}
static BOOL CopyBundle(NSString *srcPath, NSString *dstPath) {
NSFileManager *fm = [NSFileManager defaultManager];
#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4
// 10.5 or higher
if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_4) {
NSError *error = nil;
if ([fm copyItemAtPath:srcPath toPath:dstPath error:&error]) {
return YES;
}
else {
DLog(@"ERROR -- Could not copy '%@' to '%@' (%@)", srcPath, dstPath, error);
}
}
#endif
#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4
if ([fm copyPath:srcPath toPath:dstPath handler:nil]) {
return YES;
}
else {
DLog(@"ERROR -- Could not copy '%@' to '%@'", srcPath, dstPath);
}
#endif
return NO;
}
static void Relaunch(NSString *destinationPath) {
// The shell script waits until the original app process terminates.
// This is done so that the relaunched app opens as the front-most app.
int pid = [[NSProcessInfo processInfo] processIdentifier];
// Command run just before running open /final/path
NSString *preOpenCmd = @"";
// OS X >=10.5:
// Before we launch the new app, clear xattr:com.apple.quarantine to avoid
// duplicate "scary file from the internet" dialog.
#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4
if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_5) {
// Add the -r flag on 10.6
preOpenCmd = [NSString stringWithFormat:@"/usr/bin/xattr -d -r com.apple.quarantine '%@';", destinationPath];
}
else if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_4) {
preOpenCmd = [NSString stringWithFormat:@"/usr/bin/xattr -d com.apple.quarantine '%@';", destinationPath];
}
#endif
NSString *script = [NSString stringWithFormat:@"(while [ `ps -p %d | wc -l` -gt 1 ]; do sleep 0.1; done; %@ open '%@') &", pid, preOpenCmd, destinationPath];
[NSTask launchedTaskWithLaunchPath:@"/bin/sh" arguments:[NSArray arrayWithObjects:@"-c", script, nil]];
// Launched from within a DMG? -- unmount (if no files are open after 5 seconds,
// otherwise leave it mounted).
if (IsLaunchedFromDMG()) {
script = [NSString stringWithFormat:@"(sleep 5 && hdiutil detach '%@') &", [[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent]];
[NSTask launchedTaskWithLaunchPath:@"/bin/sh" arguments:[NSArray arrayWithObjects:@"-c", script, nil]];
}
exit(0);
}

37
osx/PreferencesDialog.h Normal file
View File

@@ -0,0 +1,37 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
*/
#import <Cocoa/Cocoa.h>
@interface PreferencesDialog : NSWindowController {
IBOutlet NSButton *autostartButton;
IBOutlet NSButton *showStatusIconButton;
IBOutlet NSTextField *generalText;
IBOutlet NSTextField *appearanceText;
IBOutlet NSButton *autoShowWebUI;
}
- (IBAction)autostartButtonClicked:(id)sender;
@end

116
osx/PreferencesDialog.m Normal file
View File

@@ -0,0 +1,116 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
*/
#import "PreferencesDialog.h"
@implementation PreferencesDialog
- (id)init {
return [super initWithWindowNibName:@"PreferencesDialog"];
}
- (void)windowDidLoad {
}
- (void)enableLoginItemWithLoginItemsReference:(LSSharedFileListRef )theLoginItemsRefs ForPath:(NSString *)appPath {
// We call LSSharedFileListInsertItemURL to insert the item at the bottom of Login Items list.
CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:appPath];
LSSharedFileListItemRef item = LSSharedFileListInsertItemURL(theLoginItemsRefs, kLSSharedFileListItemLast, NULL, NULL, url, NULL, NULL);
if (item)
CFRelease(item);
}
- (void)disableLoginItemWithLoginItemsReference:(LSSharedFileListRef )theLoginItemsRefs ForPath:(NSString *)appPath {
UInt32 seedValue;
CFURLRef thePath;
// We're going to grab the contents of the shared file list (LSSharedFileListItemRef objects)
// and pop it in an array so we can iterate through it to find our item.
CFArrayRef loginItemsArray = LSSharedFileListCopySnapshot(theLoginItemsRefs, &seedValue);
for (id item in (__bridge NSArray *)loginItemsArray) {
LSSharedFileListItemRef itemRef = (__bridge LSSharedFileListItemRef)item;
if (LSSharedFileListItemResolve(itemRef, 0, (CFURLRef*) &thePath, NULL) == noErr) {
if ([[(__bridge NSURL *)thePath path] hasPrefix:appPath]) {
LSSharedFileListItemRemove(theLoginItemsRefs, itemRef); // Deleting the item
}
// Docs for LSSharedFileListItemResolve say we're responsible
// for releasing the CFURLRef that is returned
CFRelease(thePath);
}
}
CFRelease(loginItemsArray);
}
- (BOOL)loginItemExistsWithLoginItemReference:(LSSharedFileListRef)theLoginItemsRefs ForPath:(NSString *)appPath {
BOOL found = NO;
UInt32 seedValue;
CFURLRef thePath;
// We're going to grab the contents of the shared file list (LSSharedFileListItemRef objects)
// and pop it in an array so we can iterate through it to find our item.
CFArrayRef loginItemsArray = LSSharedFileListCopySnapshot(theLoginItemsRefs, &seedValue);
for (id item in (__bridge NSArray *)loginItemsArray) {
LSSharedFileListItemRef itemRef = (__bridge LSSharedFileListItemRef)item;
if (LSSharedFileListItemResolve(itemRef, 0, (CFURLRef*) &thePath, NULL) == noErr) {
if ([[(__bridge NSURL *)thePath path] hasPrefix:appPath]) {
found = YES;
break;
}
// Docs for LSSharedFileListItemResolve say we're responsible
// for releasing the CFURLRef that is returned
CFRelease(thePath);
}
}
CFRelease(loginItemsArray);
return found;
}
- (void)awakeFromNib {
// This will retrieve the path for the application
// For example, /Applications/test.app
NSString * appPath = [[NSBundle mainBundle] bundlePath];
LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
if ([self loginItemExistsWithLoginItemReference:loginItems ForPath:appPath]) {
[autostartButton setState:NSOnState];
}
CFRelease(loginItems);
}
- (IBAction)autostartButtonClicked:(id)sender {
NSString * appPath = [[NSBundle mainBundle] bundlePath];
// Create a reference to the shared file list.
LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
if (loginItems) {
if ([sender state] == NSOnState)
[self enableLoginItemWithLoginItemsReference:loginItems ForPath:appPath];
else
[self disableLoginItemWithLoginItemsReference:loginItems ForPath:appPath];
}
CFRelease(loginItems);
}
@end

623
osx/PreferencesDialog.xib Normal file
View File

@@ -0,0 +1,623 @@
<?xml version="1.0" encoding="UTF-8"?>
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.10">
<data>
<int key="IBDocument.SystemTarget">1070</int>
<string key="IBDocument.SystemVersion">11G63</string>
<string key="IBDocument.InterfaceBuilderVersion">3084</string>
<string key="IBDocument.AppKitVersion">1138.51</string>
<string key="IBDocument.HIToolboxVersion">569.00</string>
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="NS.object.0">3084</string>
</object>
<object class="NSArray" key="IBDocument.IntegratedClassDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>NSButton</string>
<string>NSButtonCell</string>
<string>NSCustomObject</string>
<string>NSImageCell</string>
<string>NSImageView</string>
<string>NSTextField</string>
<string>NSTextFieldCell</string>
<string>NSUserDefaultsController</string>
<string>NSView</string>
<string>NSWindowTemplate</string>
</object>
<object class="NSArray" key="IBDocument.PluginDependencies">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
</object>
<object class="NSMutableDictionary" key="IBDocument.Metadata">
<string key="NS.key.0">PluginDependencyRecalculationVersion</string>
<integer value="1" key="NS.object.0"/>
</object>
<object class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSCustomObject" id="1001">
<string key="NSClassName">PreferencesDialog</string>
</object>
<object class="NSCustomObject" id="1003">
<string key="NSClassName">FirstResponder</string>
</object>
<object class="NSCustomObject" id="1004">
<string key="NSClassName">NSApplication</string>
</object>
<object class="NSWindowTemplate" id="1005">
<int key="NSWindowStyleMask">3</int>
<int key="NSWindowBacking">2</int>
<string key="NSWindowRect">{{196, 436}, {357, 155}}</string>
<int key="NSWTFlags">544735232</int>
<string key="NSWindowTitle">NZBGet Preferences</string>
<string key="NSWindowClass">NSWindow</string>
<nil key="NSViewClass"/>
<nil key="NSUserInterfaceItemIdentifier"/>
<object class="NSView" key="NSWindowView" id="1006">
<reference key="NSNextResponder"/>
<int key="NSvFlags">256</int>
<object class="NSMutableArray" key="NSSubviews">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSButton" id="668723838">
<reference key="NSNextResponder" ref="1006"/>
<int key="NSvFlags">268</int>
<string key="NSFrame">{{67, 119}, {213, 18}}</string>
<reference key="NSSuperview" ref="1006"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="445769"/>
<bool key="NSEnabled">YES</bool>
<object class="NSButtonCell" key="NSCell" id="474631304">
<int key="NSCellFlags">67239424</int>
<int key="NSCellFlags2">0</int>
<string key="NSContents">Start at login</string>
<object class="NSFont" key="NSSupport" id="1028951332">
<string key="NSName">LucidaGrande</string>
<double key="NSSize">13</double>
<int key="NSfFlags">1044</int>
</object>
<reference key="NSControlView" ref="668723838"/>
<int key="NSButtonFlags">1211912703</int>
<int key="NSButtonFlags2">2</int>
<object class="NSCustomResource" key="NSNormalImage" id="167410980">
<string key="NSClassName">NSImage</string>
<string key="NSResourceName">NSSwitch</string>
</object>
<object class="NSButtonImageSource" key="NSAlternateImage" id="341711700">
<string key="NSImageName">NSSwitch</string>
</object>
<string key="NSAlternateContents"/>
<string key="NSKeyEquivalent"/>
<int key="NSPeriodicDelay">200</int>
<int key="NSPeriodicInterval">25</int>
</object>
</object>
<object class="NSButton" id="445769">
<reference key="NSNextResponder" ref="1006"/>
<int key="NSvFlags">268</int>
<string key="NSFrame">{{67, 99}, {213, 18}}</string>
<reference key="NSSuperview" ref="1006"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="897618223"/>
<bool key="NSEnabled">YES</bool>
<object class="NSButtonCell" key="NSCell" id="741315823">
<int key="NSCellFlags">67239424</int>
<int key="NSCellFlags2">0</int>
<string key="NSContents">Show in menubar</string>
<reference key="NSSupport" ref="1028951332"/>
<reference key="NSControlView" ref="445769"/>
<int key="NSButtonFlags">1211912703</int>
<int key="NSButtonFlags2">2</int>
<reference key="NSNormalImage" ref="167410980"/>
<reference key="NSAlternateImage" ref="341711700"/>
<string key="NSAlternateContents"/>
<string key="NSKeyEquivalent"/>
<int key="NSPeriodicDelay">200</int>
<int key="NSPeriodicInterval">25</int>
</object>
</object>
<object class="NSButton" id="897618223">
<reference key="NSNextResponder" ref="1006"/>
<int key="NSvFlags">268</int>
<string key="NSFrame">{{67, 79}, {213, 18}}</string>
<reference key="NSSuperview" ref="1006"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="205669972"/>
<bool key="NSEnabled">YES</bool>
<object class="NSButtonCell" key="NSCell" id="395093705">
<int key="NSCellFlags">67239424</int>
<int key="NSCellFlags2">0</int>
<string key="NSContents">Show Web-Interface on start</string>
<reference key="NSSupport" ref="1028951332"/>
<reference key="NSControlView" ref="897618223"/>
<int key="NSButtonFlags">1211912703</int>
<int key="NSButtonFlags2">2</int>
<reference key="NSNormalImage" ref="167410980"/>
<reference key="NSAlternateImage" ref="341711700"/>
<string key="NSAlternateContents"/>
<string key="NSKeyEquivalent"/>
<int key="NSPeriodicDelay">200</int>
<int key="NSPeriodicInterval">25</int>
</object>
</object>
<object class="NSImageView" id="205669972">
<reference key="NSNextResponder" ref="1006"/>
<int key="NSvFlags">268</int>
<object class="NSMutableSet" key="NSDragTypes">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="set.sortedObjects">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>Apple PDF pasteboard type</string>
<string>Apple PICT pasteboard type</string>
<string>Apple PNG pasteboard type</string>
<string>NSFilenamesPboardType</string>
<string>NeXT Encapsulated PostScript v1.2 pasteboard type</string>
<string>NeXT TIFF v4.0 pasteboard type</string>
</object>
</object>
<string key="NSFrame">{{20, 30}, {32, 32}}</string>
<reference key="NSSuperview" ref="1006"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="371541245"/>
<int key="NSViewLayerContentsRedrawPolicy">2</int>
<bool key="NSEnabled">YES</bool>
<object class="NSImageCell" key="NSCell" id="15570902">
<int key="NSCellFlags">134348288</int>
<int key="NSCellFlags2">33554432</int>
<object class="NSCustomResource" key="NSContents">
<string key="NSClassName">NSImage</string>
<string key="NSResourceName">NSInfo</string>
</object>
<int key="NSAlign">0</int>
<int key="NSScale">3</int>
<int key="NSStyle">0</int>
<bool key="NSAnimates">NO</bool>
</object>
<bool key="NSEditable">YES</bool>
</object>
<object class="NSTextField" id="371541245">
<reference key="NSNextResponder" ref="1006"/>
<int key="NSvFlags">268</int>
<string key="NSFrame">{{66, 20}, {244, 42}}</string>
<reference key="NSSuperview" ref="1006"/>
<reference key="NSWindow"/>
<string key="NSReuseIdentifierKey">_NS:1535</string>
<bool key="NSEnabled">YES</bool>
<object class="NSTextFieldCell" key="NSCell" id="833412041">
<int key="NSCellFlags">67108864</int>
<int key="NSCellFlags2">4325376</int>
<string key="NSContents">Only OSX-specific options are located here. For all other options see Settings page in Web-Interface.</string>
<object class="NSFont" key="NSSupport">
<string key="NSName">LucidaGrande</string>
<double key="NSSize">11</double>
<int key="NSfFlags">3100</int>
</object>
<string key="NSCellIdentifier">_NS:1535</string>
<reference key="NSControlView" ref="371541245"/>
<object class="NSColor" key="NSBackgroundColor">
<int key="NSColorSpace">6</int>
<string key="NSCatalogName">System</string>
<string key="NSColorName">controlColor</string>
<object class="NSColor" key="NSColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MC42NjY2NjY2NjY3AA</bytes>
</object>
</object>
<object class="NSColor" key="NSTextColor">
<int key="NSColorSpace">6</int>
<string key="NSCatalogName">System</string>
<string key="NSColorName">controlTextColor</string>
<object class="NSColor" key="NSColor">
<int key="NSColorSpace">3</int>
<bytes key="NSWhite">MAA</bytes>
</object>
</object>
</object>
</object>
</object>
<string key="NSFrameSize">{357, 155}</string>
<reference key="NSSuperview"/>
<reference key="NSWindow"/>
<reference key="NSNextKeyView" ref="668723838"/>
</object>
<string key="NSScreenRect">{{0, 0}, {1440, 878}}</string>
<string key="NSMaxSize">{10000000000000, 10000000000000}</string>
<bool key="NSWindowIsRestorable">YES</bool>
</object>
<object class="NSUserDefaultsController" id="575306214">
<bool key="NSSharedInstance">YES</bool>
</object>
</object>
<object class="IBObjectContainer" key="IBDocument.Objects">
<object class="NSMutableArray" key="connectionRecords">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">window</string>
<reference key="source" ref="1001"/>
<reference key="destination" ref="1005"/>
</object>
<int key="connectionID">11</int>
</object>
<object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">autostartButtonClicked:</string>
<reference key="source" ref="1001"/>
<reference key="destination" ref="668723838"/>
</object>
<int key="connectionID">24</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">autostartButton</string>
<reference key="source" ref="1001"/>
<reference key="destination" ref="668723838"/>
</object>
<int key="connectionID">25</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">showStatusIconButton</string>
<reference key="source" ref="1001"/>
<reference key="destination" ref="445769"/>
</object>
<int key="connectionID">28</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">autoShowWebUI</string>
<reference key="source" ref="1001"/>
<reference key="destination" ref="897618223"/>
</object>
<int key="connectionID">32</int>
</object>
<object class="IBConnectionRecord">
<object class="IBOutletConnection" key="connection">
<string key="label">delegate</string>
<reference key="source" ref="1005"/>
<reference key="destination" ref="1001"/>
</object>
<int key="connectionID">12</int>
</object>
<object class="IBConnectionRecord">
<object class="IBBindingConnection" key="connection">
<string key="label">value: values.ShowInMenubar</string>
<reference key="source" ref="445769"/>
<reference key="destination" ref="575306214"/>
<object class="NSNibBindingConnector" key="connector">
<reference key="NSSource" ref="445769"/>
<reference key="NSDestination" ref="575306214"/>
<string key="NSLabel">value: values.ShowInMenubar</string>
<string key="NSBinding">value</string>
<string key="NSKeyPath">values.ShowInMenubar</string>
<object class="NSDictionary" key="NSOptions">
<string key="NS.key.0">NSValidatesImmediately</string>
<boolean value="YES" key="NS.object.0"/>
</object>
<int key="NSNibBindingConnectorVersion">2</int>
</object>
</object>
<int key="connectionID">23</int>
</object>
<object class="IBConnectionRecord">
<object class="IBBindingConnection" key="connection">
<string key="label">value: values.AutoStartWebUI</string>
<reference key="source" ref="897618223"/>
<reference key="destination" ref="575306214"/>
<object class="NSNibBindingConnector" key="connector">
<reference key="NSSource" ref="897618223"/>
<reference key="NSDestination" ref="575306214"/>
<string key="NSLabel">value: values.AutoStartWebUI</string>
<string key="NSBinding">value</string>
<string key="NSKeyPath">values.AutoStartWebUI</string>
<object class="NSDictionary" key="NSOptions">
<string key="NS.key.0">NSValidatesImmediately</string>
<boolean value="YES" key="NS.object.0"/>
</object>
<int key="NSNibBindingConnectorVersion">2</int>
</object>
</object>
<int key="connectionID">34</int>
</object>
</object>
<object class="IBMutableOrderedSet" key="objectRecords">
<object class="NSArray" key="orderedObjects">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBObjectRecord">
<int key="objectID">0</int>
<object class="NSArray" key="object" id="0">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
<reference key="children" ref="1000"/>
<nil key="parent"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">-2</int>
<reference key="object" ref="1001"/>
<reference key="parent" ref="0"/>
<string key="objectName">File's Owner</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">-1</int>
<reference key="object" ref="1003"/>
<reference key="parent" ref="0"/>
<string key="objectName">First Responder</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">-3</int>
<reference key="object" ref="1004"/>
<reference key="parent" ref="0"/>
<string key="objectName">Application</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">1</int>
<reference key="object" ref="1005"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="1006"/>
</object>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">2</int>
<reference key="object" ref="1006"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="668723838"/>
<reference ref="445769"/>
<reference ref="897618223"/>
<reference ref="371541245"/>
<reference ref="205669972"/>
</object>
<reference key="parent" ref="1005"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">3</int>
<reference key="object" ref="668723838"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="474631304"/>
</object>
<reference key="parent" ref="1006"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">4</int>
<reference key="object" ref="474631304"/>
<reference key="parent" ref="668723838"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">8</int>
<reference key="object" ref="445769"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="741315823"/>
</object>
<reference key="parent" ref="1006"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">9</int>
<reference key="object" ref="741315823"/>
<reference key="parent" ref="445769"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">14</int>
<reference key="object" ref="575306214"/>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">29</int>
<reference key="object" ref="897618223"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="395093705"/>
</object>
<reference key="parent" ref="1006"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">30</int>
<reference key="object" ref="395093705"/>
<reference key="parent" ref="897618223"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">35</int>
<reference key="object" ref="205669972"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="15570902"/>
</object>
<reference key="parent" ref="1006"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">36</int>
<reference key="object" ref="15570902"/>
<reference key="parent" ref="205669972"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">37</int>
<reference key="object" ref="371541245"/>
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="833412041"/>
</object>
<reference key="parent" ref="1006"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">38</int>
<reference key="object" ref="833412041"/>
<reference key="parent" ref="371541245"/>
</object>
</object>
</object>
<object class="NSMutableDictionary" key="flattenedProperties">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>-1.IBPluginDependency</string>
<string>-2.IBPluginDependency</string>
<string>-3.IBPluginDependency</string>
<string>1.IBPluginDependency</string>
<string>1.IBWindowTemplateEditedContentRect</string>
<string>1.NSWindowTemplate.visibleAtLaunch</string>
<string>14.IBPluginDependency</string>
<string>2.IBPluginDependency</string>
<string>2.IBUserGuides</string>
<string>29.IBPluginDependency</string>
<string>3.IBPluginDependency</string>
<string>30.IBPluginDependency</string>
<string>35.IBAttributePlaceholdersKey</string>
<string>35.IBPluginDependency</string>
<string>36.IBPluginDependency</string>
<string>37.IBPluginDependency</string>
<string>38.IBPluginDependency</string>
<string>4.IBPluginDependency</string>
<string>8.IBPluginDependency</string>
<string>9.IBPluginDependency</string>
</object>
<object class="NSArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>{{322, 782}, {298, 74}}</string>
<boolean value="NO"/>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<object class="NSMutableArray">
<bool key="EncodedWithXMLCoder">YES</bool>
</object>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<object class="NSMutableDictionary">
<string key="NS.key.0">AccessibilityDescription</string>
<object class="IBAccessibilityAttribute" key="NS.object.0">
<string key="name">AccessibilityDescription</string>
<reference key="object" ref="205669972"/>
<string key="accessibilityValue">information</string>
</object>
</object>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
</object>
</object>
<object class="NSMutableDictionary" key="unlocalizedProperties">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference key="dict.sortedKeys" ref="0"/>
<reference key="dict.values" ref="0"/>
</object>
<nil key="activeLocalization"/>
<object class="NSMutableDictionary" key="localizations">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference key="dict.sortedKeys" ref="0"/>
<reference key="dict.values" ref="0"/>
</object>
<nil key="sourceID"/>
<int key="maxID">38</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes">
<object class="NSMutableArray" key="referencedPartialClassDescriptions">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBPartialClassDescription">
<string key="className">PreferencesDialog</string>
<string key="superclassName">NSWindowController</string>
<object class="NSMutableDictionary" key="actions">
<string key="NS.key.0">autostartButtonClicked:</string>
<string key="NS.object.0">id</string>
</object>
<object class="NSMutableDictionary" key="actionInfosByName">
<string key="NS.key.0">autostartButtonClicked:</string>
<object class="IBActionInfo" key="NS.object.0">
<string key="name">autostartButtonClicked:</string>
<string key="candidateClassName">id</string>
</object>
</object>
<object class="NSMutableDictionary" key="outlets">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>appearanceText</string>
<string>autoShowWebUI</string>
<string>autostartButton</string>
<string>generalText</string>
<string>showStatusIconButton</string>
</object>
<object class="NSArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>NSTextField</string>
<string>NSButton</string>
<string>NSButton</string>
<string>NSTextField</string>
<string>NSButton</string>
</object>
</object>
<object class="NSMutableDictionary" key="toOneOutletInfosByName">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>appearanceText</string>
<string>autoShowWebUI</string>
<string>autostartButton</string>
<string>generalText</string>
<string>showStatusIconButton</string>
</object>
<object class="NSArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBToOneOutletInfo">
<string key="name">appearanceText</string>
<string key="candidateClassName">NSTextField</string>
</object>
<object class="IBToOneOutletInfo">
<string key="name">autoShowWebUI</string>
<string key="candidateClassName">NSButton</string>
</object>
<object class="IBToOneOutletInfo">
<string key="name">autostartButton</string>
<string key="candidateClassName">NSButton</string>
</object>
<object class="IBToOneOutletInfo">
<string key="name">generalText</string>
<string key="candidateClassName">NSTextField</string>
</object>
<object class="IBToOneOutletInfo">
<string key="name">showStatusIconButton</string>
<string key="candidateClassName">NSButton</string>
</object>
</object>
</object>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
<string key="majorKey">IBProjectSource</string>
<string key="minorKey">./Classes/PreferencesDialog.h</string>
</object>
</object>
</object>
</object>
<int key="IBDocument.localizationMode">0</int>
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencyDefaults">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string>
<real value="1070" key="NS.object.0"/>
</object>
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDevelopmentDependencies">
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3</string>
<integer value="3000" key="NS.object.0"/>
</object>
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
<int key="IBDocument.defaultPropertyAccessControl">3</int>
<object class="NSMutableDictionary" key="IBDocument.LastKnownImageSizes">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>NSInfo</string>
<string>NSSwitch</string>
</object>
<object class="NSArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>{32, 32}</string>
<string>{15, 15}</string>
</object>
</object>
</data>
</archive>

38
osx/RPC.h Normal file
View File

@@ -0,0 +1,38 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
*/
#import <Cocoa/Cocoa.h>
#import "WebClient.h"
@interface RPC : WebClient {
}
- (id)initWithMethod:(NSString*)method
receiver:(id)receiver
success:(SEL)successCallback
failure:(SEL)failureCallback;
+ (void)setRpcUrl:(NSString*)url;
@end

62
osx/RPC.m Normal file
View File

@@ -0,0 +1,62 @@
/*
* This file is part of nzbget
*
* Copyright (C) 2007-2013 Andrey Prygunkov <hugbug@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $Revision$
* $Date$
*
*/
#import <Cocoa/Cocoa.h>
#import "RPC.h"
@implementation RPC
NSString* rpcUrl;
- (id)initWithMethod:(NSString*)method
receiver:(id)receiver
success:(SEL)successCallback
failure:(SEL)failureCallback {
NSString* urlStr = [rpcUrl stringByAppendingString:method];
self = [super initWithURLString:urlStr receiver:receiver success:successCallback failure:failureCallback];
return self;
}
+ (void)setRpcUrl:(NSString*)url {
rpcUrl = url;
}
- (void)success {
NSError *error = nil;
id dataObj = [NSJSONSerialization
JSONObjectWithData:data
options:0
error:&error];
if (error || ![dataObj isKindOfClass:[NSDictionary class]]) {
/* JSON was malformed, act appropriately here */
failureCode = 999;
[self failure];
}
id result = [dataObj valueForKey:@"result"];
SuppressPerformSelectorLeakWarning([_receiver performSelector:_successCallback withObject:result];);
}
@end

17
osx/Resources/Credits.rtf Normal file
View File

@@ -0,0 +1,17 @@
{\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf510
{\fonttbl\f0\fnil\fcharset0 LucidaGrande;}
{\colortbl;\red255\green255\blue255;\red0\green0\blue255;}
\vieww10800\viewh8400\viewkind0
\deftab720
\pard\pardeftab720\sa260\qc
\f0\fs22 \cf0 Lightweight binary newsgrabber\
\pard\pardeftab720\qc
\cf0 NZBGet is free software; use it under the\
terms of the {\field{\*\fldinst{HYPERLINK "http://www.gnu.org/licenses/gpl-2.0.html"}}{\fldrslt GNU General Public License}}.\
\
The package also includes other software;\
see folder "licenses" inside the package.\
\
\pard\pardeftab720\qc
{\field{\*\fldinst{HYPERLINK "http://nzbget.sourceforge.net"}}{\fldrslt \cf2 \ul \ulc2 nzbget.sourceforge.net}}}

View File

Binary file not shown.

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