nzbget-16.4/0000755000175000017500000000000012630544544012560 5ustar andreasandreasnzbget-16.4/README0000644000175000017500000003712112630544544013444 0ustar andreasandreas===================================== NZBGet ReadMe ===================================== This is a short documentation. For more information please visit NZBGet home page at http://nzbget.net Contents -------- 1. About NZBGet 2. Supported OS 3. Prerequisites on POSIX 4. Installation on POSIX 5. Compiling on Windows 6. Configuration 7. Usage 8. Authors 9. Copyright 10. Contact ===================================== 1. About NZBGet ===================================== NZBGet is a binary newsgrabber, which downloads files from usenet based on information given in nzb-files. NZBGet can be used in standalone and in server/client modes. In standalone mode you pass a nzb-file as parameter in command-line, NZBGet downloads listed files and then exits. In server/client mode NZBGet runs as server in background. Then you use client to send requests to server. The sample requests are: download nzb-file, list files in queue, etc. There is also a built-in web-interface. The server has RPC-support and can be controlled from third party applications too. Standalone-tool, server and client are all contained in only one executable file "nzbget". The mode in which the program works depends on command-line parameters passed to the program. ===================================== 2. Supported OS ===================================== NZBGet is written in C++ and works on Windows, OS X, Linux and most POSIX-conform OS'es. Clients and servers running on different OS'es may communicate with each other. For example, you can use NZBGet as client on Windows to control your NZBGet-server running on Linux. The download-section of NZBGet web-site provides binary files for Windows, OS X and Linux. For most POSIX-systems you need to compile the program yourself. If you have downloaded binaries you can just jump to section "Configuration". ===================================== 3. Prerequisites on POSIX ===================================== NZBGet is developed on a linux-system, but it runs on other POSIX platforms. NZBGet absolutely needs the following libraries: - libstdc++ (usually part of compiler) - libxml2 (http://www.xmlsoft.org) And the following libraries are optional: - for curses-output-mode (enabled by default): - libcurses (usually part of commercial systems) or (better) - libncurses (http://invisible-island.net/ncurses) - for encrypted connections (TLS/SSL): - OpenSSL (http://www.openssl.org) or - GnuTLS (http://www.gnu.org/software/gnutls) - for gzip support in web-server and web-client (enabled by default): - zlib (http://www.zlib.net) All these libraries are included in modern POSIX distributions and should be available as installable packages. Please note that you also need the developer packages for these libraries too, they package names have often suffix "dev" or "devel". On other systems you may need to download the libraries at the given URLs and compile them (see hints below). ===================================== 4. Installation on POSIX ===================================== Installation from the source distribution archive (nzbget-VERSION.tar.gz): - untar the nzbget-source via tar -zxf nzbget-VERSION.tar.gz - change into nzbget-directory via cd nzbget-VERSION - configure it via ./configure (maybe you have to tell configure, where to find some libraries. ./configure --help is your friend! also see "Configure-options" later) - in a case you don't have root access or want to install the program in your home directory use the configure parameter --prefix, e. g.: ./configure --prefix ~/usr - compile it via make - to install system wide become root via: su - install it via: make install - install configuration files into /etc via: make install-conf (you can skip this step if you intend to store configuration files in a non-standard location) Configure-options ----------------- You may run configure with additional arguments: --disable-curses - to make without curses-support. Use this option if you can not use curses/ncurses. --disable-parcheck - to make without parcheck-support. Use this option if you have troubles when compiling par2-module. --with-tlslib=(OpenSSL, GnuTLS) - to select which TLS/SSL library should be used for encrypted server connections. --disable-tls - to make without TLS/SSL support. Use this option if you can not neither OpenSSL nor GnuTLS. --disable-gzip - to make without gzip support. Use this option if you can not use zlib. --enable-debug - to build in debug-mode, if you want to see and log debug-messages. Optional package: par-check --------------------------- NZBGet can check and repair downloaded files for you. For this purpose it uses library par2. For your convenience the source code of libpar2 is integrated into NZBGet’s source tree and is compiled automatically when you make NZBGet. In a case errors occur during this process the inclusion of par2-module can be disabled using configure option "--disable-parcheck": ./configure --disable-parcheck Optional package: curses ------------------------- For curses-outputmode you need ncurses or curses on your system. If you do not have one of them you can download and compile ncurses yourself. Following configure-parameters may be useful: --with-libcurses-includes=/path/to/curses/includes --with-libcurses-libraries=/path/to/curses/libraries If you are not able to use curses or ncurses or do not want them you can make the program without support for curses using option "--disable-curses": ./configure --disable-curses Optional package: TLS ------------------------- To enable encrypted server connections (TLS/SSL) you need to build the program with TLS/SSL support. NZBGet can use two libraries: OpenSSL or GnuTLS. Configure-script checks which library is installed and use it. If both are available it gives the precedence to OpenSSL. You may override that with the option --with-tlslib=(OpenSSL, GnuTLS). For example to build with GnuTLS: ./configure --with-tlslib= GnuTLS Following configure-parameters may be useful: --with-libtls-includess=/path/to/gnutls/includes --with-libtls-libraries=/path/to/gnutls/libraries --with-openssl-includess=/path/to/openssl/includes --with-openssl-libraries=/path/to/openssl/libraries If none of these libraries is available you can make the program without TLS/SSL support using option "--disable-tls": ./configure --disable-tls ===================================== 5. Compiling on Windows ===================================== NZBGet is developed using MS Visual C++ 2005. The project file and solution are provided. If you use MS Visual C++ 2005 Express you need to download and install Platform SDK. To compile the program with TLS/SSL support you need either OpenSSL or GnuTLS: - OpenSSL (http://www.openssl.org) or - GnuTLS (http://www.gnu.org/software/gnutls) Also required are: - Regex (http://gnuwin32.sourceforge.net/packages/regex.htm) - Zlib (http://gnuwin32.sourceforge.net/packages/zlib.htm) ===================================== 6. Configuration ===================================== NZBGet needs a configuration file. An example configuration file is provided in "nzbget.conf", which is installed into "/share/nzbget" (where depends on system configuration and configure options - typically "/usr/local", "/usr" or "/opt"). The installer adjusts the file according to your system paths. If you have performed the installation step "make install-conf" this file is already copied to "/etc" and NZBGet finds it automatically. If you install the program manually from a binary archive you have to copy the file from "/share/nzbget" to one of the locations listed below. Open the file in a text editor and modify it accodring to your needs. You need to set at least the option "MAINDIR" and one news server in configuration file. The file has comments on how to use each option. The program looks for configuration file in following standard locations (in this order): On POSIX systems: /nzbget.conf ~/.nzbget /etc/nzbget.conf /usr/etc/nzbget.conf /usr/local/etc/nzbget.conf /opt/etc/nzbget.conf On Windows: \nzbget.conf If you put the configuration file in other place, you can use command- line switch "-c " to point the program to correct location. In special cases you can run program without configuration file using switch "-n". You need to use switch "-o" to pass required configuration options via command-line. ===================================== 7. Usage ===================================== NZBGet can be used in either standalone mode which downloads a single file or as a server which is able to queue up numerous download requests. TIP for Windows users: NZBGet is controlled via various command line parameters. For easier using there is a simple shell script included in "nzbget-shell.bat". Start this script from Windows Explorer and you will be running a command shell with PATH adjusted to find NZBGet executable. Then you can type all commands without full path to nzbget.exe. Standalone mode: ---------------- nzbget Server mode: ------------ First start the nzbget-server: - in console mode: nzbget -s - or in daemon mode (POSIX only): nzbget -D - or as a service (Windowx only, firstly install the service with command "nzbget -install"): net start NZBGet To stop server use: nzbget -Q TIP for POSIX users: with included script "nzbgetd" you can use standard commands to control daemon: nzbgetd start nzbgetd stop etc. When NZBGet is started in console server mode it displays a message that it is ready to receive download requests. In daemon mode it doesn't print any messages to console since it runs in background. When the server is running it is possible to queue up downloads. This can be done either in terminal with "nzbget -A " or by uploading a nzb-file into server's monitor-directory (/nzb by default). To check the status of server start client and connect it to server: nzbget -C The client have three different (display) outputmodes, which you can select in configuration file (on client computer) or in command line. Try them: nzbget -o outputmode=log -C nzbget -o outputmode=color -C nzbget -o outputmode=curses -C To list files in server's queue: nzbget -L It prints something like: [1] nzbname\filename1.rar (50.00 MB) [2] nzbname\filename1.r01 (50.00 MB) [3] another-nzb\filename3.r01 (100.00 MB) [4] another-nzb\filename3.r02 (100.00 MB) This is the list of individual files listed within nzb-file. To print the list of nzb-files (without content) add G-modifier to the list command: [1] nzbname (4.56 GB) [2] another-nzb (4.20 GB) The numbers in square braces are ID's of files or groups in queue. They can be used in edit-command. For example to move file with ID 2 to the top of queue: nzbget -E T 2 or to pause files with IDs from 10 to 20: nzbget -E P 10-20 or to delete files from queue: nzbget -E D 3 10-15 20-21 16 The edit-command has also a group-mode which affects all files from the same nzb-file. You need to pass an ID of the group. For example to delete the whole group 1: nzbget -E G D 1 The switch "o" is useful to override options in configuration files. For example: nzbget -o reloadqueue=no -o dupecheck=no -o parcheck=yes -s or: nzbget -o createlog=no -C Running client & server on seperate machines: --------------------------------------------- Since nzbget communicates via TCP/IP it's possible to have a server running on one computer and adding downloads via a client on another computer. Do this by setting the "ControlIP" option in the nzbget.conf file to point to the IP of the server (default is localhost which means client and server runs on same computer) Security warning ---------------- 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 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 scripts, defined in configuration file. Example post-processing scripts are provided in directory "scripts". To use the scripts copy them into your local directory and set options , and . For information on writing your own post-processing scripts please visit NZBGet web site. Web-interface ------------- NZBGet has a built-in web-server providing the access to the program functions via web-interface. To activate web-interface set the option "WebDir" to the path with web-interface files. If you install using "make install-conf" as described above the option is set automatically. If you install using binary files you should check if the option is set correctly. To access web-interface from your web-browser use the server address and port defined in NZBGet configuration file in options "ControlIP" and "ControlPort". For example: http://localhost:6789/ For login credentials type username and the password defined by options "ControlUsername" (default "nzbget") and "ControlPassword" (default "tegbzn6789"). In a case your browser forget credentials, to prevent typing them each time, there is a workaround - use URL in the form: http://localhost:6789/username:password/ Please note, that in this case the password is saved in a bookmark or in browser history in plain text and is easy to find by persons having access to your computer. ===================================== 8. Authors ===================================== NZBGet is developed and maintained by Andrey Prygunkov (hugbug@users.sourceforge.net). The original project was initially created by Sven Henkel (sidddy@users.sourceforge.net) in 2004 and later developed by Bo Cordes Petersen (placebodk@users.sourceforge.net) until 2005. In 2007 the abandoned project was overtaken by Andrey Prygunkov. Since then the program has been completely rewritten. NZBGet distribution archive includes additional components written by other authors: Par2: Peter Brian Clements Par2 library API: Francois Lesueur Catch: Two Blue Cubes Ltd jQuery: John Resig The Dojo Foundation Bootstrap: Twitter, Inc Raphaël: Dmitry Baranovskiy Sencha Labs Elycharts: Void Labs s.n.c. iconSweets: Yummygum ===================================== 9. Copyright ===================================== 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. The complete content of license is provided in file COPYING. Additional exemption: compiling, linking, and/or using OpenSSL is allowed. ===================================== 10. Contact ===================================== If you encounter any problem, feel free to use the forum nzbget.net/forum or contact me at hugbug@users.sourceforge.net nzbget-16.4/ChangeLog0000644000175000017500000044671112630544544014347 0ustar andreasandreasnzbget-16.4: - fixed resource (socket) leak which may cause "too many open files" errors with a possible crash. nzbget-16.3: - fixed: certain downloads may fail due to a bug in the workaround implemented in v16.2. nzbget-16.2: - implemented workaround to deal with malformed news server responses, which may still contain useful data. nzbget-16.1: - fixed issues with reverse proxies; - fixed unpack failure on older AMD CPUs, when installed via universal Linux installer; - fixed: when the program was started from setup the default directories were created with wrong permission (Windows only). nzbget-16.0: - moved project hosting to GitHub: - moved source code repository from subversion to git; - updated POSIX makefile to generate revision number for testing releases; - updated Linux installer build script to work with git; - adjusted function "check for updates" in web-interface; - update-scripts for Linux installer and Windows setup fetch new versions from GitHub releases area; - cleaned up project root directory, removed many unneeded files which were required by GNU build tools; - added verification for setup authenticity during update on Linux and Windows; - improvements in Linux installer: - improved compatibility with android; - added support for paths with spaces in parameter "--destdir"; - auto-selecting "armhf"-architecture on ARM 64 bit systems (aarch64); - ignored nzbs are now added to history and processed by queue scripts: - when an nzb-file isn’t added to queue, for some reason, the file is now also added to history (in addition to messages printed to log); - on one hand that makes it easier to see errors (as history items instead of error log messages), on the other hand that provides more info for extension scripts and third-party programs; - for malformed nzb-files which cannot be parsed the status in history is "DELETE: SCAN"; - for duplicate files with exactly same content status "DELETE: COPY"; - for duplicate files having history items with status "GOOD" and for duplicate files having hidden history items with status "SUCCESS" which do not have any visible duplicates - status "DELETE: GOOD"; - new values for field "DeleteStatus" of API-Method "history": "GOOD", "COPY", "SCAN"; - new values for field "Status" of API-Method "history": "FAILURE/SCAN", "DELETED/COPY", "DELETED/GOOD" (they will also be passed to pp-scripts as NZBPP_STATUS); - new queue-script event "NZB_DELETED"; - new queue event "URL_COMPLETED", with possible details: "FAILURE", "SCAN_SKIPPED", "SCAN_FAILURE"; - improved quick filter (the search box at the top of the page): - now supporting OR, AND, NOT, parenthesis; - can search in specific fields; - can search in API-fields which are not shown in the table; - filters can be saved and loaded using new drop down menu; - new advanced duplicate par-check mode: - can be activated via option "ParScan=Dupe"; - the new mode is based on the fact that the same releases are posted to Usenet multiple times; - when an item requires repair but doesn't have enough par-blocks the par-checker can reuse parts downloaded in other duplicates of the same title; - that makes it sometimes possible to repair downloads which would fail if tried to repair individually; - the downloads must be identifiable as duplicates (same nzb name or same duplicate key); - when par-checker needs more par-blocks it goes through the history and scans the download directories of duplicates until it finds missing blocks; it's smart enough to abort the scanning if the files come from different releases (based on video file size); - adjusted option "HealthCheck"; when set to "Delete" and duplicate par-scan is active, the download is aborted only if the completion is below 10%; that's to avoid deletion of damaged downloads which can be potentially repaired using new par-check mode; - downloads which were repaired using the new mode or which have donated their blocks to repair other downloads are distinguishable in the history details dialog with new status "EXPAR: RECIPIENT" or "EXPAR: DONOR"; a tooltip on the status shows amount of par-blocks received/donated; - new field "ExParStatus" returned by API-method "history"; - to quickly find related history items use quick filter "-exparstatus:none" in history list; - when adding nzb-files via web-interface it's now possible to change name and other properties: - nzb-files in the upload-list are now clickable; - the click opens properties dialog where the name, password, duplicate key and duplicate score can be changed; - queue script activity is now indicated in web-interface: - items in download list may have new statuses "QS-QUEUED" (gray) or "QUEUE-SCRIPT" (green); - new values for field "Status" in API-method "listgroups": "QS_QUEUED", "QS_EXECUTING"; - the number of active (and queued) scripts are shown in the status dialog in web-interface; this new row is hidden if no scripts are queued; - active queue scripts account for activity indicator in web-interface (rotating button); - new field "QueueScriptCount" in API-method "status" indicates number of queue-scripts queued for execution including the currently running; - added scripting support to RSS Feeds: - new option "FeedScript" to set global rss feed scripts; - new option "FeedX.FeedScript" to define per rss feed scripts; - new option "FeedX.Backlog" to control the RSS feed behavior when fetched for the first time or after changing of URL or filter; - implemented cleanup for field "description" in RSS feeds to remove HTML formatting; - new option "FlushQueue" ("yes" by default) to force disk cache flush when saving queue; - quick toggle of speed limit; "Command/Control + click-on-speed-icon" toggles between "all servers active and speed-limit=none" and "servers and speed limit as in the config file"; - new option "RequiredDir" to wait for external drives mount on boot; - hidden webui option "rowSelect" now works for feed view too; - improved error message for password protected archives; - increased limit for log-entries in history dialog from 1000 to 10000; - completion tab of download details dialog (and history details dialog) shows per server article completion in percents; now there are also tooltips to show article counts; - do not reporting bad blocks for missing files (which are already reported as missing); - new setting to set tray icon behavior on Windows; - improvements in built-in web-server to fix communication errors which may occur on certain systems, in particular on OS X Safari: - implemented graceful disconnect strategy in web-server; - added authorization via X-Auth-Token to overcome Safari’s bug, where it may stop sending HTTP Basic Auth header when executing ajax requests; - pp-script EMail.py now supports new TLS/SSL mode "Force". When active the secure communication with SMTP server is built using secure socket on connection level instead of plain connection and following switch into secure mode using SMTP-command "STARTLS". This new mode is in particular required when using GMail on port 465; - speed improvement in built-in web-server on Windows when serving API requests (web-interface) for very large queue or history (with thousands items); - better performance when deleting many items from queue at once (hundreds or thousands); - improved performance in web-interface when working with very large queue or history (thousands of items); - integrated unit testing framework, created few first unit tests: - new configure parameter "--enable-tests" to build the program with tests; - use "nzbget --tests" to execute all tests or "nzbget --tests -h" for more options; - fixes in Linux installer: - fixed: installer may show wrong IP-address in the quick help message printed after installation (thanks to @sanderjo); - fixed: installation failed on certain WD NAS models due to Busybox limitations; - fixed: not working endianness detection on mipseb architecture; - fixed: unrar too slow on x86_64 architecture; - fixed: reporting of bad blocks for empty files could print garbage file names; - fixed: par-check did not work on UNC paths (Windows only); - fixed: config error messages were not printed to log or screen but only to stdout, where users typically don’t see them; - fixed: incorrect reading of UrlStatus from disk state; - fixed: total articles wasn't reset when downloading again; - fixed: crash on reload if a queue-script is running; - fixed: mark as bad may return items to queue if used on multiple items simultaneously; - fixed: updating may fail if NZBGet was not installed on the system drive (Windows only); - fixed: the program may hang during multithreading par-repair, only certain Linux system affected; - fixed: active URL download could not be deleted; - fixed: par-verification of repaired files were sometimes not skipped in quick verification mode (option "ParQuick=yes"); - fixed: when parsing nzb-files filenames may be incorrectly detected causing certain downloads to fail; - fixed: nzb-files containing very large individual files (many GB) may cause program to crash or print error "Could not create file ...". nzbget-15.0: - improved application for Windows: - added tray icon (near clock); - left click on icon pauses/resumes download; - right click opens menu with important functions; - console window can be shown/hidden via preferences (is hidden by default); - new preference to automatically start the program after login; - new preference to show browser on start; - new preference to hide tray icon; - menu commands to show important folders in windows explorer (destination, etc.); - on first start the config file is now placed into subdirectory "NZBGet" inside standard AppData-directory; - default destination and other directories are now placed in the AppData\NZBGet-directory instead of programs directory; this allows to install the program into "program files"-directory since the program does not write into the programs directory anymore; - the program exe has an icon now; - if the exe is started from windows explorer the program starts in application mode; if the exe is called from command prompt the program works in console mode; - added built-in update feature to windows package; accessible via web-interface -> settings -> system -> check for updates; - created installer for windows: - the program is installed into "program files" by default; - the working directory with all subdirectories is now placed into "AppData" directory; - the batch files nzbget-start.bat and nzbget-recovery-mode.bat are not needed and not installed anymore; - created installer for Linux: - included are precompiled binaries for the most common CPU architectures: x86, ARM, MIPS and PowerPC; - installer automatically detects CPU architecture of the system and installs an appropriate executable; - configuration file is automatically preconfigured for immediate use; - installation on supported platforms has become as simple as: download, run installer, start installed nzbget; - installer supports automatic updates via web-interface -> settings -> system - check for updates; - added support for password list file: - new option "UnpackPassFile" to set the location of the file; - during unpack the passwords are tried from the file until unpack succeeds or all passwords were tried; - implemented different strategies for rar4 and rar5-archives taking into account the features of formats; - for rar5-archives a wrong password is reported by unrar unambiguously and the program can immediately try other passwords from the password list; - for rar4-archives and for 7z-archives it is not possible to differentiate between damaged archive and wrong password; for those archives if the first unpack attempt (without password) fails the program executes par-check (preferably quick par-check if enabled via option "ParQuick) before trying the passwords from the list; - another optimization is that the password list is tried only when the first unpack attempt (without password) reports a password error or decryption errors; this saves unnecessary unpack attempts for damaged unencrypted archives; - options "UnrarCmd" and "SevenZipCmd" can include extra switches to pass to unrar/7-zip: - this allows for easy passing of additional parameters without creating of proxy shell scripts; - improved news server connections handling: - if a download of an article fails due to connection error the news server becomes temporary disabled (blocked) for several seconds (defined by option "RetryInterval"); - the download is then retried on another news server (of the same level) if available; - if no other news servers (of the same level) exist the program will retry the same news server after its block interval expires; - this increases failure tolerance when multiple news servers are used; - added on-demand queue sorting: - one click on column title in web interface sorts the selected or all items; - if the items were already sorted in that order they are sorted backwards; in other words the second click sorts in descending order; - when sorting selected items they are also grouped together in a case there were holes between selected items; - RPC-method "editqueue" has new command "GroupSort", parameter "Text" must be one of: "name", "priority", "category", "size", "left"; add character "+" or "-" to explicitly define ascending or descending order (for example "name-"); if none of these characters is used the auto-mode is active: the items are sorted in ascending order first, if nothing changed - they are sorted again in descending order; - added restricted user and add-user: - restricted user has access to most program functions but cannot see security related options (including usernames and passwords) and cannot save configuration; - restricted user can be used with other programs and web-sites; - add-user can only add new downloads via RPC-API and can be used with other programs or web-sites; - added per-nzb logging: - each nzb now has its own individual log; - messages printed during download or post-processing are saved; - the messages can be retrieved later at any time; - new button "Log" in the history details dialog; - button "Log" in the download details dialog is now active during download too (not only during post-processing); - the log contains all nzb-related messages except detail-messages and errors printed during retrieving of articles (they would produce way too many messages and are not that useful anyway); - new option "NzbLog" to deactivate per-nzb logging if necessary; - per-nzb logs are saved in the queue-directory (option "QueueDir"); - new RPC-method "loadlog" returns the previously saved messages for a given nzb-file; - new field "MessageCount" is returned by RPC-methods "listgroups" and "history" and indicates if there are any messages saved for the item; - parameter "NumberOfLogEntries" of RPC-method "listgroups" and the field "Log" returned by the method are now deprecated, use method "loadlog" instead; - field "PostInfoText" returned by RPC-method "listgroups" is now automatically filled with the latest message printed by a pp-script eliminating the need to access deprecated field "Log"'; - actions for history items can now be performed for multiple (selected) records: - post-process again, download again, mark as good, mark as bad; - extended RPC-API method "editqueue": for history-records of type "URL" the action "HistoryRedownload" can now be used as synonym to "HistoryReturn" (makes it easier to redownload multiple items of different types (URL and NZB) with one API call). - options "ParIgnoreExt" and "ExtCleanupDisk" can now contain wildcard characters * and ?; - added new option "ServerX.Retention" to define server retention time (days); files older than configured server retention time are not even tried on this server; - added support for negative numeric values in rss filter (useful for fields "dupescore" and "priority"); - added subcommand "HA" to remote command "--list/-L" to list the whole history including hidden records; - added optional parameters to remote command "--append/-A" allowing to pass duplicate key, duplicate mode and duplicate score; removed parameters "F" and "U" of command "--append/-A", which were used to set mode (file or URL), which is now detected automatically; the parameters are still supported for compatibility; - name and category of history items can now be changed in web-interface; RPC-API method "editqueue" extended with new actions "HistorySetName" and "HistorySetCategory"; - improved timeout handling during establishing of connections; - updated pp-script "EMail.py": - using the new nzb-log feature; - new option "SendMail" allows to choose if the e-mail should be send always or on failure only; - updated pp-script "Logger.py" to use the new nzb-log feature; - improved cleanup (option ExtCleanupDisk): - if download was successful with health 100% the cleanup is now performed even if par-check and unpack were not made; previously a successful par-check or unpack were required for cleanup; - now the files are deleted in subdirectories too (recursively); - added a small button near feed name in the feed menu on downloads-page; a click on the button fetches the feed, whereas a click on the feed title shows feed's content (as before); - improved detection of malformed nzb-files: nzbs which are valid xml-documents but without nzb content are now rejected with an appropriate error message; - new action "Mark as success" on history page and in history details dialog: - items marked as success are considered successfully downloaded and processed, which is important for duplicate check; - use this command if the download was repaired outside of NZBGet; - new action "HistoryMarkSuccess" in RPC-method "editqueue"; - new subcommand "S" of command "-E H" (command line interface); - new status "SUCCESS/MARK" can be returned by RPC-method "history"; - improved support for update-scripts: - all command line parameters used to launch nzbget are passed to the script in env vars NZBUP_CMDLINEX, where X is a parameter number starting with 0; - if the path to update-script defined in package-info.json does not start with slash the path is considered being relative to application directory; - new env var NZBUP_RUNMODE (DAEMON, SERVER) is passed to the script; - fixed: env var NZBUP_PROCESSID had wrong value (ID of the parent process instead of the nzbget process); - added button "Test Connection" to make a news server connection test from web-interface; - renamed option "CreateBrokenLog" to "BrokenLog"; the old option name is recognized and automatically converted when the configuration is saved in web-interface; - improved the quality of speed throttling when a speed limit is active; - added hidden webui setting "rowSelect" to select records by clicking on any part of the row, not just on the check mark; to activate it change the setting "rowSelect" in webui/index.js; - when moving files to final destination the hidden files (with names starting with dot) are considered unimportant and no errors are printed if they cannot be moved; such files (.AppleDouble, .DS_Store, etc.) are usually used by services to hold metadata and can be safely ignored; - option sets (such as news-servers, categories, etc.) can now be reordered using news buttons "move up" and "move down"; - updated info in about dialogs (Windows and Mac OS X); - updated description of few options; - changed defaults for few logging options; - improved timeout handling when connecting to news servers which have multiple addresses; - improved error handling when communicating with secure servers (do not trying to send quit-command if connection could not be established or was interrupted; this avoids unnecessary timeout); - improved connection handling when fetching nzb-files and rss feeds; do not print warning "Content-Length is not submitted by server..." anymore; - download speed in context menu of menubar icon is now shown in MB/s instead of KB/s (for speeds from 1 MB/s) (Mac OS X only); - configuration file nzbget.conf is now also searched in the app-directory on all platforms (for easier installation); - removed shell script "nzbgetd" which were used to control nzbget as a service; modern systems manage services in a diffreent way and do not require that old script anymore; - disabled changing of compiler options during configuring in debug mode (--enable-debug); it conflicted with cross-compiling and did not allow to pass extra options via CXXFLAGS; required debug options must be passed via CXXFLAGS now (for example for gcc: CXXFLAGS=-g ./configure --enable-debug); - disabled unnecessary assert-statements in par2-module when building in release mode; - fixed: parsing of RPC-parameters passed via URL were sometimes incorrect; - fixed: lowercase hex digits were not correctly parsed in URLs passed to RPC-API method "append"; - fixed: in JSON-RPC the request-id was not transfered back in the response as required by JSON-RPC specification; - fixed: par-check in full verification mode (not in quick mode) could not detect damaged files if they were completely empty (0 bytes), which is possible when option "DirectWrite" was not active and all articles of the file were missing; - fixed possible crash when using remote command "-B dump" to print debug info; - fixed: remote command "-L HA" (which prints the history including hidden records) could crash; - suppress printing of memory leaks reports when the program terminates because of wrong command line switches (Windows debug mode only); - fixed: command "nzbget -L H" may crash if the history contained URL-items with certain status; - fixed: action "Split" may not work for bad nzb-files with missing segments; new Field "Progress" returned by RPC-method "listfiles" shows the download progress of the file taking missing articles into account; - if the lock-file cannot be created or the lock could not be acquired an error message is printed to the log-file; - fixed: update log shown during automatic update via web-interface may show duplicate messages or messages may clear out; - fixed: web-interface may fail to load on Firefox mobile; - fixed: command "make install" installed README from par2-subdirectory instead of main README. nzbget-14.2: - fixed: the program could crash during download when article cache was active (more likely on very high download speeds); - fixed: unlike to all other scripts the update-script should not be automatically terminated when the program quits; - fixed: XML-RPC method "history" returned invalid xml when used with parameter "hidden=true" (JSON-RPC was fine). nzbget-14.1: - fixed: program could crash during unpack (Posix) or unpack failure was reported (Windows); - fixed: quick par-check could hang on certain nzb-files containing multiple par-sets (occured only in 64 bit mode); - fixed: menubar icon was not visible on Mac OS X in dark mode; system sleep on idle state is now prevented during download and post-processing (Mac OS X only); - fixed: unrar may sometimes fail with message "no files to extract" (certain Linux systems); - fixed false memory leak warning when compiled in debug mode (Windows only); - fixed: Mac OS X app didn't work on OS X 10.7 Lion. nzbget-14.0: - added article cache: - new option "ArticleCache" defines memory limit to use for cache; - when cache is active the articles are written into cache first and then all flushed to disk into the destination file; - article cache reduces disk IO and may reduce file fragmentation improving post-processing speed (unpack); - it works with both writing modes (direct write on and off); - when option "DirectWrite" is disabled the cache should be big enough (for best performance) to accommodate all articles of one file (sometimes up to 500 MB) in order to avoid writing articles into temporary files, otherwise temporary files are used for articles which do not fit into cache; - when used in combination with DirectWrite there is no such limitation and even a small cache (100 MB or even less) can be used effectively; when the cache becomes full it is flushed automatically (directly into destination file) providing room for new articles; - new row in the "statistics and status dialog" in web-interface indicates the amount of memory used for cache; - new fields "ArticleCacheLo", "ArticleCacheHi" and "ArticleCacheMB" returned by RPC-method "status"; - renamed option "WriteBufferSize" into "WriteBuffer": - changed the dimension - now option is set in kilobytes instead of bytes; - old name and value are automatically converted; - if the size of article is below the value defined by the option, the buffer is allocated with the articles size (to not waste memory); - therefore the special value "-1" is not required anymore; during conversion "-1" is replaced with "1024" (1 megabyte) but it can be of course manually changed to any other value later; - integrated par2-module (libpar2) into NZBGet’s source code tree: - the par2-module is now built automatically during building of NZBGet; - this eliminates dependency from external libpar2 and libsigc++; - making it much easier for users to compile NZBGet without patching libpar2; - added quick file verification during par-check/repair: - if par-repair is required for download the files downloaded without errors are verified quickly by comparing their checksums against the checksums stored in the par2-file; - this makes the verification of undamaged files almost instant; - damaged (partially downloaded) files are also verified quickly by comparing block's checksums against the checksums stored in the par2-file; when necessary the small amounts of data is read from files to calculate block's checksums; - this makes the verification of damaged files very fast; - new option "ParQuick" (active by default); - when quick par verification is active the repaired files are not verified to save time; the only reason for incorrect files after repair can be hardware errors (memory, disk) but this is not something NZBGet should care about; - if unpack fails (excluding invalid password errors) and quick par-check does not find any errors or quick par-check was already performed the full par-check is performed; this helps in certain rare situations caused by abnormal program termination; - added multithreading par-repair: - doesn't depend on other libraries and works everywhere, on all platforms and all CPUs (with multiple cores), no special compiling steps are required; - new option "ParThreads" to set the number of threads for repairing; - the number of repair threads is automatically reduced to the amount of bad blocks if there are too few of them; if there is only one bad block the multithreading par-repair is switched off to avoid overhead of thread synchronisation (which does not make sense for one working thread); - new option "ParBuffer" to define the memory limit to use during par-repair; - added support for detection of bad downloads (fakes, etc.): - queue-scripts are now called after every downloaded file included in nzb; - new events "FILE_DOWNLOADED" and "NZB_DOWNLOADED" of parameter "NZBNA_EVENT"; new env. var "NZBNA_DIRECTORY" passed to queue scripts; - queue-scripts have a chance to detect bad downloads when the download is in progress and cancel bad downloads by printing a special command; downloads marked as bad become status "FAILURE/BAD" and are processed by the program as failures (triggering duplicate handling); scripts executed thereafter see the new status and can react accordingly (inform an indexer or a third-party automation tool); - when a script marks nzb as bad the nzb is deleted from queue, no further internal post-processing (par, unrar, etc.) is made for the nzb but all post-processing scripts are executed; if option "DeleteCleanupDisk" is active the already downloaded files are deleted; - new status "BAD" for field "DeleteStatus" of nzb-item in RPC-method "history"; - queue-scripts can set post-processing parameters by printing special command, just like post-processing-scripts can do that; this simplifies transferring (of small amount) of information between queue-scripts and post-processing-scripts; - scripts supporting two modes (post-processing-mode and queue-mode) are now executed if selected in post-processing parameters: either in options "PostScript" and "CategoryX.PostScript" or manually on page "Postprocess" of download details dialog in web-interface; it is not necessary to select dual-mode scripts in option "QueueScript"; that provides more flexibility: the scripts can be selected per-category or activated/deactivated for each nzb individually; - added option "EventInterval" allowing to reduce the number of calls of queue-scripts, which can be useful on slow systems; - queue scripts can define what events they are interested in; this avoids unnecessary calling of the scripts which do not process certain events; - the list of scripts (pp-scripts, queue-scripts, etc.) is now read once on program start instead of reading everytime a script is executed: - that eliminates the unnecessary disk access; - the settings page of web-interface loads available scripts every time the page is shown; - this allows to configure newly added scripts without restarting the program first (just like it was before); a restart is still required to apply the settings (just like it was before); - RPC-method "configtemplates" has new parameter "loadFromDisk"; - options "ParIgnoreExt" and "ExtCleanupDisk" are now respected by par-check (in addition to being respected by par-rename): if all damaged or missing files are covered by these options then no par-repair is performed and the download assumed successful; - added new search field "dupestatus" for use in rss filters: - the search is performed through download queue and history testing items with the same dupekey or title as current rss item; - the field contains comma-separated list of following possible statuses (if duplicates were found): QUEUED, DOWNLOADING, SUCCESS, WARNING, FAILURE or an empty string if there were no matching items found; - added log file rotation: - options "CreateLog" and "ResetLog" replaced with new option "WriteLog (none, append, reset, rotate)"; - new option "RotateLog" defines rotation period; - improved joining of splitted files: - instead of performing par-repair the files are now joined by unpacker, which is much faster; - the files splitted before creating of par-sets are now joined as well (they were not joined in v13 because par-repair has nothing to repair in this case); - the unpacker can detect missing fragments and requests par-check if necessary; - added per-nzb time and size statistics: - total time, download, verify, repair and unpack times, downloaded size and average speed, shown in history details dialog via click on the row with total size in statistics block; - RPC-methods "listgroups" and "history" return new fields: "DownloadedSizeLo", "DownloadedSizeHi", "DownloadedSizeMB", "DownloadTimeSec", "PostTotalTimeSec", "ParTimeSec", "RepairTimeSec", "UnpackTimeSec"; - pp-script "EMail.py" now supports mail server relays (thanks to l2g for the patch); - when compiled in debug mode new field "process id" is printed to the file log for each row (it is easier to identify processes than threads); - if an nzb has only few failed articles it may have completion shown as 100%; now it is shown as 99.9% to indicate that not everything was successfully downloaded; - updated configure-script to not require gcrypt for newer GnuTLS versions (when gcrypt is not needed); - for downloads delayed due to propagation delay (option "PropagationDelay") a new badge "propagation" is now shown near download name; - added new option "UrlTimeout" to set timeout for URL fetching and RSS feed fetching; renamed option "ConnectionTimeout" to "ArticleTimeout"; - improved pp-script EMail.py: now it can send time statistics (thanks to JVM for the patch); - improvement in duplicate check: - if a new download with empty dupekey and empty dupescore is marked as "dupe" and the another download with the same name have non empty dupekey or dupescore these properties are copied from that download; - this is useful because the new download is most likely another upload of the same file and it should have the same duplicate properties for best duplicate handling results; - when connecting in remote mode using command line parameter "--connect/-C" the option "ControlIP" is now interpreted as "127.0.0.1" if it is set to "0.0.0.0" (instead of failing with an error message); - when option "ContinuePartial" is active the current state is saved not more often than once per second instead of after every downloaded article; this significantly reduce the amount of disk writings on high download speeds; - added commands "PausePostProcess" and "UnpausePostProcess" to scheduler; - unpack is now automatically immediately aborted if unrar reports CRC errors; - unpack is now immediately aborted if unrar reports wrong password (works for rar5 as well as for older formats); the unpack error status "PASSWORD" is now set for older formats too (not only rar5); - improved cleanup: - disk cleanup is now not performed if unrar failed even if par-check was successful; - queue cleanup (for remaining par2-files) is now made more smarter: the files are kept (parked) if they can be used by command "post-process again" and are removed otherwise; - improved scan-scripts: if the category of nzb-file is changed by the scan-script the assigned post-processing scripts are now automatically reset according to the new category; - added missing new line character at the end of the help screen printed by "nzbget -h"; - better error reporting if a temp file could not be found; - added news server name to message "Cancelling hanging download ..." to help identifying problematic servers; - added column "age" to history tab in web-interface; - debug builds for Windows now print call stack on crash to the log-file, which is very useful for debugging; - additional parameters (env. vars) are now passed to scan scripts: NZBNP_DUPEKEY, NZBNP_DUPESCORE, NZBNP_DUPEMODE; scan-scripts can now set dupekey, dupemode and dupescore by printing new special commands; - fixed potential crash which could happen in debug mode during program restart; - fixed: program could crash during restart if an extension script was running; now all active scripts are terminated during restart; - fixed: RPC-method "editqueue" with action "HistoryReturn" caused a crash if the history item did not have any remaining (parked) files; - fixed: RPC-method "saveconfig" did not work via XML-RPC (but worked via JSON-RPC); - fixed: a superfluous comma at the end of option "TaskX.Time" was interpreted as an error or may cause a crash; - fixed: relative destination paths (options "DestDir" and "CategoryX.DestDir") caused failures during unrar; - fixed: splitted .cbr-files were not properly joined; - fixed: inner files (files listed in nzb) bigger than 2GB could not be downloaded; - fixed: cleanup may leave some files undeleted (Mac OS X only); - fixed: compiler error if configured using parameter "--disable-gzip"; - fixed: one log-message was printed only to global log but not to nzb-item pp-log; - fixed: par-check could fail on valid files (bug introduced in libpar2 0.3); - fixed: scheduler tasks were not checked after wake up if the sleep time was longer than 90 minutes; - fixed: the "pause extra pars"-state was missing in the pause/resume-loop of curses interface, key "P"; - fixed: web interface showed an error box when trying to submit files with extensions other than .nzb, although these files could be processed by a scan-script; now the error is not shown if any scan-script is set in options; nzbget-13.0: - reworked download queue: - new dialog to build filters in web-interface with instant preview; - queue now holds nzb-jobs instead of individual files (contained within nzbs); - this drastically improves performance when managing queue containing big nzb-files on operations such as pause/unpause/move items; - tested with queue of 30 nzb-files each 40-100GB size (total queue size 1.5TB) - queue managing is fast even on slow device; - limitation: individual files (contained within nzbs) now cannot be moved beyond nzb borders (in older version it was possible to move individual files freely and mix files from different nzbs, although this feature was not supported in web-interface and therefore was not much known); - this change opens doors for further speed optimizations and integration of download queue with post-processing queue and possibly url-queue; - current download data such as remained size or size of paused files is now internally automatically updated on related events (download of article is completed, queue edited, etc.); - this eliminates the need of calculating this data upon each RPC-request (from web-interface) and greatly decrease CPU load of processing RPC-requests when having large download queue (and/or large nzb-files in queue); - field "Priority" was removed from individual files; - instead nzb-files (collections) now have field "Priority"; - nzb-files now also have new fields "MinTime" and "MaxTime", which are set when nzb-file is parsed and then kept; - this eliminates the need of recalculation file statistics (min and max priority, min and max time); - removed action "FileSetPriority" from RPC-command "editqueue"; - removed action "I" from remote command "--edit/-E" for individual files (now it is allowed for groups only); - removed few (not more necessary) checks from duplicate manager; - merged post-processing queue into main download queue; - changing the order of (pp-queued) items in the download queue now also means changing the order of post-processing jobs; - priorities of downloads are now respected when picking the next queued post-processing job; - the moving of download items in web-interface is now allowed for downloads queued for post-processing; - removed actions of remote command "--edit/-E" and of RPC-method "editqueue" used to move post-processing jobs in the post-processing queue (the moving of download items should be used instead); - remote command "-E/--edit" and RPC-method "editqueue" now use NZBIDs of groups to edit groups (instead of using ID of any file in the group as in older versions); - remote command "-L/--list" for groups (G) and group-view in curses-frontend now print NZBIDs instead of "FirstID-LastID"; - RPC-method "listgroups" returns NZBIDs in fields "FirstID" and "LastID", which are usually used as arguments to "editqueue" (for compatibility with existing third-party software); - items queued for post-processing and not having any remaining files now can be edited (to cancel post-processing), which was not possibly before due to lack of "LastID" in empty groups; - edit commands for download queue and post-processing queue are now both use the same IDs (NZBIDs); - merged url queue into main download queue; - urls added to queue are now immediately shown in web-interface; - urls can be reordered and deleted; - when urls are fetched the downloaded nzb-files are put into queue at the positions of their urls; - this solves the problem with fetched nzb-files ordered differently than the urls if the fetching of upper (position wise) urls were completed after the lower urls; - removed options "ReloadUrlQueue" and "ReloadPostQueue" since there are no separate url- and post-queues anymore; - nzb-files added via urls have new field "URL" which can be accessed via RPC-methods "listgroups" and "history"; - new env. var. "NZBNP_URL", "NZBNA_URL" and "NZBPP_URL" passed to scan-, queue- and pp-scripts; - removed remote command "--list U", urls are now shown as groups by command "--list G"; - RPC-method "urlqueue" is still supported for compatibility but should not be used since the urls are now returned by method "listgroups", the entries have new field "Kind" which can be "NZB" or "URL"; - added collecting of download volume statistics data per news server: - in web-interface the data is shown as chart in "Statistics and Status" dialog; - new RPC-method "servervolumes" returns the collected data; - new RPC-method "resetservervolume" to reset the custom counter; - fast par-renamer now automatically detects and renames misnamed (obfuscated) par2-files; - for downloads not having any (obviously named) par2-files the critical health is assumed 85% instead of 100% as the absense of par2-files suggests: - this avoids the possibly false triggering of health-check action (delete or pause) for downloads having misnamed (obfuscated) par2-files; - combined with improved fast par-renamer this provides proper processing of downloads with misnamed (obfuscated) par2-files; - fast par-renamer now detects missing files (files listed in par2-files but not present on disk): - when checking for missing files the files whose extensions match with option "ExtCleanupDisk" are ignored now (to avoid time consuming restoring of files which will be deleted later anyway); - added option "ParIgnoreExt" which lists files which do not trigger par-repair if they are missing (similar to option "ExtCleanupDisk" but those files are not deleted during cleanup); - added new choice "Always" for option "ParCheck": - it forces the par-check for every (even undamaged) download but in contrast to choice "Force" only one par2-file is downloaded first; - additional files are downloaded if needed; - improved par-check for damaged collections with multiple par-sets and having missing files: - only orphaned files (not belonging to any par-set) are scanned when looking for missing files; - this greatly decrease the par-check time for big collections; - eliminated the distinction between manual pause and soft-pause: - there is only one pause register now; - options "ParPauseQueue", "UnpackPauseQueue" and "ScriptPauseQueue" do not change the state of the pause but instead are respected directly; - RPC-methods "pausedownload2" and "resumedownload2" are aliases to "pausedownload" and "resumedownload" (kept for compatibility); - field "Download2Paused" of RPC-method "status" is an alias to "DownloadPaused" (kept for compatibility); - action "D2" of remote commands "--pause/-P" and "--unpause/-U" is not supported anymore; - implemented general scripts concept: - the concept is a logical extension of the post-processing scripts concept initially introduced in v11; - the general scripts concept applies to all scripts used in the program: scan-script, queue-script and scheduler-script (in addition to post-processing scripts); - option "NzbProcess" renamed to "ScanScript"; - option "NzbAddedProcess" renamed to "QueueScript"; - option "DefScript" and "CategoryX.DefScript" renamed to "PostScript" and "CategoryX.PostScript" (options with old names are recognized and automatically converted on first settings saving); - new option "TaskX.Script"; - old option "TaskX.Process" kept for scheduling of external programs not related to nzbget (to avoid writing of intermediate proxy scripts); - scan-script, queue-script and scheduler-script now work similar to post-processing scripts: - scripts must be put into scripts-directory; - scripts can be configured via web-interface and can have options; - multiple scripts can be chosen for each scripts-option, all chosen scripts are executed; - program and script options are passed to the script as env. variables; - renamed default directory with scripts from "ppscripts" to "scripts"; - script signature indicates the type of script (post-processing, scan, queue or scheduler); - one script can have mixed signature allowing it to be used for multiple purposes (for example a notification script can send a notification on both events: after adding to queue and after post-processing); - result of RPC-method "configtemplates" has new fields "PostScript", "ScanScript", "QueueScript", "SchedulerScript" to indicate the purpose of the script; - queue-script (formerly NzbAddedProcess) has new parameter "NZBNA_EVENT" indicating the reason of calling the script; currently the script is called only after adding of files to download queue and therefore the parameter is always set to "NZB_ADDED" but the queue-script can be called on other events in the future too; - post-processing scripts now have two new parameters: - env. var "NZBPP_STATUS" indicates the status of download including the total status (SUCCESS, FAILURE, etc.) and the detail field (for example in case of failures: PAR, UNPACK, etc.); - env. var "NZBPP_TOTALSTATUS" is equal to the total status of parameter "NZBPP_STATUS" and is provided for convenience (to avoid parsing of "NZBPP_STATUS"); - the new parameters provide a simple way for pp-scripts to determine download status without a guess work needed in previous versions; - parameters "NZBPP_PARSTATUS" and "NZBPP_UNPACKSTATUS" are now considered deprecated (still passed for compatibility); - updated script "EMail.py" to use new parameters "NZBPP_TOTALSTATUS" and "NZBPP_STATUS" instead of "NZBPP_PARSTATUS" and "NZBPP_UNPACKSTATUS"; - when changing category in web-interface the post-processing parameters are now automatically updated according to new category settings: - only parameters which are different in old and new category are changed; - parameters which present in both or in neither categories are not changed; - that ensures that only the relevant parameters are updated and parameters which were manually changed by user remain they settings when it make sense; - in the "download details dialog" the new parameters are updated on the postprocess-tab directly after changing of category and can be controlled before saving; - in the "edit multiple downloads dialog" the parameters are updated individually for each download on saving; - new action "CP" of remote command "--edit/-E" for groups to set category and apply parameters; - new action "GroupApplyCategory of RPC-method "editqueue" for the same purpose; - changed the way option "ContinuePartial" works: - now the information about completed articles is stored in a special file in QueueDir; - when option "DirectWrite" is active no separate flag-files per article are created in TempDir; - the file contains additional information, which were not stored/available before; - improved per-server/per-nzb article completion statistics: - the statistics are now available for active downloads in details dialog (not only for history); - the info on that page is constantly updated as long as the page is active (unless refresh is disabled); - download age info removed from details dialog to save place (it is shown in the download list anyway); - if backup news-servers start to be used for nzb-file a badge appears in the download list showing the percentage of articles downloaded from backup servers; - click on the badge opens download details dialog directly on the completion page; - per-server/per-nzb article completion statistics are now available via RPC-method "listgroups" for active downloads (not only for "history"); - improved RPC-API: - RPC-method "listgroups" now returns info about post-processing similar to info returned by method "postqueue"; - RPC-method "postqueue" is obsolete now; - web-interface requires less requests to NZBGet on each page update and it is now easier for third-party developers to obtain the info about download and post-processing status (no need to merge download queue and post queue); - RPC-method "listgroups" now returns new field "Status" making it easier for third-party apps to determine the status of download entry; - new field "Status" in RPC-method "history" to allow third-party apps easier determine the status of an item without inspecting status-fields of every processing step; - changed web-interface to use new field "Status"; - method "append" now returns id of added nzb-file or "0" on an error; - this makes it easier for third-party apps to track added nzb-files; - for backward compatibility with older software expecting a boolean result the old version of method "append" is still supported; - the new version of method "append" has a different signature (order of parameters); - parameter "content" can now be either nzb-file content (encoded in base 64) or an URL; - this makes the method "appendurl" obsolete (still supported for compatibility); - if an URL was added to queue the queue entry created for fetched nzb-file has the same "NZBID" for easier tracking; - added force-priorities: - downloads with priorities equal to or greater than 900 are downloaded and post-processed even if the program is in paused state (force mode); - in web-interface the combo for choosing priority has new entry "force" (priority value 900); - new fields "ForcedSizeLo", "ForcedSizeHi" and "ForcedSizeMB" returned by RPC-method "status"; - history items now preserve "NZBID" from queue items; that makes the tracking of items across queue and history easier for third-party apps; - field "NZBID" returned by RPC-method "history" is now available for history items of all kinds (NZB, URL, DUP); field "ID" is deprecated and should not be used; - post-processing scripts which move the whole download into a new location can inform the program about new location using command "[NZB] DIRECTORY=/new/path", allowing other scripts to process files further; - added support for power management on windows to avoid pc going into sleep mode during download or post-processing; - apostrophe is not considered an invalid file name character anymore; - adjusted modules initialization to avoid possible bugs due to delayed thread starts; - reorganized source code directory structure: created directory "daemon" with several subdirectories and put all source code files there; - added new option "PropagationDelay", which sets the minimum post age to download; newer posts are kept on hold in download queue until they get older than the defined delay, after that they are downloaded; - download speeds above 1024 KB/s are now indicated in MB/s; - data sizes above 1000 GB are now shown as TB in web-interface (instead of GB); - splitted files are now joined automatically (again); - adjusted modules initialization to avoid possible bugs due to delayed thread starts; - extended info printed by remote command "nzbget -B dump" (for debug purposes); - eliminated loop waiting time in queue coordinator on certain conditions - may improve performance on very high speed connections; - increased few wait intervals which were unnecessary too small; - improved error reporting: added error check when closing article file for writing and when deleting files or directories; - when building nzbget if both OpenSSL and GnuTLS are available now using OpenSSL by default (the preferred library can still be selected with configure-parameter --with-tlslib=OpenSSL/GnuTLS); - windows version is now configured to use OpenSSL instead of GnuTLS; windows binaries provided on download page now use OpenSSL; - column "age" in web-interface now shows minutes for recent posts (instead of "0 h"); - remote command "-B dump" now can be used also in release (non-debug) versions and prints useful debug data as "INFO" instead of "DEBUG"; - to detect daylight saving activation/deactivation the time zone information is now checked every minute if a download is active or once in 3 hours if the program is in stand-by; these delays should work well with hibernation mode on synology); - pp-script "EMail.py" now takes the status of previous pp-scripts into account and report a failure if any of the scripts has failed; - updated all links to go to new domain (nzbget.net); - impoved error reporting if unpacker or par-renamer fail to move files; - removed libpar2-patches from NZBGet source tree; the documentation now suggests to use the libpar2 version maintained by Debian/Ubuntu team, which already includes all necessary patches; - removed patches to create libpar2 and libsigc++ project files for Visual Studio on Windows, no one needed them anyway; - fixed: the program could crash during cleanup if files with invalid timestamps were found in the directory (windows only); - fixed: RSS feed preview dialog displayed slightly incorrect post ages because of the wrong time zone conversion; - fixed: sometimes URLs were removed too early from the feed history causing them to be detected as "new" and fetched again; if duplicate check was not active the same nzb-files could be downloaded again; - fixed: strange (damaged?) par2-files could cause a crash during par-renaming; - fixed: damaged nzb-files containing multiple par-sets and not having enough par-blocks could cause a crash during par-check; - fixed: if during par-repair the downloaded extra par-files were damaged and the repair was terminated with failure status the post-processing scripts were executed twice sometimes; - fixed: post-processing scripts were not executed in standalone mode ("nzbget /path/to/file.nzb"); - fixed: renaming or deleting of temporary files could fail, especially when options "UnpackPauseQueue" and "ScriptPauseQueue" were not active (windows only); - fixed: per-server/per-nzb article completion statistics could be inaccurate for nzb-files whose download were interrupted by reload/restart; - fixed: after deleting servers from config file the program could crash on start when loading server volume statistics data from disk; - fixed: download speeds above approx. 70 MB/s were not indicated correctly in web-interface and by RPC-method "status"; - fixed: cancelling of active par-job sometimes didn't work; - fixed: par-check could hang on renamed and splitted files; - fixed: the program could crash during parsing of malformed nzb-files; - fixed: errors during loading of queue from disk state may render the already loaded parts useless too; now at least these parts of queue are used; - fixed: queue was not locked during loading on program start and that could cause problems; - fixed: data sizes exactly equal to 10, 100, 1000 MB or GB were formatted using 4 digits instead of 3 (one digit after decimal point too much); - fixed: if post-processing step "move" failed, the command "post-process again" did not try to move again; - fixed: nzb-files were sometimes not deleted from NzbDir (option "NzbCleanupDisk"); - fixed: scheduler command "FetchFeed" did not work properly with parameter "0" (fetch all feeds). - fixed: port number was not sent in headers when downloading from URLs which could cause issues with RSS for web-sites using non-standard http ports; - fixed: queued nzb-files was not deleted from disk when deleting download without history tracking; 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.net/RSS#Duplicates; - created NZBGet.app - NZBGet is now a user friendly Mac OS X 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.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 OS X); - 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 is obsolete; - new option sets directory where all pp-scripts must be stored; - new option sets the default list of pp-scripts to be assigned to nzb-file when it's added to queue; - new option to set the default list of pp-scripts on a category basis; - the execution order of pp-scripts can be set using new option ; - 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 and ; - new RPC-method 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 in the edit download dialog; - option renamed to (the old name is still recognized); - new option to define the location of template configuration file (in previous versions it must be always stored in ); - 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 in RPC-method ; - new action in remote command <--edit/-E> for history items (subcommand ); - added new feature which creates new download from selected files of source download; - new command in web-interface in edit download dialog on page ; - new action in remote command <--edit/-E>; - new action in JSON-/XML-RPC method ; - added support for manual par-check: - if option is set to 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 of option renamed to and respectively; - when set to all par2-files are always downloaded; - removed option since its functionality is now covered by option ; - result of par-check can now have new value ; - field in RPC-method can have new value ; - parameter for pp-script can have new value <4 = manual repair necessary>; - when download is resumed in web-interface the option 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 removed; a post-processing script is called only once per nzb-file, this behavior cannot be altered anymore; - with a new feature 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 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 now returns ID of NZB-file in the field ; - in versions up to 0.8.0 the field was used to identify history items in the edit-commands , , ; since version 9 field is used for this purpose; in versions 9-10 field still existed and had the same value as field for compatibility with version 0.8.0; the compatibility is not provided anymore; this change was needed to provide a consistent using of field 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 ; - pp-scripts terminated with unknown status are now considered failed (status=FAILURE instead of status=UNKNOWN); - new parameter (env. var) 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 (, , etc.); the message includes error text reported by OS such as 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 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 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 from message ; - 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 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 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 must be defined in project settings; defining it in 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 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 and 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 7-Zip executables); - new options , , , , ; - web-interface now shows progress and estimated time during unpack (rar only; for 7-Zip progress is not available due to limitations of 7-Zip); - when built-in unpack is enabled, the post-processing script is called after unpack and possibly par-check/repair (if needed); - for nzb-files containing multiple collections (par-sets) the post-processing script is called only once, after the last par-set; - new parameter passed to post-processing script; - if the option is enabled the post-processing- script is called after each par-set (as in previous versions); - example post-processing script updated: removed unrar-code, added check for unpack status; - new field in result of RPC-method ; - history-dialog in web-interface shows three status: par-status, unpack-status, script-status; - with two built-in special post-processing parameters <*Unpack:> and <*Unpack:Password> the unpack can be disabled for individual nzb-file or the password can be set; - built-in special post-processing parameters can be set via web- interface on page (when built-in unpack is enabled); - added support for HTTPS to the built-in web-server (web-interface and XML/JSON-RPC): - new options , , and ; - module completely rewritten with support for server- side sockets, newer versions of GnuTLS, proper thread lockings in OpenSSL; - improved the automatic par-scan (option ) to significantly reduce the verify-time in some common cases with renamed rar-files: - the extra files are scanned in an optimized order; - the scan stops when all missings files are found; - added fast renaming of intentionally misnamed (rar-) files: - the new renaming algorithm doesn't require full par-scan and restores original filenames in just a few seconds, even on very slow computers (NAS, media players, etc.); - the fast renaming is performed automatically when requested by the built-in unpacker (option must be active); - added new option to put intermediate files during download into a separate directory (instead of storing them directly in destination directory (option ): - when nzb-file is completely (successfully) downloaded, repaired (if neccessary) and unpacked the files are moved to destination directory (option or ); - intermediate directory can significantly improve unpack performance if it is located on a separate physical hard drive; - added new option to manually select cipher for encrypted communication with news server: - manually choosing a faster cipher (such as ) can significantly improve performance (if CPU is a limiting factor); - major improvements in news-server/connection management (main and fill servers): - if download of article fails, the program tries all servers of the same level before trying higher level servers; - this ensures that fill servers are used only if all main servers fail; - this makes the configuring of multiple servers much easier than before: in most cases the simple configuration of level 0 for all main servers and level 1 for all fill servers suffices; - in previous versions the level was increased immediately after the first tried server of the level failed; to make sure all main servers were tried before downloading from fill servers it was required to create complex server configurations with duplicates; these configurations were still not as effective as now; - do not reconnect on
errors since this doesn't help but unnecessary increases CPU load and network traffic; - removed option ; it's not required anymore; - new option allows more flexible configuration of news servers when using multiple accounts on the same server; with this option it's also possible to imitate the old server management behavior regarding levels; - news servers configuration is now less error-prone: - the option is not required to start from <0> and when several news servers are configured the Levels can be any integers - the program sorts the servers and corrects the Levels to 0,1,2,etc. automatically if needed; - when option is set to <0> the server is ignored (in previous version such a server could cause hanging when the program was trying to go to the next level); - if no news servers are defined (or all definitions are invalid) a warning is printed to inform that the download is not possible; - categories can now have their own destination directories; new option ; - new feature in web-interface; new XML-/JSON-RPC method ; - improved the handling of hanging connections: if a connection hangs longer than defined by option the program tries to gracefully close connection first (this is new); if it still hangs after the download thread is terminated as a last resort (as in previous versions); - added automatic speed meter recalibration to recover after possible synchronization errors which can occur when the option is not active; this makes the default (less accurate but fast) speed meter almost as good as the accurate one; important when speed throttling is active; - when the par-checked requests more par-files, they get an extra priority and are downloaded before other files regardless of their priorities; this is needed to avoid hanging of par-checker-job if a file with a higher priority gets added to queue during par-check; - when post-processing-parameters are passed to the post-processing script a second version of each parameter with a normalized parameter- name is passed in addition to the original parameter name; in the normalized name the special characters <*> and <:> are replaced with <_> and all characters are passed in upper case; this is important for internal post-processing-parameters (*Unpack:=yes/no) which include special characters; - warning now is not printed when the connection was aborted before the request signature was read; - changed formatting of remaining time for post-processing to short format (as used for remaining download time); - added link to article to settings tab on web- interface; - removed hint from history dialog since it caused more questions than helped; - changed default value for option to ; most news servers nowadays do not require joining the group and many servers do not keep headers for many groups making the join-command fail even if the articles still can be successfully downloaded; - small change in example post-processing script: message are now printed only if ts-files really existed; - improved configure-script: - libs which are added via pkgconfig are now put into LIBS instead of LDFLAGS - improves compatibility with newer Linux linkers; - OpenSSL libs/includes are now added using pkgconfig to better handle dependencies; - additional check for libcrypto (part of OpenSSL) ensures the library is added to linker command even if pkgconfig is not used; - adding of local files via web-interface now works in IE10; - if an obsolete option is found in the config file a warning is printed instead of an error and the program is not paused anymore; - fixed: the reported line numbers for configuration errors were sometimes inaccurate; - fixed warning ; - fixed: some XML-/JSON-RPC methods may return negative values for file sizes between 2-4GB; this had also influence on web-interface. - fixed: if an external program (unrar, pp-script, etc.) could not be started, the execute-function has returned code 255 although the code -1 were expected in this case; this could break designed post- processing flow; - fixed: some characters with codes below 32 were not properly encoded in JSON-RPC; sometimes output from unrar contained such characters and could break web-interface; - fixed: special characters (quotation marks, etc.) in unpack password 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 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 version of the program); - added built-in web-interface: - completely new designed and written from scratch; - doesn't require a separate web-server; - doesn't require PHP; - 100% Javascript application; the built-in web-server hosts only static files; the javascript app communicates with NZBGet via JSON-RPC; - very efficient usage of server resources (CPU and memory); - easy installation. Since neither a separate web-server nor PHP are needed the installation of new web-interface is very easy. Actually it is performed automatically when you "make install" or "ipkg install nzbget"; - modern look: better layout, popup dialogs, nice animations, hi-def icons; - built-in phone-theme (activates automatically); - combined view for "currently downloading", "queued", "currently processing" and "queued for processing"; - renaming of nzb-files; - multiselect with multiedit or merge of downloads; - fast paging in the lists (downloads, history, messages); - search box for filtering in the lists (downloads, history, messages) and in settings; - adding nzb-files to download queue was improved in several ways: - add multiple files at once. The "select files dialog" allows to select multiple files; - add files using drag and drop. Just drop the files from your file manager directly into the web-browser; - add files via URLs. Put the URL and NZBGet downloads the nzb-file and adds it to download queue automatically; - the priority of nzb-file can now be set when adding local-files or URLs; - the history can be cleared completely or selected items can be removed; - file mode is now nzb-file related; - added the ability to queue URLs: - the program automatically downloads nzb-files from given URLs and put them to download queue. - when multiple URLs are added in a short time, they are put into a special URL-queue. - the number of simultaneous URL-downloads are controlled via new option UrlConnections. - with the new option ReloadUrlQueue can be controlled if the URL-queue should be reloaded after the program is restarted (if the URL-queue was not empty). - new switch <-U> for remote-command <--append/-A> to queue an URL. - new subcommand <-U> in the remote command <--list/-L> prints the current URL-queue. - if URL-download fails, the URL is moved into history. - with subcommand <-R> of command <--edit> the failed URL can be returned to URL-queue for redownload. - the remote command <--list/-L> for history can now print the infos for URL history items. - new XML/JSON-RPC command to add an URL or multiple URLs for download. - new XML/JSON-RPC command returns the items from the URL-queue. - the XML/JSON-RPC command was extended to provide infos about URL history items. - the URL-queue obeys the pause-state of download queue. - the URL-downloads support HTTP and HTTPS protocols; - added new field to nzb-info-object. - it is initially set to the cleaned up name of the nzb-file. - the renaming of the group changes this field. - all RPC-methods related to nzb-object return the new field, the old field is now deprecated. - the option now checks the -field instead of (the latter is not changed when the nzb is renamed). - new env-var-parameter for post-processing script; - added options and for remote command <--edit/-E>. With these options the name of group or file can be used in edit-command instead of file ID; - added support for regular expressions (POSIX ERE Syntax) in remote commands <--list/-L> and <--edit/-E> using new subcommands and ; - improved performance of RPC-command ; - added new command to RPC-method to set the order of individual files in the group; - added gzip-support to built-in web-server (including RPC); - added processing of http-request in RPC-server for better support of cross domain requests; - renamed example configuration file and postprocessing script to make the installation easier; - improved the automatic installation () to install all necessary files (not only the binary as it was before); - improved handling of configuration errors: the program now does not terminate on errors but rather logs all of them and uses default option values; - added new XML/JSON-RPC methods , and ; - with active option the NZB considered completed even if there are paused non-par-files (the paused non-par-files are treated the same way as paused par-files): as a result the reprocessable script is called; - added subcommand to remote command <-S/--scan> to scan synchronously (wait until scan completed); - added parameter to XML/JSON-RPC method ; - the command in web-interface now waits for completing of scan before reporting the status; - added remote command <--reload/-O> and JSON/XML-RPC method to reload configuration from disk and reintialize the program; the reload can be performed from web-interface; - JSON/XML-RPC method extended with parameter ; - categories available in web-interface are now configured in program configuration file (nzbget.conf) and can be managed via web-interface on settings page; - updated descriptions in example configuration file; - changes in configuration file: - renamed options , and to , and to avoid confusion with news-server options , and ; - the old option names are still recognized and are automatically renamed when the configuration is saved from web-interface; - also renamed option <$MAINDIR> to ; - extended remote command <--append/-A> with optional parameters: - - adds the file/URL to the top of queue; -

- pauses added files; - - sets category for added nzb-file/URL; - - sets nzb filename for added URL; - the old switches <--category/-K> and <--top/-T> are deprecated but still supported for compatibility; - renamed subcommand of command <--edit/-E> to (the old subcommand is still supported for compatibility); - added new option to setup a script called after a nzb-file is added to queue; - added debug messages for speed meter; - improved the startup script so it can be directly used in without modifications; - fixed: after renaming of a group, the new name was not displayed by remote commands <-L G> and <-C in curses mode>; - fixed incompatibility with OpenSLL 1.0 (thanks to OpenWRT team for the patch); - fixed: RPC-method could return wrong results if the log was filtered with options ; - fixed: free disk space calculated incorrectly on some OSes; - fixed: unrar failure was not always properly detected causing the post-processing to delete not yet unpacked rar-files; - fixed compilation error on recent linux versions; - fixed compilation error on older systems; nzbget-0.8.0: - added priorities; new action for remote command <--edit/-E> to set priorities for groups or individual files; new actions and of RPC-command ; remote command <--list/-L> prints priorities and indicates files or groups being downloaded; ncurses-frontend prints priorities and indicates files or groups being download; new command to set priority of nzb-file from nzbprocess-script; RPC-commands and return priorities and indicate files or groups being downloaded; - added renaming of groups; new subcommand for command <--edit/-E>; new action for RPC-method ; - added new option , which enables syncronisation in speed meter; that makes the indicated speed more accurate by eliminating measurement errors possible due thread conflicts; thanks to anonymous nzbget user for the patch; - improved the parsing of filename from article subject; - option now efficiently works on Windows with NTFS partitions; - added URL-based-authentication as alternative to HTTP-header authentication for XML- and JSON-RPC; - fixed: nzb-files containing umlauts and other special characters could not be parsed - replaced XML-Reader with SAX-Parser - only on POSIX (not on Windows); - fixed incorrect displaying of group sizes bigger than 4GB on many 64-bit OSes; - fixed a bug causing error on decoding of input data in JSON-RPC; - fixed a compilation error on some windows versions; - fixed: par-repair could fail when the filenames were not correctly parsed from article subjects; - fixed a compatibility issue with OpenBSD (and possibly other BSD based systems); added the automatic configuring of required signal handling logic to better support BSD without breaking the compatibility with certain Linux systems; - corrected the address of Free Software Foundation in copyright notice. nzbget-0.7.0: - added history: new option , new remote subcommand for commands (list history entries) and (delete history entries, return history item, postprocess history item), new RPC-command and subcommands , , for command ; - added support for JSON-P (extension of JSON-RPC); - changed the result code returning status for postprocessing script from <1> to <94> (needed to show the proper script status in history); - improved the detection of new files in incoming nzb directory: now the scanner does not rely on system datum, but tracks the changing of file sizes during a last few () seconds instead; - improvements in example postprocessing script: 1) if download contains only par2-files the script do not delete them during cleanup; 2) if download contains only nzb-files the script moves them to incoming nzb-directory for further download; - improved formatting of groups and added time info in curses output mode; - added second pause register, which is independent of main pause-state and therfore is intended for usage from external scripts; that allows to pause download without interfering with options and and scheduler tasks and - they all work with first (default) pause register; new subcommand for commands <--pause/-P> and <--unpause/-U>; new RPC-command and ; existing RPC-commands und renamed to and ; new field in result struct for RPC-command ; existing fields and renamed to and ; old RPC-commands and fields still exist for compatibility; the status output of command <--list/-L> indicates the state of second pause register; key

in curses-frontend can unpause second pause-register; - nzbprocess-script (option ) can now set category and post-processing parameters for nzb-file; - redesigned server pool and par-checker to avoid using of semaphores (which are very platform specific); - added subcommand to remote commands <--pause/-P> and <--unpause/-U> to pause/unpause the scanning of incoming nzb-directory; - added commands and for scheduler option ; - added remote commands and for XML-/JSON-RPC; - command now not only pauses the post-processing queue but also pauses the current post-processing job (par-job or script-job); however the script-job can be paused only after the next line printed to screen; - improved error reporting while parsing nzb-files; - added field to NZBInfo; the field is now returned by XML-/JSON-RPC methods , and ; - improvements in configure script; - added support for platforms without IPv6 (they do not have ); - debug-messages generated on early stages during initializing are now printed to screen/log-file; - messages about obsolete options are now printed to screen/log-file; - imporved example postprocessing script: added support for external configuration file, postprocessing parameters and configuration via web-interface; - option now can contain parameters which must be passed to the script; - added pausing/resuming for post-processor queue; added new modifier to remote commands <--pause/-P> and <--unpause/-U>; added new commands and to XML-/JSON-RPC; extended output of remote command <--list/-L> to indicate paused state of post-processor queue; extended command of XML-/JSON-RPC with field ; - changed the command line syntax for requesting of post-processor queue from <-O> to <-L O> for consistency with other post-queue related commands (<-P O>, <-U O> and <-E O>); - improved example post-processing script: added support for delayed par-check (try unrar first, par-repair if unrar failed); - added modifier to command <-E/--edit> for editing of post-processor-queue; following subcommands are supported: <+/-offset>, , , ; subcommand supports deletion of queued post-jobs and active job as well; deletion of active job means the cancelling of par-check/repair or terminating of post-processing-script (including child processes of the script); updated remote-server to support new edit-subcommands in XML/JSON-RPC; - extended the syntax of option in two ways: 1) it now accepts multiple comma-separated values; 2) an asterix as hours-part means ; - added svn revision number to version string (commands <-v> and <-V>, startup log entry); svn revision is automatically read from svn-repository on each build; - added estimated remaining time and better distinguishing of server state in command <--list/-L>; - added new return code (93) for post-processing script to indicate successful processing; that results in cleaning up of download queue if option is active; - added readonly options , and for usage in processing scripts (options are available as environment variables , and ); - renamed ParStatus constant to for a consistence with ScriptStatus constant , that also affects the results of RPC-command ; - added a new return code <95/POSTPROCESS_NONE> for post-processing scripts for cases when pp-script skips all post-processing work (typically upon a user's request via a pp-parameter); modified the example post-processing script to return the new code instead of a error code when a pp-parameter was set to ; - added field to result of RPC-Command and fields and for command ; - in and output-modes the download speed is now printed with one decimal digit when the speed is lower than 10 KB/s; - improvement in example post-processing script: added check for existence of and command ; - added shell batch file for windows (nzbget-shell.bat); thanks to orbisvicis (orbisvicis@users.sourceforge.net) for the script; - added debian style init script (nzbgetd); thanks to orbisvicis (orbisvicis@users.sourceforge.net) for the script; - added the returning of a proper HTTP error code if the authorization was failed on RPC-calls; thanks to jdembski (jdembski@users.sourceforge.net) for the patch; - changed the sleep-time during the throttling of bandwidth from 200ms to 10ms in order to achieve better uniformity; - modified example postprocessing script to not use the command , which is not always available; thanks to Ger Teunis for the patch; - improved example post-processing script: added the check for existence of destination directory to return a proper ERROR-code (important for reprocessing of history items); - by saving the queue to disk now using relative paths for the list of compeled files to reduce the file's size; - eliminated few compiler warnings on GCC; - fixed: when option was specified and nzbget was started as root, the lockfile was not removed; - fixed: nothing was downloaded when the option was set to <0>; - fixed: base64 decoding function used by RPC-method sometimes failed, in particular when called from Ruby-language; - fixed: JSON-RPC-commands failed, if parameters were placed before method name in the request; - fixed: RPC-method did not work properly on Posix systems (it worked only on Windows); - fixed compilation error when using native curses library on OpenSolaris; - fixed linking error on OpenSolaris when using GnuTLS; - fixed: option did not work; - fixed: seg. fault in service mode on program start (Windows only); - fixed: environment block was not passed correctly to child process, what could result in seg faults (windows only); - fixed: returning the postprocessing exit code <92 - par-check all collections> when there were no par-files results in endless calling of postprocessing script; - fixed compatibility issues with OS/2. nzbget-0.6.0: - added scheduler; new options , , , and ; - added support for postprocess-parameters; new subcommand of remote command to add/modify pp-parameter for group (nzb-file); new XML-/JSON-RPC-subcommand of method for the same purpose; updated example configuration file and example postprocess-script to indicate new method of passing arguments via environment variables; - added subcommands , and to command line switch <-L/--list>, which prints list of files, groups or only status info respectively; extended binary communication protocol to transfer nzb-infos in addition to file-infos; - added new subcommand to edit-command for merging of two (or more) groups (useful after adding pars from a separate nzb-file); - added option to automatically merge nzb-files with the same filename (useful by adding pars from a different source); - added script-processing of files in incoming directory to allow automatic unpacking and queueing of compressed nzb-files; new option ; - added the printing of post-process-parameters for groups in command <--list G>; - added the printing of nzbget version into the log-file on start; - added option to automatically delete already downloaded files from disk if nzb-file was deleted from queue (the download was cancelled); - added option to define the max time allowed for par-repair; - added command <--scan/-S> to execute the scan of nzb-directory on remote server; - changed the method to pass arguments to postprocess/nzbprocess: now using environment variables (old method is still supported for compatibility with existing scripts); - added the passing of nzbget-options to postprocess/nzbprocess scripts as environment variables; - extended the communication between nzbget and post-process-script: collections are now detected even if parcheck is disabled; - added support for delayed par-check/repair: post-process-script can request par-check/repair using special exit codes to repair current collection or all collections; - implemented the normalizing of option names and values in option list; the command <-p> also prints normalized names and values now; that makes the parsing of output of command <-p> for external scripts easier; - replaced option with new option which is now used by all scripts (PostProcess, NzbProcess, TaskX.Process); - improved entering to paused state on connection errors (do not retry failed downloads if pause was activated); - improved error reporting on decoding failures; - improved compatibility of yenc-decoder; - improved the speed of deleting of groups from download queue (by avoiding the saving of queue after the deleting of each individual file); - updated configure-script for better compatibility with FreeBSD; - cleaning up of download queue (option ) and deletion of source nzb-file (option ) after par-repair now works also if par-repair was cancelled (option ); since required par-files were already downloaded the repair in an external tool is possible; - added workaround to avoid hangs in child processes (by starting of postprocess or nzbprocess), observed on uClibC based systems; - fixed: TLS/SSL didn't work in standalone mode; - fixed compatibility issues with Mac OS X; - fixed: not all necessary par2-files were unpaused on first request for par-blocks (although harmless, because additional files were unpaused later anyway); - fixed small memory leak appeared if process-script could not be started; - fixed: configure-script could not detect the right syntax for function on OpenSolaris. - fixed: files downloaded with disabled decoder (option decode=no) sometimes were malformed and could not be decoded; - fixed: empty string parameters did not always work in XML-RPC. nzbget-0.5.1: - improved the check of server responses to prevent unnecessary retrying if the article does not exist on server; - fixed: seg.fault in standalone mode if used without specifying the category (e.g. without switch <-K>); - fixed: download speed indicator could report not-null values in standby-mode (when paused); - fixed: parameter in JSON/XML-RPC was not properly decoded by server, making the setting of a nested category (containing slash or backslash character) via nzbgetweb not possible; nzbget-0.5.0: - added TLS/SSL-support for encrypted communication with news-servers; - added IPv6-support for communication with news-servers as well as for communication between nzbget-server and nzbget-client; - added support for categories to organize downloaded files; - new option to create the subdirectory for each category; - new switch <-K> for usage with switch <-A> to define a category during the adding a file to download queue; - new command in switch <-E> to change the category of nzb-file in download queue; the already downloaded files are automatically moved to new directory if the option is active; - new parameter in XML-/JSON-RPC-command to allow the changing of category via those protocols; - new parameter in a call to post-process-script with category name; - scanning of subdirectories inside incoming nzb-directory to automatically assign category names; nested categories are supported; - added option to connect to servers, that do not accept -command; - added example post-process script for unraring of downloaded files (POSIX only); - added options and useful on slow CPUs; - added option to delete source nzb-file after successful download and parcheck; - switch <-P> can now be used together with switches <-s> and <-D> to start server/daemon in paused state; - changed the type of messages logged in a case of connection errors from to to provide better error reporting; - now using OS-specific line-endings in log-file and brokenlog-file: LF on Posix and CRLF on Windows; - added detection of adjusting of system clock to correct uptime/download time (for NAS-devices, that do not have internal clock and set time from internet after booting, while nzbget may be already running); - added the printing of stack on segmentation faults (if configured with <--enable-debug>, POSIX only); - added option for better debugging on Linux in a case of abnormal program termination; - fixed: configure-script could not automatically find libsigc++ on 64-bit systems; - few other small fixes; nzbget-0.4.1: - to avoid accidental deletion of file in curses-frontend the key now must be pressed in uppercase; - options and in news-server's configuration are now optional; - added the server's name to the detail-log-message, displayed on the start of article's download; - added the option to help to post-process-scripts, which make par-check/-repair on it's own; - improved download-speed-meter: it uses now a little bit less cpu and calculates the speed for the last 30 seconds (instead of 5 seconds), providing better accuracy; Thanks to ydrol for the patch; - reduced CPU-usage in curses-outputmode; Thanks to ydrol for the patch ; - fixed: line-endings in windows-style (CR-LF) in config-file were not read properly; - fixed: trailing spaces in nzb-filenames (before the file's extension) caused errors on windows. Now they will be trimmed; - fixed: XML-RPC and JSON-RPC did not work on Big-Endian-CPUs (ARM, PPC, etc), preventing the using of web-interface; - fixed: umask-option did not allow to enable write-permissions for and ; - fixed: in curses-outputmode the remote-client showed on first screen-update only one item of queue; - fixed: edit-commands with negative offset did not work via XML-RPC (but worked via JSON-RPC); - fixed incompatibility issues with gcc 4.3; Thanks to Paul Bredbury for the patch; - fixed: segmentation fault if a file listed in nzb-file does not have any segments (articles); nzbget-0.4.0: - added the support for XML-RPC and JSON-RPC to easier control the server from other applications; - added web-interface - it is available for download from nzbget-project's home page as a separate package "web-interface"; - added the automatic cleaning up of the download queue (deletion of unneeded paused par-files) after successful par-check/repair - new option ; - added option to allow to filter the (not so important) log-messages from articles' downloads (they have now the type instead of ); - added the gathering of progress-information during par-check; it is available via XML-RPC or JSON-RPC; it is also showed in web-interface; - improvements in internal decoder: added support for yEnc-files without ypart-statement (sometimes used for small files); added support for UU-format; - removed support for uulib-decoder (it did not work well anyway); - replaced the option with the option ; - added detection of errors and (special case for NNTPCache-server) to consider them as connect-errors (and therefore not count as retries); - added check for incomplete articles (also mostly for NNTPCache-server) to differ such errors from CrcErrors (better error reporting); - improved error-reporting on moving of completed files from tmp- to dst-directory and added code to move files across drives if renaming fails; - improved handling of nzb-files with multiple collections in par-checker; - improved the parchecker: added the detection and processing of files splitted after parring; - added the queueing of post-process-scripts and waiting for script's completion before starting of a next job in postprocessor (par-job or script) to provide more balanced cpu utilization; - added the redirecting of post-process-script's output to log; new option to specify the default message-kind for unformatted log-messages; - added the returning of script-output by command via XML-RPC and JSON-RPC; the script-output is also showed in web-interface; - added the saving and restoring of the post-processor-queue (if server was stopped before all items were processed); new option ; - added new parameter to postprocess-script to indicate if any of par-jobs for the same nzb-file failed; - added remote command (switch O/--post) to request the post-processor-queue from server; - added remote command (switch -W/--write) to write messages to server's log; - added option to automatically pause the download on low disk space; - fixed few incompatibility-issues with unslung-platform on nslu2 (ARM); - fixed: articles with trailing text after binary data caused the decode failures and the reporting of CRC-errors; - fixed: dupecheck could cause seg.faults when all articles for a file failed; - fixed: by dupe-checking of files contained in nzb-file the files with the same size were ignored (not deleted from queue); - updated libpar2-patch for msvc to fix a segfault in libpar2 (windows only); - fixed: by registering the service on windows the fullpath to nzbget.exe was not always added to service's exename, making the registered service unusable; - fixed: the pausing of a group could cause the start of post-processing for that group; - fixed: too many info-messages could be printed during par-check (appeared on posix only); nzbget-0.3.1: - Greatly reduced the memory consumption by keeping articles' info on disk until the file download starts; - Implemented decode-on-the-fly-technique to reduce disk-io; downloaded and decoded data can also be saved directly to the destination file (without any intermediate files at all); this eliminates the necessity of joining of articles later (option "DirectWrite"); - Improved communication with news-servers: connections are now keeped open until all files are downloaded (or server paused); this eliminates the need for establishing of connections and authorizations for each article and improves overal download speed; - Significantly better download speed is now possible on fast connection; it was limited by delays on starting of new articles' downloads; the synchronisation mechanism was reworked to fix this issue; - Download speed meter is much more accurate, especially on fast connections; this also means better speed throttling; - Speed optimisations in internal decoder (up to 25% faster); - CRC-calculation can be bypassed to increase performance on slow CPUs (option "CrcCheck"); - Improved parsing of artcile's subject for better extracting of filename part from it and implemented a fallback-option if the parsing was incorrect; - Improved dupe check for files from the same nzb-request to detect reposted files and download only the best from them (if option "DupeCheck" is on); - Articles with incorrect CRC can be treated as "possibly recoverable errors" and relaunched for download (option "RetryOnCrcError"), it is useful if multiple servers are available; - Improved error-check for downloaded articles (crc-check and check for received message-id) decreases the number of broken files; - Extensions in curses-outputmode: added group-view-mode (key "G") to show items in download queue as groups, where one group represents all files from the same nzb-file; the editing of queue works also in group-mode (for all files in this group): pause/unpause/delete/move of groups; - Other extensions in curses-outputmode: key "T" toggles timestamps in log; added output of statistical data: uptime, download-time, average session download speed; - Edit-command accepts more than one ID or range of IDs. E.g: "nzbget -E P 2,6-10,33-39"; The switch "-I" is not used anymore; - Move-actions in Edit-command affect files in a smart order to guarantee that the relative order of files in queue is not changed after the moving; - Extended syntax of edit-command to edit groups (pause/unpause/delete/move of groups). E.g: "nzbget -E G P 2"; - Added option "DaemonUserName" to set the user that the daemon (POSIX only) normally runs at. This allows nzbget daemon to be launched in rc.local (at boot), and download items as a specific user id; Thanks to Thierry MERLE for the patch; - Added option "UMask" to specify permissions for newly created files and dirs (POSIX only); - Communication protocol used between server and client was revised to define the byte order for transferred data. This allows hosts with different endianness to communicate with each other; - Added options "CursesNzbName", "CursesGroup" and "CursesTime" to define initial state of curses-outputmode; - Added option "UpdateInterval" to adjust update interval for Frontend-output (useful in remote-mode to reduce network usage); - Added option "WriteBufferSize" to reduce disk-io (but it could slightly increase memory usage and therefore disabled by default); - List-command prints additional statistical info: uptime, download-time, total amount of downloaded data and average session download speed; - The creation of necessary directories on program's start was extended with automatic creation of all parent directories or error reporting if it was not possible; - Printed messages are now translated to oem-codepage to correctly print filenames with non-english characters (windows only); - Added remote-command "-V (--serverversion)" to print the server's version; - Added option "ThreadLimit" to prevent program from crash if it wants to create too many threads (sometimes could occur in special cases); - Added options "NzbDirInterval" and "NzbDirFileAge" to adjust interval and delay by monitoring of incoming-directory for new nzb-files; - Fixed error on parsing of nzb-files containing percent and other special characters in their names (bug appeared on windows only); - Reformated sample configuration file and changed default optionnames from lowercase to MixedCase for better readability; - Few bugs (seg faults) were fixed. nzbget-0.3.0: - The download queue now contains newsgroup-files to be downloaded instead of nzb-jobs. By adding a new job, the nzb-file is immediately parsed and each newsgroup-file is added to download queue. Each file can therefore be managed separately (paused, deleted or moved); - Current queue state is saved after every change (file is completed or the queue is changed - entries paused, deleted or moved). The state is saved on disk using internal format, which allows fast loading on next program start (no need to parse xml-files again); - The remaining download-size is updated after every article is completed to indicate the correct remaining size and time for total files in queue; - Downloaded articles, which are saved in temp-directory, can be reused on next program start, if the file was not completed (option "continuepartial" in config-file); - Along with uulib the program has internal decoder for yEnc-format. This decoder was necessary, because uulib is so slow, that it prevents using of the program on not so powerful systems like linux-routers (MIPSEL CPU 200 MHz). The new decoder is very fast. It is controlled over option "decoder" in config-file; - The decoder can be completely disabled. In this case all downloaded articles are saved in unaltered form and can be joined with an external program; UUDeview is one of them; - If download of article fails, the program attempts to download it again so many times, what the option "retries" in config-file says. This works even if no servers with level higher than "0" defined. After each retry the next server-level is used, if there are no more levels, the program switches to level "0" again. The pause between retries can be set with config-option "retryinterval"; - If despite of a stated connection-timeout (it can be changed via config-option "connectiontimeout") connection hangs, the program tries to cancel the connection (after "terminatetimeout" seconds). If it doesn't work the download thread is killed and the article will be redownloaded in a new thread. This ensures, that there are no long-time hanging connections and all articles are downloaded, when a time to rejoin file comes; - Automatic par-checking and repairing. Only reuired par-files are downloaded. The program uses libpar2 and does not require any external tools. The big advantage of library is, that it allows to continue par-check after new par-blocks were downloaded. This were not possible with external par2cmdline-tool; - There is a daemon-mode now (command-line switch "-D" (--daemon)). In this mode a lock-file (default location "/tmp/nzbget.lock", can be changed via option "lockfile") contains PID of daemon; - The format of configuration-file was changed from xml to more common text-format. It allows also using of variables like "tempdir=${MAINDIR}/tmp"; - Any option of config-file can be overwritten via command-line switch "-o" (--option). This includes also the definition of servers. This means that the program can now be started without a configuration-file at all (all required options must be passed via command-line); - The command-line switches were revised. The dedicated switches to change options in config-file were eliminated, since any option can now be changed via switch "-o" (--option); - If the name of configuration-file was not passed via command-line the program search it in following locations: "~/.nzbget", "/etc/nzbget.conf", "/usr/etc/nzbget.conf", "/usr/local/etc/nzbget.conf", "/opt/etc/nzbget.conf"; - The new command-line switch "-n" (--noconfigfile) prevents the loading of a config-file. All required config-options must be passed via command-line (switch "-o" (--option)); - To start the program in server mode either "-s" (--server) or "-D" (--daemon) switch must be used. If the program started without any parameters it prints help-screen. There is no a dedicated switch to start in a standalone mode. If switches "-s" and "-D" are omitted and none of client-request-switches used the standalone mode is default. This usage of switches is more common to programs like "wget". To add a file to server's download queue use switch "-A" (--append) and a name of nzb-file as last command-line parameter; - There is a new switch "-Q" (--quit) to gracefully stop server. BTW the SIGKIL-signal is now handled appropriately, so "killall nzbget" is also OK, where "killall -9 nzbget" terminates server immediately (helpful if it hangs, but it shouldn't); - With new switch "-T" (--top) the file will be added to the top of download queue. Use it with switch "-A" (--append); - The download queue can be edited via switch "-E" (--edit). It is possible to pause, unpause, delete and move files in queue. The IDs of file(s) to be affected are passed via switch "-I" (fileid), either one ID or a range in a form "IDForm-IDTo". This also means, that every file in queue have ID now; - The switch "-L" (--list) prints IDs of files consequently. It prints also name, size, percentage of completing and state (paused or not) of each file. Plus summary info: number of files, total remaining size and size of paused files, server state (paused or running), number of threads on server, current speed limit; - With new switch "-G" (--log) the last N lines printed to server's screen-log, can be printed on client. The max number of lines which can be returned from servers depends on option "logbuffersize"; - The redesigned Frontends (known as outputmodes "loggable", "colored" and "curses") can connect to (remote) server and behave as if you were running server-instance of program itself (command-line switch "-C" (--connect)). The log-output updates constantly and even all control-functions in ncurses-mode works: pause/unpause server, set download rate limit, edit of queue (pause/unpause, delete, move entries). The number of connected clients is not limited. The "outputmode" on a client can be set independently from server. The client-mode is especially useful if the server runs as a daemon; - The writing to log-file can be disabled via option "createlog". The location of log-file controls the option "log-file"; - Switch "-p" (--printconfig) prints the name of configuration file being used and all option/value-pairs, taking into account all used "-o" (--option) - switches; - The communication protocol between server and client was optimized to minimize the size of transferred data. Instead of fixing the size for Filenames in data-structures to 512 bytes only in fact used data are transferred; - Extensions in ncurses-outputmode: scrolling in queue-list works better, navigation in queue with keys Up, Down, PgUp, PgDn, Home, End. Keys to move entries are "U" (move up), "N" (move down), "T" (move to top), "B" (move to bottom). "P" to pause/unpause file. The size, percentage 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 - 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. There are new configure-options now: "--disable-uulib" to compile the program without uulib; "--disable-ncurses" to disable ncurses-support (eliminates necessity of ncurses-libs), useful on embedded systems with little resources; "--disable-parcheck" to compile without par-check; - The algorithm for parsing of nzb-files now uses XMLReader instead of DOM-Parser to minimize memory usage (no mor needs to build complete DOM-tree in memory). Thanks to Thierry MERLE for the patch; - The log-file contains now thread-ID for all entry-types and additionally for debug-entries: filename, line number and function's name of source code, where the message was printed. Debug-messages can be disabled in config-file (option "debugtarget") like other messages; - The program is now compatible with windows. Project file for MS Visual C++ 2005 is included. Use "nzbget -install" and "nzbget -remove" to install/remove nzbget-Service. Servers and clients can run on diferrent operating systems; - Improved compatibility with POSIX systems; Tested on: - Linux Debian 3.1 on x86; - Linux BusyBox with uClibc on MIPSEL; - PC-BSD 1.4 (based on FreeBSD 6.2) on x86; - Solaris 10 on x86; - Many memory-leaks and thread issues were fixed; - The program was thoroughly worked over. Almost every line of code was revised. nzbget-0.2.3 - Fixed problem with losing connection to newsserver after too long idle time - Added functionality for dumping lots of debug info nzbget-0.2.2 - Added Florian Penzkofers fix for FreeBSD, exchanging base functionality in SingleServerPool.cpp with a more elegant solution - Added functionality for forcing answer to reloading queue upon startup of server + use -y option to force from command-line + use "reloadqueue" option in nzbget.cfg to control behavior - Added nzbget.cfg options to control where info, warnings and errors get directed to (either screen, log or both) - Added option "createbrokenfilelog" in nzbget.cfg nzbget-0.2.1 - Changed and extended the TCP client/server interface - Added timeout on sockets which prevents certain types of nzbget hanging - Added Kristian Hermansen's patch for renaming broken files nzbget-0.2.0 - Moved 0.1.2-alt4 to a official release as 0.2.0 - Small fixes nzbget-0.1.2-alt4 - implemented tcp/ip communication between client & server (removing the rather defunct System V IPC) - added queue editing functionality in server-mode nzbget-0.1.2-alt1 - added new ncurses frontend - added server/client-mode (using System V IPC) - added functionality for queueing download requests nzbget-0.1.2 - performance-improvements - commandline-options - fixes nzbget-0.1.1 - new output - fixes nzbget-0.1.0a - compiling-fixes nzbget-0.1.0 - initial release nzbget-16.4/pubkey.pem0000644000175000017500000000070312630544544014562 0ustar andreasandreas-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8m960JctNp/i7cnaNzW9 CINq4TJ0c5mkTn9pnRR5h3BPf9FhOyeCm8YipAqj1GN5Ct8UfN8GZ6ChwS1ebMSL WePXFKS9URDyCBRRhnPLQTgqOoBYD7kr7wPPekYSheT2jajYJtQu6DV+zf49hHl1 8z7xGd2ihNmrPBG2Mo72E8zDrMyMbipjPXAJE1+/IrkGTVBtaaiht6qfwty6Imbx 74k1k5/ZyYdTrdE2ar94sbfrFIM8BAm733VoBz0Y3Ncqnx5LDkXLxg/wEf536l6+ ypZwCf+d46E17As0D0v+hOTD4ZA0hLZrqmb09YwWwyDyoWI090zsJvA+NDtK/xSk twIDAQAB -----END PUBLIC KEY----- nzbget-16.4/Makefile.am0000644000175000017500000003417512630544544014626 0ustar andreasandreas# # This file is part of nzbget # # Copyright (C) 2008-2015 Andrey Prygunkov # # 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. # # bin_PROGRAMS = nzbget nzbget_SOURCES = \ daemon/connect/Connection.cpp \ daemon/connect/Connection.h \ daemon/connect/TLS.cpp \ daemon/connect/TLS.h \ daemon/connect/WebDownloader.cpp \ daemon/connect/WebDownloader.h \ daemon/extension/FeedScript.cpp \ daemon/extension/FeedScript.h \ daemon/extension/NzbScript.cpp \ daemon/extension/NzbScript.h \ daemon/extension/PostScript.cpp \ daemon/extension/PostScript.h \ daemon/extension/QueueScript.cpp \ daemon/extension/QueueScript.h \ daemon/extension/ScanScript.cpp \ daemon/extension/ScanScript.h \ daemon/extension/SchedulerScript.cpp \ daemon/extension/SchedulerScript.h \ daemon/extension/ScriptConfig.cpp \ daemon/extension/ScriptConfig.h \ daemon/feed/FeedCoordinator.cpp \ daemon/feed/FeedCoordinator.h \ daemon/feed/FeedFile.cpp \ daemon/feed/FeedFile.h \ daemon/feed/FeedFilter.cpp \ daemon/feed/FeedFilter.h \ daemon/feed/FeedInfo.cpp \ daemon/feed/FeedInfo.h \ daemon/frontend/ColoredFrontend.cpp \ daemon/frontend/ColoredFrontend.h \ daemon/frontend/Frontend.cpp \ daemon/frontend/Frontend.h \ daemon/frontend/LoggableFrontend.cpp \ daemon/frontend/LoggableFrontend.h \ daemon/frontend/NCursesFrontend.cpp \ daemon/frontend/NCursesFrontend.h \ daemon/main/CommandLineParser.cpp \ daemon/main/CommandLineParser.h \ daemon/main/DiskService.cpp \ daemon/main/DiskService.h \ daemon/main/Maintenance.cpp \ daemon/main/Maintenance.h \ daemon/main/nzbget.cpp \ daemon/main/nzbget.h \ daemon/main/Options.cpp \ daemon/main/Options.h \ daemon/main/Scheduler.cpp \ daemon/main/Scheduler.h \ daemon/main/StackTrace.cpp \ daemon/main/StackTrace.h \ daemon/nntp/ArticleDownloader.cpp \ daemon/nntp/ArticleDownloader.h \ daemon/nntp/ArticleWriter.cpp \ daemon/nntp/ArticleWriter.h \ daemon/nntp/Decoder.cpp \ daemon/nntp/Decoder.h \ daemon/nntp/NewsServer.cpp \ daemon/nntp/NewsServer.h \ daemon/nntp/NNTPConnection.cpp \ daemon/nntp/NNTPConnection.h \ daemon/nntp/ServerPool.cpp \ daemon/nntp/ServerPool.h \ daemon/nntp/StatMeter.cpp \ daemon/nntp/StatMeter.h \ daemon/postprocess/Cleanup.cpp \ daemon/postprocess/Cleanup.h \ daemon/postprocess/DupeMatcher.cpp \ daemon/postprocess/DupeMatcher.h \ daemon/postprocess/ParChecker.cpp \ daemon/postprocess/ParChecker.h \ daemon/postprocess/ParCoordinator.cpp \ daemon/postprocess/ParCoordinator.h \ daemon/postprocess/ParParser.cpp \ daemon/postprocess/ParParser.h \ daemon/postprocess/ParRenamer.cpp \ daemon/postprocess/ParRenamer.h \ daemon/postprocess/PrePostProcessor.cpp \ daemon/postprocess/PrePostProcessor.h \ daemon/postprocess/Unpack.cpp \ daemon/postprocess/Unpack.h \ daemon/queue/DiskState.cpp \ daemon/queue/DiskState.h \ daemon/queue/DownloadInfo.cpp \ daemon/queue/DownloadInfo.h \ daemon/queue/DupeCoordinator.cpp \ daemon/queue/DupeCoordinator.h \ daemon/queue/HistoryCoordinator.cpp \ daemon/queue/HistoryCoordinator.h \ daemon/queue/NZBFile.cpp \ daemon/queue/NZBFile.h \ daemon/queue/QueueCoordinator.cpp \ daemon/queue/QueueCoordinator.h \ daemon/queue/QueueEditor.cpp \ daemon/queue/QueueEditor.h \ daemon/queue/Scanner.cpp \ daemon/queue/Scanner.h \ daemon/queue/UrlCoordinator.cpp \ daemon/queue/UrlCoordinator.h \ daemon/remote/BinRpc.cpp \ daemon/remote/BinRpc.h \ daemon/remote/MessageBase.h \ daemon/remote/RemoteClient.cpp \ daemon/remote/RemoteClient.h \ daemon/remote/RemoteServer.cpp \ daemon/remote/RemoteServer.h \ daemon/remote/WebServer.cpp \ daemon/remote/WebServer.h \ daemon/remote/XmlRpc.cpp \ daemon/remote/XmlRpc.h \ daemon/util/Log.cpp \ daemon/util/Log.h \ daemon/util/Observer.cpp \ daemon/util/Observer.h \ daemon/util/Script.cpp \ daemon/util/Script.h \ daemon/util/Thread.cpp \ daemon/util/Thread.h \ daemon/util/Service.cpp \ daemon/util/Service.h \ daemon/util/Util.cpp \ daemon/util/Util.h \ code_revision.cpp if WITH_PAR2 nzbget_SOURCES += \ lib/par2/commandline.cpp \ lib/par2/commandline.h \ lib/par2/crc.cpp \ lib/par2/crc.h \ lib/par2/creatorpacket.cpp \ lib/par2/creatorpacket.h \ lib/par2/criticalpacket.cpp \ lib/par2/criticalpacket.h \ lib/par2/datablock.cpp \ lib/par2/datablock.h \ lib/par2/descriptionpacket.cpp \ lib/par2/descriptionpacket.h \ lib/par2/diskfile.cpp \ lib/par2/diskfile.h \ lib/par2/filechecksummer.cpp \ lib/par2/filechecksummer.h \ lib/par2/galois.cpp \ lib/par2/galois.h \ lib/par2/letype.h \ lib/par2/mainpacket.cpp \ lib/par2/mainpacket.h \ lib/par2/md5.cpp \ lib/par2/md5.h \ lib/par2/par2cmdline.h \ lib/par2/par2creatorsourcefile.cpp \ lib/par2/par2creatorsourcefile.h \ lib/par2/par2fileformat.cpp \ lib/par2/par2fileformat.h \ lib/par2/par2repairer.cpp \ lib/par2/par2repairer.h \ lib/par2/par2repairersourcefile.cpp \ lib/par2/par2repairersourcefile.h \ lib/par2/parheaders.cpp \ lib/par2/parheaders.h \ lib/par2/recoverypacket.cpp \ lib/par2/recoverypacket.h \ lib/par2/reedsolomon.cpp \ lib/par2/reedsolomon.h \ lib/par2/verificationhashtable.cpp \ lib/par2/verificationhashtable.h \ lib/par2/verificationpacket.cpp \ lib/par2/verificationpacket.h endif AM_CPPFLAGS = \ -I$(srcdir)/daemon/connect \ -I$(srcdir)/daemon/extension \ -I$(srcdir)/daemon/feed \ -I$(srcdir)/daemon/frontend \ -I$(srcdir)/daemon/main \ -I$(srcdir)/daemon/nntp \ -I$(srcdir)/daemon/postprocess \ -I$(srcdir)/daemon/queue \ -I$(srcdir)/daemon/remote \ -I$(srcdir)/daemon/util \ -I$(srcdir)/lib/par2 if WITH_TESTS nzbget_SOURCES += \ lib/catch/catch.h \ tests/suite/TestMain.cpp \ tests/suite/TestMain.h \ tests/suite/TestUtil.cpp \ tests/suite/TestUtil.h \ tests/main/CommandLineParserTest.cpp \ tests/main/OptionsTest.cpp \ tests/feed/FeedFilterTest.cpp \ tests/postprocess/ParCheckerTest.cpp \ tests/postprocess/ParRenamerTest.cpp \ tests/queue/NZBFileTest.cpp \ tests/util/UtilTest.cpp AM_CPPFLAGS += \ -I$(srcdir)/lib/catch \ -I$(srcdir)/tests/suite endif EXTRA_DIST = \ $(windows_FILES) \ $(osx_FILES) \ $(linux_FILES) \ $(testdata_FILES) windows_FILES = \ daemon/windows/NTService.cpp \ daemon/windows/NTService.h \ daemon/windows/win32.h \ daemon/windows/WinConsole.cpp \ daemon/windows/WinConsole.h \ nzbget.vcproj \ windows/nzbget-command-shell.bat \ windows/install-update.bat \ windows/README-WINDOWS.txt \ windows/package-info.json \ windows/resources/mainicon.ico \ windows/resources/nzbget.rc \ windows/resources/resource.h \ windows/resources/trayicon_idle.ico \ windows/resources/trayicon_paused.ico \ windows/resources/trayicon_working.ico \ windows/setup/nzbget-setup.nsi \ windows/setup/install.bmp \ windows/setup/uninstall.bmp 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/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 linux_FILES = \ linux/installer.sh \ linux/install-update.sh \ linux/package-info.json \ linux/build-info.txt \ linux/build-nzbget \ linux/build-unpack doc_FILES = \ lib/par2/AUTHORS \ lib/par2/README \ README \ ChangeLog \ COPYING exampleconf_FILES = \ 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/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/lib/raphael.js \ webui/lib/raphael.min.js \ webui/lib/elycharts.js \ webui/lib/elycharts.min.js \ webui/img/icons.png \ webui/img/icons-2x.png \ webui/img/transmit.gif \ webui/img/transmit-file.gif \ webui/img/favicon.ico \ webui/img/download-anim-green-2x.png \ webui/img/download-anim-orange-2x.png \ webui/img/transmit-reload-2x.gif scripts_FILES = \ scripts/EMail.py \ scripts/Logger.py testdata_FILES = \ tests/testdata/dupematcher1/testfile.part01.rar \ tests/testdata/dupematcher1/testfile.part24.rar \ tests/testdata/dupematcher2/testfile.part04.rar \ tests/testdata/dupematcher2/testfile.part43.rar \ tests/testdata/nzbfile/dotless.nzb \ tests/testdata/nzbfile/dotless.txt \ tests/testdata/nzbfile/plain.nzb \ tests/testdata/nzbfile/plain.txt \ tests/testdata/parchecker/crc.txt \ tests/testdata/parchecker/testfile.dat \ tests/testdata/parchecker/testfile.nfo \ tests/testdata/parchecker/testfile.par2 \ tests/testdata/parchecker/testfile.vol00+1.PAR2 \ tests/testdata/parchecker/testfile.vol01+2.PAR2 \ tests/testdata/parchecker/testfile.vol03+3.PAR2 # Install dist_doc_DATA = $(doc_FILES) exampleconfdir = $(datadir)/nzbget dist_exampleconf_DATA = $(exampleconf_FILES) webuidir = $(datadir)/nzbget nobase_dist_webui_DATA = $(webui_FILES) scriptsdir = $(datadir)/nzbget nobase_dist_scripts_SCRIPTS = $(scripts_FILES) # Note about "sed": # We need to make some changes in installed files. # On Linux "sed" has option "-i" for in-place-edit. Unfortunateley the BSD version of "sed" # has incompatible syntax. To solve the problem we perform in-place-edit in three steps: # 1) copy the original file to original.temp (delete existing original.temp, if any); # 2) sed < original.temp > original # 3) delete original.temp # These steps ensure that the output file has the same permissions as the original file. # 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:^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/scripts:installed to $(scriptsdir)/scripts:' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf" rm "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" # Install configuration files into /etc # (only if they do not exist there to prevent override by update) install-conf: if test ! -f "$(DESTDIR)$(sysconfdir)/nzbget.conf" ; then \ $(mkinstalldirs) "$(DESTDIR)$(sysconfdir)" ; \ cp "$(DESTDIR)$(exampleconfdir)/nzbget.conf" "$(DESTDIR)$(sysconfdir)/nzbget.conf" ; \ fi uninstall-conf: rm -f "$(DESTDIR)$(sysconfdir)/nzbget.conf" # Determining git revision: # 1) If directory ".git" exists we take revision from git log. # File is recreated only if revision number was changed. # 2) If directory ".git" doesn't exists we keep and reuse file "code_revision.cpp", # which was possibly created early. # 3) If neither directory ".git" nor file "code_revision.cpp" are available # we create new file "code_revision.c" with empty revision number. code_revision.cpp: FORCE @ if test -d ./.git ; then \ B="$(shell git branch | sed -n -e 's/^\* \(.*\)/\1/p')"; \ M="$(shell git status --porcelain)" ; \ if test "$$M" != "" ; then \ M="M" ; \ fi ; \ if test "$$B" = "master" ; then \ V="$$M" ; \ elif test "$$B" = "develop" ; then \ V="$(shell git rev-list HEAD | wc -l | xargs)" ; \ V="$${V}$$M" ; \ else \ V="$(shell git rev-list HEAD | wc -l | xargs)" ; \ V="$${V}$$M ($$B)" ; \ fi ; \ H="$(shell test -f ./code_revision.cpp && head -n 1 code_revision.cpp)"; \ if test "/* $$V */" != "$$H" ; then \ ( \ echo "/* $$V */" ;\ echo "/* This file is automatically regenerated on each build. Do not edit it. */" ;\ echo "const char* code_revision(void)" ;\ echo "{" ;\ echo " const char* revision = \"$$V\";" ;\ echo " return revision;" ;\ echo "}" ;\ ) > code_revision.cpp ; \ fi \ elif test -f ./code_revision.cpp ; then \ test "ok, reuse existing file"; \ else \ ( \ echo "/* */" ;\ echo "/* This file is automatically regenerated on each build. Do not edit it. */" ;\ echo "const char* code_revision(void)" ;\ echo "{" ;\ echo " const char* revision = \"\";" ;\ echo " return revision;" ;\ echo "}" ;\ ) > code_revision.cpp ; \ fi FORCE: # Ignore "code_revision.cpp" in distcleancheck distcleancheck_listfiles = \ find . -type f -exec sh -c 'test -f $(srcdir)/$$1 || echo $$1' \ sh '{}' ';' clean-bak: rm *~ # Fix premissions dist-hook: find $(distdir)/daemon -type f -print -exec chmod -x {} \; find $(distdir)/webui -type f -print -exec chmod -x {} \; find $(distdir)/lib -type f -print -exec chmod -x {} \; find $(distdir)/tests -type f -print -exec chmod -x {} \; nzbget-16.4/.gitattributes0000644000175000017500000000161512630544544015456 0ustar andreasandreas* text=auto # Use CRLF for certain Windows files. *.vcproj eol=crlf *.sln eol=crlf *.bat eol=crlf README-WINDOWS.txt eol=crlf nzbget-setup.nsi eol=crlf windows/package-info.json eol=crlf windows/resources/resource.h eol=crlf windows/resources/nzbget.rc eol=crlf # Configure GitHub's language detector lib/* linguist-vendored linguist-language=C++ webui/lib/* linguist-vendored Makefile.in linguist-vendored configure linguist-vendored config.sub linguist-vendored aclocal.m4 linguist-vendored config.guess linguist-vendored depcomp linguist-vendored install-sh linguist-vendored missing linguist-vendored configure.ac linguist-vendored=false Makefile.am linguist-vendored=false nzbget-16.4/config.h.in0000644000175000017500000001337112630544544014610 0ustar andreasandreas/* config.h.in. Generated from configure.ac by autoheader. */ /* Define to 1 to include debug-code */ #undef DEBUG /* Define to 1 if deleting of files during reading of directory is not properly supported by OS */ #undef DIRBROWSER_SNAPSHOT /* Define to 1 to not use curses */ #undef DISABLE_CURSES /* Define to 1 to disable gzip-support */ #undef DISABLE_GZIP /* Define to 1 to disable par-verification and repair */ #undef DISABLE_PARCHECK /* Define to 1 to not use TLS/SSL */ #undef DISABLE_TLS /* Define to 1 to enable unit and integration tests */ #undef ENABLE_TESTS /* Define to the name of macro which returns the name of function being compiled */ #undef FUNCTION_MACRO_NAME /* Define to 1 to create stacktrace on segmentation faults */ #undef HAVE_BACKTRACE /* Define to 1 if ctime_r takes 2 arguments */ #undef HAVE_CTIME_R_2 /* Define to 1 if ctime_r takes 3 arguments */ #undef HAVE_CTIME_R_3 /* Define to 1 if you have the header file. */ #undef HAVE_CURSES_H /* Define to 1 if you have the header file, and it defines `DIR'. */ #undef HAVE_DIRENT_H /* Define to 1 if you have the header file. */ #undef HAVE_ENDIAN_H /* Define to 1 if fdatasync is supported */ #undef HAVE_FDATASYNC /* Define to 1 if fseeko (and presumably ftello) exists and is declared. */ #undef HAVE_FSEEKO /* Define to 1 if F_FULLFSYNC is supported */ #undef HAVE_FULLFSYNC /* Define to 1 if getaddrinfo is supported */ #undef HAVE_GETADDRINFO /* Define to 1 if gethostbyname_r is supported */ #undef HAVE_GETHOSTBYNAME_R /* Define to 1 if gethostbyname_r takes 3 arguments */ #undef HAVE_GETHOSTBYNAME_R_3 /* Define to 1 if gethostbyname_r takes 5 arguments */ #undef HAVE_GETHOSTBYNAME_R_5 /* Define to 1 if gethostbyname_r takes 6 arguments */ #undef HAVE_GETHOSTBYNAME_R_6 /* Define to 1 if you have the `getopt' function. */ #undef HAVE_GETOPT /* Define to 1 if you have the header file. */ #undef HAVE_GETOPT_H /* Define to 1 if getopt_long is supported */ #undef HAVE_GETOPT_LONG /* Define to 1 if you have the header file. */ #undef HAVE_INTTYPES_H /* Define to 1 to use GnuTLS library for TLS/SSL-support. */ #undef HAVE_LIBGNUTLS /* Define to 1 if you have the `memcpy' function. */ #undef HAVE_MEMCPY /* Define to 1 if you have the header file. */ #undef HAVE_MEMORY_H /* Define to 1 if you have the header file. */ #undef HAVE_NCURSES_H /* Define to 1 if you have the header file. */ #undef HAVE_NCURSES_NCURSES_H /* Define to 1 if you have the header file, and it defines `DIR'. */ #undef HAVE_NDIR_H /* Define to 1 to use OpenSSL library for TLS/SSL-support. */ #undef HAVE_OPENSSL /* Define to 1 if you have the header file. */ #undef HAVE_REGEX_H /* Define to 1 if _SC_NPROCESSORS_ONLN is present in unistd.h */ #undef HAVE_SC_NPROCESSORS_ONLN /* Define to 1 if stdbool.h conforms to C99. */ #undef HAVE_STDBOOL_H /* Define to 1 if you have the header file. */ #undef HAVE_STDINT_H /* Define to 1 if you have the header file. */ #undef HAVE_STDIO_H /* Define to 1 if you have the header file. */ #undef HAVE_STDLIB_H /* Define to 1 if you have the `strcasecmp' function. */ #undef HAVE_STRCASECMP /* Define to 1 if you have the `strchr' function. */ #undef HAVE_STRCHR /* Define to 1 if you have the `stricmp' function. */ #undef HAVE_STRICMP /* Define to 1 if you have the header file. */ #undef HAVE_STRINGS_H /* Define to 1 if you have the header file. */ #undef HAVE_STRING_H /* Define to 1 if you have the header file, and it defines `DIR'. */ #undef HAVE_SYS_DIR_H /* Define to 1 if you have the header file, and it defines `DIR'. */ #undef HAVE_SYS_NDIR_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_PRCTL_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_STAT_H /* Define to 1 if you have the header file. */ #undef HAVE_SYS_TYPES_H /* Define to 1 if you have the header file. */ #undef HAVE_UNISTD_H /* Define to 1 if variadic macros are supported */ #undef HAVE_VARIADIC_MACROS /* Define to 1 if the system has the type `_Bool'. */ #undef HAVE__BOOL /* Define to 1 to exclude debug-code */ #undef NDEBUG /* Name of package */ #undef PACKAGE /* Define to the address where bug reports for this package should be sent. */ #undef PACKAGE_BUGREPORT /* Define to the full name of this package. */ #undef PACKAGE_NAME /* Define to the full name and version of this package. */ #undef PACKAGE_STRING /* Define to the one symbol short name of this package. */ #undef PACKAGE_TARNAME /* Define to the version of this package. */ #undef PACKAGE_VERSION /* Define to 1 to install an empty signal handler for SIGCHLD */ #undef SIGCHLD_HANDLER /* Determine what socket length (socklen_t) data type is */ #undef SOCKLEN_T /* Define to 1 if you have the ANSI C header files. */ #undef STDC_HEADERS /* Version number of package */ #undef VERSION /* Define to 1 if your processor stores words with the most significant byte first (like Motorola and SPARC, unlike Intel and VAX). */ #undef WORDS_BIGENDIAN /* Number of bits in a file offset, on hosts where this is settable. */ #undef _FILE_OFFSET_BITS /* Define to 1 to make fseeko visible on some hosts (e.g. glibc 2.2). */ #undef _LARGEFILE_SOURCE /* Define for large files, on AIX-style hosts. */ #undef _LARGE_FILES /* Define to empty if `const' does not conform to ANSI C. */ #undef const /* Define to `__inline__' or `__inline' if that's what the C compiler calls it, or to nothing if 'inline' is not supported under any name. */ #ifndef __cplusplus #undef inline #endif /* Define to `unsigned int' if does not define. */ #undef size_t nzbget-16.4/webui/0000755000175000017500000000000012630544544013673 5ustar andreasandreasnzbget-16.4/webui/downloads.js0000644000175000017500000006116312630544544016232 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2012-2015 Andrey Prygunkov * * 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$ * */ /* * In this module: * 1) Download tab; * 2) Functions for html generation for downloads, also used from other modules (edit and add dialogs). */ /*** DOWNLOADS TAB ***********************************************************/ var Downloads = (new function($) { 'use strict'; // Controls var $DownloadsTable; var $DownloadsTabBadge; var $DownloadsTabBadgeEmpty; var $DownloadQueueEmpty; var $DownloadsRecordsPerPage; var $DownloadsTable_Name; // State var notification = null; var updateTabInfo; var groups; var urls; var nameColumnWidth = null; var minLevel = null; var statusData = { 'QUEUED': { Text: 'QUEUED', PostProcess: false }, 'FETCHING': { Text: 'FETCHING', PostProcess: false }, 'DOWNLOADING': { Text: 'DOWNLOADING', PostProcess: false }, 'QS_QUEUED': { Text: 'QS-QUEUED', PostProcess: true }, 'QS_EXECUTING': { Text: 'QUEUE-SCRIPT', PostProcess: true }, 'PP_QUEUED': { Text: 'PP-QUEUED', PostProcess: true }, 'PAUSED': { Text: 'PAUSED', PostProcess: false }, 'LOADING_PARS': { Text: 'CHECKING', PostProcess: true }, 'VERIFYING_SOURCES': { Text: 'CHECKING', PostProcess: true }, 'REPAIRING': { Text: 'REPAIRING', PostProcess: true }, 'VERIFYING_REPAIRED': { Text: 'VERIFYING', PostProcess: true }, 'RENAMING': { Text: 'RENAMING', PostProcess: true }, 'MOVING': { Text: 'MOVING', PostProcess: true }, 'UNPACKING': { Text: 'UNPACKING', PostProcess: true }, 'EXECUTING_SCRIPT': { Text: 'PROCESSING', PostProcess: true }, 'PP_FINISHED': { Text: 'FINISHED', PostProcess: false } }; this.statusData = statusData; this.init = function(options) { updateTabInfo = options.updateTabInfo; $DownloadsTable = $('#DownloadsTable'); $DownloadsTabBadge = $('#DownloadsTabBadge'); $DownloadsTabBadgeEmpty = $('#DownloadsTabBadgeEmpty'); $DownloadQueueEmpty = $('#DownloadQueueEmpty'); $DownloadsRecordsPerPage = $('#DownloadsRecordsPerPage'); $DownloadsTable_Name = $('#DownloadsTable_Name'); var recordsPerPage = UISettings.read('DownloadsRecordsPerPage', 10); $DownloadsRecordsPerPage.val(recordsPerPage); $('#DownloadsTable_filter').val(''); $DownloadsTable.fasttable( { filterInput: $('#DownloadsTable_filter'), filterClearButton: $("#DownloadsTable_clearfilter"), pagerContainer: $('#DownloadsTable_pager'), infoContainer: $('#DownloadsTable_info'), headerCheck: $('#DownloadsTable > thead > tr:first-child'), infoEmpty: ' ', // this is to disable default message "No records" pageSize: recordsPerPage, maxPages: UISettings.miniTheme ? 1 : 5, pageDots: !UISettings.miniTheme, fillFieldsCallback: fillFieldsCallback, renderCellCallback: renderCellCallback, updateInfoCallback: updateInfo }); $DownloadsTable.on('click', 'a', itemClick); $DownloadsTable.on('click', UISettings.rowSelect ? 'tbody tr' : 'tbody div.check', function(event) { $DownloadsTable.fasttable('itemCheckClick', UISettings.rowSelect ? this : this.parentNode.parentNode, event); }); $DownloadsTable.on('click', 'thead div.check', function() { $DownloadsTable.fasttable('titleCheckClick') }); $DownloadsTable.on('mousedown', Util.disableShiftMouseDown); } this.applyTheme = function() { $DownloadsTable.fasttable('setPageSize', UISettings.read('DownloadsRecordsPerPage', 10), UISettings.miniTheme ? 1 : 5, !UISettings.miniTheme); } this.update = function() { if (!groups) { $('#DownloadsTable_Category').css('width', DownloadsUI.calcCategoryColumnWidth()); } RPC.call('listgroups', [], groups_loaded); } function groups_loaded(_groups) { groups = _groups; prepare(); RPC.next(); } function prepare() { for (var j=0, jl=groups.length; j < jl; j++) { var group = groups[j]; group.postprocess = statusData[group.Status].PostProcess; } } this.redraw = function() { redraw_table(); Util.show($DownloadsTabBadge, groups.length > 0); Util.show($DownloadsTabBadgeEmpty, groups.length === 0 && UISettings.miniTheme); Util.show($DownloadQueueEmpty, groups.length === 0); } this.resize = function() { calcProgressLabels(); } /*** TABLE *************************************************************************/ var SEARCH_FIELDS = ['name', 'status', 'priority', 'category', 'estimated', 'age', 'size', 'remaining']; function redraw_table() { var data = []; for (var i=0; i < groups.length; i++) { var group = groups[i]; group.name = group.NZBName; group.status = DownloadsUI.buildStatusText(group); group.priority = DownloadsUI.buildPriorityText(group.MaxPriority); group.category = group.Category; group.estimated = DownloadsUI.buildEstimated(group); group.size = Util.formatSizeMB(group.FileSizeMB, group.FileSizeLo); group.sizemb = group.FileSizeMB; group.sizegb = group.FileSizeMB / 1024; group.left = Util.formatSizeMB(group.RemainingSizeMB-group.PausedSizeMB, group.RemainingSizeLo-group.PausedSizeLo); group.leftmb = group.RemainingSizeMB-group.PausedSizeMB; group.leftgb = group.leftmb / 1024; group.dupe = DownloadsUI.buildDupeText(group.DupeKey, group.DupeScore, group.DupeMode); var age_sec = new Date().getTime() / 1000 - (group.MinPostTime + UISettings.timeZoneCorrection*60*60); group.age = Util.formatAge(group.MinPostTime + UISettings.timeZoneCorrection*60*60); group.agem = Util.round0(age_sec / 60); group.ageh = Util.round0(age_sec / (60*60)); group.aged = Util.round0(age_sec / (60*60*24)); group._search = SEARCH_FIELDS; var item = { id: group.NZBID, data: group }; data.push(item); } $DownloadsTable.fasttable('update', data); } function fillFieldsCallback(item) { var group = item.data; var status = DownloadsUI.buildStatus(group); var priority = DownloadsUI.buildPriority(group.MaxPriority); var progresslabel = DownloadsUI.buildProgressLabel(group, nameColumnWidth); var progress = DownloadsUI.buildProgress(group, item.data.size, item.data.left, item.data.estimated); var dupe = DownloadsUI.buildDupe(group.DupeKey, group.DupeScore, group.DupeMode); var age = new Date().getTime() / 1000 - (group.MinPostTime + UISettings.timeZoneCorrection*60*60); var propagation = ''; if (group.ActiveDownloads == 0 && age < parseInt(Options.option('PropagationDelay')) * 60) { propagation = 'delayed '; } var name = '' + Util.textToHtml(Util.formatNZBName(group.NZBName)) + ''; var url = ''; if (group.Kind === 'URL') { url = 'URL '; } var health = ''; if (group.Health < 1000 && (!group.postprocess || (group.Status === 'PP_QUEUED' && group.PostTotalTimeSec === 0))) { health = ' health: ' + Math.floor(group.Health / 10) + '% '; } var backup = ''; var backupPercent = calcBackupPercent(group); if (backupPercent > 0) { backup = ' backup: ' + (backupPercent < 10 ? Util.round1(backupPercent) : Util.round0(backupPercent)) + '% '; } var category = Util.textToHtml(group.Category); if (!UISettings.miniTheme) { var info = name + ' ' + url + priority + dupe + health + backup + propagation + progresslabel; item.fields = ['

', status, info, category, item.data.age, progress, item.data.estimated]; } else { var info = '
' + name + '' + url + ' ' + (group.Status === 'QUEUED' ? '' : status) + ' ' + priority + dupe + health + backup + propagation; if (category) { info += ' ' + category + ''; } if (progresslabel) { progress = '
' + progresslabel + '
' + progress; } item.fields = [info, progress]; } } function renderCellCallback(cell, index, item) { if (4 <= index && index <= 7) { cell.className = 'text-right'; } } function calcBackupPercent(group) { var downloadedArticles = group.SuccessArticles + group.FailedArticles; if (downloadedArticles === 0) { return 0; } if (minLevel === null) { for (var i=0; i < Status.status.NewsServers.length; i++) { var server = Status.status.NewsServers[i]; var level = parseInt(Options.option('Server' + server.ID + '.Level')); if (minLevel === null || minLevel > level) { minLevel = level; } } } var backupArticles = 0; for (var j=0; j < group.ServerStats.length; j++) { var stat = group.ServerStats[j]; var level = parseInt(Options.option('Server' + stat.ServerID + '.Level')); if (level > minLevel && stat.SuccessArticles > 0) { backupArticles += stat.SuccessArticles; } } var backupPercent = 0; if (backupArticles > 0) { backupPercent = backupArticles * 100.0 / downloadedArticles; } return backupPercent; } this.recordsPerPageChange = function() { var val = $DownloadsRecordsPerPage.val(); UISettings.write('DownloadsRecordsPerPage', val); $DownloadsTable.fasttable('setPageSize', val); } function updateInfo(stat) { updateTabInfo($DownloadsTabBadge, stat); } function calcProgressLabels() { var progressLabels = $('.label-inline', $DownloadsTable); if (UISettings.miniTheme) { nameColumnWidth = null; progressLabels.css('max-width', ''); return; } progressLabels.hide(); nameColumnWidth = Math.max($DownloadsTable_Name.width(), 50) - 4*2; // 4 - padding of span progressLabels.css('max-width', nameColumnWidth); progressLabels.show(); } /*** EDIT ******************************************************/ function itemClick(e) { e.preventDefault(); e.stopPropagation(); var nzbid = $(this).attr('data-nzbid'); var area = $(this).attr('data-area'); $(this).blur(); DownloadsEditDialog.showModal(nzbid, groups, area); } function editCompleted() { Refresher.update(); if (notification) { Notification.show(notification); notification = null; } } /*** CHECKMARKS ******************************************************/ function checkBuildEditIDList(allowPostProcess, allowUrl, allowEmpty) { var checkedRows = $DownloadsTable.fasttable('checkedRows'); var hasIDs = false; var checkedEditIDs = []; for (var i = 0; i < groups.length; i++) { var group = groups[i]; if (checkedRows[group.NZBID]) { if (group.postprocess && !allowPostProcess) { Notification.show('#Notif_Downloads_CheckPostProcess'); return null; } if (group.Kind === 'URL' && !allowUrl) { Notification.show('#Notif_Downloads_CheckURL'); return null; } checkedEditIDs.push(group.NZBID); } } if (checkedEditIDs.length === 0 && !allowEmpty) { Notification.show('#Notif_Downloads_Select'); return null; } return checkedEditIDs; } /*** TOOLBAR: SELECTED ITEMS ******************************************************/ this.editClick = function() { var checkedEditIDs = checkBuildEditIDList(false, true); if (!checkedEditIDs) { return; } if (checkedEditIDs.length == 1) { DownloadsEditDialog.showModal(checkedEditIDs[0], groups); } else { DownloadsMultiDialog.showModal(checkedEditIDs, groups); } } this.mergeClick = function() { var checkedEditIDs = checkBuildEditIDList(false, false); if (!checkedEditIDs) { return; } if (checkedEditIDs.length < 2) { Notification.show('#Notif_Downloads_SelectMulti'); return; } DownloadsMergeDialog.showModal(checkedEditIDs, groups); } this.pauseClick = function() { var checkedEditIDs = checkBuildEditIDList(false, false); if (!checkedEditIDs) { return; } notification = '#Notif_Downloads_Paused'; RPC.call('editqueue', ['GroupPause', 0, '', checkedEditIDs], editCompleted); } this.resumeClick = function() { var checkedEditIDs = checkBuildEditIDList(false, false); if (!checkedEditIDs) { return; } notification = '#Notif_Downloads_Resumed'; RPC.call('editqueue', ['GroupResume', 0, '', checkedEditIDs], function() { if (Options.option('ParCheck') === 'force') { editCompleted(); } else { RPC.call('editqueue', ['GroupPauseExtraPars', 0, '', checkedEditIDs], editCompleted); } }); } this.deleteClick = function() { var checkedRows = $DownloadsTable.fasttable('checkedRows'); var downloadIDs = []; var postprocessIDs = []; var hasNzb = false; var hasUrl = false; for (var i = 0; i < groups.length; i++) { var group = groups[i]; if (checkedRows[group.NZBID]) { if (group.postprocess) { postprocessIDs.push(group.NZBID); } downloadIDs.push(group.NZBID); hasNzb = hasNzb || group.Kind === 'NZB'; hasUrl = hasUrl || group.Kind === 'URL'; } } if (downloadIDs.length === 0 && postprocessIDs.length === 0) { Notification.show('#Notif_Downloads_Select'); return; } notification = '#Notif_Downloads_Deleted'; var deletePosts = function() { if (postprocessIDs.length > 0) { RPC.call('editqueue', ['PostDelete', 0, '', postprocessIDs], editCompleted); } else { editCompleted(); } }; var deleteGroups = function(command) { if (downloadIDs.length > 0) { RPC.call('editqueue', [command, 0, '', downloadIDs], deletePosts); } else { deletePosts(); } }; DownloadsUI.deleteConfirm(deleteGroups, true, hasNzb, hasUrl); } this.moveClick = function(action) { var checkedEditIDs = checkBuildEditIDList(true, true); if (!checkedEditIDs) { return; } var EditAction = ''; var EditOffset = 0; switch (action) { case 'top': EditAction = 'GroupMoveTop'; checkedEditIDs.reverse(); break; case 'bottom': EditAction = 'GroupMoveBottom'; break; case 'up': EditAction = 'GroupMoveOffset'; EditOffset = -1; break; case 'down': EditAction = 'GroupMoveOffset'; EditOffset = 1; checkedEditIDs.reverse(); break; } notification = ''; RPC.call('editqueue', [EditAction, EditOffset, '', checkedEditIDs], editCompleted); } this.sort = function(order) { var checkedEditIDs = checkBuildEditIDList(true, true, true); notification = '#Notif_Downloads_Sorted'; RPC.call('editqueue', ['GroupSort', 0, order, checkedEditIDs], editCompleted); } }(jQuery)); /*** FUNCTIONS FOR HTML GENERATION (also used from other modules) *****************************/ var DownloadsUI = (new function($) { 'use strict'; // State var categoryColumnWidth = null; var dupeCheck = null; this.fillPriorityCombo = function(combo) { combo.empty(); combo.append(''); combo.append(''); combo.append(''); combo.append(''); combo.append(''); combo.append(''); } this.fillCategoryCombo = function(combo) { combo.empty(); combo.append(''); for (var i=0; i < Options.categories.length; i++) { combo.append($('').text(Options.categories[i])); } } this.buildStatusText = function(group) { var statusText = Downloads.statusData[group.Status].Text; if (statusText === undefined) { statusText = 'Internal error(' + group.Status + ')'; } return statusText; } this.buildStatus = function(group) { var statusText = Downloads.statusData[group.Status].Text; var badgeClass = ''; if (group.postprocess && group.Status !== 'PP_QUEUED' && group.Status !== 'QS_QUEUED') { badgeClass = Status.status.PostPaused && group.MinPriority < 900 ? 'label-warning' : 'label-success'; } else if (group.Status === 'DOWNLOADING' || group.Status === 'FETCHING' || group.Status === 'QS_EXECUTING') { badgeClass = 'label-success'; } else if (group.Status === 'PAUSED') { badgeClass = 'label-warning'; } else if (statusText === undefined) { statusText = 'INTERNAL_ERROR (' + group.Status + ')'; badgeClass = 'label-important'; } return '' + statusText + ''; } this.buildProgress = function(group, totalsize, remaining, estimated) { if (group.Status === 'DOWNLOADING' || (group.postprocess && !(Status.status.PostPaused && group.MinPriority < 900))) { var kind = 'progress-success'; } else if (group.Status === 'PAUSED' || (group.postprocess && !(Status.status.PostPaused && group.MinPriority < 900))) { var kind = 'progress-warning'; } else { var kind = 'progress-none'; } var totalMB = group.FileSizeMB-group.PausedSizeMB; var remainingMB = group.RemainingSizeMB-group.PausedSizeMB; var percent = Math.round((totalMB - remainingMB) / totalMB * 100); var progress = ''; if (group.postprocess) { totalsize = ''; remaining = ''; percent = Math.round(group.PostStageProgress / 10); } if (group.Kind === 'URL') { totalsize = ''; remaining = ''; } if (!UISettings.miniTheme) { progress = '
'+ '
'+ '
'+ '
'+ '
' + totalsize + '
'+ '
' + remaining + '
'+ '
'; } else { progress = '
'+ '
'+ '
'+ '
'+ '
' + (totalsize !== '' ? 'total ' : '') + totalsize + '
'+ '
' + (estimated !== '' ? '[' + estimated + ']': '') + '
'+ '
' + remaining + (remaining !== '' ? ' left' : '') + '
'+ '
'; } return progress; } this.buildEstimated = function(group) { if (group.postprocess) { if (group.PostStageProgress > 0) { return Util.formatTimeLeft(group.PostStageTimeSec / group.PostStageProgress * (1000 - group.PostStageProgress)); } } else if (group.Status !== 'PAUSED' && Status.status.DownloadRate > 0) { return Util.formatTimeLeft((group.RemainingSizeMB-group.PausedSizeMB)*1024/(Status.status.DownloadRate/1024)); } return ''; } this.buildProgressLabel = function(group, maxWidth) { var text = ''; if (group.postprocess && !(Status.status.PostPaused && group.MinPriority < 900)) { switch (group.Status) { case "LOADING_PARS": case "VERIFYING_SOURCES": case "VERIFYING_REPAIRED": case "UNPACKING": case "RENAMING": case "EXECUTING_SCRIPT": text = group.PostInfoText; break; } } return text !== '' ? ' ' + text + '' : ''; } this.buildPriorityText = function(priority) { switch (priority) { case 0: return ''; case 900: return 'force priority'; case 100: return 'very high priority'; case 50: return 'high priority'; case -50: return 'low priority'; case -100: return 'very low priority'; default: return 'priority: ' + priority; } } this.buildPriority = function(priority) { switch (priority) { case 0: return ''; case 900: return ' force priority'; case 100: return ' very high priority'; case 50: return ' high priority'; case -50: return ' low priority'; case -100: return ' very low priority'; } if (priority > 0) { return ' priority: ' + priority + ''; } else if (priority < 0) { return ' priority: ' + priority + ''; } } function formatDupeText(dupeKey, dupeScore, dupeMode) { dupeKey = dupeKey.replace('rageid=', ''); dupeKey = dupeKey.replace('imdb=', ''); dupeKey = dupeKey.replace('series=', ''); dupeKey = dupeKey.replace('nzb=', '#'); dupeKey = dupeKey.replace('=', ' '); dupeKey = dupeKey === '' ? 'title' : dupeKey; return dupeKey; } this.buildDupeText = function(dupeKey, dupeScore, dupeMode) { if (dupeCheck == null) { dupeCheck = Options.option('DupeCheck') === 'yes'; } if (dupeCheck && dupeKey != '' && UISettings.dupeBadges) { return formatDupeText(dupeKey, dupeScore, dupeMode); } else { return ''; } } this.buildDupe = function(dupeKey, dupeScore, dupeMode) { if (dupeCheck == null) { dupeCheck = Options.option('DupeCheck') === 'yes'; } if (dupeCheck && dupeKey != '' && UISettings.dupeBadges) { return ' ' + formatDupeText(dupeKey, dupeScore, dupeMode) + ' '; } else { return ''; } } this.resetCategoryColumnWidth = function() { categoryColumnWidth = null; } this.calcCategoryColumnWidth = function() { if (categoryColumnWidth === null) { var widthHelper = $('
').css({'position': 'absolute', 'float': 'left', 'white-space': 'nowrap', 'visibility': 'hidden'}).appendTo($('body')); // default (min) width categoryColumnWidth = 60; for (var i = 1; ; i++) { var opt = Options.option('Category' + i + '.Name'); if (!opt) { break; } widthHelper.text(opt); var catWidth = widthHelper.width(); categoryColumnWidth = Math.max(categoryColumnWidth, catWidth); } widthHelper.remove(); categoryColumnWidth += 'px'; } return categoryColumnWidth; } this.deleteConfirm = function(actionCallback, multi, hasNzb, hasUrl) { var dupeCheck = Options.option('DupeCheck') === 'yes'; var cleanupDisk = Options.option('DeleteCleanupDisk') === 'yes'; var history = Options.option('KeepHistory') !== '0'; var dialog = null; function init(_dialog) { dialog = _dialog; if (!multi) { var html = $('#ConfirmDialog_Text').html(); html = html.replace(/downloads/g, 'download'); $('#ConfirmDialog_Text').html(html); } $('#DownloadsDeleteConfirmDialog_Delete', dialog).prop('checked', true); $('#DownloadsDeleteConfirmDialog_Delete', dialog).prop('checked', true); $('#DownloadsDeleteConfirmDialog_DeleteDupe', dialog).prop('checked', false); $('#DownloadsDeleteConfirmDialog_DeleteFinal', dialog).prop('checked', false); Util.show($('#DownloadsDeleteConfirmDialog_Options', dialog), history); Util.show($('#DownloadsDeleteConfirmDialog_Simple', dialog), !history); Util.show($('#DownloadsDeleteConfirmDialog_DeleteDupe,#DownloadsDeleteConfirmDialog_DeleteDupeLabel', dialog), dupeCheck && hasNzb); Util.show($('#DownloadsDeleteConfirmDialog_Remain', dialog), !cleanupDisk && hasNzb); Util.show($('#DownloadsDeleteConfirmDialog_Cleanup', dialog), cleanupDisk && hasNzb); Util.show('#ConfirmDialog_Help', history && dupeCheck && hasNzb); }; function action() { var deleteNormal = $('#DownloadsDeleteConfirmDialog_Delete', dialog).is(':checked'); var deleteDupe = $('#DownloadsDeleteConfirmDialog_DeleteDupe', dialog).is(':checked'); var deleteFinal = $('#DownloadsDeleteConfirmDialog_DeleteFinal', dialog).is(':checked'); var command = deleteNormal ? 'GroupDelete' : (deleteDupe ? 'GroupDupeDelete' : 'GroupFinalDelete'); actionCallback(command); } ConfirmDialog.showModal('DownloadsDeleteConfirmDialog', action, init); } }(jQuery)); nzbget-16.4/webui/style.css0000644000175000017500000012470512630544544015556 0ustar andreasandreas/*! * This file is part of nzbget * * Copyright (C) 2012-2015 Andrey Prygunkov * * 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$ * */ body { padding-left: 0; padding-right: 0; } /* NAVBAR */ .navbar-fixed-top { margin-bottom: 18px; margin-left: 0px; margin-right: 0px; position: static; } .navbar-fixed-top .navbar-inner { padding: 0; min-height: 0; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; color: #bbb; } /* fixed navbar (applied dynamically if screen is wide enough) */ body.navfixed { margin-top: 57px; } body.navfixed .navbar-fixed-top { position: fixed; } body.navfixed.scrolled .navbar-fixed-top .navbar-inner { -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1); -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25), inset 0 -1px 0 rgba(0, 0, 0, 0.1); } /* end of fixed navbar */ #Logo { float: left; margin-top: 1px; margin-right: 5px; width: 88px; height: 36px; cursor: pointer; opacity: 1; } #Logo i, #Logo i:hover { opacity: 1; filter: alpha(opacity=100); } .img-logo { background-position: -8px -139px; width: 88px; height: 38px; display: inline-block; } #PlayBlock { width: 70px; height: 38px; display: block; float: left; position: relative; z-index: 2; } #PlayButton, #PauseButton, #PlayPauseBg { width: 50px; height: 50px; position: absolute; padding: 0; z-index: 3; } .img-download-orange { background-position: -176px -80px; } .img-download-green { background-position: -112px -80px; } .img-download-bg { background-position: -240px -80px; width: 50px; height: 50px; } .PlayBlockInner { width: 38px; height: 38px; margin-left: 0px; margin-top: 0px; cursor: pointer; top: 6px; left: 6px; position: absolute; -webkit-border-radius: 18px; -moz-border-radius: 18px; border-radius: 18px; } .img-download-btn { background-position: -16px -80px; width: 21px; height: 21px; margin-left: 9px; margin-top: 8px; } .PlayBlockInner:hover .img-download-btn, .PlayBlockInner:hover .img-download-btn { /* white */ background-position: -16px -112px; opacity: 0.9; } #PlayCaretBlock { left: 37px; top: 1px; position: absolute; } #PlayCaretButton { border: 0; background: none; width: 24px; height: 22px; padding: 3px; line-height: 10px; vertical-align: top; } #PlayCaret { margin-top: 3px; margin-left: 9px; border-top-color: #ffffff; border-bottom-color: #ffffff; opacity: 0.75; filter: alpha(opacity=75); } #PlayCaretButton:hover #PlayCaret { opacity: 1; filter: alpha(opacity=100); } @-webkit-keyframes play-rotate { 0% { -webkit-transform: rotate(0); } 100% { -webkit-transform: rotate(360deg); } } @-moz-keyframes play-rotate { 0% { -moz-transform: rotate(0); } 100% { -moz-transform: rotate(360deg); } } @-ms-keyframes play-rotate { 0% { -ms-transform: rotate(0); } 100% { -ms-transform: rotate(360deg); } } #PlayAnimation { position: absolute; z-index: 4; left: -5px; top: -5px; -webkit-background-size: 70px 70px; -moz-background-size: 70px 70px; background-size: 70px 70px; background-position: center; width: 60px; height: 60px; cursor: pointer; pointer-events: none; -webkit-animation: play-rotate 1s linear infinite; -moz-animation: play-rotate 1s linear infinite; -ms-animation: play-rotate 1s linear infinite; } #PlayAnimation.play { background-image: url("./img/download-anim-green-2x.png"); } #PlayAnimation.pause { background-image: url("./img/download-anim-orange-2x.png"); } #InfoBlock { float: left; margin-right: 10px; padding-top: 2px; cursor: pointer; width: 86px; } #InfoBlock div { margin-top: 1px; font-size: 12px; font-weight: bold; margin: 0; padding: 0; } #InfoBlock div:hover { color: #fff; } #InfoBlock i { margin-right: 1px; } .navbar-inner i { opacity: 0.8; filter: alpha(opacity=80); } #InfoBlock div:hover i { opacity: 1; filter: alpha(opacity=100); } #StatusTime.scheduled-resume { color: #F08929; } #StatusTime.scheduled-resume:hover { color: #FFA15A; } .navbar-inner .btn:hover i { opacity: 1; filter: alpha(opacity=100); } #NavLinks { margin-right: 0; } .navbar-container { padding-left: 10px; padding-right: 10px; } .navbar .btn-group { padding: 0; } /* needed for Safari 4 */ .btn-toolbar .btn { height: 28px; } .navbar .nav { margin-right: 0; } .navbar .nav > li > a { color: inherit; } .navbar .nav .active > a, .navbar .nav .active > a:hover { outline: 0; color: #000; text-shadow: none; background: rgb(255,255,255); /* Old browsers */ background: -moz-linear-gradient(top, rgb(255,255,255) 0%, rgb(238,238,238) 45%, rgb(231,231,231) 55%, rgb(255,255,255) 100%); /* FF3.6+ */ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgb(255,255,255)), color-stop(45%,rgb(238,238,238)), color-stop(55%,rgb(231,231,231)), color-stop(100%,rgb(255,255,255))); /* Chrome,Safari4+ */ background: -webkit-linear-gradient(top, rgb(255,255,255) 0%,rgb(238,238,238) 45%,rgb(231,231,231) 55%,rgb(255,255,255) 100%); /* Chrome10+,Safari5.1+ */ background: -o-linear-gradient(top, rgb(255,255,255) 0%,rgb(238,238,238) 45%,rgb(231,231,231) 55%,rgb(255,255,255) 100%); /* Opera 11.10+ */ background: -ms-linear-gradient(top, rgb(255,255,255) 0%,rgb(238,238,238) 45%,rgb(231,231,231) 55%,rgb(255,255,255) 100%); /* IE10+ */ background: linear-gradient(to bottom, rgb(255,255,255) 0%,rgb(238,238,238) 45%,rgb(231,231,231) 55%,rgb(255,255,255) 100%); /* W3C */ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#ffffff',GradientType=0 ); /* IE6-9 */ } #NavLinks .badge { min-width: 14px; display: inline-block; text-align: center; padding: 1px 4px; } #NavLinks .badge.badge2 { min-width: 18px; } #NavLinks .badge.badge3 { min-width: 28px; } #NavLinks .badge-empty { background: none; } /* headers in navbar menu */ .menu-header { display: block; padding: 3px 15px; font-size: 11px; font-weight: bold; line-height: 18px; color: #999999; text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); text-transform: uppercase; } /* for checkboxes in dropdown menu */ .menu-refresh td:first-child , .menu-check td:first-child { width: 18px; } /* for checkboxes in dropdown menu "Refresh" */ .menu-refresh td:nth-child(2) { width: 20px; text-align: right; padding-right: 5px; } /* Search box */ .navbar-search { margin-top: 0px; margin-right: 10px; } .navbar-search .search-query, .navbar-search .search-query:focus, .navbar-search .search-query.focused { width: 130px; padding: 3px 28px 3px 25px; -webkit-border-radius: 12px; -moz-border-radius: 12px; border-radius: 12px; } .navbar-search .search-query:focus, .navbar-search .search-query.focused { margin-top: 1px; } .search-clear { position: absolute; top: 6px; right: 9px; cursor: pointer; opacity: 0.65; filter: alpha(opacity=65); } .search-caret-block { left: -2px; top: 2px; position: absolute; } .search-caret-button { border: 0; background: none; width: 24px; height: 22px; padding: 3px; line-height: 10px; vertical-align: top; } .search-caret-button .caret { margin-top: 3px; margin-left: 9px; border-top-color: #ffffff; border-bottom-color: #ffffff; opacity: 0.75; filter: alpha(opacity=75); } .focused .search-caret-button .caret { margin-left: 5px; border-top-color: #000; border-bottom-color: #000; } .search-caret-button:hover .caret { opacity: 1; filter: alpha(opacity=100); } /* MENUS */ #RefreshMenu { min-width: 160px; } #SettingsMenu { min-width: 190px; } #PlayMenu { min-width: 190px; } #ToolbarOptMenu { min-width: 215px; } #RssMenu { max-width: 300px; overflow: hidden; } .menu-with-button a { padding-right: 70px; overflow: hidden; } .menu-with-button a .btn, .phone .menu-with-button a .btn { visibility: hidden; height: 22px; line-height: 12px; font-size: 8pt; padding: 2px 7px; margin-right: 0px; margin-top: -2px; margin-bottom: -1px; right: 15px; position: absolute; } .menu-with-button a:hover .btn { visibility: visible; } .menu-with-button .menu-no-items { padding-left: 15px; } #FilterMenu { left: -3px; right: auto; margin-top: 3px; min-width: 170px; max-width: 270px; } #FilterMenu::after { left: 13px; right: auto; } #FilterMenu::before { left: 12px; right: auto; } #DownloadsEdit_ActionsMenu, #HistoryEdit_ActionsMenu { min-width: 120px; } ul.dropdown-menu > li > a { line-height: 20px; } ul.dropdown-menu > li > a.has-table { line-height: 18px; } ul.dropdown-menu > li > a > i { margin-right: 5px; } /* BEGIN: Icons */ [class^="icon-"], [class*=" icon-"], [class^="img-"], [class*=" img-"] { background-image: url("./img/icons.png"); } /* HiDPI screens */ @media only screen and (-webkit-min-device-pixel-ratio: 2) { [class^="icon-"], [class*=" icon-"], [class^="img-"], [class*=" img-"] { background-image: url("./img/icons-2x.png"); -webkit-background-size: 700px 300px; -moz-background-size: 700px 300px; background-size: 700px 300px; } } [class^="icon-"], [class*=" icon-"] { display: inline-block; vertical-align: text-top; width: 16px; height: 16px; line-height: 16px; } .icon-empty { background-position: -1000px -1000px; } .icon-plus { background-position: -16px -16px; } .icon-minus { background-position: -368px -112px; } .icon-remove-white { background-position: -48px -16px; } .icon-ok { background-position: -80px -16px; } .icon-time { background-position: -112px -16px; } .icon-file { background-position: -144px -16px; } .icon-messages { background-position: -176px -16px; } .icon-play { background-position: -208px -16px; } .icon-pause { background-position: -240px -16px; } .icon-down { background-position: -272px -16px; } .icon-up { background-position: -304px -16px; } .icon-bottom { background-position: -336px -16px; } .icon-top { background-position: -368px -16px; } .icon-back { background-position: -304px -80px; } .icon-forward { background-position: -336px -80px; } .icon-nextpage { background-position: -368px -80px; } .icon-refresh { background-position: -16px -48px; } .icon-edit { background-position: -48px -48px; } .icon-trash { background-position: -80px -48px; } .icon-settings { background-position: -112px -48px; } .icon-downloads { background-position: -144px -48px; } .icon-plane { background-position: -176px -48px; } .icon-truck { background-position: -208px -48px; } .icon-history { background-position: -240px -48px; } .icon-remove, .icon-close { background-position: -272px -48px; } .icon-merge { background-position: -304px -48px; } .icon-rss { background-position: -304px -112px; } .icon-trash-white { background-position: -336px -48px; } .icon-downloads-white { background-position: -368px -48px; } .icon-history-white { background-position: -400px -48px; } .icon-settings-white { background-position: -432px -48px; } .icon-messages-white { background-position: -464px -48px; } .icon-time-orange { background-position: -400px -80px; } .icon-split { background-position: -432px -80px; } .img-checkmark { background-position: -432px -16px; } .img-checkminus { background-position: -400px -16px; } .icon-postcard { background-position: -432px -112px; } .icon-link { background-position: -400px -112px; } .icon-alert { background-position: -336px -112px; } .icon-process { background-position: -304px -144px; } .icon-process-auto { background-position: -336px -144px; } .icon-duplicates { background-position: -368px -144px; } .icon-mask { background-position: -400px -144px; } .icon-mask-white { background-position: -432px -144px; } /* END: Icons */ .btn-toolbar { margin-top: 6px; margin-bottom: 0px; } .section-toolbar, .modal-toolbar { margin-top: 0; margin-bottom: 7px; } .section-title { margin-right: 10px; } .label { /* -ms-padding-bottom: 1px; */ } .label-status { text-transform: uppercase; } .label-inline { display: inline-block; margin-bottom: -4px; overflow-x: hidden; text-overflow: ellipsis; } .controls .label-status { line-height: 22px; } /* links in black color */ .table a { color: #000000; } /* links in black color */ .table a:hover { color: #000000; } .table a.badge-link:hover { text-decoration: none; } table.datatable > tbody > tr > td { word-wrap: break-word; } #MainTabContent { margin-top: 0; overflow: visible; /* fix problem with dropdown menus */ } /* top toolbox (length-combo and pager) for tables */ .toolbox-top { margin-bottom: 8px; } /* combobox with page length for tables */ div.toolbox-length select { width: 75px; height: 28px; } .toolbox-info { margin-top: 10px; } .pagination { height: 32px; margin: 0; } .pagination a { padding: 0 10px; line-height: 26px; } .modal-tab .pagination { margin-bottom: 10px; } .padded-tab { padding-left: 20px; padding-right: 20px; } h1 { font-size: 24px; line-height: 36px; margin-bottom: 8px; margin-right: 20px; } h2 { font-size: 20px; line-height: 26px; margin-bottom: 8px; margin-right: 20px; } .alert-heading { margin-bottom: 10px; } /* remove focus border */ .nav-tabs > .active > a, .nav-tabs > .active > a:hover, .nav-pills > .active > a, .nav-pills > .active > a:hover, .nav-list > .active > a, .nav-list > .active > a:hover, .btn, .btn-group .btn, .pagination a, .btn-toolbar .btn , .control-group .btn, .modal-footer .btn, .form-search .btn, .btn:focus { outline: 0; } form { margin-bottom: 0px; } #DownloadsEdit_PostParamData, #HistoryEdit_PostParamData { padding-bottom: 1px; } #DownloadsEdit_FileTable_filter, #DownloadsEdit_LogTable_filter #HistoryEdit_LogTable_filter, #FeedDialog_ItemTable_filter { width: 180px; } #DownloadsLogRecordsPerPageBlock, #HistoryLogRecordsPerPageBlock { margin-bottom: 12px; } #DownloadsEdit_LogTable_filterBlock, #HistoryEdit_LogTable_filterBlock { float: left; margin-left: 20px; margin-right: 20px; margin-bottom: 12px; } .phone .modal-toolbox { margin-bottom: 20px; } .loading-block { position: absolute; left: 0; top: 0; right: 0; bottom: 0; text-align: center; } .loading-block img { position: absolute; top: 50%; left: 50%; } .modal-body { max-height: 360px; } .modal.no-footer .modal-body { max-height: 420px; } .modal-footer .btn-primary { min-width: 40px; } .modal-body .alert { margin-bottom: 0px; padding: 6px; } .modal-max { margin: 15px; left: 0; top: 0; bottom: 0; right: 0; width: auto; height: auto; } .modal-max .modal-body { position: absolute; left: 0; right: 0; max-height: inherit; /* top: 46px; // must be calculated at runtime */ /* bottom: 58px; // must be calculated at runtime */ } .modal-max .modal-inner-scroll { top: 54px; } .modal-max .modal-footer { position: absolute; left: 0; right: 0; bottom: 0; } .modal-inner-scroll { bottom: 0; left: 15px; overflow-y: auto; position: absolute; right: 0; padding-right: 15px; } .modal-inner-scroll .toolbox-info { margin-bottom: 10px; } .modal-2 { margin-top: -200px; z-index: 1060; } .modal-backdrop.modal-2 { z-index: 1055; opacity: 0.6; } .badge-active { background-color: #FFFFFF; color: #000000; text-shadow: none; } /* BEGIN: Tables */ .table { margin-bottom: 0px; } /* text-align for tables */ td.text-right,th.text-right { text-align: right; } /* text-align for tables */ td.text-center,th.text-center { text-align: center; } .table-striped tbody tr:nth-child(odd) { background-color: #f9f9f9; } .table tbody tr:hover { background-color: #f5f5f5; } .table th, .table td { padding: 5px; } .table-condensed th, .table-condensed td { padding: 2px; } .table-nonbordered, .table-nonbordered td { border: none; } /* END: Tables */ /* BEGIN: Checkmarks in the table */ table.table-check > thead > tr > th:first-child, table.table-check > tbody > tr > td:first-child { width: 14px; height: 14px; } div.check { width: 12px; height: 12px; border-color: #DDDDDD; border: 1px solid #DDDDDD; margin-top: 2px; margin-bottom: 3px; -webkit-border-radius: 2px; -moz-border-radius: 2px; border-radius: 2px; } div.check:hover { border-color: #0088cc; border: 1px solid #0088cc; } table.table-cancheck tr div.img-check { background-position: 10px 10px; } table.table-cancheck tr.checked div.img-check { background-position: -434px -18px; } table.table-cancheck tr.checkremove div.img-check { background-position: -402px -18px; } tr.checked, tr.checked td { background-color: #FFFFE1; } .table-striped tbody tr.checked:nth-child(odd) td { background-color: #FFFFE1; } .table tbody tr.checked:hover, .table tbody tr.checked:hover td { background-color: #FFFFCC; } .check-simple tr.checked, .check-simple tr.checked td, .table-striped.check-simple tbody tr.checked:nth-child(odd) td { background-color: inherit; } .table.check-simple tbody tr.checked:hover, .table.check-simple tbody tr.checked:hover td { background-color: #f5f5f5; } table.table-hidecheck thead > tr > th:first-child, table.table-hidecheck tbody > tr > td:first-child { display: none; } .checked .progress { background-color: #FFFFE1; } /* END: Checkmarks in the table */ /* BEGIN: Progress bars */ .progress-block { position: relative; width: 120px; } .progress { margin-bottom: 0px; background: #f0f0f0; } /* style for queued downloads, based on ".progress-success.progress-striped .bar" from bootstrap.css */ .progress-none.progress-striped .bar { background-color: #c0c0c0; background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); background-image: -webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-image: -moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-image: -ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-image: -o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); background-image: linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); filter: progid:dximagetransform.microsoft.gradient(startColorstr='#d0d0d0', endColorstr='#c0c0c0', GradientType=0); } /* text on left side of progress bar */ .bar-text-left { position: absolute; top: 0; left: 5px; text-align: left; } /* text on right side of progress bar */ .bar-text-right { position: absolute; top: 0; right: 5px; text-align: right; } /* text on left side of progress bar */ .bar-text-center { position: absolute; top: 0; left: 5px; width: 100%; text-align: center; } /* END: Progress bars */ input[readonly], select[readonly], textarea[readonly], .uneditable-input { cursor: inherit; } .input-prepend .add-on-small, .input-append .add-on-small { font-size: 11px; } .toolbtn { min-width: 56px; } /* BEGIN: override bootstrap styles for modals */ .modal-header h3 { text-align: center; } .modal-header .close { margin-top: 6px; margin-left: 10px; opacity: 0.6; filter: alpha(opacity=60); outline: 0; } .modal-header .close:hover { opacity: 0.9; filter: alpha(opacity=90); } .modal-header .back { float: left; margin-top: 6px; margin-right: 10px; font-size: 20px; font-weight: bold; line-height: 18px; opacity: 0.6; filter: alpha(opacity=60); outline: 0; } .modal-header .back:hover { cursor: pointer; opacity: 0.9; filter: alpha(opacity=90); } .modal-header .back-hidden:hover { cursor: inherit; } .form-horizontal .control-group { margin-bottom: 12px; } .modal .form-horizontal .control-group:last-child { margin-bottom: 0; } .modal .form-horizontal .retain-margin .control-group:last-child { margin-bottom: 15px; } .form-horizontal .control-group-last { margin-bottom: 0; } .form-horizontal .help-block { margin-top: 4px; margin-bottom: -2px; line-height: 18px; } .form-horizontal .help-block-uneditable { margin-top: 3px; } .modal .input-medium { width: 200px; } .modal .input-xlarge { width: 350px; } .modal.modal-padded .input-xxlarge { width: 470px; } /* END: override bootstrap styles for modals */ .modal-bottom-toolbar .btn { margin-top: 12px; } /* based on uneditable-input */ .uneditable-mulitline-input { display: inline-block; width: 340px; overflow: hidden; cursor: not-allowed; background-color: #ffffff; border-color: #eee; border: 1px solid #eee; padding: 4px; padding-right: 20px; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); } .input-medium.uneditable-input, .uneditable-mulitline-input { margin-bottom: -4px; } .modal-mini { width: 420px; margin-left:-210px; } .modal-small { width: 480px; margin-left:-240px; } .modal-large { width: 700px; margin-left:-350px; } .modal-padded-small .modal-body { padding-left: 25px; padding-right: 25px; } .modal-padded .modal-body { padding-left: 40px; padding-right: 40px; } .modal-tab-padded { padding-left: 25px; padding-right: 25px; } .modal-tab-padded-small { padding-left: 10px; padding-right: 10px; } .dragover, .dragover .table-striped tbody tr:nth-child(odd) td, .dragover .table-striped tbody tr:nth-child(odd) th { background-color: #dff0d8; } .dialog-add-files { } ul.help > li { margin-bottom: 10px; } /* Make "select files" native control invisible */ .hidden-file-input { position: absolute; left: 0; top: 0; width: 0; height: 0; opacity: 0; filter: alpha(opacity=0); } /* BEGIN: Notification alerts */ .alert-inverse { color: #ffffff; text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); background-color: #414141; border-color: #222222; } .alert-center { position: fixed; padding: 20px; top: 50%; left: 50%; z-index: 2000; overflow: auto; text-align: center; opacity: 0.9; filter: alpha(opacity=90); } .alert-center-small { width: 200px; margin: -80px 0 0 -100px; } .alert-center-medium { width: 400px; margin: -80px 0 0 -200px; } .alert-error.alert-center { border-color: #B94A48; } .alert-success.alert-center { border-color: #468847; } .alert-info.alert-center { border-color: #3a87ad; } /* END: Notification alerts */ .confirm-help-block { color: #555555; font-size: 13px; line-height: 16px; margin-bottom: 0; } .table .btn-success, .table .btn-success:hover { color: #ffffff; } .btn-group { margin-right: 9px; } .btn-toolbar .btn-group { margin-right: 4px; } .btn-group + .btn-group { margin-left: 0; } .input-prepend .add-on:first-child { margin-left: 0; } /* important for group of buttons with different colors like toggle switch */ .btn-group > .btn:hover, .btn-group > .btn:focus, .btn-group > .btn:active, .btn-group > .btn.active { z-index: inherit; } #ErrorAlert, #FirstUpdateInfo, #ConfigReloadInfo, .unsupported-browser { margin-left: 20px; margin-right: 20px; } #ErrorAlert, #FirstUpdateInfo, #ConfigReloadInfo, .unsupported-browser { margin-top: 20px; } .confirm-menu { text-align: left; min-width: 10px; right: 0; left: auto; } .footer-button-menu { text-align: left; } .data-statistics { width: 300px; } .data-statistics-full { width: 364px; } .modal-center { margin-top: 0; } /*** STATISTICS DIALOG */ #StatisticsTab table { width: 350px; } #StatDialog_VolumesBlock { text-align: center; } #StatDialog_ChartBlock { height: 300px; width: 100%; margin-top: 15px; margin-bottom: 15px; } #StatDialog_Chart { height: 100%; *width: 670px; width: 100%; } #StatDialog_Tooltip { float: right; margin-top: -3px; margin-bottom: -6px; padding-right: 15px; font-style: italic; } .stat-size { font-weight: bold; } #StatDialog_TooltipSum { font-weight: bold; } #StatDialog_CountersBlock { padding-right: 20px; } #StatDialog_Counters { margin-top: 8px; text-align: center; } #StatDialog_Counters .span3 { min-height: 0; } #StatDialog hr { margin: 5px 10px; } #StatDialog_Custom a, #StatDialog_Custom a:hover, #AddDialog_Files a, #AddDialog_Files a:hover { color: #000000; cursor: pointer; } /*** CONFIG PAGE */ #ConfigNav.nav-list a { color: #000; text-decoration: none; padding-top: 5px; padding-bottom: 5px; font-size: 12px; } #ConfigNav.nav-list.long-list a { padding-top: 3px; padding-bottom: 3px; } #ConfigNav.nav-list > .active > a, #ConfigNav.nav-list > .active > a:hover { color: #ffffff; *background-color: #505050; } #ConfigNav.nav .nav-header { font-size: 12px; } #ConfigNav { -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; border: 1px solid #eeeeee; padding: 8px 15px; background-color: #F7F7F9; margin-bottom: 15px; } #ConfigContent .config-header { padding: 7px; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; margin-bottom: 20px; padding-right: 0; padding-top: 0; border-bottom: 1px solid #eeeeee; } #ConfigTitle { margin-top: 15px; margin-right: 15px; font-size: 16px; font-weight: bold; } .config-header .btn-group { margin-right: 0; } .config-header .btn { margin-top: 7px; margin-right: 0; background-color: #ffffff; background-image: none; border: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; } span.help-option-title { color: #8D1212; } .failure-message { color: #8D1212; } #ConfigContent p.help-block { margin-top: 6px; line-height: 16px; } #ConfigContent.hide-help-block p.help-block { display: none; } #ConfigContent .control-label { font-weight: bold; } #ConfigContent select { width: inherit; } #ConfigContent .editnumeric { width: 70px; } #ConfigContent .editlarge { width: 95%; } #ConfigContent .editsmall { width: 150px; } #ConfigContent table.editor { width: 97%; } #ConfigContent table.editor td:first-child { width: 100%; padding-right:15px; } #ConfigContent table.editor input { width: 100%; } .ConfigFooter hr { margin: 6px 0 15px; } div.ConfigFooter { padding-bottom: 15px; } #ConfigContent hr { margin: 15px 0; } .config-settitle { font-size: 14px; font-weight: bold; background-color: #505050; color: #ffffff; padding: 7px; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; margin-bottom: 20px; border-bottom: 1px solid #eeeeee; } #ConfigContent.hide-help-block .config-settitle { margin-bottom: 15px; } .config-multicaption { color: #c0c0c0; font-weight: normal; } #DownloadsEdit_ParamTab div.control-group.wants-divider, #HistoryEdit_ParamTab div.control-group.wants-divider, #ConfigContent div.control-group, #ConfigContent.search div.control-group.multiset { border-bottom: 1px solid #eeeeee; margin-bottom: 15px; padding-bottom: 12px; } #ConfigContent.hide-help-block div.control-group, #ConfigContent.hide-help-block div.control-group.multiset { border-bottom: none; margin-bottom: 0px; padding-bottom: 12px; } div.control-group.last-group { margin-bottom: 0; } #ConfigContent div.control-group.last-group, #ConfigContent.search div.control-group.last-group.multiset { border-bottom: none; } #ConfigContent div.control-group.multiset { border-bottom: none; margin-bottom: 12px; padding-bottom: 8px; } #ConfigContent .control-label { width: 170px; } #ConfigContent .form-horizontal .controls { margin-left: 180px; } .btn-switch input:focus { border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; } .btn-switch .btn { text-transform: capitalize; } .option { font-weight: bold; font-style:italic; color: inherit; } .option-name, .option-name:focus, .option-name:hover { color: inherit; outline: 0; cursor: inherit; text-decoration: none; } .search .option-name, .search .option-name:focus { cursor: pointer; } .search .option-name:hover { cursor: pointer; text-decoration: underline; color: #005580; } div.multiset-toolbar button { margin-right: 15px; } #ScriptListDialog_ScriptTable td:nth-child(2) { padding-right: 100px; } #ScriptListDialog_ScriptTable .btn-row-order-block { float: right; width: 100px; margin-right: -115px; display: block; } #ScriptListDialog_ScriptTable .btn-row-order { float: none; width: 20px; display: none; } #ScriptListDialog_ScriptTable tr:hover .btn-row-order { display: inline-block; cursor: pointer; } #ScriptListDialog_ScriptTable tbody > tr:first-child div.btn-row-order:first-child, #ScriptListDialog_ScriptTable tbody > tr:last-child div.btn-row-order:last-child, #ScriptListDialog_ScriptTable tbody > tr:first-child div.btn-row-order:nth-child(2), #ScriptListDialog_ScriptTable tbody > tr:last-child div.btn-row-order:nth-child(3) { opacity: 0.4; } /* UPDATE DIALOG */ .table .update-release-notes { color: #005580; font-size: 11px; height: 10px; outline: none; } #UpdateDialog_InstallStable, #UpdateDialog_InstallTesting, #UpdateDialog_InstallDevel { margin-top: 5px; } .table .update-row-name { font-weight: bold; padding-top: 14px; } #UpdateDialog_Versions #UpdateDialog_AvailRow td:hover { background-color: #f5f5f5; } #UpdateDialog_Versions #UpdateDialog_AvailRow td:first-child:hover, #UpdateDialog_Versions tr:hover td { background-color: #ffffff; } #UpdateProgressDialog { width: 640px; margin-left: -320px; } #UpdateProgressDialog .modal-body { min-height: 280px; position: relative; } #UpdateProgressDialog_Log { min-height: 270px; background-color: #222222; color: #cccccc; padding: 3px 6px; margin-bottom: 0px; position: absolute; top: 15px; bottom: 15px; left: 15px; right: 15px; overflow-y: auto; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; } .update-log-error { color: #ff0000; } /* FEED FILTER DIALOG */ #FeedFilterDialog_FilterBlock { position: absolute; left: 15px; width: 300px; bottom: 0; height: auto; padding-top: 0; margin-bottom: 12px; padding: 0; font-size: 12px; line-height: 18px; border: 1px solid #ccc; border: 1px solid rgba(0, 0, 0, 0.15); -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; } #FeedFilterDialog_FilterHeader { font-size: 13px; font-weight: bold; margin-top: 5px; padding-left: 6px; height: 23px; border-bottom: 1px solid #ccc; border-bottom: 1px solid rgba(0, 0, 0, 0.15); } #FeedFilterDialog_FilterLines { position: absolute; left: 0; top: 29px; bottom: 0px; width: 32px; height: auto; overflow: hidden; background-color: #ffffff; border-right: 1px solid #ccc; border-right: 1px solid rgba(0, 0, 0, 0.15); } #FeedFilterDialog_FilterNumbers { font-family: Menlo, Monaco, Consolas, "Courier New", monospace; } #FeedFilterDialog_FilterNumbers .lineno { color: #3A87AD; padding-right: 5px; padding-top: 0; text-align: right; font-weight: bold; white-space: nowrap; } #FeedFilterDialog_FilterClient { position: absolute; left: 33px; right: 0px; top: 29px; bottom: 0px; width: auto; padding-left: 3px; height: auto; background-color: #ffffff; } #FeedFilterDialog_FilterInput { width: 100%; height: 100%; margin: 0; padding: 0; border: 0; resize: none; outline: none; border: none; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; -webkit-border-radius: 0; -moz-border-radius: 0; border-radius: 0; } #FeedFilterDialog_PreviewBlock { position: absolute; left: 325px; right: 0; bottom: 0; height: auto; width: auto; padding-right: 15px; margin-bottom: 12px; } #FeedFilterDialog_Splitter { position: absolute; left: 319px; width: 5px; padding: 0; right: 5px; bottom: 0; height: auto; cursor: col-resize; } .phone #FeedFilterDialog_FilterBlock, .phone #FeedFilterDialog_PreviewBlock, .phone #FeedFilterDialog_FilterClient { position: static; top: inherit; left: 0; width: inherit; padding-right: 0; } .phone #FeedFilterDialog_FilterInput { height: 380px; } .filter-rule { cursor: pointer; } /****************************************************************************/ /* SMARTPHONE THEME */ body.phone { margin-top: 0; } .phone .navbar-fixed-top { position: static; } .phone #PlayBlock { width: 75px; } .phone #PlayCaretButton { width: 25px; margin-left: 10px; } .phone #InfoBlock { width: 190px; height: 42px; margin-right: 0; } .phone #InfoBlock div { display: inline-block; margin-left: 5px; margin-top: 5px; font-size: 14px; line-height: 32px; height: inherit; } /* GENERAL CLASSES */ .phone-only, .btn-group.phone-only { display: none; } .phone .phone-hide, .phone .btn-group.phone-hide { display: none; } .phone .phone-only { display: block; } .phone .phone-only.inline { display: inline-block; } /* FONTS */ body.phone, .phone p, .phone .form-horizontal .help-block , .phone h4 { font-size: 18px; line-height: 22px; } .phone table td { line-height: 22px; } .phone select, .phone input, .phone textarea, .phone label, .phone button, .phone .btn, .phone .btn-toolbar .btn, .phone .uneditable-input { font-size: 18px; line-height: 24px; height: inherit; } .phone .controls .label-status { line-height: 28px; } .phone .menu-header { font-size: 18px; line-height: 24px; } /* SECTION MARGINGS */ .phone .section-toolbar, .phone .toolbox-top, .phone .toolbox-info, .phone #ConfigTabData { padding-left: 5px; padding-right: 5px; } .phone #ErrorAlert, .phone #FirstUpdateInfo, .phone #ConfigReloadInfo, .phone #DownloadQueueEmpty { margin-left: 5px; margin-right: 5px; } .phone #FirstUpdateInfo, .phone #ConfigReloadInfo { margin-top: 5px; } .phone #MainContent { padding-left: 0px; padding-right: 0px; } .phone .section-toolbar{ margin-top: 8px; margin-bottom: 0; } .phone .toolbox-top { margin-top: 0; margin-bottom: 8px; } /* NAVBAR */ .phone .navbar-fixed-top { margin-bottom: 8px; } .phone .navbar-container { padding-left: 5px; padding-right: 5px; } .phone .navbar-inner .btn-toolbar { margin: 6px 0 0; } .phone ul.nav > li { text-align: center; min-width: 52px; } .phone .menu-header { text-align: left; } .phone .navbar .nav > li > a { padding: 4px 4px 6px; } .phone .navbar .nav > li.active > #DownloadsTabLink > i { /* icon-downloads (black) */ background-position: -144px -48px; } .phone .navbar .nav > li.active > #HistoryTabLink > i { /* icon-history (black) */ background-position: -240px -48px; } .phone .navbar .nav > li.active > #MessagesTabLink > i { /* icon-messages (black) */ background-position: -176px -16px; } .phone .navbar .nav > li.active > #ConfigTabLink > i { /* icon-settings (black) */ background-position: -112px -48px; } .phone .navbar .btn-toolbar .btn { padding: 3px; min-width: 40px; } .phone #RefreshBlockPhone { padding-left: 5px; } .phone .navbar-search .search-query, .phone .navbar-search .search-query:focus, .phone .navbar-search .search-query.focused { width: 140px; padding: 4px 28px 4px 28px; font-size: 16px; -webkit-border-radius: 16px; -moz-border-radius: 16px; border-radius: 16px; margin-bottom: 5px; margin-top: 1px; border: 0; } .phone .search-clear { top: 9px; } /* DATATABLE */ .phone table.datatable , .phone table.datatable > tbody, .phone table.datatable > tbody > tr, .phone table.datatable > tbody > tr > td { display: block; } .phone table.datatable > thead { display: none; } .phone table.datatable > tbody > tr > td { width: inherit; height: inherit; } .phone .datatable td { border: 0; } .phone table.datatable > tbody > tr > td:first-child { border-top: 1px solid #DDDDDD; } .phone table.datatable > tbody > tr:last-child > td:last-child { border-bottom: 1px solid #DDDDDD; } .phone table.datatable > tbody > tr > td:first-child { padding-top: 10px; } .phone table.datatable > tbody > tr > td:last-child { padding-bottom: 10px; } .phone table.table-check tbody td { padding-left: 60px; } /* CHECKMARKS IN DATATABLE */ .phone div.check { margin-top: -2px; margin-left: -48px; width: 30px; height: 30px; display: block; position: absolute; border-width: 2px; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; } .phone table.table-cancheck tr.checked div.img-check { /* icon-OK */ background-position: -74px -10px; } /* SPECIAL TABLE STYLES */ .phone .row-title { font-weight: bold; } .phone .progress-block { margin-top: -2px; } .phone .downloads-progresslabel { margin-top: -8px; margin-bottom: 10px; } .phone .label-inline { display: inline; } /* CONTROLS AROUND DATATABLE */ .phone .records-label { display: none; } /* PAGER */ .phone .toolbox-info div { float: none; width: 100%; text-align: center; display: block; margin-top: 5px; } .phone div.toolbox-length { margin-top: 10px; } .phone .pagination { height: auto; width: 100%; text-align: center; margin-top: 10px; } .phone .pagination a { padding: 0 10px; line-height: 34px; } /* STATUS LABELS */ .phone .label { font-size: 14px; line-height: 18px; vertical-align: middle; } .phone .datatable .label { line-height: 21px; } /* PROGRESS */ .phone .progress-block { font-size: 16px; width: 100%; } .phone .progress, .phone .progress .bar { height: 24px; } .phone .bar-text-left, .phone .bar-text-center, .phone .bar-text-right { padding-top: 1px; margin-top: 0px; } /* STATISTICS TABLE */ .phone #StatisticsTab table { width: 100%; } .phone #StatisticsTab td:first-child { font-weight: bold; } .phone #StatisticsTab td { text-align: left; } /* MENUS */ .phone .dropdown-menu a { padding: 7px 15px; } .phone .dropdown-toggle { position: static; } .phone #FilterMenu { left: inherit; right: inherit; margin-top: inherit; width: inherit; } /* hide arrow */ .phone .navbar .dropdown-menu:before, .phone .navbar .dropdown-menu:after { display: none; } .phone #RefreshMenu { min-width: 200px; } .phone #SettingsMenu { min-width: 230px; } .phone #PlayMenu { min-width: 250px; } .phone #ToolbarOptMenu { min-width: 270px; } /* TOOLBAR AND INPUTS */ .phone .btn-toolbar .btn, .phone .btn-toolbar input { padding: 6px; min-width: 45px; } .phone .btn-toolbar .btn-group { margin-right: 0; } .phone .btn-toolbar .input-prepend .add-on, .phone .btn-toolbar .input-append .add-on { padding: 6px; min-width: 45px; } .phone .btn { min-width: 40px; } .phone input, .phone textarea, .phone .uneditable-input { height: 24px; } .phone input.btn { height: inherit; } .phone .input-prepend .add-on, .phone .input-append .add-on { height: 24px; line-height: 22px; min-width: 16px; } .phone select { height: auto; } .phone [class^="icon-"] { line-height: 24px; vertical-align: baseline; } .phone .caret { line-height: 24px; margin-top: -2px; vertical-align: middle; } /*** MODALS */ .phone .modal-footer > .btn, .phone .modal-footer > .btn-group { display: block; float: none; width: 100%; margin: 10px auto; } .phone .modal-footer .btn { padding: 7px 0; } .phone .modal-footer > .btn-group > .btn { width: 100%; margin: 0; } .phone .modal-footer { padding-top: 5px; padding-bottom: 5px; } .phone .modal-footer .confirm-menu { text-align: center; right: inherit; left: inherit; float: none; width: 100%; } .phone .modal-footer .confirm-menu .menu-header { text-align: center; } .phone .modal-padded .modal-body, .phone .modal-padded-small .modal-body { padding-left: 15px; padding-right: 15px; } .phone .modal-tab-padded, .phone .modal-tab-padded-small { padding-left: 0; padding-right: 0; } .phone .modal-max .modal-body { position: static; } .phone .modal-max .modal-footer { position: static; } .phone .modal-max .modal-inner-scroll { position: static; top: inherit; left: 0; padding-right: 0; } .phone .data-statistics, .phone .data-statistics-wide, .phone .data-statistics-full { width: 100%; } .phone .btn-caption { display: none; } .phone div.toolbox-length select { height: 36px; } .phone .navbar .btn-toolbar { position: relative; } .phone #ConfigNav.nav-list a { font-size: 18px; } .phone #ConfigNav.nav .nav-header { font-size: 20px; } .phone #ConfigContent .config-header { font-size: 20px; } .phone .config-settitle { font-size: 18px; } /*** STATISTICS DIALOG */ .phone #StatDialog_Tooltip { margin-top: 3px; margin-bottom: 3px; } .phone #StatDialog hr { margin-top: 30px; } /* MEDIA SMALL SCREENS */ @media (max-width: 700px) { #ConfigContent [class*="span"] { display: block; float: none; width: auto; margin-left: 0; } .modal-large { width: 600px; margin-left:-300px; } } @media (max-width: 568px) { input[type="checkbox"], input[type="radio"] { border: 1px solid #ccc; } [class*="span"], .row-fluid [class*="span"] { display: block; float: none; width: auto; margin-left: 0; } .form-horizontal .control-group > label { float: none; width: auto; padding-top: 0; text-align: left; } .form-horizontal .controls, #ConfigContent .form-horizontal .controls { margin-left: 0; } .form-horizontal .control-list { padding-top: 0; } .form-horizontal .form-actions { padding-right: 10px; padding-left: 10px; } .modal { position: absolute; top: 0px; right: 0px; left: 0px; width: auto; margin: 0; } .modal.fade.in { top: auto; } .modal .input-xlarge , .modal .input-xxlarge, .modal.modal-padded .input-xxlarge, .uneditable-mulitline-input { width: 95%; } .modal-body, .modal.no-footer .modal-body { max-height: none; } .modal-center { right: 20px; left: 20px; } .alert-center-small, .alert-center-medium { right: 20px; left: 20px; width: auto; margin: -10% 0 0; } } @media (max-width: 479px) { #SearchBlock { display: none; } } @media (max-width: 549px) { #Logo { display: none; } } @media (max-width: 480px) { .dialog-transmit { display: none; } } /* END: MEDIA SMALL SCREENS */ nzbget-16.4/webui/index.js0000644000175000017500000004776312630544544015361 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2012-2015 Andrey Prygunkov * * 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$ * */ /* * In this module: * 1) Web-interface intialization; * 2) Web-interface settings; * 3) Refresh handling; * 4) Window resize handling including automatic theme switching (desktop/phone); * 5) Confirmation dialog; * 6) Popup notifications. */ /*** WEB-INTERFACE SETTINGS (THIS IS NOT NZBGET CONFIG!) ***********************************/ var UISettings = (new function($) { 'use strict'; /*** Web-interface configuration (edit if neccessary) *************/ // Animation on refresh button. this.refreshAnimation = true; // Animation on play/pause button. this.activityAnimation = true; // Animation of tab changes in tabbed dialogs. this.slideAnimation = true; // Automatically set focus to the first control in dialogs. // Not good on touch devices, because may pop up an on-screen-keyboard. this.setFocus = false; // Show popup notifications. this.notifications = true; // Show badges with duplicate info (downloads and history). this.dupeBadges = false; // Select records by clicking on any part of the row, not just on the check mark. this.rowSelect = false; // Time zone correction in hours. // You shouldn't require this unless you can't set the time zone on your computer/device properly. this.timeZoneCorrection = 0; // Default refresh interval. // The choosen interval is saved in web-browser and then restored. // The default value sets the interval on first use only. this.refreshInterval = 1; // Number of refresh attempts if a communication error occurs. // If all attempts fail, an error is displayed and the automatic refresh stops. this.refreshRetries = 4; // URL for communication with NZBGet via JSON-RPC this.rpcUrl = './jsonrpc'; /*** No user configurable settings below this line (do not edit) *************/ // Current state this.miniTheme = false; this.showEditButtons = true; this.connectionError = false; this.load = function() { this.refreshInterval = parseFloat(this.read('RefreshInterval', this.refreshInterval)); } this.save = function() { this.write('RefreshInterval', this.refreshInterval); } this.read = function(key, def) { var v = localStorage.getItem(key); if (v === null || v === '') { return def; } else { return v; } } this.write = function(key, value) { localStorage.setItem(key, value); } }(jQuery)); /*** START WEB-APPLICATION ***********************************************************/ $(document).ready(function() { Frontend.init(); }); /*** FRONTEND MAIN PAGE ***********************************************************/ var Frontend = (new function($) { 'use strict'; // State var initialized = false; var firstLoad = true; var mobileSafari = false; var scrollbarWidth = 0; var switchingTheme = false; var activeTab = 'Downloads'; var lastTab = ''; this.init = function() { window.onerror = error; if (!checkBrowser()) { return; } $('#FirstUpdateInfo').show(); UISettings.load(); Refresher.init(); initControls(); switchTheme(); windowResized(); Options.init(); Status.init(); Downloads.init({ updateTabInfo: updateTabInfo }); Messages.init({ updateTabInfo: updateTabInfo }); History.init({ updateTabInfo: updateTabInfo }); Upload.init(); Feeds.init(); FeedDialog.init(); FeedFilterDialog.init(); Config.init({ updateTabInfo: updateTabInfo }); ConfigBackupRestore.init(); ConfirmDialog.init(); UpdateDialog.init(); AlertDialog.init(); ScriptListDialog.init(); RestoreSettingsDialog.init(); LimitDialog.init(); DownloadsEditDialog.init(); DownloadsMultiDialog.init(); DownloadsMergeDialog.init(); DownloadsSplitDialog.init(); HistoryEditDialog.init(); $(window).resize(windowResized); initialized = true; Refresher.update(); } function initControls() { mobileSafari = $.browser.safari && navigator.userAgent.toLowerCase().match(/(iphone|ipod|ipad)/) != null; scrollbarWidth = calcScrollbarWidth(); var FadeMainTabs = !$.browser.opera; if (!FadeMainTabs) { $('#DownloadsTab').removeClass('fade').removeClass('in'); } $('#Navbar a[data-toggle="tab"]').on('show', beforeTabShow); $('#Navbar a[data-toggle="tab"]').on('shown', afterTabShow); setupSearch(); $('li > a:has(table)').addClass('has-table'); $(window).scroll(windowScrolled); } function checkBrowser() { if ($.browser.msie && parseInt($.browser.version, 10) < 9) { $('#FirstUpdateInfo').hide(); $('#UnsupportedBrowserIE8Alert').show(); return false; } return true; } function error(message, source, lineno) { if (source == "") { // ignore false errors without source information (sometimes happen in Safari) return false; } $('#FirstUpdateInfo').hide(); $('#ErrorAlert-title').text('Error in ' + source + ' (line ' + lineno + ')'); $('#ErrorAlert-text').text(message); $('#ErrorAlert').show(); if (Refresher) { Refresher.pause(); } return false; } this.loadCompleted = function() { Downloads.redraw(); Status.redraw(); Messages.redraw(); History.redraw(); if (firstLoad) { Feeds.redraw(); $('#FirstUpdateInfo').hide(); $('#Navbar').show(); $('#MainTabContent').show(); $('#version').text(Options.option('Version')); windowResized(); firstLoad = false; } } function beforeTabShow(e) { var tabname = $(e.target).attr('href'); tabname = tabname.substr(1, tabname.length - 4); if (activeTab === 'Config' && !Config.canLeaveTab(e.target)) { e.preventDefault(); return; } lastTab = activeTab; activeTab = tabname; $('#SearchBlock .search-query, #SearchBlock .search-clear').hide(); $('#' + activeTab + 'Table_filter, #' + activeTab + 'Table_clearfilter').show(); switch (activeTab) { case 'Config': Config.show(); break; case 'Messages': Messages.show(); break; case 'History': History.show(); break; } FilterMenu.setTab(activeTab); } function afterTabShow(e) { switch (lastTab) { case 'Config': Config.hide(); break; case 'Messages': Messages.hide(); break; case 'History': History.hide(); break; } switch (activeTab) { case 'Config': Config.shown(); break; } } function setupSearch() { $('.navbar-search .search-query').on('focus', function() { $(this).next().removeClass('icon-remove-white').addClass('icon-remove'); $('#SearchBlock_Caret').addClass('focused'); }); $('.navbar-search .search-query').on('blur', function() { $(this).next().removeClass('icon-remove').addClass('icon-remove-white'); $('#SearchBlock_Caret').removeClass('focused'); }); $('.navbar-search').show(); beforeTabShow({target: $('#DownloadsTabLink')}); } function windowScrolled() { $('body').toggleClass('scrolled', $(window).scrollTop() > 0 && !UISettings.miniTheme); } function calcScrollbarWidth() { var div = $('
'); // Append our div, do our calculation and then remove it $('body').append(div); var w1 = $('div', div).innerWidth(); div.css('overflow-y', 'scroll'); var w2 = $('div', div).innerWidth(); $(div).remove(); return (w1 - w2); } function windowResized() { var oldMiniTheme = UISettings.miniTheme; UISettings.miniTheme = $(window).width() < 560; if (oldMiniTheme !== UISettings.miniTheme) { switchTheme(); } resizeNavbar(); alignPopupMenu('#PlayMenu'); alignPopupMenu('#RefreshMenu'); alignPopupMenu('#RssMenu'); alignPopupMenu('#StatDialog_MonthMenu', true); alignCenterDialogs(); if (initialized) { Downloads.resize(); } } function alignPopupMenu(menu, right) { var center = UISettings.miniTheme; var $elem = $(menu); if (center) { $elem.removeClass('pull-right'); var top = ($(window).height() - $elem.outerHeight())/2; top = top > 0 ? top : 0; var off = $elem.parent().offset(); top -= off.top; var left = ($(window).width() - $elem.outerWidth())/2; left -= off.left; $elem.css({ left: left, top: top, right: 'inherit' }); } else { $elem.css({ left: '', top: '', right: '' }); var off = $elem.parent().offset(); if (off.left + $elem.outerWidth() > $(window).width()) { var left = $(window).width() - $elem.outerWidth() - off.left; $elem.css({ left: left }); } if (right) { $elem.addClass('pull-right'); } } } this.alignPopupMenu = alignPopupMenu; function alignCenterDialogs() { $.each($('.modal-center'), function(index, element) { Util.centerDialog(element, true); }); } function resizeNavbar() { var ScrollDelta = scrollbarWidth; if ($(document).height() > $(window).height()) { // scrollbar is already visible, not need to acount on it ScrollDelta = 0; } if (UISettings.miniTheme) { var w = $('#NavbarContainer').width() - $('#RefreshBlockPhone').outerWidth() - ScrollDelta; var $btns = $('#Navbar ul.nav > li'); var buttonWidth = w / $btns.length; $btns.css('min-width', buttonWidth + 'px'); $('#NavLinks').css('margin-left', 0); $('body').toggleClass('navfixed', false); } else { var InfoBlockMargin = 10; var w = $('#SearchBlock').position().left - $('#InfoBlock').position().left - $('#InfoBlock').width() - InfoBlockMargin * 2 - ScrollDelta; var n = $('#NavLinks').width(); var offset = (w - n) / 2; var fixed = true; if (offset < 0) { w = $('#NavbarContainer').width() - ScrollDelta; offset = (w - n) / 2; fixed = false; } offset = offset > 0 ? offset : 0; $('#NavLinks').css('margin-left', offset); // as of Aug 2012 Mobile Safari does not support "position:fixed" $('body').toggleClass('navfixed', fixed && !mobileSafari); if (switchingTheme) { $('#Navbar ul.nav > li').css('min-width', ''); } } } function updateTabInfo(control, stat) { control.toggleClass('badge-info', stat.available == stat.total).toggleClass('badge-warning', stat.available != stat.total); control.html(stat.available); control.toggleClass('badge2', stat.total > 9); control.toggleClass('badge3', stat.total > 99); if (control.lastOuterWidth !== control.outerWidth()) { resizeNavbar(); control.lastOuterWidth = control.outerWidth(); } } function switchTheme() { switchingTheme = true; $('#DownloadsTable tbody').empty(); $('#HistoryTable tbody').empty(); $('#MessagesTable tbody').empty(); $('body').toggleClass('phone', UISettings.miniTheme); $('.datatable').toggleClass('table-bordered', !UISettings.miniTheme); $('#DownloadsTable').toggleClass('table-check', !UISettings.miniTheme || UISettings.showEditButtons); $('#HistoryTable').toggleClass('table-check', !UISettings.miniTheme || UISettings.showEditButtons); alignPopupMenu('#PlayMenu'); alignPopupMenu('#RefreshMenu'); alignPopupMenu('#RssMenu'); alignPopupMenu('#StatDialog_MonthMenu', true); if (UISettings.miniTheme) { $('#RefreshBlock').appendTo($('#RefreshBlockPhone')); $('#DownloadsRecordsPerPageBlock').appendTo($('#DownloadsRecordsPerPageBlockPhone')); $('#HistoryRecordsPerPageBlock').appendTo($('#HistoryRecordsPerPageBlockPhone')); $('#MessagesRecordsPerPageBlock').appendTo($('#MessagesRecordsPerPageBlockPhone')); $('#StatDialog_MonthMenu').appendTo($('#StatDialog_MonthBlockPhone')); } else { $('#RefreshBlock').appendTo($('#RefreshBlockDesktop')); $('#DownloadsRecordsPerPageBlock').appendTo($('#DownloadsTableTopBlock')); $('#HistoryRecordsPerPageBlock').appendTo($('#HistoryTableTopBlock')); $('#MessagesRecordsPerPageBlock').appendTo($('#MessagesTableTopBlock')); $('#StatDialog_MonthMenu').appendTo($('#StatDialog_MonthBlockTop')); } if (initialized && !firstLoad) { Downloads.redraw(); History.redraw(); Messages.redraw(); Downloads.applyTheme(); History.applyTheme(); Messages.applyTheme(); windowResized(); } switchingTheme = false; } }(jQuery)); /*** REFRESH CONTROL *********************************************************/ var Refresher = (new function($) { 'use strict'; // State var loadQueue; var firstLoad = true; var secondsToUpdate = -1; var refreshTimer = 0; var indicatorTimer = 0; var indicatorFrame=0; var refreshPaused = 0; var refreshing = false; var refreshNeeded = false; var refreshErrors = 0; this.init = function() { RPC.rpcUrl = UISettings.rpcUrl; RPC.connectErrorMessage = 'Cannot establish connection to NZBGet.' RPC.defaultFailureCallback = rpcFailure; RPC.next = loadNext; $('#RefreshMenu li a').click(refreshIntervalClick); $('#RefreshButton').click(refreshClick); updateRefreshMenu(); } function refresh() { UISettings.connectionError = false; $('#ErrorAlert').hide(); refreshStarted(); loadQueue = new Array( function() { Options.update(); }, function() { Status.update(); }, function() { Downloads.update(); }, function() { Messages.update(); }, function() { History.update(); }); if (!firstLoad) { // query NZBGet configuration only on first refresh loadQueue.shift(); } loadNext(); } function loadNext() { if (loadQueue.length > 0) { var nextStep = loadQueue[0]; loadQueue.shift(); nextStep(); } else { firstLoad = false; Frontend.loadCompleted(); refreshCompleted(); } } function rpcFailure(res, result) { // If a communication error occurs during status refresh we retry: // first attempt is made immediately, other attempts are made after defined refresh interval if (refreshing && !(result && result.error)) { refreshErrors = refreshErrors + 1; if (refreshErrors === 1 && refreshErrors <= UISettings.refreshRetries) { refresh(); return; } else if (refreshErrors <= UISettings.refreshRetries) { $('#RefreshError').show(); scheduleNextRefresh(); return; } } Refresher.pause(); UISettings.connectionError = true; $('#FirstUpdateInfo').hide(); $('#ErrorAlert-text').html(res); $('#ErrorAlert').show(); $('#RefreshError').hide(); if (Status.status) { // stop animations Status.redraw(); } $('html, body').animate({scrollTop: 0 }, 400); }; function refreshStarted() { clearTimeout(refreshTimer); refreshPaused = 0; refreshing = true; refreshNeeded = false; refreshAnimationShow(); } function refreshCompleted() { refreshing = false; refreshErrors = 0; $('#RefreshError').hide(); scheduleNextRefresh(); } this.pause = function() { clearTimeout(refreshTimer); refreshPaused++; } this.resume = function() { refreshPaused--; if (refreshPaused === 0 && UISettings.refreshInterval > 0) { countSeconds(); } } this.update = function() { refreshNeeded = true; refreshPaused = 0; if (!refreshing) { scheduleNextRefresh(); } } function refreshClick() { if (indicatorFrame > 10) { // force animation restart indicatorFrame = 0; } refreshErrors = 0; refresh(); } function scheduleNextRefresh() { clearTimeout(refreshTimer); secondsToUpdate = refreshNeeded ? 0 : UISettings.refreshInterval; if (secondsToUpdate > 0 || refreshNeeded) { secondsToUpdate += 0.1; countSeconds(); } } function countSeconds() { if (refreshPaused > 0) { return; } secondsToUpdate -= 0.1; if (secondsToUpdate <= 0) { refresh(); } else { refreshTimer = setTimeout(countSeconds, 100); } } function refreshAnimationShow() { if (UISettings.refreshAnimation && indicatorTimer === 0) { refreshAnimationFrame(); } } function refreshAnimationFrame() { // animate next frame indicatorFrame++; if (indicatorFrame === 20) { indicatorFrame = 0; } var f = indicatorFrame <= 10 ? indicatorFrame : 0; var degree = 360 * f / 10; $('#RefreshAnimation').css({ '-webkit-transform': 'rotate(' + degree + 'deg)', '-moz-transform': 'rotate(' + degree + 'deg)', '-ms-transform': 'rotate(' + degree + 'deg)', '-o-transform': 'rotate(' + degree + 'deg)', 'transform': 'rotate(' + degree + 'deg)' }); if ((!refreshing && indicatorFrame === 0 && (UISettings.refreshInterval === 0 || UISettings.refreshInterval > 1 || !UISettings.refreshAnimation)) || UISettings.connectionError) { indicatorTimer = 0; } else { // schedule next frame update indicatorTimer = setTimeout(refreshAnimationFrame, 100); } } function refreshIntervalClick() { var data = $(this).parent().attr('data'); UISettings.refreshInterval = parseFloat(data); scheduleNextRefresh(); updateRefreshMenu(); UISettings.save(); if (UISettings.refreshInterval === 0) { // stop animation Status.redraw(); } } function updateRefreshMenu() { Util.setMenuMark($('#RefreshMenu'), UISettings.refreshInterval); } }(jQuery)); function TODO(text) { $('#Notif_NotImplemented_Param').html(text === undefined ? '' : ': ' + text); Notification.show('#Notif_NotImplemented'); } /*** CONFIRMATION DIALOG *****************************************************/ var ConfirmDialog = (new function($) { 'use strict'; // Controls var $ConfirmDialog; // State var actionCallback; this.init = function() { $ConfirmDialog = $('#ConfirmDialog'); $ConfirmDialog.on('hidden', hidden); $('#ConfirmDialog_OK').click(click); } this.showModal = function(id, _actionCallback, initCallback) { $('#ConfirmDialog_Title').html($('#' + id + '_Title').html()); $('#ConfirmDialog_Text').html($('#' + id + '_Text').html()); $('#ConfirmDialog_OK').html($('#' + id + '_OK').html()); var helpId = $('#' + id + '_Help').html(); $('#ConfirmDialog_Help').attr('href', '#' + helpId); Util.show('#ConfirmDialog_Help', helpId !== null); actionCallback = _actionCallback; if (initCallback) { initCallback($ConfirmDialog); } Util.centerDialog($ConfirmDialog, true); $ConfirmDialog.modal({backdrop: 'static'}); // avoid showing multiple backdrops when the modal is shown from other modal var backdrops = $('.modal-backdrop'); if (backdrops.length > 1) { backdrops.last().remove(); } } function hidden() { // confirm dialog copies data from other nodes // the copied DOM nodes must be destroyed $('#ConfirmDialog_Title').empty(); $('#ConfirmDialog_Text').empty(); $('#ConfirmDialog_OK').empty(); } function click(event) { event.preventDefault(); // avoid scrolling actionCallback($ConfirmDialog); $ConfirmDialog.modal('hide'); } }(jQuery)); /*** ALERT DIALOG *****************************************************/ var AlertDialog = (new function($) { 'use strict'; // Controls var $AlertDialog; this.init = function() { $AlertDialog = $('#AlertDialog'); } this.showModal = function(title, text) { $('#AlertDialog_Title').html(title); $('#AlertDialog_Text').html(text); Util.centerDialog($AlertDialog, true); $AlertDialog.modal(); } }(jQuery)); /*** NOTIFICATIONS *********************************************************/ var Notification = (new function($) { 'use strict'; this.show = function(alert, completeFunc) { if (UISettings.notifications || $(alert).hasClass('alert-error')) { $(alert).animate({'opacity':'toggle'}); var duration = $(alert).attr('data-duration'); if (duration == null) { duration = 1000; } window.setTimeout(function() { $(alert).animate({'opacity':'toggle'}, completeFunc); }, duration); } else if (completeFunc) { completeFunc(); } } }(jQuery)); nzbget-16.4/webui/status.js0000644000175000017500000011466412630544544015570 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2012-2015 Andrey Prygunkov * * 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$ * */ /* * In this module: * 1) Status Infos on main page (speed, time, paused state etc.); * 2) Statistics and Status dialog; * 3) Limit dialog (speed and active news servers); * 4) Filter menu. */ /*** STATUS INFOS ON MAIN PAGE AND STATISTICS DIALOG ****************************************/ var Status = (new function($) { 'use strict'; // Properties (public) this.status; // Controls var $CHPauseDownload; var $CHPausePostProcess; var $CHPauseScan; var $StatusPausing; var $StatusPaused; var $StatusLeft; var $StatusSpeed; var $StatusSpeedIcon; var $StatusTimeIcon; var $StatusTime; var $StatusURLs; var $PlayBlock; var $PlayButton; var $PauseButton; var $PlayAnimation; var $StatDialog; var $ScheduledPauseDialog; var $PauseForInput; // State var status; var lastPlayState = 0; var lastAnimState = 0; var playInitialized = false; var modalShown = false; this.init = function() { $CHPauseDownload = $('#CHPauseDownload'); $CHPausePostProcess = $('#CHPausePostProcess'); $CHPauseScan = $('#CHPauseScan'); $PlayBlock = $('#PlayBlock'); $PlayButton = $('#PlayButton'); $PauseButton = $('#PauseButton'); $PlayAnimation = $('#PlayAnimation'); $StatusPausing = $('#StatusPausing'); $StatusPaused = $('#StatusPaused'); $StatusLeft = $('#StatusLeft'); $StatusSpeed = $('#StatusSpeed'); $StatusSpeedIcon = $('#StatusSpeedIcon'); $StatusTimeIcon = $('#StatusTimeIcon'); $StatusTime = $('#StatusTime'); $StatusURLs = $('#StatusURLs'); $ScheduledPauseDialog = $('#ScheduledPauseDialog') $PauseForInput = $('#PauseForInput'); if (UISettings.setFocus) { $ScheduledPauseDialog.on('shown', function() { $('#PauseForInput').focus(); }); } $PlayAnimation.hover(function() { $PlayBlock.addClass('hover'); }, function() { $PlayBlock.removeClass('hover'); }); // temporary pause the play animation if any modal is shown (to avoid artifacts in safari) $('body >.modal').on('show', modalShow); $('body > .modal').on('hide', modalHide); StatDialog.init(); FilterMenu.init(); } this.update = function() { var _this = this; RPC.call('status', [], function(curStatus) { status = curStatus; _this.status = status; StatDialog.update(); }); } this.redraw = function() { redrawInfo(); StatDialog.redraw(); } function redrawInfo() { Util.show($CHPauseDownload, status.DownloadPaused); Util.show($CHPausePostProcess, status.PostPaused); Util.show($CHPauseScan, status.ScanPaused); updatePlayAnim(); updatePlayButton(); if (status.ServerStandBy) { $StatusSpeed.html('--- MB/s'); if (status.ResumeTime > 0) { $StatusTime.html(Util.formatTimeLeft(status.ResumeTime - status.ServerTime)); } else if (status.RemainingSizeMB > 0 || status.RemainingSizeLo > 0) { if (status.AverageDownloadRate > 0) { $StatusTime.html(Util.formatTimeLeft(status.RemainingSizeMB*1024/(status.AverageDownloadRate/1024))); } else { $StatusTime.html('--h --m'); } } else { $StatusTime.html('0h 0m'); } } else { $StatusSpeed.html(Util.formatSpeed(status.DownloadRate)); if (status.DownloadRate > 0) { $StatusTime.html(Util.formatTimeLeft( (status.DownloadPaused ? status.ForcedSizeMB : status.RemainingSizeMB) *1024/(status.DownloadRate/1024))); } else { $StatusTime.html('--h --m'); } } var limit = status.DownloadLimit > 0; if (!limit) { for (var i=0; i < Status.status.NewsServers.length; i++) { limit = !Status.status.NewsServers[i].Active; if (limit) { break; } } } $StatusSpeedIcon.toggleClass('icon-plane', !limit); $StatusSpeedIcon.toggleClass('icon-truck', limit); $StatusTime.toggleClass('scheduled-resume', status.ServerStandBy && status.ResumeTime > 0); $StatusTimeIcon.toggleClass('icon-time', !(status.ServerStandBy && status.ResumeTime > 0)); $StatusTimeIcon.toggleClass('icon-time-orange', status.ServerStandBy && status.ResumeTime > 0); } function updatePlayButton() { var Play = !status.DownloadPaused; if (Play === lastPlayState) { return; } lastPlayState = Play; var hideBtn = Play ? $PlayButton : $PauseButton; var showBtn = !Play ? $PlayButton : $PauseButton; if (playInitialized) { hideBtn.fadeOut(500); showBtn.fadeIn(500); if (!Play && !status.ServerStandBy) { Notification.show('#Notif_Downloads_Pausing'); } } else { hideBtn.hide(); showBtn.show(); } if (Play) { $PlayAnimation.removeClass('pause').addClass('play'); } else { $PlayAnimation.removeClass('play').addClass('pause'); } playInitialized = true; } function updatePlayAnim() { // Animate if either any downloads or post-processing is in progress var Anim = (!status.ServerStandBy || status.FeedActive || status.QueueScriptCount > 0 || (status.PostJobCount > 0 && !status.PostPaused) || (status.UrlCount > 0 && (!status.DownloadPaused || Options.option('UrlForce') === 'yes'))) && (UISettings.refreshInterval !== 0) && !UISettings.connectionError; if (Anim === lastAnimState) { return; } lastAnimState = Anim; if (UISettings.activityAnimation && !modalShown) { if (Anim) { $PlayAnimation.fadeIn(1000); } else { $PlayAnimation.fadeOut(1000); } } } this.playClick = function() { //Notification.show('#Notif_Play'); if (lastPlayState) { // pause all activities RPC.call('pausedownload', [], function(){RPC.call('pausepost', [], function(){RPC.call('pausescan', [], Refresher.update)})}); } else { // resume all activities RPC.call('resumedownload', [], function(){RPC.call('resumepost', [], function(){RPC.call('resumescan', [], Refresher.update)})}); } } this.pauseClick = function(data) { switch (data) { case 'download': var method = status.DownloadPaused ? 'resumedownload' : 'pausedownload'; break; case 'post': var method = status.PostPaused ? 'resumepost' : 'pausepost'; break; case 'scan': var method = status.ScanPaused ? 'resumescan' : 'pausescan'; break; } RPC.call(method, [], Refresher.update); } this.statDialogClick = function() { StatDialog.showModal(); } this.scheduledPauseClick = function(seconds) { RPC.call('pausedownload', [], function(){RPC.call('pausepost', [], function(){RPC.call('pausescan', [], function(){RPC.call('scheduleresume', [seconds], Refresher.update)})})}); } this.scheduledPauseDialogClick = function() { $PauseForInput.val(''); $ScheduledPauseDialog.modal(); } this.pauseForClick = function() { var val = $PauseForInput.val(); var minutes = parseInt(val); if (isNaN(minutes) || minutes <= 0) { return; } $ScheduledPauseDialog.modal('hide'); this.scheduledPauseClick(minutes * 60); } function modalShow() { modalShown = true; if (lastAnimState) { $PlayAnimation.hide(); } } function modalHide() { if (lastAnimState) { $PlayAnimation.show(); } modalShown = false; } this.serverName = function(server) { var name = Options.option('Server' + server.ID + '.Name'); if (name === null || name === '') { var host = Options.option('Server' + server.ID + '.Host'); var port = Options.option('Server' + server.ID + '.Port'); name = (host === null ? '' : host) + ':' + (port === null ? '119' : port); } return name; } }(jQuery)); /*** STATISTICS DIALOG *******************************************************/ var StatDialog = (new function($) { 'use strict'; // Controls var $StatDialog; var $StatDialog_DataVersion; var $StatDialog_DataUptime; var $StatDialog_DataDownloadTime; var $StatDialog_DataTotalDownloaded; var $StatDialog_DataRemaining; var $StatDialog_DataFree; var $StatDialog_DataAverageSpeed; var $StatDialog_DataCurrentSpeed; var $StatDialog_DataSpeedLimit; var $StatDialog_ArticleCache; var $StatDialog_QueueScripts; var $StatDialog_ChartBlock; var $StatDialog_Server; var $StatRangeDialog; var $StatRangeDialog_PeriodInput; var $StatDialog_Tooltip; var $StatDialog_TodaySize; var $StatDialog_MonthSize; var $StatDialog_AllTimeSize; var $StatDialog_CustomSize; var $StatDialog_Custom; // State var visible = false; var lastPage; var lastTab = null; var lastFullscreen; var servervolumes = null; var prevServervolumes = null; var curRange = 'MIN'; var redrawLock = 0; var needChartUpdate = false; var curServer = 0; var monthListInitialized = false; var curMonth = null; var monYear = false; var monStartIndex = 0; var monEndIndex = 0; var monStartDate; var chartData = null; var mouseOverIndex = -1; var clockOK = false; var monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; this.init = function() { $StatDialog = $('#StatDialog'); $StatDialog_DataVersion = $('#StatDialog_DataVersion'); $StatDialog_DataUptime = $('#StatDialog_DataUptime'); $StatDialog_DataDownloadTime = $('#StatDialog_DataDownloadTime'); $StatDialog_DataTotalDownloaded = $('#StatDialog_DataTotalDownloaded'); $StatDialog_DataRemaining = $('#StatDialog_DataRemaining'); $StatDialog_DataFree = $('#StatDialog_DataFree'); $StatDialog_DataAverageSpeed = $('#StatDialog_DataAverageSpeed'); $StatDialog_DataCurrentSpeed = $('#StatDialog_DataCurrentSpeed'); $StatDialog_DataSpeedLimit = $('#StatDialog_DataSpeedLimit'); $StatDialog_ArticleCache = $('#StatDialog_ArticleCache'); $StatDialog_QueueScripts = $('#StatDialog_QueueScripts'); $StatDialog_ChartBlock = $('#StatDialog_ChartBlock'); $StatDialog_Server = $('#StatDialog_Server'); $StatRangeDialog = $('#StatRangeDialog'); $StatRangeDialog_PeriodInput = $('#StatRangeDialog_PeriodInput'); $StatDialog_Tooltip = $('#StatDialog_Tooltip'); $StatDialog_TodaySize = $('#StatDialog_TodaySize'); $StatDialog_MonthSize = $('#StatDialog_MonthSize'); $StatDialog_AllTimeSize = $('#StatDialog_AllTimeSize'); $StatDialog_CustomSize = $('#StatDialog_CustomSize'); $StatDialog_Custom = $('#StatDialog_Custom'); $('#StatDialog_ServerMenuAll').click(chooseServer); $('#StatDialog_Volumes').click(tabClick); $('#StatDialog_Back').click(backClick); $StatDialog.on('hidden', function() { // cleanup lastTab = null; servervolumes = null; prevServervolumes = null; $StatDialog_ChartBlock.empty(); visible = false; }); if (UISettings.setFocus) { $StatRangeDialog.on('shown', function() { $StatRangeDialog_PeriodInput.focus(); }); } $StatRangeDialog.on('hidden', StatRangeDialogHidden); TabDialog.extend($StatDialog); } this.update = function() { if (visible) { RPC.call('servervolumes', [], servervolumes_loaded); } else { RPC.next(); } } function servervolumes_loaded(volumes) { prevServervolumes = servervolumes; servervolumes = volumes; RPC.next(); } function firstLoadStatisticsData() { RPC.call('servervolumes', [], function (volumes) { prevServervolumes = servervolumes; servervolumes = volumes; updateMonthList(); StatDialog.redraw(); }); } this.showModal = function() { $('#StatDialog_GeneralTab').show(); $('#StatDialog_VolumesTab').hide(); $('#StatDialog_Back').hide(); $('#StatDialog_BackSpace').show(); $('#StatDialog_Title').text('Statistics and Status'); Util.show('#StatDialog_ArticleCache_Row', Options.option('ArticleCache') !== '0'); Util.show('#StatDialog_QueueScripts_Row', Status.status.QueueScriptCount > 0); $StatDialog.removeClass('modal-large').addClass('modal-mini'); monthListInitialized = false; updateServerList(); lastTab = null; $StatDialog.restoreTab(); visible = true; redrawStatistics(); $StatDialog.modal(); firstLoadStatisticsData(); } this.redraw = function() { if (visible) { redrawStatistics(); if (servervolumes !== null && lastTab === '#StatDialog_VolumesTab') { if (redrawLock > 0) { needChartUpdate = true; } else { if (!monthListInitialized) { updateMonthList(); } redrawChart(); } } } } function redrawStatistics() { var status = Status.status; $StatDialog_DataVersion.text(Options.option('Version')); $StatDialog_DataUptime.text(Util.formatTimeHMS(status.UpTimeSec)); $StatDialog_DataDownloadTime.text(Util.formatTimeHMS(status.DownloadTimeSec)); $StatDialog_DataTotalDownloaded.html(Util.formatSizeMB(status.DownloadedSizeMB)); $StatDialog_DataRemaining.html(Util.formatSizeMB(status.RemainingSizeMB)); $StatDialog_DataFree.html(Util.formatSizeMB(status.FreeDiskSpaceMB)); $StatDialog_DataAverageSpeed.html(Util.formatSpeed(status.AverageDownloadRate)); $StatDialog_DataCurrentSpeed.html(Util.formatSpeed(status.DownloadRate)); $StatDialog_DataSpeedLimit.html(Util.formatSpeed(status.DownloadLimit)); $StatDialog_ArticleCache.html(Util.formatSizeMB(status.ArticleCacheMB, status.ArticleCacheLo)); $StatDialog_QueueScripts.html(status.QueueScriptCount); var content = ''; content += 'Download' + (status.DownloadPaused ? 'paused' : 'active') + ''; content += 'Post-processing' + (Options.option('PostProcess') === '' ? 'disabled' : (status.PostPaused ? 'paused' : 'active')) + ''; content += 'NZB-Directory scan' + (Options.option('NzbDirInterval') === '0' ? 'disabled' : (status.ScanPaused ? 'paused' : 'active')) + ''; if (status.ResumeTime > 0) { content += 'Autoresume' + Util.formatTimeHMS(status.ResumeTime - status.ServerTime) + ''; } $('#StatusTable tbody').html(content); } function tabClick(e) { e.preventDefault(); $('#StatDialog_Back').fadeIn(500); $('#StatDialog_BackSpace').hide(); lastTab = '#' + $(this).attr('data-tab'); lastPage = $(lastTab); lastFullscreen = ($(this).attr('data-fullscreen') === 'true') && !UISettings.miniTheme; redrawLock++; $StatDialog.switchTab($('#StatDialog_GeneralTab'), lastPage, e.shiftKey || !UISettings.slideAnimation ? 0 : 500, { fullscreen: lastFullscreen, toggleClass: 'modal-mini modal-large', mini: UISettings.miniTheme, complete: tabSwitchCompleted}); if (lastTab === '#StatDialog_VolumesTab') { redrawChart(); $('#StatDialog_Title').text('Downloaded volumes'); } } function backClick(e) { e.preventDefault(); $('#StatDialog_Back').fadeOut(500, function() { $('#StatDialog_BackSpace').show(); }); $StatDialog.switchTab(lastPage, $('#StatDialog_GeneralTab'), e.shiftKey || !UISettings.slideAnimation ? 0 : 500, { fullscreen: lastFullscreen, toggleClass: 'modal-mini modal-large', mini: UISettings.miniTheme, back: true}); lastTab = null; $('#StatDialog_Title').text('Statistics and Status'); } function tabSwitchCompleted() { redrawLock--; if (needChartUpdate) { needChartUpdate = false; if (!monthListInitialized) { updateMonthList(); } redrawChart(); } Frontend.alignPopupMenu('#StatDialog_MonthMenu', true); } function size64(size) { return size.SizeMB < 2000 ? size.SizeLo / 1024.0 / 1024.0 : size.SizeMB; } function redrawChart() { var serverNo = curServer; var lineLabels = []; var dataLabels = []; var chartDataTB = []; var chartDataGB = []; var chartDataMB = []; var chartDataKB = []; var chartDataB = []; var curPoint = null; var sumMB = 0; var sumLo = 0; var maxSizeMB = 0; var maxSizeLo = 0; function addData(bytes, dataLab, lineLab) { dataLabels.push(dataLab); lineLabels.push(lineLab); if (bytes === null) { chartDataTB.push(null); chartDataGB.push(null); chartDataMB.push(null); chartDataKB.push(null); chartDataB.push(null); return; } chartDataTB.push(bytes.SizeMB / 1024.0 / 1024.0); chartDataGB.push(bytes.SizeMB / 1024.0); chartDataMB.push(size64(bytes)); chartDataKB.push(bytes.SizeLo / 1024.0); chartDataB.push(bytes.SizeLo); if (bytes.SizeMB > maxSizeMB) { maxSizeMB = bytes.SizeMB; } if (bytes.SizeLo > maxSizeLo) { maxSizeLo = bytes.SizeLo; } sumMB += bytes.SizeMB; sumLo += bytes.SizeLo; } function drawMinuteGraph() { // the current slot may be not fully filled yet, // to make the chart smoother for current slot we use the data from the previous reading // and we show the previous slot as current. curPoint = servervolumes[serverNo].SecSlot; for (var i = 0; i < 60; i++) { addData((i == curPoint && prevServervolumes !== null ? prevServervolumes : servervolumes)[serverNo].BytesPerSeconds[i], i + 's', i % 10 == 0 || i == 59 ? i : ''); } if (prevServervolumes !== null) { curPoint = curPoint > 0 ? curPoint-1 : 59; } } function drawHourGraph() { for (var i = 0; i < 60; i++) { addData(servervolumes[serverNo].BytesPerMinutes[i], i + 'm', i % 10 == 0 || i == 59 ? i : ''); } curPoint = servervolumes[serverNo].MinSlot; } function drawDayGraph() { for (var i = 0; i < 24; i++) { addData(servervolumes[serverNo].BytesPerHours[i], i + 'h', i % 3 == 0 || i == 23 ? i : ''); } curPoint = servervolumes[serverNo].HourSlot; } function drawMonthGraph() { var len = servervolumes[serverNo].BytesPerDays.length; var daySlot = servervolumes[serverNo].DaySlot; var slotDelta = servervolumes[0].FirstDay - servervolumes[serverNo].FirstDay; var dt = new Date(monStartDate.getTime()); var day = 1; for (var i = monStartIndex; i <= monEndIndex; i++, day++) { dt.setDate(day); var slot = i + slotDelta; addData((slot >= 0 && slot < len ? servervolumes[serverNo].BytesPerDays[slot] : null), dt.toDateString(), (day == 1 || day % 5 == 0 || (day < 30 && i === monEndIndex) ? day : '')); if (slot === daySlot) { curPoint = day-1; } } // ensure the line has always the same length (looks nicer) for (; day < 32; day++) { addData(null, null, null); } } function drawYearGraph() { var firstMon = -1; var lastMon = -1; var monDataMB = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var monDataLo = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // aggregate daily volumes into months var len = servervolumes[serverNo].BytesPerDays.length; var daySlot = servervolumes[serverNo].DaySlot; var slotDelta = servervolumes[0].FirstDay - servervolumes[serverNo].FirstDay; var startDate = new Date(monStartDate.getTime()); var day = 0; for (var i = monStartIndex; i <= monEndIndex; i++, day++) { var dt = new Date(monStartDate.getTime() + day*24*60*60*1000); var slot = i + slotDelta; var bytes = servervolumes[serverNo].BytesPerDays[slot]; if (bytes) { var mon = dt.getMonth(); monDataMB[mon] += bytes.SizeMB; monDataLo[mon] += bytes.SizeLo; if (firstMon === -1) { firstMon = mon; } if (mon > lastMon) { lastMon = mon; } if (slot === daySlot) { curPoint = mon; } } } for (var i = 0; i < 12; i++) { addData(firstMon > -1 && i >= firstMon && i <= lastMon ? {SizeMB: monDataMB[i], SizeLo: monDataLo[i]} : null, monthNames[i] + ' ' + curMonth, monthNames[i].substr(0, 3)); } } if (curRange === 'MIN') { drawMinuteGraph(); } else if (curRange === 'HOUR') { drawHourGraph(); } else if (curRange === 'DAY') { drawDayGraph(); } else if (curRange === 'MONTH' && !monYear) { drawMonthGraph(); } else if (curRange === 'MONTH' && monYear) { drawYearGraph(); } var serieData = maxSizeMB > 1024*1024 ? chartDataTB : maxSizeMB > 1024 ? chartDataGB : maxSizeMB > 1 || maxSizeLo == 0 ? chartDataMB : maxSizeLo > 1024 ? chartDataKB : chartDataB; var units = maxSizeMB > 1024*1024 ? ' TB' : maxSizeMB > 1024 ? ' GB' : maxSizeMB > 1 || maxSizeLo == 0 ? ' MB' : maxSizeLo > 1024 ? ' KB' : ' B'; var curPointData = []; for (var i = 0; i < serieData.length; i++) { curPointData.push(i===curPoint ? serieData[i] : null); } chartData = { serieData: serieData, serieDataMB: chartDataMB, serieDataLo: chartDataB, sumMB: sumMB, sumLo: sumLo, dataLabels: dataLabels }; $StatDialog_ChartBlock.empty(); $StatDialog_ChartBlock.html('
'); $('#StatDialog_Chart').chart({ values: { serie1 : serieData, serie2: curPointData }, labels: lineLabels, type: 'line', margins: [10, 15, 20, 60], defaultSeries: { rounded: 0.5, fill: true, plotProps: { 'stroke-width': 3.0 }, dot: true, dotProps: { stroke: '#FFF', size: 3.0, 'stroke-width': 1.0, fill: '#5AF' }, highlight: { scaleSpeed: 0, scaleEasing: '>', scale: 2.0 }, tooltip: { active: false, }, color: '#5AF' }, series: { serie2: { dotProps: { stroke: '#F21860', fill: '#F21860', size: 3.5, 'stroke-width': 2.5 }, highlight: { scale: 1.5 }, } }, defaultAxis: { labels: true, labelsProps: { 'font-size': 13 }, labelsDistance: 12 }, axis: { l: { labels: true, suffix: units } }, features: { grid: { draw: [true, false], forceBorder: true, props: { stroke: '#e0e0e0', 'stroke-width': 1 }, ticks: { active: [true, false, false], size: [6, 0], props: { stroke: '#e0e0e0' } } }, mousearea: { type: 'axis', onMouseOver: chartMouseOver, onMouseExit: chartMouseExit, onMouseOut: chartMouseExit }, } }); simulateMouseEvent(); updateCounters(); } function chartMouseOver(env, serie, index, mouseAreaData) { if (mouseOverIndex > -1) { var env = $('#StatDialog_Chart').data('elycharts_env'); $.elycharts.mousemanager.onMouseOutArea(env, false, mouseOverIndex, env.mouseAreas[mouseOverIndex]); } mouseOverIndex = index; $StatDialog_Tooltip.html(chartData.dataLabels[index] + ': ' + Util.formatSizeMB(chartData.serieDataMB[index], chartData.serieDataLo[index]) + ''); } function chartMouseExit(env, serie, index, mouseAreaData) { mouseOverIndex = -1; var title = curRange === 'MIN' ? '60 seconds' : curRange === 'HOUR' ? '60 minutes' : curRange === 'DAY' ? '24 hours' : curRange === 'MONTH' ? $('#StatDialog_Volume_MONTH').text() : 'Sum'; $StatDialog_Tooltip.html(title + ': ' + Util.formatSizeMB(chartData.sumMB, chartData.sumLo) + ''); } function simulateMouseEvent() { if (mouseOverIndex > -1) { var env = $('#StatDialog_Chart').data('elycharts_env'); $.elycharts.mousemanager.onMouseOverArea(env, false, mouseOverIndex, env.mouseAreas[mouseOverIndex]); } else { chartMouseExit() } } this.chooseRange = function(range) { curRange = range; updateRangeButtons(); mouseOverIndex = -1; redrawChart(); } function updateRangeButtons() { $('#StatDialog_Toolbar .volume-range').removeClass('btn-inverse'); $('#StatDialog_Volume_' + curRange + ',#StatDialog_Volume_' + curRange + '2,#StatDialog_Volume_' + curRange + '3').addClass('btn-inverse'); } function updateServerList() { var menu = $('#StatDialog_ServerMenu'); var menuItemTemplate = $('.volume-server-template', menu); var insertPos = $('#StatDialog_ServerMenuDivider', menu); $('.volume-server', menu).remove(); for (var i=0; i < Status.status.NewsServers.length; i++) { var server = Status.status.NewsServers[i]; var name = server.ID + '. ' + Status.serverName(server); var item = menuItemTemplate.clone().removeClass('volume-server-template hide').addClass('volume-server'); var a = $('a', item); a.html('' + Util.textToHtml(name)); a.attr('data-id', server.ID); a.click(chooseServer); insertPos.before(item); } $('#StatDialog_ServerCap').text(curServer > 0 ? Status.serverName(Status.status.NewsServers[curServer-1]) : 'All news servers'); $('#StatDialog_ServerMenuAll i').toggleClass('icon-ok', curServer === 0).toggleClass('icon-empty', curServer !== 0); } function chooseServer(server) { curServer = parseInt($(this).attr('data-id')); updateServerList(); redrawChart(); } function dayToDate(epochDay) { var dt = new Date(epochDay * 24*60*60 * 1000); dt = new Date(dt.getTime() + dt.getTimezoneOffset() * 60*1000); return dt; } function dateToDay(date) { var epochDay = Math.ceil((date.getTime() - date.getTimezoneOffset() * 60*1000) / (1000*24*60*60)); return epochDay; } function updateMonthList() { monthListInitialized = true; var firstDay = servervolumes[0].FirstDay; var lastDay = firstDay + servervolumes[0].BytesPerDays.length - 1; var curDay = firstDay + servervolumes[0].DaySlot; var firstDt = dayToDate(firstDay); var lastDt = dayToDate(lastDay); var curDt = dayToDate(curDay); var menu = $('#StatDialog_MonthMenu'); var menuItemTemplate = $('.volume-month-template', menu); var insertPos = $('#StatDialog_MonthMenuYears', menu); $('.volume-month', menu).remove(); // does computer running NZBGet has correct date (after 1-Jan-2013)? clockOK = firstDay > 0 && servervolumes[0].DaySlot > -1; if (!clockOK) { updatePeriod(); return; } // show last three months in the menu firstDt.setDate(1); var monDt = new Date(curDt.getTime()); monDt.setDate(1); for (var i=0; i<3; i++) { if (monDt < firstDt) { break; } var name = monthNames[monDt.getMonth()] + ' ' + monDt.getFullYear(); var monId = '' + monDt.getFullYear() + '-' + monDt.getMonth(); if (curMonth === null) { curMonth = monId; } var item = menuItemTemplate.clone().removeClass('volume-month-template hide').addClass('volume-month'); var a = $('a', item); a.html('' + name); a.attr('data-id', monId); a.click(chooseMonth); insertPos.before(item); monDt.setMonth(monDt.getMonth() - 1); } // show last two years in the menu var insertPos = $('#StatDialog_MonthMenuDivider', menu); firstDt.setMonth(0); monDt = new Date(curDt.getTime()); monDt.setDate(1); monDt.setMonth(0); for (var i=0; i<2; i++) { if (monDt < firstDt) { break; } var name = monDt.getFullYear(); var monId = '' + monDt.getFullYear(); var item = menuItemTemplate.clone().removeClass('volume-month-template hide').addClass('volume-month'); var a = $('a', item); a.html('' + name); a.attr('data-id', monId); a.click(chooseMonth); insertPos.before(item); monDt.setFullYear(monDt.getFullYear() - 1); } updatePeriod(); } function updatePeriod() { if (!clockOK) { monStartDate = new Date(2000, 1); monStartIndex = -1; monEndIndex = -1; return; } var cap; var monStart; var monEnd; monYear = curMonth.indexOf('-') === -1; if (monYear) { cap = curMonth; var year = parseInt(curMonth); monStart = new Date(year, 0); monEnd = new Date(year, 11, 31); } else { var month = parseInt(curMonth.substr(5, 2)); var year = parseInt(curMonth.substring(0, 4)); cap = monthNames[month] + ' ' + year; monStart = new Date(year, month); monEnd = new Date(year, month + 1); monEnd.setDate(0); } $('#StatDialog_Volume_MONTH, #StatDialog_Volume_MONTH2').text(cap); monStartDate = monStart; var firstDay = servervolumes[0].FirstDay; monStart = dateToDay(monStart); monEnd = dateToDay(monEnd); monStartIndex = monStart - firstDay; monEndIndex = monEnd - firstDay; } function updateCounters() { if (servervolumes[curServer].DaySlot > -1) { var bytes = servervolumes[curServer].BytesPerDays[servervolumes[curServer].DaySlot]; $StatDialog_TodaySize.html(Util.formatSizeMB(bytes.SizeMB, bytes.SizeLo)); } $StatDialog_AllTimeSize.html(Util.formatSizeMB(servervolumes[curServer].TotalSizeMB, servervolumes[curServer].TotalSizeLo)); $StatDialog_CustomSize.html(Util.formatSizeMB(servervolumes[curServer].CustomSizeMB, servervolumes[curServer].CustomSizeLo)); $StatDialog_Custom.attr('title', 'reset on ' + Util.formatDateTime(servervolumes[curServer].CustomTime)); // calculate volume for current month var sizeMB = 0; var sizeLo = 0; if (clockOK) { var firstDay = servervolumes[0].FirstDay; var monStart = dayToDate(firstDay + servervolumes[0].DaySlot); monStart.setDate(1); var monEnd = new Date(monStart.getFullYear(), monStart.getMonth() + 1); monEnd.setDate(0); monStart = dateToDay(monStart); monEnd = dateToDay(monEnd); var monStartIndex = monStart - firstDay; var monEndIndex = monEnd - firstDay; var slotDelta = servervolumes[0].FirstDay - servervolumes[curServer].FirstDay; for (var i = monStartIndex; i <= monEndIndex; i++) { var slot = i + slotDelta; var bytes = servervolumes[curServer].BytesPerDays[slot]; if (bytes) { sizeMB += bytes.SizeMB; sizeLo += bytes.SizeLo; } } } $StatDialog_MonthSize.html(Util.formatSizeMB(sizeMB, sizeLo)); } function chooseMonth() { setMonth($(this).attr('data-id')); } function setMonth(month) { curRange = 'MONTH'; curMonth = month; updateRangeButtons(); updateMonthList(); redrawChart(); } this.chooseOtherMonth = function() { $StatRangeDialog_PeriodInput.val(''); redrawLock++; $StatRangeDialog.modal({backdrop: false}); } function StatRangeDialogHidden() { redrawLock--; StatDialog.redraw(); } this.setPeriod = function() { var period = $StatRangeDialog_PeriodInput.val(); if (period.indexOf('-') === -1) { var year = parseInt(period); if (year < 2013 || year > 2050) { Notification.show('#Notif_StatRangeError'); return; } period = '' + year; } else { var month = parseInt(period.substr(5, 2)); var year = parseInt(period.substring(0, 4)); if (year < 2013 || year > 2050 || month < 1 || month > 12) { Notification.show('#Notif_StatRangeError'); return; } period = year + '-' + (month-1); } $StatRangeDialog.modal('hide'); setMonth(period); } this.resetCounter = function() { $('#StatDialogResetConfirmDialog_Server').text(curServer === 0 ? 'all news servers' : $('#StatDialog_ServerCap').text()); $('#StatDialogResetConfirmDialog_Time').text(Util.formatDateTime(servervolumes[curServer].CustomTime)); ConfirmDialog.showModal('StatDialogResetConfirmDialog', doResetCounter); } function doResetCounter() { RPC.call('resetservervolume', [curServer === 0 ? -1 : curServer, 'CUSTOM'], function() { Notification.show('#Notif_StatReset'); Refresher.update(); }); } }(jQuery)); /*** LIMIT DIALOG *******************************************************/ var LimitDialog = (new function($) { 'use strict' // Controls var $LimitDialog; var $ServerTable; var $LimitDialog_SpeedInput; // State var changed; this.init = function() { $LimitDialog = $('#LimitDialog'); $LimitDialog_SpeedInput = $('#LimitDialog_SpeedInput'); $('#LimitDialog_Save').click(save); $ServerTable = $('#LimitDialog_ServerTable'); $ServerTable.fasttable( { pagerContainer: $('#LimitDialog_ServerTable_pager'), headerCheck: $('#LimitDialog_ServerTable > thead > tr:first-child'), hasHeader: false, pageSize: 100 }); $ServerTable.on('click', 'tbody div.check', function(event) { $ServerTable.fasttable('itemCheckClick', this.parentNode.parentNode, event); }); $ServerTable.on('click', 'thead div.check', function() { $ServerTable.fasttable('titleCheckClick') }); $ServerTable.on('mousedown', Util.disableShiftMouseDown); if (UISettings.setFocus) { $LimitDialog.on('shown', function() { $('#LimitDialog_SpeedInput').focus(); }); } $LimitDialog.on('hidden', function() { // cleanup $ServerTable.fasttable('update', []); }); } this.clicked = function(e) { if (e.metaKey || e.ctrlKey) { toggleLimit(); } else { showModal(); } } function showModal() { var rate = Util.round0(Status.status.DownloadLimit / 1024); $LimitDialog_SpeedInput.val(rate > 0 ? rate : ''); updateTable(); $LimitDialog.modal({backdrop: 'static'}); } function updateTable() { var data = []; for (var i=0; i < Status.status.NewsServers.length; i++) { var server = Status.status.NewsServers[i]; var name = Status.serverName(server); var fields = ['
', server.ID + '. ' + name]; var item = { id: server.ID, fields: fields, }; data.push(item); $ServerTable.fasttable('checkRow', server.ID, server.Active); } $ServerTable.fasttable('update', data); Util.show('#LimitDialog_ServerBlock', data.length > 0); } function save(e) { var val = $LimitDialog_SpeedInput.val(); var rate = 0; if (val == '') { rate = 0; } else { rate = parseInt(val); if (isNaN(rate)) { return; } } var checkedRows = $ServerTable.fasttable('checkedRows'); var servers = []; for (var i=0; i < Status.status.NewsServers.length; i++) { var server = Status.status.NewsServers[i]; var selected = checkedRows[server.ID] !== undefined; if (server.Active != selected) { servers.push([server.ID, selected]); } } saveLimit(rate, servers); } function saveLimit(rate, servers) { function saveServers() { if (servers.length > 0) { changed = true; RPC.call('editserver', servers, function() { completed(); }); } else { completed(); } } changed = false; var oldRate = Util.round0(Status.status.DownloadLimit / 1024); if (rate != oldRate) { changed = true; RPC.call('rate', [rate], function() { saveServers(); }); } else { saveServers(); } } function completed() { $LimitDialog.modal('hide'); if (changed) { Notification.show('#Notif_SetSpeedLimit'); } Refresher.update(); } function toggleLimit() { var limited = Status.status.DownloadLimit > 0; for (var i=0; i < Status.status.NewsServers.length; i++) { var server = Status.status.NewsServers[i]; limited = limited || !server.Active; } var defRate = Options.option('DownloadRate'); var rate = limited ? 0 : parseInt(defRate === '' ? 0 : defRate); var servers = []; for (var i=0; i < Status.status.NewsServers.length; i++) { var server = Status.status.NewsServers[i]; var defActive = Options.option('Server' + (i + 1) + '.Active') === 'yes'; var newActive = limited ? true : defActive; if (server.Active != newActive) { servers.push([server.ID, newActive]); } } saveLimit(rate, servers); } }(jQuery)); /*** FILTER MENU *********************************************************/ var FilterMenu = (new function($) { 'use strict'; var $SaveFilterDialog; var $SaveFilterInput; var $Table_filter; var ignoreClick = false; var $Table_filter; var tabName; var items; this.init = function() { $SaveFilterDialog = $('#SaveFilterDialog'); $SaveFilterInput = $('#SaveFilterInput'); if (UISettings.setFocus) { $SaveFilterDialog.on('shown', function () { $SaveFilterInput.focus(); }); } } this.setTab = function(tabname) { tabName = tabname; $Table_filter = $('#' + tabName + 'Table_filter'); load(); } this.redraw = function() { var menu = $('#FilterMenu'); var menuItemTemplate = $('.filter-menu-template', menu); var insertPos = $('#FilterMenu_Divider', menu); $('.filter-menu', menu).remove(); for (var i = 0; i < items.length; i++) { var name = items[i].name; var item = menuItemTemplate.clone().removeClass('filter-menu-template').removeClass('hide').addClass('filter-menu'); var t = $('span', item); t.text(name); var a = $('a', item); a.click(applyFilter); a.attr('data-id', i); var im = $('button', item); im.click(deleteFilter); im.attr('data-id', i); insertPos.before(item); } Util.show('#FilterMenu_Empty', items.length === 0); if (UISettings.miniTheme) { Frontend.alignPopupMenu('#FilterMenu'); } } function applyFilter() { if (ignoreClick) { ignoreClick = false; return; } var id = parseInt($(this).attr('data-id')); $Table_filter.val(items[id].filter); $('#' + tabName +'Table').fasttable('applyFilter', $Table_filter.val()); } function deleteFilter() { ignoreClick = true; var id = parseInt($(this).attr('data-id')); items.splice(id, 1); save(); } this.saveDialogClick = function() { if ($Table_filter.val() === '') { Notification.show('#Notif_SaveFilterEmpty'); return; } var filter = $Table_filter.val(); var name = filter; // reuse the name if the filter already exists for (var i = 0; i < items.length; i++) { if (items[i].filter === filter) { name = items[i].name; break; } } $SaveFilterInput.val(name); $SaveFilterDialog.modal(); } this.saveClick = function() { $SaveFilterDialog.modal('hide'); var name = $SaveFilterInput.val(); var filter = $Table_filter.val(); // rename if already exists for (var i = 0; i < items.length; i++) { if (items[i].filter === filter) { items[i].name = name; save(); return; } } // doesn't exist - add new items.push({name: name, filter: filter}); save(); } function load() { items = JSON.parse(UISettings.read('Filter_' + tabName, '[]')); } function save() { UISettings.write('Filter_' + tabName, JSON.stringify(items)); } }(jQuery)); nzbget-16.4/webui/img/0000755000175000017500000000000012630544544014447 5ustar andreasandreasnzbget-16.4/webui/img/transmit-reload-2x.gif0000644000175000017500000001012212630544544020566 0ustar andreasandreasGIF89a FGջضܼƝ֝šǴүɢͮΩjiyw}̦qpLMFGۼع__~{WWsqϪ!Created with ajaxload.info! ! NETSCAPE2.0, @pH$8Gq$NA3( L VK|P(:(rBь_@X!/ BxBnb}EgorEg^ oWD c JCg oqmo  E  {p~ rD}ŠMdKr 񋇅o |̩ò]qāӤ` 9Cf)$'=Ê}C^u-H.!ܻݧ OK"15.&{jT BBo e6<@B?1 )Gb K ! , @pH$&4Bq$Db(V[4_:t:"rqh@a)ȉgBk_oEg~#rJDgxloWF C ~mg  oD B wK!  CɶwE  ʢdX r s'xM &T$$|MCڟ ABldڵKdV?oFl-X L[J*Ξ6!" 5\@poIm N!QXm@ק %2u:uH2\R#a! , @pH$ Bq$&D(LV [$ :4P( rs_IȉgBxBoE gw^ rFgWoWD cB g oD%# v JEinE ~"#L d$L  X"yrvE ͻd)xWO<2I&1a"UJ(qz +kQ\" *tRjO}C7":sdE& R*"#-=L֯Q2s:d+qV ! , @pH$.Aq4 Bd&"t jQP(.q8Hy"3\HV$QBm{Oe u  pzB eRumTDC W mDCe tB]c~ aVjUUf{ L {[HøU{i98\q *3 n% L\@02T* 9}tI*D9x6ϴF]l"!M!0ըT,D*FT׮pq ! , @pH,*rILGA\&gLEB*.FCУv~,$wSGIr jmgB GTrjTDGWHj\ CG vB da zw% V` E |m%mvL ي` L䩻wہKUbȀ-FA;!:tp_3G8 CE69
Pd8@,9J,7p +A`! , @pH,*rILGAQT&gXP$ӭp1r͙Z9| jfB Gi~WeE y }RinlHCqNN ]aGuzC `LjzLgRdz z ʽDU sPLsJ6LHQ V0'hZ%8@@.EDtC lV`XS?X q!+@*-҄FxB \F3֮ ]! , @pH,*rILGAQT&gXP$ӭp1r͙Z9| jE Gi~WeE y#D}R inlHB!$CfC N ]Ej $sLjNszEgdG z z \U jcb  Ӫ\] 2!dZnVTgL0 F 2Tȇ `Ǖ2Fj k XT.5X:) 5,'R,tPuyz*R0|! , @pH,*rI48e*TJ,("Ӂ * GNy#`xDdk\GnEdi C DdS Gm}GIB$J  ^Ea jD xKnxEx GnhaC JV8pIh> KE8D\2$C!"Хj1YB ӂS T@D5d852^I$ ?*aij'T5U*G N:Jlӱ#2a-;nzbget-16.4/webui/img/download-anim-orange-2x.png0000644000175000017500000003537412630544544021522 0ustar andreasandreasPNG  IHDRA>sRGBbKGD pHYs%%IR$tIME 9 !_ IDATxy]uw{ҍ flr8$gHQhI,ZX)ˑ,**U)ˉ*vDBJE8aG,ʖ"KNIK ) \<$Ep2zy˽[Nk;(|UF~ s:έs:έs:έs:έs-~;1~`9`+ ~A-0q,7}:ᷩA`zCpM\ò pAY{%kBWJM}3cB eofɃ79, d!ƈh.ٳgX7 $}NoFh&e@Q5UU|/ƸA0^ DI'*Fo"XZd0`n8Z89ʿ!@cnKr?m߱1y4Eb`ii)@D]ݐ"Q&NJIHc((62MUU,喈X'>$8[UWY` ;wwfQȥs!4 ZR*N?>N#jU Yf8HB$XkB VudE#D"u'1]0@iFe)HZ 09@IQ&z#"Fs(6M@DDx!pn$hFEli&QʲT"M*H9$!VJ)̔n1oavB!FĈo1$]h4 D("b+i$eZx.lb_7hFeTUD1 V1Fbf*$CDRcTJ)j0e2m,`D0s!D" 1~!QH0T@ĘKGkGQZsi3(k ^'XpZLE`&ՏʲT{ޫ RZ 1d"1F53"$Mk#EnFD1c90g怈s}Q^)EhCNUU!qVVVR˲Z@iR9Gsss$R(=9电VTJQ (KD: 3+ADPH 02u$ϓc,R%c3{!8f;3ϤZ8Z렵UUDdcLֶlE\mh:҆sXV*9%{rb  X"hQF)ebU! tf1F\I]afsR]X;N)EU9R*Zkx<4u= bL󂚦Y-,"U**"%J0~afe=fY? 1׍>L'tzK`LjjF+cZ=_Gz鵱:ʲ>;cc !1'1&Q uUk K&YQ^RJPc f.f.R%XD,0V{dg} tGGܿyy ^Yѧ[|xi9IB:Xkf'pfn@ZkOD'$6M/ͅ$Mv%sd!Zk7EQ.˲J"*RH D,nw<箅=3KϜ*^љ/]ga抙cu'S9(BɥM4"uj0ĭ@sS3:lihuRALQ6'EQJ"*( :?o7zjzF_:OpBb暙<!ޏP@SuufxU,S`QJUBSuQ zJ.u4ow3H¹(IQg̕snB4537IUT׋{R (wAXQEQhZ""B"ZcL;Z1QJPJ"=x?yG=>Th2pu?;V9!i"jcCDR}FDl;+{b4 v1eс!'OIaύf m&YqzM05=uO)5CD38CD3oȓ,_P]wG5|=;VM (HDD D/1234h1034{:-r%bXk]vh4Ԑ4It]1dJ3Xf s?3/sMvvw7oW-|l5$(A)(f!l&L}.*,Zk,˒:DM׌1ffh;o'.^+kCbS;#kxuIBswX-Yի))%7"|!hL? GI_x-j)R$X:3ţg='&@W4:3Z/ҹC{KVff]g ;an 3v \[iT|xz旾:RU;0RϡN1Ϊ YT,k5]]/B񖕕s433s9SjK(¢~7wl uzjYx?i;FIe}[w徖.S k_ ! bCf+4MZxʻ =kmJx`o4Yk^i "[2sj{8UJ,vG<2B3sLS@E]s2"NubcX۷?0~,;5ҫ Z\( C\%4M~|9.B9 +Dl-v2Xfqgu/tEN{E=%A2`bj[iJ&=T?RAkT^RgϨ(5Bŋ5rZl伪m[{Fa]^LUS1; [沁eREЕh1ZM8'P_<*35ns#fRnN% x{3K C}kmcB`fv+Ƙd/^_ck_e콐@ iJ+i˨K4x׹IQ.].lsJ4w6Wp6ѧnm%8`8J-N2@ׁbVu;#t:&"Nc'y/Ԭ!N-,*%^?9};wۺ!|? ̹/S1HH+i˨ˁ%WG)HιvADBjhcdg~-gyRsC.*3ιRKFlBp o"Ma0mc|jh9S1.#b+'I% Q)!4IP:9wTl#2ȮO4trCȜHLc o!I[&p̙ 53W u"]R҄R( UJPϏ߳W4~ ?WٔWx!aV)֤Z{cO@ֆi|1c:Zk6.//G}tεruι4 ζ^콏{q_YIslBΗT({]ᡭklkU$~Bi "' .0J{HYE<_$] o{ΕiR7|OtOƭxDL4u] 2ƴE@ !Hlzã֚mCkeeD5 @u] (!{)03kLLLtrl_غ|\󩗺OMr}21f-Gs0|i{ODD1 TZZ{HخѾhr{uPJ97NԺ&0I'uR֮~r'ƧVUH Nw4 eIy=$cD y 3?y=czh}+f.%axlus 3KH%`1FTJHPGRo: }!<5Scx%cil7Ģ(1֚1\w:z>O5IzQC`"km,2z8;;Bm>tP\XXRFUZƘvR)r^c_G>ncӳ>TqݐijiqW D׺Ys.Pc,g.x͛=^T4"*XvҶ4Y&%\uA\e.p8Mz^0!vz@l>J=m͸c^7vQ~sQ2 RF%bޅf2C=Ie>( :jn0SߤW/3! \-bFqw<9@`ɥ/GL41ưR`i -h~d~lEdspTJ598/6L뼣/HDhdIG{Ǜ~z6M:R4{"j;hjH5 101+pSc$P)E!r"1VDTI`WnݽF 1Zهsoinna+#a6 T'*Aaqd!9)!:>Yݿg/ȇL5=).W^#''leH0JL3=xP!Ԉk"j d"Hb-bL,x.˒E5I'](8B[kcr \otsw ip"afZx%yIbc˲l%Lj FHDѝM|LFfyL"J\ ~/#ʄiR&ET)NGC7yhl2/Mmj1|B-uU%1{BdDyrO.~f#c\<[. 5Z`51\^}HD¤ԖسjWD %!<UҤ$ Q"aP)EX;{b/gj>)O Gt7JjL&RF1cXj EϞ[v;s3+Dlgˈ\i%dlI f&8vwTUs2U7Rؔ*Od>Q[kVtIQaJfC2ٮ/|HiObvDSіdXaVN=xcՖuf]DΉn+u*xɞ9'3U­lRz[ЎWJ,alGY0$pL~wt3)Rkcu]|/) s%_=PI-Ip20sLVQhdCkR:+o4`ڭe Ym"a8'oX8N_nؓ{K%b6ۉj a~OU$)ML$tۢ |l?ޖ txZF-,Y@`NNk(.HBn4|JK!5cڿo|'rn#Ƹ碖TD}R!b6Kż[Sϟ7IM"y)}'rr2YgcxKB8 s~}{䩖Q&Rd& yYJ3b~U:՛ZmP&`g& M dnI옴@4Q("J"ꓯ|3mI9/Q"6iJ[n^-`$OWkxiAe8-jl(4H'pIDAT$jcDyKI=oAT:zmT:}bh?]lf_g7DrpV%IfY  >j+Xv وڰvD+} /}2+5lp95RRze43cYmz>ƈ-(۩ky%vy4yI@:ƈ8 !F;t|4>_t>B8̧:f?ݗ>+i ylb;%q{*I_'9@JېFR3s8=0u{TIJ)rΑbm&c4W MnGQ`c$}H)cԉ/~gcaZ3sCf{G.1YkcUUZKKKeJ#"]$Ԋ ɴ'ʪ=2޹0OT@nmRLTQ034JJ!Dz,Y.ZG8MՒyy··sK1A]׃x4U*hݑ$m[hy9HDQp|}ǹo'Ymc ('jZ=ǔ7WRX%u2u]Ǎ!TιYeTg9BXFġsnHDx<bGRmyBk j)M1FwrW{35Tb0(=O$1 M+uDyS!s15GVJg5IU(83tULPF˷-a _)^;:˪fKYlȯnKZÉވj)2bˤ2k9ͮ~|wjiFZ늢p9,yGh.].P27$?"v>$aDt_=چOۗfDZkJEQKu- MD1!_Ji)q"^` &ଵaqrP:dBhҰSM sq&n@ż-fknnDTUَ ^fuٳguZ;OVow: &d܉HxaAdUfZP*b5OjIKr#ҌaCyؗ6[ukҌd4Z4~'b2\=m;6Mkkt( 0ƠD߼s2Z^k~ -F߮r9s^uR-%{]QFuT99spZ/}i3 kDEQrfF*ऽRAsǾPnKA*zn7&(BZ].U9,~l򒤒,h4b$ bG'v<`çΕw!UJJ)hBZk*ƈv3sDʈj0:4<&BP7œ*m.v:VjZ ?s)&zT !mAyɲ˲a$ZV4`y+:t~ygh;PJDTEaFZh:j \5  ,fgg鐵dN#{aWO_ !T嬵( Re݊djťÛk/0ښ:m ?&_`ZDTfNt +k1I `--4>JU8 ɾ^G{ !Hu:Y|N}lX7̿l$,Y+)Yzd qIػw/z{ʲĤ20 JĔ!5 Nqhq]\^^/^Y'X(KYAQ~^h#v$=Zʵ$e,<9!B*B>*R?w>w3Oʼ(mǎ 2>( VJA.Yz+˥Ӿ(6a;23VUlSʀ!ꙥΫk](Gvt~)UD -kܻw/߿3p.^DlE\\\Dini߾}׎:64̬R>?$?:|/`!?x|ǕR{1aeeȶ[Ր,,'G8pΜ9#Ff\vT{z^:eYo١{tp3?i='W{~Vsܨ1mzll ¼ 7 {&ܨl?>8Zk@c֪>/;kw}+x\#?|]UU´+ ˶$fiRʈ-c{ZS$Ƨ#ӕ1Mo߷g,ƽpM&*{/[8$sR;v 3C󄹽k4Q>*1s&֒ċzE\c(:y.v?s|Įo$ :QϓEk=)YJrE-lLO!bWkHzwW-oec6uοy=!,-,Ih1Wu:,1LeIX.]IrPb栤ؐ&Hʒ65ZN~ǛW?z}kVUCD?x<Ν;yA`ljt];* z=B4EDRJp{o_yWȽv5[/ZHyZ)!vh9acNuws[gڕ%y;HQT:xRLE)]`:NV)e}w{8ͩ}u튢+++[.SeO}U2 ݮ~`vCd #"B_^-^|ǞgΔ4i=Mfu]֚1=!Sɩ"qVvܹvΩnKɎӽ^];EZZ];~#J/d˔q'ڃtՀ-RFT2K/tֺ4Mt)+c8moD-yr_85{%8PHC1_C#?;Y_#.eJs.qQ7şΝ; "(L-c,-"Ώ|a{}mO]/ RꢶKHWS]0SioR QM ^G7Һ|u9 <м9RVh(޳OĵW"+PJA*⤚H|x1bH ziڵRWnWw]Vlݮ5(죳~į>3ӿSbouMӄ3g\岀 Gl4EQ@UU(MgdhB2bAe?~`{$_Wh(}SCuҴqLC1##`9ZkvkؼIɲͮNz.RE˲!ٲ,e@ah>#OZ~˽_o!94M(¹sx%BJK^2hMaZzYkS29Nчf zz^\l'P`f3hd= IMVQJ,$;1t ee7 ,K4SD0!($FNsHect*q!yi2gXy"Qd;3̿ YyZ`EdBML~ kv$MlGl28Rd$}Hq(_\*S֯[B^6"־9꺎 S6^a\ *M#'_KVU~by DӞ ~4JjN\ZZj`0|1 `\-IiGX['(c3B\墢ERN$YUu?#plE\` uf+H{ҶI Rfnn{9>>ttt000BBBؤZZZ$$$~~~rrr|||hhhjjj```xxx΂XXX222***bbb^^^ppp!Created with ajaxload.info! ! NETSCAPE2.0, ))4)3* 5 A9@ +&<  )KFN!Ƶ҇% "!'ш , D# 6. `xU-TA d 1 _r`AQ'LpH`AQ0BKA 1 F `c.pdld(`b Rp"a=xa!/{6B?6%bRu$`2$..6dCE c!F(CAS%hE̴@ $'rbPI )D vԠnj(wFj2Ѓ3>Xp@ cF<:IˈT#JD'7ݼ-MK %&`@! , TT)I((K/4FFKIAFLFA ( XMDF%$:(NIҠ.<<(06[CIB!$EZ 3Q8$8V+r`B"on)O`0Lދ' "(Hc#  B?081[0  ' B~`+AFB(M;z"D<bC t1J'Uj̉! $ u 8{e#Q%UPN(NңD&$sĶ`GeJ& 8D0 A)жKjE@L:D8'N [ <-\Q'["& /_ %%: M.O%. T :&9A*G,N& J.  T`.sŰBNp! '({XqP zD1@Q6}A&-Th#=BlA!BY @@-=!ɖ2@THUATh AS<@qb`PJ)˖]Xm*phJ#F,;@ :qMZK:@( %5Q4Ѓ5Pa)Pb+JF"lQpE ъ8y3 #$!@'IJ(;#9ޫ&TA#8跟 \{Av%HZ ! , SM[@KD %b`-b%QEA-AN"* Q-)*=Ic0/XG%SE 5 M <5I/5`EЁ (<Т>[UP`(5YPʔxȒ& #(8[tA8a!AR\C.Wp,pe'0 t.LAo-FZ  6\ ` 2%O ,,8@ń# >Ihi !0 zlre,YF8BkPJ+P nF@uX@'KA$<.,l` װ}DL)lFD-H )E !ƒOTAXPFUB/W p-X` ! , ADA@<EAD/%% ] Q#X=2Q?@b C[: VC5=M?2+3ІDNt Gx2Q ("4P J0)BH.~PP 6DAQсTxa`n` zs(LhD,XٷbLdH`1 . ǽbF:t@ \wquD*<$`Н{%I.|0PV*\BHR:0B (?Z՘9P`A҅` !qKf60'iC>,@B-r-=bIW5T'TL#=PAAQBCܥ=PQ $}RPQiۆv! , _/BS]F=@9P&%[D2F/D]WAEf J 3A [ IS)HB  >ԅ %+Zˇ-= UhPeX@R6B֡0XL#!r ` %p & Bf4jvP@` @$'PPAXe]L!">t(DQAѢ P<$ n0|TP Id@S`׀/,!70P PIU fd 0k`aD!@8kE#:&D"5XK L I@LXAB .h-4a % - D(VH ! , ) ("% 4aIFPR N)WZ<] 0.FD͈FG A#!5<*H .&QEA։N$OƋKZ*6 N $JV=pAצNAvDi"1Q .•BeP;Dd8 .xdU/1CwzGC c'  EXeC+a(c Hz|*0RA@qRF`!X!Sjh,A4Tt(uV,bD\TУ yxb2 #.J2".!Cp"!$taa)(X9`@@DP<,WnR0H@ `Ԡh-$}WpЁQ! , _>AO(PD(!O<- ->)C & [-ĈN\M;D̉ $ڊ>V]Bz0K (vx  e %Dȝ>@D" Eđ- E.T:0dE1? Th`¦M"6$`DG B<I J1" u@Rc  HkTHxPE*(etЃ N|T7QEKiDv( Q GlX$QP@e':lP!ETV  UP" #\C TŸGA80D&#`"ʋTl0aCD`@&X0%piAئDl P-"Cw%DC<EX@ph/W\N;nzbget-16.4/webui/img/icons-2x.png0000644000175000017500000012012312630544544016616 0ustar andreasandreasPNG  IHDRxXbKGD pHYs  tIME #o IDATxT},3, ˮ $CBb!!5!1%yxܷm[)^&MIͅ%)tŅ\aϞ=3?33sfg>-^r6X96P%YĴ[Ҋqe8i؍$Օ9OKĆY4G934OQ|SJ&ICEmjl_jwsQ%Kn0 yrP39=nOJ,of7YU͟OxurQ9uU//yຈq^/I'iq:hP' V pxl4fvk\i3u"GR>?̞p;q3{nw?d7\rQӡKjv̓2`X޻R$-?y i|ji̺BAu!4]YHDQ(keϻBټRxY#IM)Rf`N5 KշP=y @}Oŝ]邳Q֕YfDf>H1|k,W97g8OEG¾K(ܞyޟoTpן: o=/wkd^>)/$fS4|OKwk:B__&\GǢ }1|,ê2=cf Ur Q_P,Twȫ!Xg|yeInOi1bj'iܭ o=xWiW'z6U}~MA?-jZ]Q!kfvNe4Ǽo f0UXW0iinz~n):xk;'-T^}5mL@^Z'JbYi /^O=aFy7wafIAVXW' |V|`&Wjw}y+xk]g/3S/#nD{{$_q\Y^@j'i 0?u(}A^aF77{[l=s~¾,uo]mk6}=y=ee.sTޗ(R_Oyo@Z>\wAuiFf5!˦q]#36d] >{_7++ 6ww-ei𚤧% rW:7n,驔B[Sxn#[>pwmgm)Ì s6xwU[{:gW(JB?wVy]c^7{gv{GzL n}]t4Œֶ$9KQs=U?2]j?Uw7`?hB }OMyṠJO۵JI+^X^I\[=yxS=(Q:x %T2U~fiq .lԐ{K/%4¼ wz 뫘м =ԚD tw ~ۿop?(/c}FZxkC7 rK?rly_%|?hӻQK3<%$HEL]xݜe::%-.3oN }:a}5*wֻɭwk [$MQ۶ -{k=nժMuXԨެƟsK? yM =weI)jS^:^ѶV8$.f'Jz\Rct%M3_0o4% _…dJMe.%U/uPkn>@}oZ#{&sofIbWљ7yII1UZ}VR1a0+i+DM¥ ?7U^v9eKޠjLϑAWak3ӺNK-M'yOW;(=.ijjD-7(q2zۨ ?[lye6WY!.4p-e4 F3l凸[6&8eQrK4lppOhxn KG ;7^j'iʾP=i-TTjo7F}KQB}l"h*yb4. O@[ SIvuOxIlj >2@^ׯYݘC#bR 紿:O-ݒz EyT]翱Y@?䦣Y LHWt%}mf7ֱu1MSwJ.kʾiXAM[pa=X o=wH^ hB3,2kef$3e2&ծnf`יYesn~A3۬&R#Rtޯ$RmAnI4*IG輷/zPí-\a;¥lq(oZ/Vr.z7.Hqe$F6hJxy[AnIsO{d}MWCLlr]`i3Mp0٪dz>P_N3#كfvXė'4s4h@uCR? AB/nôx@]] }fIVŅM*kfniwS|~<0ΓQAns5Ɣ/uH}e>viRI#\< `fMfe>j+5s44ynmI6^#A̓&Q} W|ءdBwxQY)iOCvLxnTI$4s=(f6%95X"w_Q'·SFj6JJG2Ua/G"SI ?P ŒBpsPȱ5bm$凰Ւ6I YK`93[lfݴxA{}f:f6YuҢP(,   "5]I&Acmn&6/8sĂ^Fz׹l:f63Kz3}3~of޸P( Bc3m3W(湟(v]R:fI:mb=nz̬ɝ30;9ei_1˽ w}Sj\,3;mdOq56ffR𴙽;bt_qmcPX7?e\B0P( ͷ")L`ϟi%=[kIO+:(iILúZs 3mfpv,U0I~2lz{FS1n{F|LU03pƞBŮl5/]j)I>˼6so([ xK74 |ݸ3PFa(:.?U(`P(Lfب m񮞗R҃2q;%-rp`Ke}.@&iN%nI3,z$]znI9pYg8̦{X~[6h{ :O)f4s\sryRaKew˴pD<G81m_gƺ_̇{lrkX54Ͼ?of+Aif tEepMfgz}ywKnpع_;qmk(P{.  BwJI2S3FfH5QY?@@Fm)m"mdfKl{|uey_t51iJ]hZ!i`>[T9{Cr ݱ^K ; usKsf6 ܗ=fveK@31m >MK$OٕfV1p&s>2` Qbb$ةG f?* Fl_6 ? /MRwS&P(ܘ_mKa .0oOD{7ӵQ^=^8x{$ﻇ7^#*\t=:3Y3˅79pQ^ςo2z3W꯴w O\}ʪ2*Z\[ % żݾEϭdf/@waS|.3ș٪Pt@䁖Kq .0oOv?Ĵ7ip2l߬rٝ oNk- WP桻P(\ox8|ps6!$Cx:z e 3f`n=v3z帻\X. )ζ+:;W9y_U.IQ 6B}1oysvo kcs&!|(^ ^iۖ;ȿѯh5,?i>v%[!U(&hxtzHT佯mqK̃\%-_&vmbb {֑xayCD-|jL}< MB*S^-j|]tIS f6ļgbx1n?ۥbİ347=?L]iW7~Tv~ ˿/fo x B>@pB/ IEV O/\2w&X튮)nE Wx;OQ?-.SfVϘYs=\ػuvy:Ιx{ +I/|5fI*ֹ[{3 f;n߿ey}ݠiҐop;\fϯ1)}ilU%݅Aܾ_aϗ 5[!Y(u=`e jʮ@` [cK r$aС`Jmj,IŅf /r063;%1:CZ^ުڂkX_4Мpnwuf@` [v"<7ߖ /Uv}GT8v>=~;%eH@i?"r;2DxcQkP(,( eBa`:Rošߛ%R)8DʻM9HݲSK .33{Qs1oӹnC ޛ|I"#I e !ꄜ㮬[f P÷5!JG ])"7xX53 p˪`\| YJҬP;.3bĴT*}"LpP(ʺ_gM\Y`mYeٙCv> .IO (/C,kE6$ʝfV&iepl[7X, VSۚ(n,;2;ntW/t8q ek\sjfaOY̳T b YJҧBw57Ĺa œ2}BaP;Qӡ֛e:͑tRv^w$]x;5 jJP]U\c'uSASv_U3u8vzc&y;e4_x,mm8^#Opʕ-#mȣn|hf|_OKe Yp@Ri5qp_"{ ̡;&6.LoU`Kqa{[O{sN"[9b׹{][Ss.Y 9~kǾ$`?׉U die OM= 8Dž8T[:m h3 ]㗯p:1OV)BZJpօ=e_3P8wolrTn :?cfOJRipGJ{B݇ ”XwLܧ%[]wGhM?`5)9LV+:GnV%lO6nSe&%mU)7jv>^]bݤ%YM|8Z2XoIGv+} ~WW\sI(MAwn $4׉>|]i5 l[rJm(ڒ2.5M@ w{\\N;˼mw7&}Qn1)nؠ;C1' &3 >i`foa{{{l!xPs$#7@{@&-Ű@}3?,'/uѾvn o;50֪!N 6 @mw<^;(i^wC[\ `P,`f ф6T:䈨;<)&xNLK e lxy7Ț٭ }\{/-w[w_ A lۨ-f6eĹ#M`7'/}X^E|X%=Z]^޲;]pdUNUNn. (>b1oant\䲹 Qpaz8jpןV'?_Q%y )[fَ,I>jt{̦p=!mc\0~է5%̶hQX([~mU tJe*<[pmU IDATxx}x _jXn/Ĺx ѽ]@kন %J~=&IO Z6vH^]Io}؟ܴ@}5KzW*a%h;ݾX0E2Ɂ xow;Lz[ꂻU]V?дuv =^utowa?x~r.<Vpk 4{?pNLGْ~lϛ >1L_np!  оu77uvDjv?7o}Vy` <&ke: fʻuGҊ$T~i>c7.3.X#e%.Sra#bfw"uxwyN,n[tA݇Ex/VvWq̱u;f=}fQGrᗹjs[13{K ?*/xw jLפ})#iVoyٯ=dyk 26@oXv3[\a]X1eOT}nB$shIjW!J./Lq> O4< n}͊g*.Pvٜ!G.h;Έuvl f5~_g( fFn_[f.yj*2xܝ'Ef*Ak 4dkĹIAmsؗ צ2˶$ T[f yo8mS \b;U#σK!>A c7 W Rf cG/g]\w uǦ}̖txtr }s?Ude|3Ba0wiB5M im;P[ͼjEϹ6 .vk'ov$xJzTVŁu]n[rcz\`fB%'k\:ֈεQD7l;oTu%/OӪ>(C@7̶v_,`߈o o v ˫\1] 96}i)g0r06fv -s i/ CAޑ&hT9YiJ@;_55Y6f뮔}V^gk.ٶX᭻[n'P]'`܍ 8.L:!/{\61I-?ܕRbw~qtۢ\Y]}8eוev|җ6?[ו>qݠl]fD#A.afMs6~BxIfYj ^f7o\(/])n~ 67˷S./^ \x(>|υ ܮy'0G s3jҘs㞘;{bo澔 go+ɰr16|;2!HOU13fe7oٔP( ½Bavub%l7y"o.ye nq}V^s<ȟ\M:nr/6_rp{.}eo1ml{x;,Isk?'g o?b\HA:{j@q:r@u4 Gآ}=4hpP9ð).P>Swo33>fϲk Ay5v3[͖ V7Gdgh3sr 6ȫ;ܦɫlr׏+enlI 1}"&ʰ yH;\hnngf76ё#G}!/ʝ?@{{3Q^ࠦ-``7eSNRFIʗF˅?oTCRsP`0. _,_"~B@?ntfH)IqICw%}eM:\_EIg$x)&Z8gj geSM=y .>tx켤$%9b&It#i;]mg$=o/I8;3%[ {K?_0SꛩN錤`/ @_%4MqVIڛΞ!qIs}I$4]R%͒tᵳ?ڲmGSySNE>:3ꛝFKj Lc~_k7\ve_p&L4nc|>$X,O>}ԩS]O?k9p];wy͝'Oƺn? OJpla7_StT^Oho^;{ }Aҗ%}'*3e.tY#>ܲx-iQI, @zo4GK#/h$|3-[`֬Y/B If3e:d2RW /}{$uԛOF'O{IZ m"e&Tkvʫ$9j@&y]>;IY׺&}VI􇒾زXŲ9yptu2yjV#;.ÒSOyen:Ν۹e˖"]::ƃ~yqp s>OwܹW?~|`P,"f/cgLF^/@FѣGlٲ?cn:`i$GcdIzpqꑗqOD *n?ym}Ne>ղ=#I-kYf= 6&I?D-;'juRoH }Hͻh˖-b-[c;wc777}K_;eʔK$ wMH&SSF{_ݳ6Ӧ;-otsi}tEn? AԶٷI-k֔$eM[fuUo&ﵬioj/7 aJti޼ylݺ h;w-[tHqel6ɻzE]4{]oy7޻Zn?e*Kڽ{vءݻwk޽:p^;;SNIFqiBs&]p."]r%1c.eٳٛP(cǎ?xsܷݻ_mm; A>JMx=-I"gtY %mTn?sQI#ڛ nW?Uޗ?eVI&/8zH%-kN&=}3Xi>@/͛w֭[w ";}˖-/Hz{flNhll?cƍ |;v͛uVɡ]566jٚ;w/f];yr-pɃ}~Fgz|%GMJ_gZ4PpW%+WIZګh;5eilᵳ-kھ93ƂȕQtǮ%qHgԜ5o޼-[F#n6e o&MtJ^f˽۳g|I=Sڿ5OS3L(sAF Rf|F1$d]5k4~SiyṀɓ'}޿tN❌2>A2`+'?Ǐ$&?A Ɣ4.gz^IƘMMc]=R錎@WF{:},Y$-/c7&/ Vo+4)i0V^;V+3ql(F˚O%]\ = ITtжC_ TH?nqkY[ZZ}^};ޛffm?ַmmٲwPܬo({YFC;UQSiT|]~Hrzަ~?~lNfVz>jէ<|wgsk-%’O*ꊖ&6l?)~r :|2K;yޯc┼zwܮe6"F>./XWtx6I$=ٲ*(/B˚ о,iZ%M^6/齒^ H1%l_s^XR`-7??;vxkKy~-e~c ەl%gY9r9͘1C'5<>k&~z:thw7Sl-FGLVhĒFe&gqWy53jG|I'M o6IExq7oVg$^;_iY Wҭi;/] !/HnyY4o޼֭[j`;w-[z{ 5QҤ{w?ό0dULwփ_~)ecKjxwVEYeƜSueؠ^oΜ9?/}===gov>H: /SpSoX|Q{}? <-m|iqu~-="}GҔ:7&`?:kޠ?jY{U-kڮ ̻NRuH<$/Bs' sXn[{$ ݒߒ.zV_s{Ibl`7ӧ9Oj&Ye5OfLFFwcSZt>(ϟɚeG7>}],sX줼 ߹$Ǐ6]n-~F༼7҇/?M/n>v$?#/6MހP9]m__~ij<5zL^[$>#M[n<[3f2;wy[l9N#m8:nܸiO<]q ,p+i׮]zgX ϨtMIƘTxfF?7F ]tEm]r%.Sdd?뮻رc/˫EwLA4grUѲiuNjxm=}ڴoEwf&iz3dG?HUF=- o'd:k$[%]P0w%~ڲmn }IAy @Ȳ ޖ?NR .ٴiӗg͞X,*8}ڿ⒊sZ,]y-^rJ9ztAٟ]{JgΞ=M&LD^8Mgg8ߞ$m0{]~)6SE:h"֩)l۷N_yGKb3SaJ[{}?kg'w I_8&r}d] i7^Ug+k_{Xub7s֬YW=#%MrfViUP?[_zˉ JҬq7B 化ЙYƙnNa۾#V $jN . y͡X#I_IhpW UX' R,\D#ajuW[֭[|޵̿%T*S=una/jr`nEO<4`oYNMkSǏ?+z;߹^./1MXohzI>%Zuycg=@֫>jVm4jdjiK{[kg4nsԭ7L^]}(AN[.zod/qƕ >r◾omŊ}K_:P,sԢEΰ{ & /~WG J*LgΜ7ziK:z\}li f?M b2.e>^OFݮɨ>J'Sz/wjK Gu_*ْ{ .e9zLs=9HE_m?:v4Iϩw ?eM[es2wow5m/`,7`;*~̙3߻wO>p]3􌭵oCCÉL&3~>E]䗿ebpf]'iĉg?ĉZ2m?N^Ritm~pP7S;cnP%ZS9aӛ4k7kU&Q6U&Q{{%KN{{I$sE%o9wB؆!6;G&jPd xNΫ/g%CORھ6 {z:-蓴7@^Y? I_rIwd~InY49;?.o(/G/20nܸ8q;b@ *Ο厎;vc1WKt[/ַKLɬ-[詧RwCv}hΌ-z#}[`PLbM IDAT7'\?^_|w.Z9srvL&͛7KzEK'N]Js~wO51=lQ>2??4Gz5ugGr&NIR^7JzX҅tY$#[7uOHx%iK՚\҇R[Txm-&H\&MSNVT(C8744kmm޾]1]-7pmIR$87hEtT//qlǔUC5vÃ9{fL&ʇu1_TWs.3IԩS{E]4ѣ;~핗,o>5M>1,/~66 ۟2C 6f{taS/}r1Ne~:!/{>ay=ڛ" ^(4A^қe%G$}V5mGx]^Ic[&N8bxA&13_o.;8a„&w2NRѣ/{_֖ .6+̫cR{=ֽU@8kY)}R韁 ~n\cfN{]c&L}CjhhP6Q&աÇ^y{:uj,V`Nij]cw}7h`P-o}{a'=F\&]SfKɦMWan/:^ImYv7Wvԉ8q[[[[wtt̙3kYfԨQ&LX|m ?{3/i wuGZ&\\*eo{9=zL'&9wo@/\!TA[S&2fU}M/k5ֹlF/}Kw)y}=JF?ihB_&e>an*'ncnƼ]4f^ u3u_"[n[}M$ 0ߑϒ^eSP_q:- %/v8믧a\@ŸI&Mk?V*T,T*ޮ^xAI씲அ߳^\WE“LCekgX9*bp5ۤixMbQbIbQK,ؤI&˂޲4CVm Kr+ÐK5B}<=e" _˘>иE T\IXm y7c'Nt̙&_aԨQ'Ld#ګ$]n7o]-JǏk=:sԨQZw\~3:/_ ƏzXZ,z旊E:t2Agm]0S7k.dY7-?;X 7ɫ2yw]+^ӁU,ybQ mǷam& ?6wFhS@6~䵈LNgn {0WodQ*95ݶT&ݶm[Gss;vKkem۶u f[g63T*X*Tҋ/l&߸[QŊ+*sO>{ff7 "[(; oϨd2ڱcJŢ7Jj3f޺T77Ԙz/5:3:[?7, ;.W!<p=~n&l2gtU- _mJfY8O]6i_|WfY&⋯:peG~x\k;kKŢW{Xҁ٩3hWk[sr x-i'Ͼ = Uv@*y/zI եÇX*ZI6+ފߔ[F+,y,] efBv~s=;D6^lFqfgklT?ʅP|mCHl]v:uQF*gϏ5jԩSڵ]k|9\Ţv2٬^tJʥDٯ/KWd`@l78kU|+er%+eY6}^mkk%G4mۆQ5*S HOL >LB0#74O&4Y qpj7Z9a7JiqFmMsí/Y\P-Ͽ;wS>Tĉzd %2֜*U  ldvFލkRyJA]w+S'TrXK>W^ָަ_uE]ՠ^Op78^B(74٠n.P@9 de"2jxmS^=Ir>ͩm&߻wUV ZjՌ{c_f|U~bXf^:Sq]hշBo^;;`?34rzoUj8pF{&l6ݦ{bIo|̫$m/kteu(7QC7V֮J:D|-"4gj읂N2ۙ2_ NC6mKHlCiiiyW>! w_hiiyC=tFRܸ|GgܛYGfkZioz3b#K3DpP`i:Y9}|o v="u2:::E;yGϖu"M\ `,[ ௕DMf(υZ3ےaqJd[@%44b޽{?O]Y(~ffBO}SWݻw/]C]9II㪒Y*9sFFґ+0ۛ `\l&m8k}]@&oޠ\). [1ZuY)Euw\}_AgEgߔ$dBL1eCR ~Ѐj@ys߃ɪl9ȶJii\vesO$).~z2w~h+/i̥\rET<:v٬L_cH*_%5Yn06n&r`.+)kDd _V7n٬N8bRK/Bsgm-p\+X¡Ϡ{kï[8R?`R+I( )a*.i{,YB1wRLh-4i4M&w-O61y=Y$Ilm % )eq1c$x:gtthFŖ4w^g$w~w4DPOOO_jx[/dKK{P9Z,244D"ppukm\)uՆ*wx.Ol 6G<q8H"`ddldKUzՙK[=_yŖPHP_^TvppLL]D:ţq.[ΩH|A{޹x`r- ruꕎAoy[w|=uջ$=yKPۢҾ}We72c###cmyEs=$[AgeScmd L D[oMm {MTuhO| |#mQTDDDDD xENh-@ۊ+N&* L$8qtjkOF) ~p;Ȥi Um&< \݂|?mtMC]hD[˗/?w~w{Gj^y0>6v="}'`FLȭo(܍ޝh?a]C2[ΩH5O>Wh:%JbIr}Fu-ƵѶ$cUtuuBkUN8;hennpJ!lvB7z p5`^k[(j苼hsuNEDDDDDW~\] /;A6j؄mqmJ5 RuKh#R%܊TK"'_86Kb"EU& [`F>c3ѯX B`=׶l;V} `S1BHi ՛c$ &DɖJ^=TpV=^aJ+g'z^5eY{ m<<\bQ?NunEDDDDDW~|D-\i P+a啑eLpptL2LifℲ$ IP,/HQ5yǧS9Tϵ%:Y_cS~m+)Vo?lunEDDDDDW yűʃK͇p+lH r=*~o ~cy}W[^wDI7؛p[P+W5&4FcUȲnB<7Q^~#:ќ?nA[*HzuikWW=M7< LsA]- uYh+%rdcD?AiүF)k} 1`#c : 7x}_DDDDDD xE-&%ZZZ0ڽ6U =ܗcZ*̱`ld3ϿVIDZʖ/K-T@hI zEjoZ_L,Nk) n&m l',hmԊy:45N4NHݰCoxdh8<C"dJH3ʞ3zo{@]᮵1e{5%|Y{豁cpg%Ha&VAm7\Y ?'3hq@$@]D2+&++=rh|GDݎP{\MF7h>xL!ƿ4+MWyi-QVDDDDDZ xEh |bD"5E vMYnyhCL)4ea P5Ѹ8UvPn KI$GZ|/.+R_pWs7mE J"cn&e!n9cAncտѐ Ni;[;[W>X>z`8%LS&]n~qRm~`gc> ,:\KCebz桂f9DR%wuajwڣ-ly,z .ʪޙ0:?γz[9^/x>:N"TNif\kpE &RuXHxAxʠ5L27N5E+H&$|.<<. VltBFP[^ze1Upju%OQTDDDDD xEC#O?6J  It ij}e\啹[43e Z6hSM\Ǫi'08xܯ޴xO?40Bm/v,esjkᵡi&6@Ǒpa 6 ]օ;6yTo:""""""P+R?ovhxx(\y844D2`]=LL Jձ&ZieAݲ x,3e&8W5Ug8ӜE2رcxdz(| 5g׼RVеmwp{Wk!룖vq[ΩH5ԇ >|pgP HrTӊM\P% ]SoA U?0.ooBߩgXXK2 kyXk9rs(`Ic]Nk+NP Bhhk"mltRPKz}y&ohL 9}^׃̌Q4"#{ I&8H,<5eUek͔*k=vK&kb#$L 1y-H&-<޽;cK.N[>Yۆ'*L tmL]SzNq:lm'qp配8Qy`g4"!}J!c=0㠹m:0y7Wk&S ږ݊f߇H7oh5Xh_QRDDDDDd2 xEK7=ܳXXXg=xd"ɯ&~5U_$U&Rmi B olPkH;Y{KZd{A@{|#nϝS[{L>n5Ij@{yn[Υdԏ ŅP=hh$/O^|OqUі &4YxR6 v &s+UkAo޲ML0Yz.n{O i@gW.;znJ~\5c(C1{a2%/< Jۛ9حT=گ]JGKر뇺nAWR D]5>cyk9Dgv3׀zk4 xNr"'Hs @n׮]w^|o.5?x|n^5IWvKly4Knז3KR^kC OV2{SCkK7ݲ>Du1ưk׮;~S՛Ugb =?y#?P%T뇽&SXA 䵑1fL[z ӱ?~_7qJ?u _6ël>qW P|>^""""2H}^8oR?ѝ;wL&z̫oǵG0&*;֟7r Jqc( tdջ|^$<;Oz ;y~M_M; ϵ'3^1okegFt0k 2.:?oW'W끤[ t:}O:T/}k95'x9⦧ zwxZ۷C]] ? DeOVILh5c &4Y/ ض L9xmjnr-/8WqWskaEiMzU&pׄCm^S wv,jpkv46fD:nK_j l_lO_Hma[4/ϣVS.)۾f͚+!߃] ,^cwp$"̿}m^SןZKM{LDDD_#W0wA VUmGFF{Ͻ^ѳY(wy'5k9Uo`\["ջ&pufxq|C2Շ緿^m=KygGGGy)RsyodxYfF6nB}w+vbn4.9/}S%>埫sܭ\:?i3_:^li{|o>? U[כc|I2oi~Z[5 o7A|𾞞&8x {-=,M.z&L#N^K\E uJn:]\ K֖6~ӟ xų=tzF\O1k笠`l< ƷRkj_[~k)RzI6WqI=VY_gg·:;;`gg'rrw3|~s\>uKc{-p;wtJDDD_#Siإ!q\#rW pCk.Zx2ʫyKA)]VWkM)kZ3v˶_T".q~E n(^[zlMgy=ng&;!m ]Isa +{nW|OggXt:av{߆4Ww) ++pHk؀w-.9HHmޟόCo{rZ*z{i3]8ƪwdZ#۷o^JWG1t߈MΘЭ41^LE-d;ï(EWxyn*\?٧;;;r&d6𱽾I!k)p=%!'R|X8S??7]!""295@+K.;=ϣX,R,7ooYx1S?I+Ly6=ߛ)i޿R>8~|d2A"$L FhޫSsQmf8??Yk?Yo|Wې:;;ï\@s>pp-p. \n_R࿀+m't:]ei>^_]]]W_h9[GR}5,snnVL>7YVi> S.տ뮻nٽ{6W 7|3|O)o؄Gdhnl̺jtJ)#G_ _.vwB4~77+2 R+ ѫLrOmz^\4\.Wrù\n?+wCO} pfX$ۘo0;.\xhU^Y㲗gM&"""s[;vHxy^[;vi@AH5>pnCt!  _wyL{L[8L𿩺#gWĪU<ܳ|ѣeÇܶmۗ.o){5#WƝ@;"kn&x\J;Ox3~8WXrOИ;66yV5k4\{XǕfF>7n1ZDD5rƘ~cL[?Юa&Uhh}[oxѣy64T?ٱc]mi7o"@$I%GV}Emx}|>?vz=zoqʛ\Ѝ0zcM㌿5IoH.Wv 9G:;;WrB.CB\ s4v?^pw&'NJ7QDDD_`ڱcG /2њ~8~^gz4M"t:}~Џ5ze7d2ɓO򅃟=u}VSu Q,͏cdd"A"P(_[ r3z} tuuU___|&}&Q_ ̯A\odXА B@'mW vrX+z=Zw;Y~rFQ~V:7q͒hM //|RLL$?###{p^ýi?Qcȱ:?Ƽ$Z4S7|3זi*wxU#m - \/^}Kr\D6$k~k>ZFoq8 쌌ǀ'*}op.k3L U*iޠ!M5M$P# cNݰa;OYb+R,Jd2^*}#}|ȷ`ԃi3mVU7E[wCX$WmޡÇs=_/O5ہ`'wIyגX$:igp3&=ono \ Won O`L iǎ^x xE?c!_;KN?^zKe Ey[Yg޷콃'vNqW.M.]*:13OO<$~_H$ݿk׮ݻ!\QdcCpo=}'v׭p`vc1/ qHkHjgz/ixr멊r:;;Mvo.{|;^WrmceL5HہKx|>_&{$ܝ7-! 잉cDF;Xh 4*x_~@C/Ms|m@mmm+ׯ_ ^nMpYEE]1awvt<_],m[o zAo&H$='l{_s/(ܛx00VKH!WH~%&}i>j`~Wq_T"rB^`:x7'2Y'$'Y|>Cou/l |-MpT+"""3 ԕ??H޽{i.KTzNX,nڴi_ iӦd29-~! &pAX5U.YfƖdE/uqxt+`ŊX,X88|gf;y\1@MՑ\@gKŜҾz8Sږ- /w޽{OH&}~Ϟ=(< <&86x=xpx? //,X\ZEbY/!y;.] +.~v. N(-\n8~`ydr|ÿ?\*' li5(M2W|>4~UOͅjH LeA"""ͩOvz?jkk[[J]cLeAǬO ^vm^"`_Ts.J$֮]{Mwwc'ಶs9K.sQ E{~:vֳG{gy!\Tm/W\l~?X6K[H-\"9reg 3%T=loAo Gw~^Tl$ɅU2X1Lbرk.\x^?ALMµm3׼3y1fa1DիWs9fV\)ŋd|qpp\.G/rAswݻwygq|.GϞ {yJU|&\w֓/#O\.s-1rݸV\.st>u|W? %t1wWMޑS/3 \d---'j#G\bŊgҐ&َfOsaKKҕ+W^Nhkk;dpO.s޽w@?AZU񗙱'x_,{<,͌Uֻt:|mN׀wa7+:""""uȑ#vuuݑL&YkCCGrҥe!M3np&)gUuv /^;::~Dh?yp^gi5k=Z5-3=?q}T5镯fy3rmN/>\R"wUհ |T];tKkmm횭m=zSN9I$IV\ [%K[p;mM"hx^l8[0V܍*3*-XV_/"2wnS!"""R/ c׮]]gu֓Lw]###_dݺu}z.ÿDnA%f8+FnsA_/""""""2_ʁYxZZZΨ7Ԗ_|˟K@ EOuVDDDDDDDDDDݻ>{n i}׵^:}nW֔rlIDAT1D зy<*`,NFxxxk㮟OtmLHo'u@&&x1`>ڸkO`W֚?}ꬊifƝXc6_m^1S53AЫWDDDDDDDDD@Yk7w;ƌ۾ =<8,bSO_<+ wkf7e2M:""""""""lNS5/@u ;Y< llh|_ٟX6] l.d2zHi)8atmܵo'm^ x$po6ڣv &`Ά4T*u?pq;BB%QU4U[ַy]+p*pX*r6[`啼OoڸaB7l W{i~UH9R** |#"""" xڀ+kM"8 x=R\ǵlѠ2o.oQ^u}]wH8l6v<Hi,Z< f97ǵz(_]#Ԣӧ?'p&mB_wė6z]vm׷yݟ76.>dAC'Sw($>oTj6\NyV8n<\ʇ;6k#_*y6{KuIa6|߀WDs.p;/񋈈- xO V  \ p}qu6YF@l4Ñ8oat"""""sZ4xn]wo p![ Gp:pc]6{Z\E M}׽k:""" wΥ}tw T*uqBNV+psT!(8B" ,:qᮈ+eYk:~c,Wc""""MM}lULZLoVCoյqhup>9CHzp,4FOڹ~jJ^$ 븀2$0 8qڝo P0_:1ۈHQoڸkoq?u?n. -J:""" g$?6\5*_Ou:&R(i;BuWXJfЋ @Cgp!fX/_\EkH9}"""2NZk2Ƙf?B+kM]m^*OK.;q| n]?hl6C/dO9fqvg2q9_p}@/p~]/m^8ܵqWNg OpS&Ynxf2-S=yPᗷ~`;Du5Eԍ1mQ,""u)8pJN7"ppqRQO$7&.m\ujGQ9{skbD ?q`%mZ{RwHәɀ7anc.\{԰;ȶ70AkB3r~O%[Yk@oE>ˏL&֓j!lrvczY49 hco | X< W ž? |7T*xr4x.?*P?+0|}~,Vp71_ ?1@DDDά~<l,׀ׯڽɎo6zPp=5L&;݀7vTn븠om)Fv-77`0G:ƑJpDk7\%v_~FJ5Z Blp[୵eo9:~ch̲s """te:.rS w珮~|]pUpvۙ^ b#""RS9,R 7.<~ n%"ϋXRT*MQ& w@8< ׃97G <]pz1f wEDDd%fyz=ٶT5{`no0Yl uUd\%H3W$ncǟO͡ߏ HRצR{pUʕދkcpQUN=\hy]Y<`pp\󿁳q-^|_CAp|ڻ֞eg]fg[kEڗYk?h4-pºƘr]n`|eFzmM5U}Ƙv tŶg𷽁 q Fc̦VWfu߭vlvUhf7Qݸ ݶ,C&TayeqNADDyWE#J\kS \7R =zށ_9p ˋ|ECWj>9( 5J1[q'I5c?]vaޠ%|=1󈈈Hә̀7ӵ@|8pY4Y_Un?f7>l(LNbҀZ+d27M6:*U?d2L]DDܵw(z<\JNU+v+o;?oIRރޝ_? B"fTq\0r :)WY\A[C`4Hw୵'18A1BJcMTYˍ3~OTcM*WmP_& w2L?< w}>lҏ4Զ+)\(? |:7,Ij:?wW?Ijx~܋!Zny YnRP0*;\3CzBҠGmo8 1git"?֦ o[Dl!biV m')7d }-wTԯk3qc&R˺};m%DDDF*W*tRO;Q(vxU.o-ƫaٓ3vڱ7xU.oYkǎeEDD9vS\nl,݅0vnܸ ةU3|*-j4"i$PaI&I=q&{0 מܤep5:>[pA1;kMB0X(> wŵuxE#.T*eS:-x9J@964xƘTZc̨S+""2v}͝徻3|*Udf}6H/芈H[RP:޻75]E\Y(?Gm?}P(UPx,L*)x-\ţ^  ~DDDDl}uzܳwr9qwZk7w_v_Y؇֣WDDS??\k-5Yf~#nrsw!.KBBpuPh) O w [ BP7qy??i>݃7[o<}w1Z[ӻfzSX~ugfiբADDpՙKqWU =_;p=q՟s gxU/-\uR|%:o"O-&$t6\+""""Me. `Wo`|uo\%in:澷UH p-WO)up\;~ߌz'pצMp^ {>pp.._A\_OOP,"""" l~ݭn@_cj 8 ^ޠg'V Q)rBJBnp$m l&g攘CǺ r̶[pA&;B0X(>pWDDD͉ ^k وfﮈȬ* ϥR +U? wsɾRfDVWDDDBpuw묈]Mƙ龻'‰ gb2v̞fww&Kc\}d_$f=tDDDDDDDDDdM侻]'PzWOs.$9):軻z! -%nlo~DDDDDDDDDfWuw ?jC}Txn\ۆKl6!N7^6UDDDDDDDD\3VnݍfVx;'p9ܕfm6Ʊ4 xOrݨx +hc3L>pC d26MAn4_H|K9i:ui^)~c l6[Sfo qpΩw.2v芈4I['}wTK/^k2rLf+\6j4sT>DgaMSٹ,""""""""Row w7 $jyuB`dN@3nC!?p.Lqя43S+ژ/Jv5ng11Ͻ~`ufs1fw5q7p}t*\jv.n'\կ *Z7z^Zd2&jdp +ǦL&swT:LfSV5j """"""""ͤTP΄qak}g|ُNi *}SKj*٧I^'9~a.`d2նUA-""""""""҈Zt fEֿ+QooDžUd2ƾ~x* g2o*3&xlsQK.0"""""""""'SB`vcncL_ -.Qg2{L:oTzH#S;6IkF7Cgqq 9}5&\3`u&ָ-UzH#S;1}Ƙ j*j{?2dRdvwG.DUAv 4we2k2T5'EJEDDDDDDDZۅ 3ہ1nzf٭"wWNg\UjƇq؎amMd$k5id xel6E&IgGDDDDDDDD~EC+cgRWftEDDDDDDDDKNASx8͂s4Vl :"""""""""E \kzN51d_\ xCO}M1m)? xC\ܷf5$WUXDDDDDDDD(mح~OIe;i :TP)IAAJX:H: pP)HƼw8I'^j1Lw6I'{uwI^ܽ/LӍs!mU iRHwIt[EUKɊe+\r&3C$?dUU))xgI$$gƎIvI6U5J ?wI5 Mq*|IENDB`nzbget-16.4/webui/img/transmit.gif0000644000175000017500000000476112630544544017007 0ustar andreasandreasGIF89aర莎Ȝبvvvhhh! NETSCAPE2.0!Created with ajaxload.info! , $AeZ <䠒ÌQ46<A ßHa:ID0Fa\xG3! O:-RjTJ*  t ~" ds]  )t-"i;H>nQg]_* R3 GI? ˴v$ýj3!! , $0eZy0q PУW )";qX^D50 Ո%`rJ{ 1$ʈ!! , $@e6$Ơ` 3*=  P\"F`P-d5V"2|?n"!( )e4xyc?   3 #wyJ l% o^[b_0 V T[0m $4>'VZ c3$X%!! , $`e:D3 H0,'j0Qs L(2HMj#ȉB \Oi`u=YEVL=I  > suI WJm| \"_b0 BcV"d]*K1" H|@B?I4# S$-||!! , $4ea:D hI /K$W- 0(`3F=pf@tQ  {f~*yS*mg) enu E^Z^ g@ kw(b& -w#" xW"t ##%U$`to!! , $4ea:* 1v/Kdzk#  F Y" % E  Cb AI4$ (z:2 mI Ll## F##>F!! , $4ea:* 1v/KdVtKG227D"$)Qqp8 y l |~6zw2j# F " % VC ]6a$ Q :2 \  EF I&x "͓F4$]#x!! , $4eZi䠒J16e E,C\3 ^3[ S|?!;nzbget-16.4/webui/img/icons.png0000644000175000017500000004223612630544544016277 0ustar andreasandreasPNG  IHDR,!4`bKGD pHYs  tIME){c\ IDATx|\UIҦ)MRZZ+?DQ-?EQK,(..E]A@EZ@KJ !%i3㞛Lg;IדǐΝ;ssg=g>\ ` W"6GeAp6Y/Ȅ$|,9f)`* ;/,3{,rfmt`>~bfy32%3{̺2zK]Ƃ݇a ~?4M'2oHKc=w`^3; >7 ea60_̦ͬYfL+A!~&I&Ĵx܇$,iV&"/!산kf6=Nl\.7Gb>:?61-]ɞƅ̟tp[jXKŖ9yyᵙ3gMKNJ;y=H͕vZ~6Ƕ2ߛ;)6db,$|~po,I]E{ >{#Em 3Q {3~,lA[v y<͒.s0Nm=O_efW N%{x_$j%͗u\RV޽  Fm,6m } e?v+wH5Ѳ7Bo0 |Iflv8=׷2JoT(I 8%Dd5Ix7+`圤=L?^I:D06CeW|KJ^37L}_֛+ }7T ^a5R!V,V:n]]1V: =]~&O#=%IRx-1LGmcî&3s->l]w=X/$}t-pWۧQnnOhzyJCSyxHo}Ж~n3iI_#k%$icSS Wrp/Q$G` lbElvTh/鳒/'6? 1,YԊ9̋O]i.~]mO2Ք^rfgB@_z vv{TqE^s*:Oao@A*65]폫mB/|mj%psBt[kk|>gfه n;VVd _ZHRQ TO"z*;*KXJ8+IIɗ3bh EB EE^+fO0A>o3$|s>zޟTem(kpZ9#=x,jI `T^SfvJ@ܜ@9C1*YI=3f' o ~)MV:d쮭O#\lox_6JcR'{Pb~xlVN9+1mx*Rs$ڗ Q-Z6vF1$!).mfffv}7cۃ e\$yZRNһh% ? gIjrsAK_ov6VTi+΄wuٶ2$^K綄Yfbfgivz6&lf3 `f{q탄~k$=*@Ih=HZke~=r7K (6e%iߥfVff옂y6f6z_~c }zYҙEn;3`I -o]]㯛՛Y̬O[`f˴Gb6y4m׹r3o;F3ffAl1A8I Af6/9P&6ߏ.vL!r41t,mg?f3]XLCMK:EC"|̄{ܜ¹u#Jߴ ރ `_6wwIeY|ۙ }yb/Bo qff /b?APn>K:6Oe\jmOeq:IOWe^knC ]I._aefvaC[Qrzౣ{vA5Iϔy-E\':tSWiXmgqefnrۣ^2mRϟXnע="\p/>?LsUy3=ֻkfvJowŸٟtO[K:nt}?0p$ kt]\Q}c.+soސ>7iEmĴʼ[J$k'IUanJRU0R5 IH/_SUbmIpH{뾔cf3lO3k@୍}ණ 5BI2N>XRz ~pMy@c $df{ޞ\X޶ϭ-Q I _9Ena]5GTpS~Z|pgX -RC|+;8nsEBbX!M@‚ ۿC&AP#fK}nS O*L/w@*f*,ajUVsʼQ MKX?k{˸m -0@a7JIoY%>'x w+D=Wq˕e%c+z}3;6|^mi~Q qs*32!1|̞5I8pW7 mcA#2o6,ܯaK<DqY;xQ߈J gt|[7 ^qga EoK~fvN4"D2S rۢشo&>p/Xn7{٢20^韓kxrGݽ`],Unfgǃ9+A5AP[d3/w5LW.-\ܻ@ˏwm}\Ykxѻ) fv>}PNcV"r%'I޳UbY 3/F#8H`_[Gkh oA];Kķee^FA%H~ff]ASmf[քp^{|/dc8Fi֣^[Z|O.-'̮+xdmVԊr!luP Y.J0@m7ڤoQ1 Ґ6Gy3JA̎M )M 1Qﻏ}o8y*~v-qxlN,1Sʼ_.Ϣmf/r3bp4[ޭW.U;o{aOtޤc޲"؎7f|OV iB3i - N,C-%z?Zw%;h̷̓9Bd8Ox}~H^m}#cO:`o i Ah|a;Dl]y`dٴz4kgfvGJpOk@]ZuְžmWٷuIج1'Q{&I'k%);*UtaK_9?҃3;]uL`=ewEX%Jef'8Cm.Y άM;)%M`scK|82`'~M%9o9 OYI($ g鑴DҹaZI$}Tz^&ؒt$Xp|("i0s};̌3f744>qĺqHRWwW[6nܸa+_xᅦE=K/.mIchN)- IH#iNVHZ"i0LtG:e:!i( K:SүR \8]kK_J:%.GH/; I$@|p\*wwxDI3wg}vI{{{OwwuuuYgguvvZGgutt]ήN겶g}vwoWUU!i*bj|(Ka qH=ðܥ~LLpuccpuc>شW7ZpuشǂsՍ$𺮖TŗKwqp-[騖4YҌ{GO=siSLfVԳe|jnni&IҤI4uT͚5Ksա}G99~{^xO$0=95c&J@҅Z+Ue{0 ]!azjoV0J$`GɒvM-nNtfJpWjaoʴI6.+Vd[0KK+i ;I<.ү͙3gN`MZx~_huJI)\g"Imm4k%{-Pgڙ?#G>I&9+WU߹1Iֺ;C{66Щ>LS'Džpkv69-+֧T5emyah|I utsou8I 7$ufgǦ*璾-Iofn\%ɥfgy{og|_[=^tR [&LH;\^^sf毈 uqg|ꎎvwܡ>&>m{-~Wv駜z֪;_Gbv:zoNvݶ[)=8=iW|mtII:6;wUhy$* -I?I$8 7\^?H^O-!qMl6Rߐb,^[7`{xef4$+%iw,^v\N{~ϵM}sjxx7tOT:]lN: s G2O:P:Av+}vvztK:?ۘ7%tnOe2f 0_g%{ja2-~‘CNR8*>*{M)u@MC[7A_s{NO~rz۲Z䉳{I:יҴ_w;Z詧zxJԴl+O}hbu2ЖKs4~ gPpJ]TIdcD']&d*,980]dcN4'0fW!V2/0ƃFߗjIӮ<t{7 &9LJ,/+mz{ݭ#89O ퟰI]뉅]I닳^Q'q5<=q:L%U#uʯpzc+i/ek u AO?\aw4 -?@QsYL4cqz9==zܖgNQ$*mdiIiݴbWp ּwmwzzzt)s/]O{Rx`J~{郭31eim\@u I'uHN-^%I{IepuƵՍ[$=KR Wyћ|hؿ>*l=M'Nܫ3#lK-9OIn :::)N?3gΜܬ)?._4솽]d=s%-x]Uj|04}t^zq `'(_{ΟGMH CmfN"<ɄۃjݼXTΕ#lEZV#õ.o*cvTҏS Il% TxR B/SWW@{#ӍnwΩaՒ73?yQwW.!Lݼuu=z>Л.Bo[z߲Dj}+UaoWOԯϸش_0Z}a*$^N҇+ k*sŸRs$`^` +Ommm#[//^g^Ъݚ ܖ23}N7~5}VoiCz޴Qh.lݵU+V襗^ROO>pgn~˃]_$U]dUYuoZ2-y폻`]ujv{IAwCǏ?1J*]\q;OCٞ]򕯜2yzyjH7송7XS/*9ݿƷxu}_VѡS/S$Ro|z_|ұt_mXHNG.%Z*Gj.xP~$@IowKjkkO {zݚl$M9#OѪUƴ5** ._`M []Tt邾Z*tG*^ӤTǀ{K\Ub=\P]\+I?rF{u*- $B Ǐ?&JmN<ᜓsNƍ\WW׵l>f͚5s̙r[NhzNt[+Q Cn*_}uEFnX9m6nܨ͜9sެYf*Y?TNRbA7⽽ 7?Q5˓l;z/Y>R[[{s/Ѵ켑M_v~` IDATMVyfVˤ!/uj1xÞܠoHTFz8-IRi~Ż$?-M ٍz}=ZAo_ Doavߵ1˯w}l$x}Lo7tww͞8aٙ\ON7m[SJwׇR}Uo~9Fv }y]7iӣ}'.p֨ Gf6vF(/Dwޠ;JV?2~'gxrrRe_6L-% |\.;7h+ [\tn96E6n? f|gGGwjځsn ߯}-q%t:uvvvKu/{s %Cv%*\`=ƒҢye)WUT#;>@Ŷ0Iݛ7oijXmЃu! .ؼA蛵X)L\N7onԭUjJϩreì1igKK$q I?~'gx-a//eC.S>4ͦ;z{Q_dî.*5'{hںARvtEUg{[írb|(v>dtx ';~.mHذa|..f0+ asiG{X=}ҳѡ\N6lX_>wL3pD{s[mt]._\,*R횼_RL2/m|ͫr6oޤU謯_C kQn [1{6u`ՁjiiQ>תU$<[sK]v6[XUaЍ]\~IZwկ+^l񮮮ܖ-[4gfjaW}_BT z}MooೂrYnvMMՕ{WiI#=Y.Nk2X9CKl~Dn9jd葝`_}D;֮]5k^Z~W/V y /u;8YF|^k֬yqݺuKRFiتg~\Abzzcåk>'&;v^Qxm{r^t\N::kz1ESWY+V'JjVqd.=4|=Jl+I~ ͕l%=1':%^z_\__S}}OWҗURȅ>~X&ie?Qyc ޢ6n|/'7nع% =@?Ưy^P2:s[ C-ba#JA /pҥKԢd*ewQQ zE'kNusNGInTޒΖx}}3^g/{JSɓڵy7[G}z|zlf?߹zJVKr E %{WH:U .K_5X֮ kx?=yomm}JyvzIwJp[X@Kq\,6~e isnsnsnڵ} |h.{7M7ݤ uu|W⿁TN}Z:p ֺuz.S8DS*W\֢GoT0]{Kz腷qOcZ޾֋iLa nmmUe^nIɋ%-`Y7ntV LTqx49?ۻ6zBOO8s9ZRդ[7ܢ ;ݫwק~F|Fs^\qƇUId?w9mdAm8M_]߭Y;B*u =+%[[[)}Ed{+FRkkk뎎T[d%5gW$w{2?)<@%I4[a{|$%}l=* %-ٸqh>&ooʆfvaמAu:ҩ;ן;^ڦ>۩:hu=(+N]l6{ž-lGOp~W=MWs / 5(c(x#W8JCJtnkk w*,GJ&T$J{t({YG\,~!Y7n\Upْ^޸qc;z$M4mƌ͞=kszz5n|>'zj^l[fYMVIҤzMY4@6pŋuC555Y|՚5kS7?oWgLJ)x >/۸BM3lܮs`>R[;K %}S2?zCm JE߯P$ k)Nt(,Q_or[aO+}f@SXS}QH/a /Ҹ ΝFy;=Х< : O1xן霛a6g}tٳ5m4M4Ii&_^+WԲe3hʕ/1[[[oWphDp  lGZ%;@7/h=On>@=Rx2[bףqxkuIEgiIOp% ORvY a@͛0a3ιO\e&L$;wSs{^ן<~yιmz7\gg狭Y>Io(U {v4eܸq3w}jkkUSS3;NNs5m|~Cwwʎݰac]]]NzKI+<ć©ˁ|_/ižѥ!G׿O0TkZ[[gsu.PX3,I$ݧs(~_9Ja4C?Nl^3Sgg&Lp;OŰ=XUXZ/5)6_(;aHR[0%}"`{B!|~YD׃ uvv[kHpW&Ky]gzߙZAXggl͕~X/ϯ:y FK=2ooWWel*mmmr5fkWEsuM1iRf9_x;U5%͖49E>-23}s6r6555L&c~^ٝHsIR Lܙssdv{̎rU#0ޗdNauH[%%; n3/rüb0o/r3).(9 /vdٻ%S&qz@-t$i(^>θS]8Z$s3|Ʒ/x/qxrv'$=)vj^3ʖO]]]K_@;i$}ݜb$0olsN&; / s744?;fI:^j$%}Sa~tfI%u mmmOKRm2,V'$#i :tɜ_$^l]K{ \nl $]*i>GJIˍ-ioI~5oٗ_,n41~;h>/齒FfMI?4Oh3Zs)^B7Q7/;N,}s+b}== >㜻9x-n}~}j gjɲ%]?2^"qSY2̟c%__owܝLvIKڬrŸ%i&F´]+i /Ÿ/ / iW i Ѱl?)IIznosiE{{06TmsoRr=bf4SyY+i8~ff΂_rEAb 7~=s]a9D[\񦦦I)܏IW:eкUnjjaZo$M2OJ:Oi\&fv%fVw%/3V6^srJakLIZ)Ζ4#vHʯ|?g 7Jh_3c_>Pـ7$`413 >;vm|72̎!3*rc?gٶ~i"al6kl6Ypf[KI ktm۽⯿ M'3k57b׻<-xc K܆"ӿSI{sO9q}s-W(G8&g2W d:^ ᜛\lL&Q6T'@ԨOgKGR_?Ї*Z#'髒>`^藱 {VX+'a56IzC %~Rҫ kߒN/^ wΕ\ss%_^>h7Q]b99/3;"ǦW&>?Ə3@=#WFe@t[t^ĥRw|C ]%G;$2]Flkkv-uuuoIVyKnpKPιFn|ιo:džf`f?uΙ_TOh>3@~_>țϊMfs@ؘ7~;Tħ:pzӇޓ%_~UlkGIǷ.Iuuu?s>|/KNa/ K 9^uνX8R}5^.ش?(cx~Q{*vsy3?wwlۼX*=E'|?v/(]ŸgKZˏF8I+Q~ˢY kvk$=Xg {/jkk˳  *Qf!ƭ<- 25vjWJL郹)lb*$5 x3 ˿¿ OT[[[w]]ݨtƭmmmuuusں]x1;-C3)ca}Wٯ3ւ.U xjZ>$9( &̦*/?k>OlfoXh3t u٬klld2¡Ф2$;T%ff>:I.58}J,Xm^. fsn)jn\U {2vS4=ss=[l6@6oؽHv}\=cV׏MMM(Vd9:<h> #}g̦9n;XWY}@i3{9򦦦 ѐgJ.񏭦6z0< J\ߋ sarν✻<>973;G%7t$#)^ι٫a޴%ݧغ!\M3"il69'03/ƨ~ظ`nIDATG7;$VL&s!x'@ifvRx}ιJnT8dL)T*S$f w% !3w->O1 ֒j&B?ff_W؋T8> AZ0ia.~mGrp}UoBss(w;KZ#·d2gf7ƴaJ-2L2`;.ACswThjjr%f2 `+YԼ)IENDB`nzbget-16.4/webui/img/download-anim-green-2x.png0000644000175000017500000003326612630544544021345 0ustar andreasandreasPNG  IHDRA>sRGBbKGD pHYs%%IR$tIME ;+ IDATxi]}_9-  )HȒ%{<5]X3Iy=T͗c!T*_I*rC[ESqc[<#Ѵ)EwI~޳5F!^}8>8>8>8>8>3^T;`|V~nϟ4M3Z!_t)(!Q .\<0B71p(/^ ޥJQJ $4=s?L">@@*$͒rXA "AP̃e}}]Q>D 3%K__pU4M} 2Rw= G SsCu.NX!!#.--B!"XȖ4JHiGD)L(Q@]Ihn(}SRBҗ"" ؗ,9Z$e 8HHQ><.G ["PB  k-+ )%* ?1/a>DI%<(1&-gwudˍ@) 9XdpPD(DdfUA,}3O1Ƙ1R#Qki6f8G   9cIƘ"is;K/aQ[eT#cL(.R!-"Dt`2M)D 5OsAu ^D" !xDRJ>2ODAUU!QƦiB3OE , =c. [rq)Ub?t.@Q@*"|0p^9i^S8J'` e kL-؂ zZ[p-oKO_ӦvREEf!̉PQ8?߂8RHBh3T!QVMԦ1Ƥ@sWsq7e,1"Z퓪%C2$Qe@DANF +\D%48E:61h۶m9BEҮQhkn4xBD6D^ dCp< ?sY`9P aӴxH1ؠ&ldh!A4nдm+DΝ;/_>PdngE햔.//Sun6JaQ'Yh DYP`a+D&_+i XD !Y} 8I˲ K0e ßsiZ=D$aͪQWAc TUMx}+J)h42s$Kg3ƌyr+DŽNBd߳'}uo'%lT1nfuJVpb*H ,Kc>'}hC$}^jI)O} 4p5[ Bs-Y)(U&h427k:`.}UB c 9`blUU3qgYAĕ_wgbжƗ6SJJP2$DlMN!6:{D YxD$hB .aJShn}>U+iX= X1WF"|$fXk!w!ea9}4Q"]s&:)cd2v-i G{c7o4/ 0gSNajDlBJED<"nY1T4DD스hN))L!@BP$:>E߶t2+υҲO *UdfhzQ_JJIRJL)[vKCWAD&"-v*"%D\!ƏO7?/VDd6c&FEDycJ)b-L)tb!!lWՖrJjOҳ$w3yt/e|#͙71F] BD4 -㜓[e l23t9ƨP./tzݓ?_M,Pocl{ߊH>DVg$X3rХ$K@m!һa}<_Wnd; ET8 p|+̭-R¥% 3"vqlVXYk,+s89" QIlDc n/0Jkh4#UUw1ٯYyG /lܪGEai۶Q['Qb$c2ĔR1&ffzZk%ҶFnU NQ1 " @ d֬ PNS3~`sV'YN C21f-SF穥s0|qr;c8\2[;_'5T}X)+KPHamC|Om˟2ӭ-ɽߒ{/9Qh4<c<:H޴`׿HG̻RpOo|0ǜ*ED֜ qZqWJ~.pJM)q hc/K̼,"Kτ8Cm7ؤ"zۥ5Z4MojmH&fͮZ+9b=_xtU)ϵ5Y]]E\а{42;Rfo234`]Lzu!}hoe'LO䐁4siR$5~:5kt$Lw,--Q۶XUy)פ9,Fzip6[3'9_+ڻ- %XJHփ\t)#.,qx;N\T<8Fr#hr|G,2t]0Y-.Wda(F;Xgsm}ium0jmgDmʷUM[0Q HwŽ|JNnXTS)etse2CrU䤢OSp*W5:4h\`<0\Sgv`鵄vXn?Tʨ-4M*PhZ],o y{f23jFV sRALQ5/JjŬǃUɢ @ 6x4-SԿhiD*R6~c-1k{m 8^"=3ԆA_r!PW @:ϿAK((EeT-s.Rʥs6޳_'t\23.n/<4K!?>+uAt`Z k:"j%\.?l@)x6Ike0)8fsJЦ<^>HLSYŒqm,=FM!tqn(6SQ:rb3R) _˵8 KXUɳv 3lEY$0G0 6r&住d+vrdyji( Bf%u.I[C<#-( a>+_5wT};7%RX2i^jD/thͷ-P obLw'^:dZZB!nB>G萋s"NC4g?݁S:BS#JQ~'{l5CQTm(^DZ"ؒ`V2%V*SF:&+}&ϋ`7"eGcX{]]ms{W=5m.Qy(r̓ 5X t'cHMǜtsKHP f#FcuyO JQ 5"8}xm0)ūB_ë3HQyW#Z-ec!"bm.MJWrz>q?o3Uqu]ya2i[W%if9X"91+U4u#!E9=W3YUDf} ؜?e_ ɍ]9ki܌o`J}\gz@f{$?8HiiIwIMXҁz^jIUUI]םB"$! &>_GP]){h)w4Oӯ?݌Ҋs5q߾_í^)1B͙Nonad32?RHh6F i\!+\u>>i=I4/I2c^sQ2ӫ>Sd0:l8ȵUY !\Ͷm7SJӺ<"#$mǭ.=IqO}V~l-Dͫ3jfC4uilyXq*IDATV!"q(cCck8 !Lnf+s_WKT(6`dZ*\qN)ktjz0b܈ŹܐJXrK*BM”E@<QN~8F!ӊ4Ƹc\1yL|62@VJi441A%sozh%L,uqUW^ĝe')4SljRU-::Д tp-cuh#G.':}+4 !lkcz]ZQvVwy},UQO1ȹ笻 ;ÌG 3ۙ˜wF)%<#9_L7Q!7J|:U"&inSJZuފpK6L-k,1.}<9/gߛOU;zkHL4X;%i3wW 2Ƙ11=skUUEfVUPnmƦiv,H6`;***b[B;P@:y$3W`1ڌ: B{U-^LӢ(N_""q΁Փ2_l^ !toZp}z]nEttUjIk=b0)~|e{W}\{ADc峛@c^nlkICf[`p΁s1*拃 n~6cd2TYp8)喼$$վZZ'VZDl_ou<fi0#>q6,"B!og0`A~xܟk]/!#O#YR8K6L﫥B´c5fyjφ`Ci;f*esNHWB[*[kUl*[[[q|$^,nYn6@J?mOܗqb};0k*0 cLC眥L!|g?C΃E{ӋI::ZKDUUi#-|"f ̢Prʬ^իW*1ƈu]sNǕaۋ9C1F4ƨl!"3^/lo=Nk9Y5ȫe<uu]o,"Wzs$遲mB1F#ֲN/K?g^~lzBʕ+Q}mdyg{SN>yBA=2R1P[8 "rx_.W,B@s߰[;txqڙns^@m[\]]ųgb H?N>OXTXѹ1áɫ fgG u0RB4딧wRXArٳg.IXHm[4 _Z>ϖ%jҫU(i[OMAt+9Q)#>}Zϓ {=4M'uy x'Odƛ "|tocPE\[[ MӤr:nd70!\|VWW;)tRFӪ {4"y} }Fqۂ>Pc#1ͬn8簪*NWvĉsy\YYJiƸz=dpH<ݰ%;3`"sn}~b^7777},7-#.\@ׯ_Kͽ723#! sKDd9AD'lx{&"!5؈1nڶx<%4&1}KpGM.ZYBnJ 1,"ggOXf߿6+Hb6iAİ4M0-@aٗYRFm21FUQ<;ev<Fr\8ϙoB&Ws:m浻4YtntfwEy2N8mnE(Da1v)Saf`YD4f##~{9}9677"]3Gt:`Aض@ͣt~l@@yh6W sr!`N[!Ekm[`f2@T"5{ZIfiiªp Re; R29g[\UOoz^s[Mw=H y@)eT59:G>~6y ]zO}y|4}=)*lDD4NT+7:V%O4""fyy"u`P̕s!5j_T|^p3 mDn:)`@ݞ={uq:D6M#t$fmmsZ‡&aqx~j*"OȑNʕ'zb:p 4Vi޹@tuhUU``s ҕr O\TշN 4Onhc-o6xrK,ˠ[fڶ`@ "dt Nylۊap_Jh01LujO!fm[)c Zk{|YUCckJgv9PccGr‰ŕQS½-3u*ʝ]nEhomcEi*u%CIDyp`4U^ S!+98ۡ{T1L'!(8Mlw+Ԥ?!iF7ccr\^P.oT}8^Z%*kiSJmZ^9Or cWT3seYo}_1Ru| |i}<&KPSȭ*kފC <Ț \UUq~>PA.9 fM910Wrx~n㟵n~Sѥa0zl1m)ץ_{#n^ T4 ,%SaJ۶by25~̓I@w2yY?Ae39 Ϻ -gšK5:@Q솈VUe3 1fDD~pq.juUJl"뺎/)ثr9ʳ^Ȏ~6КcZ!; *-"c"Z¡n=t]uך OU!oEɷm5'yjg^&;g~Ӝb15): ݚ VF?h|/N6iB#"M۶M.5y/_^2q#ut;FXEefmoMѨ*4c"'Ϧvh|cm"!o[>cQay}V@p_6ޡJ,DS[a\noyG61ƺ5XEz8BWh䍴1fCcG0 ǧߔ**Fv X:@@K(7F-ljPPՕaQ%("8g?͙^}.5m&6Єcۗ,Dнk9hcH%1hb`QhPSG?nOlQr {x۾oWuXw {CsP=.P}o>Wx7?nV tVai۶ !ļlt[vqo{%hlcsdt8f卪+gT|hLb[BMqWd^Ry6h$5 qNo񆶐.HWNX}AS\PlXDXkujEDqX 9Q}pk[35B `q`9YYJ. wxi3/7Zkӛor\XԺrۀhb8'Qi98frC`'7Uh£çәtOs궽m7h%'?mR8tlKDmJjkoI!Xuڊ`)K.:U?bkTLD&KΰXkլ/":GD,!m^Ôu/uw |ۦ|Յa!BƘr[/4!ޗJdI1,u,6غ] ӚYuwsK}n5/Kir T`+M~)h^Ll]uQVrzIBdTu2Ƥʹd.s#h8MيQ(6rn{ȬAD30fc̶ǺJ0ޔ1#4(:CoWbJ+4 I۶!9NӐR %(ew,H/mrmnYj@955oդ2пt.1W P y PNA%J(YvbX HN,/q [V}.&ujfZKVWV3$NUU]'Sֺ%;ƀ *ڶMTE첂rG 46ziiIIU/pc2)9& Rz_o)U4,w ,mJpTꔽʥ*>@C>8!(}Cr?^QGhJ/umn<8RJ~j~lf̵oQ[g7`zf}}}:CˑfjJ(%>ַur'JrnG Fa X Тq7zl=VBғ$+a9T< ї [ 47z %s$n@ ]MB-7 `aοLr|/Ä ,q|q|q|q|q|KIENDB`nzbget-16.4/webui/img/favicon.ico0000644000175000017500000000030612630544544016567 0ustar andreasandreas( @@@@{7cKJ$Y8hKnzbget-16.4/webui/fasttable.js0000644000175000017500000005436612630544544016214 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2012-2015 Andrey Prygunkov * * 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$ * */ /* * Some code was borrowed from: * 1. Greg Weber's uiTableFilter jQuery plugin (http://gregweber.info/projects/uitablefilter) * 2. Denny Ferrassoli & Charles Christolini's TypeWatch jQuery plugin (http://github.com/dennyferra/TypeWatch) * 3. Justin Britten's tablesorterFilter jQuery plugin (http://www.justinbritten.com/work/2008/08/tablesorter-filter-results-based-on-search-string/) * 4. Allan Jardine's Bootstrap Pagination jQuery plugin for DataTables (http://datatables.net/) */ /* * In this module: * HTML tables with: * 1) very fast content updates; * 2) automatic pagination; * 3) search/filtering. * * What makes it unique and fast? * The tables are designed to be updated very often (up to 10 times per second). This has two challenges: * 1) updating of whole content is slow because the DOM updates are slow. * 2) if the DOM is updated during user interaction the user input is not processed correctly. * For example if the table is updated after the user pressed mouse key but before he/she released * the key, the click is not processed because the element, on which the click was performed, * doesn't exist after the update of DOM anymore. * * How Fasttable solves these problems? The solutions is to update only rows and cells, * which were changed by keeping the unchanged DOM-elements. * * Important: the UI of table must be designed in a way, that the cells which are frequently changed * (like remaining download size) should not be clickable, whereas the cells which are rarely changed * (e. g. Download name) can be clickable. */ (function($) { 'use strict'; $.fn.fasttable = function(method) { if (methods[method]) { return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 )); } else if ( typeof method === 'object' || ! method ) { return methods.init.apply( this, arguments ); } else { $.error( 'Method ' + method + ' does not exist on jQuery.fasttable' ); } }; var methods = { defaults : function() { return defaults; }, init : function(options) { return this.each(function() { var $this = $(this); var data = $this.data('fasttable'); // If the plugin hasn't been initialized yet if (!data) { /* Do more setup stuff here */ var config = {}; config = $.extend(config, defaults, options); config.filterInput = $(config.filterInput); config.filterClearButton = $(config.filterClearButton); config.pagerContainer = $(config.pagerContainer); config.infoContainer = $(config.infoContainer); config.headerCheck = $(config.headerCheck); var searcher = new FastSearcher(); // Create a timer which gets reset upon every keyup event. // Perform filter only when the timer's wait is reached (user finished typing or paused long enough to elapse the timer). // Do not perform the filter is the query has not changed. // Immediately perform the filter if the ENTER key is pressed. var timer; config.filterInput.keyup(function() { var timerWait = 500; var overrideBool = false; var inputBox = this; // Was ENTER pushed? if (inputBox.keyCode == 13) { timerWait = 1; overrideBool = true; } var timerCallback = function() { var value = inputBox.value.trim(); var data = $this.data('fasttable'); if ((value != data.lastFilter) || overrideBool) { applyFilter(data, value); } }; // Reset the timer clearTimeout(timer); timer = setTimeout(timerCallback, timerWait); return false; }); config.filterClearButton.click(function() { var data = $this.data('fasttable'); data.config.filterInput.val(''); applyFilter(data, ''); }); config.pagerContainer.on('click', 'li', function (e) { e.preventDefault(); var data = $this.data('fasttable'); var pageNum = $(this).text(); if (pageNum.indexOf('Prev') > -1) { data.curPage--; } else if (pageNum.indexOf('Next') > -1) { data.curPage++; } else if (isNaN(parseInt(pageNum))) { return; } else { data.curPage = parseInt(pageNum); } refresh(data); }); $this.data('fasttable', { target : $this, config : config, pageSize : parseInt(config.pageSize), maxPages : parseInt(config.maxPages), pageDots : Util.parseBool(config.pageDots), curPage : 1, checkedRows: {}, checkedCount: 0, lastClickedRowID: null, searcher: searcher }); } }); }, destroy : function() { return this.each(function() { var $this = $(this); var data = $this.data('fasttable'); // Namespacing FTW $(window).unbind('.fasttable'); $this.removeData('fasttable'); }); }, update : updateContent, setPageSize : setPageSize, setCurPage : setCurPage, applyFilter : function(filter) { applyFilter($(this).data('fasttable'), filter); }, filteredContent : function() { return $(this).data('fasttable').filteredContent; }, availableContent : function() { return $(this).data('fasttable').availableContent; }, checkedRows : function() { return $(this).data('fasttable').checkedRows; }, checkedCount : function() { return $(this).data('fasttable').checkedCount; }, checkRow : function(id, checked) { checkRow($(this).data('fasttable'), id, checked); }, itemCheckClick : itemCheckClick, titleCheckClick : titleCheckClick }; function updateContent(content) { var data = $(this).data('fasttable'); if (content) { data.content = content; } refresh(data); } function applyFilter(data, filter) { data.lastFilter = filter; if (data.content) { data.curPage = 1; data.hasFilter = filter !== ''; data.searcher.compile(filter); refresh(data); } if (filter !== '' && data.config.filterInputCallback) { data.config.filterInputCallback(filter); } if (filter === '' && data.config.filterClearCallback) { data.config.filterClearCallback(); } } function refresh(data) { refilter(data); validateChecks(data); updatePager(data); updateInfo(data); updateTable(data); } function refilter(data) { data.availableContent = []; data.filteredContent = []; for (var i = 0; i < data.content.length; i++) { var item = data.content[i]; if (data.hasFilter && item.search === undefined && data.config.fillSearchCallback) { data.config.fillSearchCallback(item); } if (!data.hasFilter || data.searcher.exec(item.data)) { data.availableContent.push(item); if (!data.config.filterCallback || data.config.filterCallback(item)) { data.filteredContent.push(item); } } } } function updateTable(data) { var oldTable = data.target[0]; var newTable = buildTBody(data); updateTBody(data, oldTable, newTable); } function buildTBody(data) { var table = $('
')[0]; for (var i=0; i < data.pageContent.length; i++) { var item = data.pageContent[i]; var row = table.insertRow(table.rows.length); row.fasttableID = item.id; if (data.checkedRows[item.id]) { row.className = 'checked'; } if (data.config.renderRowCallback) { data.config.renderRowCallback(row, item); } if (!item.fields) { if (data.config.fillFieldsCallback) { data.config.fillFieldsCallback(item); } else { item.fields = []; } } for (var j=0; j < item.fields.length; j++) { var cell = row.insertCell(row.cells.length); cell.innerHTML = item.fields[j]; if (data.config.renderCellCallback) { data.config.renderCellCallback(cell, j, item); } } } titleCheckRedraw(data); if (data.config.renderTableCallback) { data.config.renderTableCallback(table); } return table; } function updateTBody(data, oldTable, newTable) { var oldTRs = oldTable.rows; var newTRs = newTable.rows; var oldTBody = $('tbody', oldTable)[0]; var oldTRsLength = oldTRs.length - (data.config.hasHeader ? 1 : 0); // evlt. skip header row var newTRsLength = newTRs.length; for (var i=0; i < newTRs.length; ) { var newTR = newTRs[i]; if (i < oldTRsLength) { // update existing row var oldTR = oldTRs[i + (data.config.hasHeader ? 1 : 0)]; // evlt. skip header row var oldTDs = oldTR.cells; var newTDs = newTR.cells; oldTR.className = newTR.className; oldTR.fasttableID = newTR.fasttableID; for (var j=0, n = 0; j < oldTDs.length; j++, n++) { var oldTD = oldTDs[j]; var newTD = newTDs[n]; var oldHtml = oldTD.outerHTML; var newHtml = newTD.outerHTML; if (oldHtml !== newHtml) { oldTR.replaceChild(newTD, oldTD); n--; } } i++; } else { // add new row oldTBody.appendChild(newTR); } } var maxTRs = newTRsLength + (data.config.hasHeader ? 1 : 0); // evlt. skip header row; while (oldTRs.length > maxTRs) { oldTable.deleteRow(oldTRs.length - 1); } } function updatePager(data) { data.pageCount = Math.ceil(data.filteredContent.length / data.pageSize); if (data.curPage < 1) { data.curPage = 1; } if (data.curPage > data.pageCount) { data.curPage = data.pageCount; } var startIndex = (data.curPage - 1) * data.pageSize; data.pageContent = data.filteredContent.slice(startIndex, startIndex + data.pageSize); var pagerObj = data.config.pagerContainer; var pagerHtml = buildPagerHtml(data); var oldPager = pagerObj[0]; var newPager = $(pagerHtml)[0]; updatePagerContent(data, oldPager, newPager); } function buildPagerHtml(data) { var iListLength = data.maxPages; var iStart, iEnd, iHalf = Math.floor(iListLength/2); if (data.pageCount < iListLength) { iStart = 1; iEnd = data.pageCount; } else if (data.curPage -1 <= iHalf) { iStart = 1; iEnd = iListLength; } else if (data.curPage - 1 >= (data.pageCount-iHalf)) { iStart = data.pageCount - iListLength + 1; iEnd = data.pageCount; } else { iStart = data.curPage - 1 - iHalf + 1; iEnd = iStart + iListLength - 1; } var pager = '
    '; pager += '← Prev'; if (iStart > 1) { pager += '
  • 1
  • '; if (iStart > 2 && data.pageDots) { pager += '
  • '; } } for (var j=iStart; j<=iEnd; j++) { pager += '' + j + ''; } if (iEnd != data.pageCount) { if (iEnd < data.pageCount - 1 && data.pageDots) { pager += '
  • '; } pager += '
  • ' + data.pageCount + '
  • '; } pager += 'Next →'; pager += '
'; return pager; } function updatePagerContent(data, oldPager, newPager) { var oldLIs = oldPager.getElementsByTagName('li'); var newLIs = newPager.getElementsByTagName('li'); var oldLIsLength = oldLIs.length; var newLIsLength = newLIs.length; for (var i=0, n=0; i < newLIs.length; i++, n++) { var newLI = newLIs[i]; if (n < oldLIsLength) { // update existing LI var oldLI = oldLIs[n]; var oldHtml = oldLI.outerHTML; var newHtml = newLI.outerHTML; if (oldHtml !== newHtml) { oldPager.replaceChild(newLI, oldLI); i--; } } else { // add new LI oldPager.appendChild(newLI); i--; } } while (oldLIs.length > newLIsLength) { oldPager.removeChild(oldPager.lastChild); } } function updateInfo(data) { if (data.content.length === 0) { var infoText = data.config.infoEmpty; } else if (data.curPage === 0) { var infoText = 'No matching records found (total ' + data.content.length + ')'; } else { var firstRecord = (data.curPage - 1) * data.pageSize + 1; var lastRecord = firstRecord + data.pageContent.length - 1; var infoText = 'Showing records ' + firstRecord + '-' + lastRecord + ' from ' + data.filteredContent.length; if (data.filteredContent.length != data.content.length) { infoText += ' filtered (total ' + data.content.length + ')'; } } data.config.infoContainer.html(infoText); if (data.config.updateInfoCallback) { data.config.updateInfoCallback({ total: data.content.length, available: data.availableContent.length, filtered: data.filteredContent.length, firstRecord: firstRecord, lastRecord: lastRecord }); } } function setPageSize(pageSize, maxPages, pageDots) { var data = $(this).data('fasttable'); data.pageSize = parseInt(pageSize); data.curPage = 1; if (maxPages !== undefined) { data.maxPages = maxPages; } if (pageDots !== undefined) { data.pageDots = pageDots; } refresh(data); } function setCurPage(page) { var data = $(this).data('fasttable'); data.curPage = parseInt(page); refresh(data); } function titleCheckRedraw(data) { var filteredContent = data.filteredContent; var checkedRows = data.checkedRows; var hasSelectedItems = false; var hasUnselectedItems = false; for (var i = 0; i < filteredContent.length; i++) { if (checkedRows[filteredContent[i].id]) { hasSelectedItems = true; } else { hasUnselectedItems = true; } } if (hasSelectedItems && hasUnselectedItems) { data.config.headerCheck.removeClass('checked').addClass('checkremove'); } else if (hasSelectedItems) { data.config.headerCheck.removeClass('checkremove').addClass('checked'); } else { data.config.headerCheck.removeClass('checked').removeClass('checkremove'); } } function itemCheckClick(row, event) { var data = $(this).data('fasttable'); var checkedRows = data.checkedRows; var id = row.fasttableID; var doToggle = true; if (event.shiftKey && data.lastClickedRowID != null) { var checked = checkedRows[id]; doToggle = !checkRange(data, id, data.lastClickedRowID, !checked); } if (doToggle) { toggleCheck(data, id); } data.lastClickedRowID = id; refresh(data); } function titleCheckClick() { var data = $(this).data('fasttable'); var filteredContent = data.filteredContent; var checkedRows = data.checkedRows; var hasSelectedItems = false; for (var i = 0; i < filteredContent.length; i++) { if (checkedRows[filteredContent[i].id]) { hasSelectedItems = true; break; } } data.lastClickedRowID = null; checkAll(data, !hasSelectedItems); } function toggleCheck(data, id) { var checkedRows = data.checkedRows; var index = checkedRows[id]; if (checkedRows[id]) { checkedRows[id] = undefined; data.checkedCount--; } else { checkedRows[id] = true; data.checkedCount++; } } function checkAll(data, checked) { var filteredContent = data.filteredContent; for (var i = 0; i < filteredContent.length; i++) { checkRow(data, filteredContent[i].id, checked); } refresh(data); } function checkRange(data, from, to, checked) { var filteredContent = data.filteredContent; var indexFrom = indexOfID(filteredContent, from); var indexTo = indexOfID(filteredContent, to); if (indexFrom === -1 || indexTo === -1) { return false; } if (indexTo < indexFrom) { var tmp = indexTo; indexTo = indexFrom; indexFrom = tmp; } for (var i = indexFrom; i <= indexTo; i++) { checkRow(data, filteredContent[i].id, checked); } return true; } function checkRow(data, id, checked) { if (checked) { if (!data.checkedRows[id]) { data.checkedCount++; } data.checkedRows[id] = true; } else { if (data.checkedRows[id]) { data.checkedCount--; } data.checkedRows[id] = undefined; } } function indexOfID(content, id) { for (var i = 0; i < content.length; i++) { if (id === content[i].id) { return i; } } return -1; } function validateChecks(data) { var filteredContent = data.filteredContent; var checkedRows = data.checkedRows; data.checkedRows = {} data.checkedCount = 0; for (var i = 0; i < data.content.length; i++) { if (checkedRows[data.content[i].id]) { data.checkedRows[data.content[i].id] = true; data.checkedCount++; } } } var defaults = { filterInput: '#table-filter', filterClearButton: '#table-clear', pagerContainer: '#table-pager', infoContainer: '#table-info', pageSize: 10, maxPages: 5, pageDots: true, hasHeader: true, infoEmpty: 'No records', renderRowCallback: undefined, renderCellCallback: undefined, renderTableCallback: undefined, fillFieldsCallback: undefined, updateInfoCallback: undefined, filterInputCallback: undefined, filterClearCallback: undefined, fillSearchCallback: undefined, filterCallback: undefined, headerCheck: '#table-header-check' }; })(jQuery); function FastSearcher() { 'use strict'; this.source; this.len; this.p; this.initLexer = function(source) { this.source = source; this.len = source.length; this.p = 0; } this.nextToken = function() { while (this.p < this.len) { var ch = this.source[this.p++]; switch (ch) { case ' ': case '\t': continue; case '-': case '(': case ')': case '|': return ch; default: this.p--; var token = ''; var quote = false; while (this.p < this.len) { var ch = this.source[this.p++]; if (quote) { if (ch === '"') { quote = false; ch = ''; } } else { if (ch === '"') { quote = true; ch = ''; } else if (' \t()|'.indexOf(ch) > -1) { this.p--; return token; } } token += ch; } return token; } } return null; } this.compile = function(searchstr) { var _this = this; this.initLexer(searchstr); function expression(greedy) { var node = null; while (true) { var token = _this.nextToken(); var node2 = null; switch (token) { case null: case ')': return node; case '-': node2 = expression(false); node2 = node2 ? _this.not(node2) : node2; break; case '(': node2 = expression(true); break; case '|': node2 = expression(false); break; default: node2 = _this.term(token); } if (node && node2) { node = token === '|' ? _this.or(node, node2) : _this.and(node, node2); } else if (node2) { node = node2; } if (!greedy && node) { return node; } } } this.root = expression(true); } this.root = null; this.data = null; this.exec = function(data) { this.data = data; return this.root ? this.root.eval() : true; } this.and = function(L, R) { return { L: L, R: R, eval: function() { return this.L.eval() && this.R.eval(); } }; } this.or = function(L, R) { return { L: L, R: R, eval: function() { return this.L.eval() || this.R.eval(); } }; } this.not = function(M) { return { M: M, eval: function() { return !this.M.eval();} }; } this.term = function(term) { return this.compileTerm(term); } var COMMANDS = [ ':', '>=', '<=', '<>', '>', '<', '=' ]; this.compileTerm = function(term) { var _this = this; var text = term.toLowerCase(); var field; var command; var commandIndex; for (var i = 0; i < COMMANDS.length; i++) { var cmd = COMMANDS[i]; var p = term.indexOf(cmd); if (p > -1 && (p < commandIndex || commandIndex === undefined)) { commandIndex = p; command = cmd; } } if (command !== undefined) { field = term.substring(0, commandIndex); text = text.substring(commandIndex + command.length); } return { command: command, text: text, field: field, eval: function() { return _this.evalTerm(this); } }; } this.evalTerm = function(term) { var text = term.text; var field = term.field; var content = this.fieldValue(this.data, field); if (content === undefined) { return false; } switch (term.command) { case undefined: case ':': return content.toString().toLowerCase().indexOf(text) > -1; case '=': return content.toString().toLowerCase() == text; case '<>': return content.toString().toLowerCase() != text; case '>': return parseInt(content) > parseInt(text); case '>=': return parseInt(content) >= parseInt(text); case '<': return parseInt(content) < parseInt(text); case '<=': return parseInt(content) <= parseInt(text); default: return false; } } this.fieldValue = function(data, field) { var value = ''; if (field !== undefined) { value = data[field]; if (value === undefined) { if (this.nameMap === undefined) { this.buildNameMap(data); } value = data[this.nameMap[field.toLowerCase()]]; } } else { if (data._search === true) { for (var prop in data) { value += ' ' + data[prop]; } } else { for (var i = 0; i < data._search.length; i++) { value += ' ' + data[data._search[i]]; } } } return value; } this.nameMap; this.buildNameMap = function(data) { this.nameMap = {}; for (var prop in data) { this.nameMap[prop.toLowerCase()] = prop; } } } nzbget-16.4/webui/lib/0000755000175000017500000000000012655350221014433 5ustar andreasandreasnzbget-16.4/webui/lib/jquery.js0000644000175000017500000075572112630544544016337 0ustar andreasandreas/*! * jQuery JavaScript Library v1.7.2 * http://jquery.com/ * * Copyright 2011, John Resig * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * Includes Sizzle.js * http://sizzlejs.com/ * Copyright 2011, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * * Date: Wed Mar 21 12:46:34 2012 -0700 */ (function( window, undefined ) { // Use the correct document accordingly with window argument (sandbox) var document = window.document, navigator = window.navigator, location = window.location; var jQuery = (function() { // Define a local copy of jQuery var jQuery = function( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced' return new jQuery.fn.init( selector, context, rootjQuery ); }, // Map over jQuery in case of overwrite _jQuery = window.jQuery, // Map over the $ in case of overwrite _$ = window.$, // A central reference to the root jQuery(document) rootjQuery, // A simple way to check for HTML strings or ID strings // Prioritize #id over to avoid XSS via location.hash (#9521) quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, // Check if a string has a non-whitespace character in it rnotwhite = /\S/, // Used for trimming whitespace trimLeft = /^\s+/, trimRight = /\s+$/, // Match a standalone tag rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, // JSON RegExp rvalidchars = /^[\],:{}\s]*$/, rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, // Useragent RegExp rwebkit = /(webkit)[ \/]([\w.]+)/, ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, rmsie = /(msie) ([\w.]+)/, rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, // Matches dashed string for camelizing rdashAlpha = /-([a-z]|[0-9])/ig, rmsPrefix = /^-ms-/, // Used by jQuery.camelCase as callback to replace() fcamelCase = function( all, letter ) { return ( letter + "" ).toUpperCase(); }, // Keep a UserAgent string for use with jQuery.browser userAgent = navigator.userAgent, // For matching the engine and version of the browser browserMatch, // The deferred used on DOM ready readyList, // The ready event handler DOMContentLoaded, // Save a reference to some core methods toString = Object.prototype.toString, hasOwn = Object.prototype.hasOwnProperty, push = Array.prototype.push, slice = Array.prototype.slice, trim = String.prototype.trim, indexOf = Array.prototype.indexOf, // [[Class]] -> type pairs class2type = {}; jQuery.fn = jQuery.prototype = { constructor: jQuery, init: function( selector, context, rootjQuery ) { var match, elem, ret, doc; // Handle $(""), $(null), or $(undefined) if ( !selector ) { return this; } // Handle $(DOMElement) if ( selector.nodeType ) { this.context = this[0] = selector; this.length = 1; return this; } // The body element only exists once, optimize finding it if ( selector === "body" && !context && document.body ) { this.context = document; this[0] = document.body; this.selector = selector; this.length = 1; return this; } // Handle HTML strings if ( typeof selector === "string" ) { // Are we dealing with HTML string or an ID? if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { // Assume that strings that start and end with <> are HTML and skip the regex check match = [ null, selector, null ]; } else { match = quickExpr.exec( selector ); } // Verify a match, and that no context was specified for #id if ( match && (match[1] || !context) ) { // HANDLE: $(html) -> $(array) if ( match[1] ) { context = context instanceof jQuery ? context[0] : context; doc = ( context ? context.ownerDocument || context : document ); // If a single string is passed in and it's a single tag // just do a createElement and skip the rest ret = rsingleTag.exec( selector ); if ( ret ) { if ( jQuery.isPlainObject( context ) ) { selector = [ document.createElement( ret[1] ) ]; jQuery.fn.attr.call( selector, context, true ); } else { selector = [ doc.createElement( ret[1] ) ]; } } else { ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes; } return jQuery.merge( this, selector ); // HANDLE: $("#id") } else { elem = document.getElementById( match[2] ); // Check parentNode to catch when Blackberry 4.6 returns // nodes that are no longer in the document #6963 if ( elem && elem.parentNode ) { // Handle the case where IE and Opera return items // by name instead of ID if ( elem.id !== match[2] ) { return rootjQuery.find( selector ); } // Otherwise, we inject the element directly into the jQuery object this.length = 1; this[0] = elem; } this.context = document; this.selector = selector; return this; } // HANDLE: $(expr, $(...)) } else if ( !context || context.jquery ) { return ( context || rootjQuery ).find( selector ); // HANDLE: $(expr, context) // (which is just equivalent to: $(context).find(expr) } else { return this.constructor( context ).find( selector ); } // HANDLE: $(function) // Shortcut for document ready } else if ( jQuery.isFunction( selector ) ) { return rootjQuery.ready( selector ); } if ( selector.selector !== undefined ) { this.selector = selector.selector; this.context = selector.context; } return jQuery.makeArray( selector, this ); }, // Start with an empty selector selector: "", // The current version of jQuery being used jquery: "1.7.2", // The default length of a jQuery object is 0 length: 0, // The number of elements contained in the matched element set size: function() { return this.length; }, toArray: function() { return slice.call( this, 0 ); }, // Get the Nth element in the matched element set OR // Get the whole matched element set as a clean array get: function( num ) { return num == null ? // Return a 'clean' array this.toArray() : // Return just the object ( num < 0 ? this[ this.length + num ] : this[ num ] ); }, // Take an array of elements and push it onto the stack // (returning the new matched element set) pushStack: function( elems, name, selector ) { // Build a new jQuery matched element set var ret = this.constructor(); if ( jQuery.isArray( elems ) ) { push.apply( ret, elems ); } else { jQuery.merge( ret, elems ); } // Add the old object onto the stack (as a reference) ret.prevObject = this; ret.context = this.context; if ( name === "find" ) { ret.selector = this.selector + ( this.selector ? " " : "" ) + selector; } else if ( name ) { ret.selector = this.selector + "." + name + "(" + selector + ")"; } // Return the newly-formed element set return ret; }, // Execute a callback for every element in the matched set. // (You can seed the arguments with an array of args, but this is // only used internally.) each: function( callback, args ) { return jQuery.each( this, callback, args ); }, ready: function( fn ) { // Attach the listeners jQuery.bindReady(); // Add the callback readyList.add( fn ); return this; }, eq: function( i ) { i = +i; return i === -1 ? this.slice( i ) : this.slice( i, i + 1 ); }, first: function() { return this.eq( 0 ); }, last: function() { return this.eq( -1 ); }, slice: function() { return this.pushStack( slice.apply( this, arguments ), "slice", slice.call(arguments).join(",") ); }, map: function( callback ) { return this.pushStack( jQuery.map(this, function( elem, i ) { return callback.call( elem, i, elem ); })); }, end: function() { return this.prevObject || this.constructor(null); }, // For internal use only. // Behaves like an Array's method, not like a jQuery method. push: push, sort: [].sort, splice: [].splice }; // Give the init function the jQuery prototype for later instantiation jQuery.fn.init.prototype = jQuery.fn; jQuery.extend = jQuery.fn.extend = function() { var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {}, i = 1, length = arguments.length, deep = false; // Handle a deep copy situation if ( typeof target === "boolean" ) { deep = target; target = arguments[1] || {}; // skip the boolean and the target i = 2; } // Handle case when target is a string or something (possible in deep copy) if ( typeof target !== "object" && !jQuery.isFunction(target) ) { target = {}; } // extend jQuery itself if only one argument is passed if ( length === i ) { target = this; --i; } for ( ; i < length; i++ ) { // Only deal with non-null/undefined values if ( (options = arguments[ i ]) != null ) { // Extend the base object for ( name in options ) { src = target[ name ]; copy = options[ name ]; // Prevent never-ending loop if ( target === copy ) { continue; } // Recurse if we're merging plain objects or arrays if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { if ( copyIsArray ) { copyIsArray = false; clone = src && jQuery.isArray(src) ? src : []; } else { clone = src && jQuery.isPlainObject(src) ? src : {}; } // Never move original objects, clone them target[ name ] = jQuery.extend( deep, clone, copy ); // Don't bring in undefined values } else if ( copy !== undefined ) { target[ name ] = copy; } } } } // Return the modified object return target; }; jQuery.extend({ noConflict: function( deep ) { if ( window.$ === jQuery ) { window.$ = _$; } if ( deep && window.jQuery === jQuery ) { window.jQuery = _jQuery; } return jQuery; }, // Is the DOM ready to be used? Set to true once it occurs. isReady: false, // A counter to track how many items to wait for before // the ready event fires. See #6781 readyWait: 1, // Hold (or release) the ready event holdReady: function( hold ) { if ( hold ) { jQuery.readyWait++; } else { jQuery.ready( true ); } }, // Handle when the DOM is ready ready: function( wait ) { // Either a released hold or an DOMready/load event and not yet ready if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) { // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). if ( !document.body ) { return setTimeout( jQuery.ready, 1 ); } // Remember that the DOM is ready jQuery.isReady = true; // If a normal DOM Ready event fired, decrement, and wait if need be if ( wait !== true && --jQuery.readyWait > 0 ) { return; } // If there are functions bound, to execute readyList.fireWith( document, [ jQuery ] ); // Trigger any bound ready events if ( jQuery.fn.trigger ) { jQuery( document ).trigger( "ready" ).off( "ready" ); } } }, bindReady: function() { if ( readyList ) { return; } readyList = jQuery.Callbacks( "once memory" ); // Catch cases where $(document).ready() is called after the // browser event has already occurred. if ( document.readyState === "complete" ) { // Handle it asynchronously to allow scripts the opportunity to delay ready return setTimeout( jQuery.ready, 1 ); } // Mozilla, Opera and webkit nightlies currently support this event if ( document.addEventListener ) { // Use the handy event callback document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); // A fallback to window.onload, that will always work window.addEventListener( "load", jQuery.ready, false ); // If IE event model is used } else if ( document.attachEvent ) { // ensure firing before onload, // maybe late but safe also for iframes document.attachEvent( "onreadystatechange", DOMContentLoaded ); // A fallback to window.onload, that will always work window.attachEvent( "onload", jQuery.ready ); // If IE and not a frame // continually check to see if the document is ready var toplevel = false; try { toplevel = window.frameElement == null; } catch(e) {} if ( document.documentElement.doScroll && toplevel ) { doScrollCheck(); } } }, // See test/unit/core.js for details concerning isFunction. // Since version 1.3, DOM methods and functions like alert // aren't supported. They return false on IE (#2968). isFunction: function( obj ) { return jQuery.type(obj) === "function"; }, isArray: Array.isArray || function( obj ) { return jQuery.type(obj) === "array"; }, isWindow: function( obj ) { return obj != null && obj == obj.window; }, isNumeric: function( obj ) { return !isNaN( parseFloat(obj) ) && isFinite( obj ); }, type: function( obj ) { return obj == null ? String( obj ) : class2type[ toString.call(obj) ] || "object"; }, isPlainObject: function( obj ) { // Must be an Object. // Because of IE, we also have to check the presence of the constructor property. // Make sure that DOM nodes and window objects don't pass through, as well if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { return false; } try { // Not own constructor property must be Object if ( obj.constructor && !hasOwn.call(obj, "constructor") && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { return false; } } catch ( e ) { // IE8,9 Will throw exceptions on certain host objects #9897 return false; } // Own properties are enumerated firstly, so to speed up, // if last one is own, then all properties are own. var key; for ( key in obj ) {} return key === undefined || hasOwn.call( obj, key ); }, isEmptyObject: function( obj ) { for ( var name in obj ) { return false; } return true; }, error: function( msg ) { throw new Error( msg ); }, parseJSON: function( data ) { if ( typeof data !== "string" || !data ) { return null; } // Make sure leading/trailing whitespace is removed (IE can't handle it) data = jQuery.trim( data ); // Attempt to parse using the native JSON parser first if ( window.JSON && window.JSON.parse ) { return window.JSON.parse( data ); } // Make sure the incoming data is actual JSON // Logic borrowed from http://json.org/json2.js if ( rvalidchars.test( data.replace( rvalidescape, "@" ) .replace( rvalidtokens, "]" ) .replace( rvalidbraces, "")) ) { return ( new Function( "return " + data ) )(); } jQuery.error( "Invalid JSON: " + data ); }, // Cross-browser xml parsing parseXML: function( data ) { if ( typeof data !== "string" || !data ) { return null; } var xml, tmp; try { if ( window.DOMParser ) { // Standard tmp = new DOMParser(); xml = tmp.parseFromString( data , "text/xml" ); } else { // IE xml = new ActiveXObject( "Microsoft.XMLDOM" ); xml.async = "false"; xml.loadXML( data ); } } catch( e ) { xml = undefined; } if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { jQuery.error( "Invalid XML: " + data ); } return xml; }, noop: function() {}, // Evaluates a script in a global context // Workarounds based on findings by Jim Driscoll // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context globalEval: function( data ) { if ( data && rnotwhite.test( data ) ) { // We use execScript on Internet Explorer // We use an anonymous function so that context is window // rather than jQuery in Firefox ( window.execScript || function( data ) { window[ "eval" ].call( window, data ); } )( data ); } }, // Convert dashed to camelCase; used by the css and data modules // Microsoft forgot to hump their vendor prefix (#9572) camelCase: function( string ) { return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); }, nodeName: function( elem, name ) { return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); }, // args is for internal usage only each: function( object, callback, args ) { var name, i = 0, length = object.length, isObj = length === undefined || jQuery.isFunction( object ); if ( args ) { if ( isObj ) { for ( name in object ) { if ( callback.apply( object[ name ], args ) === false ) { break; } } } else { for ( ; i < length; ) { if ( callback.apply( object[ i++ ], args ) === false ) { break; } } } // A special, fast, case for the most common use of each } else { if ( isObj ) { for ( name in object ) { if ( callback.call( object[ name ], name, object[ name ] ) === false ) { break; } } } else { for ( ; i < length; ) { if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) { break; } } } } return object; }, // Use native String.trim function wherever possible trim: trim ? function( text ) { return text == null ? "" : trim.call( text ); } : // Otherwise use our own trimming functionality function( text ) { return text == null ? "" : text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); }, // results is for internal usage only makeArray: function( array, results ) { var ret = results || []; if ( array != null ) { // The window, strings (and functions) also have 'length' // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 var type = jQuery.type( array ); if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { push.call( ret, array ); } else { jQuery.merge( ret, array ); } } return ret; }, inArray: function( elem, array, i ) { var len; if ( array ) { if ( indexOf ) { return indexOf.call( array, elem, i ); } len = array.length; i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; for ( ; i < len; i++ ) { // Skip accessing in sparse arrays if ( i in array && array[ i ] === elem ) { return i; } } } return -1; }, merge: function( first, second ) { var i = first.length, j = 0; if ( typeof second.length === "number" ) { for ( var l = second.length; j < l; j++ ) { first[ i++ ] = second[ j ]; } } else { while ( second[j] !== undefined ) { first[ i++ ] = second[ j++ ]; } } first.length = i; return first; }, grep: function( elems, callback, inv ) { var ret = [], retVal; inv = !!inv; // Go through the array, only saving the items // that pass the validator function for ( var i = 0, length = elems.length; i < length; i++ ) { retVal = !!callback( elems[ i ], i ); if ( inv !== retVal ) { ret.push( elems[ i ] ); } } return ret; }, // arg is for internal usage only map: function( elems, callback, arg ) { var value, key, ret = [], i = 0, length = elems.length, // jquery objects are treated as arrays isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ; // Go through the array, translating each of the items to their if ( isArray ) { for ( ; i < length; i++ ) { value = callback( elems[ i ], i, arg ); if ( value != null ) { ret[ ret.length ] = value; } } // Go through every key on the object, } else { for ( key in elems ) { value = callback( elems[ key ], key, arg ); if ( value != null ) { ret[ ret.length ] = value; } } } // Flatten any nested arrays return ret.concat.apply( [], ret ); }, // A global GUID counter for objects guid: 1, // Bind a function to a context, optionally partially applying any // arguments. proxy: function( fn, context ) { if ( typeof context === "string" ) { var tmp = fn[ context ]; context = fn; fn = tmp; } // Quick check to determine if target is callable, in the spec // this throws a TypeError, but we will just return undefined. if ( !jQuery.isFunction( fn ) ) { return undefined; } // Simulated bind var args = slice.call( arguments, 2 ), proxy = function() { return fn.apply( context, args.concat( slice.call( arguments ) ) ); }; // Set the guid of unique handler to the same of original handler, so it can be removed proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; return proxy; }, // Mutifunctional method to get and set values to a collection // The value/s can optionally be executed if it's a function access: function( elems, fn, key, value, chainable, emptyGet, pass ) { var exec, bulk = key == null, i = 0, length = elems.length; // Sets many values if ( key && typeof key === "object" ) { for ( i in key ) { jQuery.access( elems, fn, i, key[i], 1, emptyGet, value ); } chainable = 1; // Sets one value } else if ( value !== undefined ) { // Optionally, function values get executed if exec is true exec = pass === undefined && jQuery.isFunction( value ); if ( bulk ) { // Bulk operations only iterate when executing function values if ( exec ) { exec = fn; fn = function( elem, key, value ) { return exec.call( jQuery( elem ), value ); }; // Otherwise they run against the entire set } else { fn.call( elems, value ); fn = null; } } if ( fn ) { for (; i < length; i++ ) { fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); } } chainable = 1; } return chainable ? elems : // Gets bulk ? fn.call( elems ) : length ? fn( elems[0], key ) : emptyGet; }, now: function() { return ( new Date() ).getTime(); }, // Use of jQuery.browser is frowned upon. // More details: http://docs.jquery.com/Utilities/jQuery.browser uaMatch: function( ua ) { ua = ua.toLowerCase(); var match = rwebkit.exec( ua ) || ropera.exec( ua ) || rmsie.exec( ua ) || ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || []; return { browser: match[1] || "", version: match[2] || "0" }; }, sub: function() { function jQuerySub( selector, context ) { return new jQuerySub.fn.init( selector, context ); } jQuery.extend( true, jQuerySub, this ); jQuerySub.superclass = this; jQuerySub.fn = jQuerySub.prototype = this(); jQuerySub.fn.constructor = jQuerySub; jQuerySub.sub = this.sub; jQuerySub.fn.init = function init( selector, context ) { if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) { context = jQuerySub( context ); } return jQuery.fn.init.call( this, selector, context, rootjQuerySub ); }; jQuerySub.fn.init.prototype = jQuerySub.fn; var rootjQuerySub = jQuerySub(document); return jQuerySub; }, browser: {} }); // Populate the class2type map jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { class2type[ "[object " + name + "]" ] = name.toLowerCase(); }); browserMatch = jQuery.uaMatch( userAgent ); if ( browserMatch.browser ) { jQuery.browser[ browserMatch.browser ] = true; jQuery.browser.version = browserMatch.version; } // Deprecated, use jQuery.browser.webkit instead if ( jQuery.browser.webkit ) { jQuery.browser.safari = true; } // IE doesn't match non-breaking spaces with \s if ( rnotwhite.test( "\xA0" ) ) { trimLeft = /^[\s\xA0]+/; trimRight = /[\s\xA0]+$/; } // All jQuery objects should point back to these rootjQuery = jQuery(document); // Cleanup functions for the document ready method if ( document.addEventListener ) { DOMContentLoaded = function() { document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); jQuery.ready(); }; } else if ( document.attachEvent ) { DOMContentLoaded = function() { // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). if ( document.readyState === "complete" ) { document.detachEvent( "onreadystatechange", DOMContentLoaded ); jQuery.ready(); } }; } // The DOM ready check for Internet Explorer function doScrollCheck() { if ( jQuery.isReady ) { return; } try { // If IE is used, use the trick by Diego Perini // http://javascript.nwbox.com/IEContentLoaded/ document.documentElement.doScroll("left"); } catch(e) { setTimeout( doScrollCheck, 1 ); return; } // and execute any waiting functions jQuery.ready(); } return jQuery; })(); // String to Object flags format cache var flagsCache = {}; // Convert String-formatted flags into Object-formatted ones and store in cache function createFlags( flags ) { var object = flagsCache[ flags ] = {}, i, length; flags = flags.split( /\s+/ ); for ( i = 0, length = flags.length; i < length; i++ ) { object[ flags[i] ] = true; } return object; } /* * Create a callback list using the following parameters: * * flags: an optional list of space-separated flags that will change how * the callback list behaves * * By default a callback list will act like an event callback list and can be * "fired" multiple times. * * Possible flags: * * once: will ensure the callback list can only be fired once (like a Deferred) * * memory: will keep track of previous values and will call any callback added * after the list has been fired right away with the latest "memorized" * values (like a Deferred) * * unique: will ensure a callback can only be added once (no duplicate in the list) * * stopOnFalse: interrupt callings when a callback returns false * */ jQuery.Callbacks = function( flags ) { // Convert flags from String-formatted to Object-formatted // (we check in cache first) flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {}; var // Actual callback list list = [], // Stack of fire calls for repeatable lists stack = [], // Last fire value (for non-forgettable lists) memory, // Flag to know if list was already fired fired, // Flag to know if list is currently firing firing, // First callback to fire (used internally by add and fireWith) firingStart, // End of the loop when firing firingLength, // Index of currently firing callback (modified by remove if needed) firingIndex, // Add one or several callbacks to the list add = function( args ) { var i, length, elem, type, actual; for ( i = 0, length = args.length; i < length; i++ ) { elem = args[ i ]; type = jQuery.type( elem ); if ( type === "array" ) { // Inspect recursively add( elem ); } else if ( type === "function" ) { // Add if not in unique mode and callback is not in if ( !flags.unique || !self.has( elem ) ) { list.push( elem ); } } } }, // Fire callbacks fire = function( context, args ) { args = args || []; memory = !flags.memory || [ context, args ]; fired = true; firing = true; firingIndex = firingStart || 0; firingStart = 0; firingLength = list.length; for ( ; list && firingIndex < firingLength; firingIndex++ ) { if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) { memory = true; // Mark as halted break; } } firing = false; if ( list ) { if ( !flags.once ) { if ( stack && stack.length ) { memory = stack.shift(); self.fireWith( memory[ 0 ], memory[ 1 ] ); } } else if ( memory === true ) { self.disable(); } else { list = []; } } }, // Actual Callbacks object self = { // Add a callback or a collection of callbacks to the list add: function() { if ( list ) { var length = list.length; add( arguments ); // Do we need to add the callbacks to the // current firing batch? if ( firing ) { firingLength = list.length; // With memory, if we're not firing then // we should call right away, unless previous // firing was halted (stopOnFalse) } else if ( memory && memory !== true ) { firingStart = length; fire( memory[ 0 ], memory[ 1 ] ); } } return this; }, // Remove a callback from the list remove: function() { if ( list ) { var args = arguments, argIndex = 0, argLength = args.length; for ( ; argIndex < argLength ; argIndex++ ) { for ( var i = 0; i < list.length; i++ ) { if ( args[ argIndex ] === list[ i ] ) { // Handle firingIndex and firingLength if ( firing ) { if ( i <= firingLength ) { firingLength--; if ( i <= firingIndex ) { firingIndex--; } } } // Remove the element list.splice( i--, 1 ); // If we have some unicity property then // we only need to do this once if ( flags.unique ) { break; } } } } } return this; }, // Control if a given callback is in the list has: function( fn ) { if ( list ) { var i = 0, length = list.length; for ( ; i < length; i++ ) { if ( fn === list[ i ] ) { return true; } } } return false; }, // Remove all callbacks from the list empty: function() { list = []; return this; }, // Have the list do nothing anymore disable: function() { list = stack = memory = undefined; return this; }, // Is it disabled? disabled: function() { return !list; }, // Lock the list in its current state lock: function() { stack = undefined; if ( !memory || memory === true ) { self.disable(); } return this; }, // Is it locked? locked: function() { return !stack; }, // Call all callbacks with the given context and arguments fireWith: function( context, args ) { if ( stack ) { if ( firing ) { if ( !flags.once ) { stack.push( [ context, args ] ); } } else if ( !( flags.once && memory ) ) { fire( context, args ); } } return this; }, // Call all the callbacks with the given arguments fire: function() { self.fireWith( this, arguments ); return this; }, // To know if the callbacks have already been called at least once fired: function() { return !!fired; } }; return self; }; var // Static reference to slice sliceDeferred = [].slice; jQuery.extend({ Deferred: function( func ) { var doneList = jQuery.Callbacks( "once memory" ), failList = jQuery.Callbacks( "once memory" ), progressList = jQuery.Callbacks( "memory" ), state = "pending", lists = { resolve: doneList, reject: failList, notify: progressList }, promise = { done: doneList.add, fail: failList.add, progress: progressList.add, state: function() { return state; }, // Deprecated isResolved: doneList.fired, isRejected: failList.fired, then: function( doneCallbacks, failCallbacks, progressCallbacks ) { deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks ); return this; }, always: function() { deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments ); return this; }, pipe: function( fnDone, fnFail, fnProgress ) { return jQuery.Deferred(function( newDefer ) { jQuery.each( { done: [ fnDone, "resolve" ], fail: [ fnFail, "reject" ], progress: [ fnProgress, "notify" ] }, function( handler, data ) { var fn = data[ 0 ], action = data[ 1 ], returned; if ( jQuery.isFunction( fn ) ) { deferred[ handler ](function() { returned = fn.apply( this, arguments ); if ( returned && jQuery.isFunction( returned.promise ) ) { returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify ); } else { newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] ); } }); } else { deferred[ handler ]( newDefer[ action ] ); } }); }).promise(); }, // Get a promise for this deferred // If obj is provided, the promise aspect is added to the object promise: function( obj ) { if ( obj == null ) { obj = promise; } else { for ( var key in promise ) { obj[ key ] = promise[ key ]; } } return obj; } }, deferred = promise.promise({}), key; for ( key in lists ) { deferred[ key ] = lists[ key ].fire; deferred[ key + "With" ] = lists[ key ].fireWith; } // Handle state deferred.done( function() { state = "resolved"; }, failList.disable, progressList.lock ).fail( function() { state = "rejected"; }, doneList.disable, progressList.lock ); // Call given func if any if ( func ) { func.call( deferred, deferred ); } // All done! return deferred; }, // Deferred helper when: function( firstParam ) { var args = sliceDeferred.call( arguments, 0 ), i = 0, length = args.length, pValues = new Array( length ), count = length, pCount = length, deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? firstParam : jQuery.Deferred(), promise = deferred.promise(); function resolveFunc( i ) { return function( value ) { args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; if ( !( --count ) ) { deferred.resolveWith( deferred, args ); } }; } function progressFunc( i ) { return function( value ) { pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; deferred.notifyWith( promise, pValues ); }; } if ( length > 1 ) { for ( ; i < length; i++ ) { if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) { args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) ); } else { --count; } } if ( !count ) { deferred.resolveWith( deferred, args ); } } else if ( deferred !== firstParam ) { deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); } return promise; } }); jQuery.support = (function() { var support, all, a, select, opt, input, fragment, tds, events, eventName, i, isSupported, div = document.createElement( "div" ), documentElement = document.documentElement; // Preliminary tests div.setAttribute("className", "t"); div.innerHTML = "
a"; all = div.getElementsByTagName( "*" ); a = div.getElementsByTagName( "a" )[ 0 ]; // Can't get basic test support if ( !all || !all.length || !a ) { return {}; } // First batch of supports tests select = document.createElement( "select" ); opt = select.appendChild( document.createElement("option") ); input = div.getElementsByTagName( "input" )[ 0 ]; support = { // IE strips leading whitespace when .innerHTML is used leadingWhitespace: ( div.firstChild.nodeType === 3 ), // Make sure that tbody elements aren't automatically inserted // IE will insert them into empty tables tbody: !div.getElementsByTagName("tbody").length, // Make sure that link elements get serialized correctly by innerHTML // This requires a wrapper element in IE htmlSerialize: !!div.getElementsByTagName("link").length, // Get the style information from getAttribute // (IE uses .cssText instead) style: /top/.test( a.getAttribute("style") ), // Make sure that URLs aren't manipulated // (IE normalizes it by default) hrefNormalized: ( a.getAttribute("href") === "/a" ), // Make sure that element opacity exists // (IE uses filter instead) // Use a regex to work around a WebKit issue. See #5145 opacity: /^0.55/.test( a.style.opacity ), // Verify style float existence // (IE uses styleFloat instead of cssFloat) cssFloat: !!a.style.cssFloat, // Make sure that if no value is specified for a checkbox // that it defaults to "on". // (WebKit defaults to "" instead) checkOn: ( input.value === "on" ), // Make sure that a selected-by-default option has a working selected property. // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) optSelected: opt.selected, // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) getSetAttribute: div.className !== "t", // Tests for enctype support on a form(#6743) enctype: !!document.createElement("form").enctype, // Makes sure cloning an html5 element does not cause problems // Where outerHTML is undefined, this still works html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>", // Will be defined later submitBubbles: true, changeBubbles: true, focusinBubbles: false, deleteExpando: true, noCloneEvent: true, inlineBlockNeedsLayout: false, shrinkWrapBlocks: false, reliableMarginRight: true, pixelMargin: true }; // jQuery.boxModel DEPRECATED in 1.3, use jQuery.support.boxModel instead jQuery.boxModel = support.boxModel = (document.compatMode === "CSS1Compat"); // Make sure checked status is properly cloned input.checked = true; support.noCloneChecked = input.cloneNode( true ).checked; // Make sure that the options inside disabled selects aren't marked as disabled // (WebKit marks them as disabled) select.disabled = true; support.optDisabled = !opt.disabled; // Test to see if it's possible to delete an expando from an element // Fails in Internet Explorer try { delete div.test; } catch( e ) { support.deleteExpando = false; } if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { div.attachEvent( "onclick", function() { // Cloning a node shouldn't copy over any // bound event handlers (IE does this) support.noCloneEvent = false; }); div.cloneNode( true ).fireEvent( "onclick" ); } // Check if a radio maintains its value // after being appended to the DOM input = document.createElement("input"); input.value = "t"; input.setAttribute("type", "radio"); support.radioValue = input.value === "t"; input.setAttribute("checked", "checked"); // #11217 - WebKit loses check when the name is after the checked attribute input.setAttribute( "name", "t" ); div.appendChild( input ); fragment = document.createDocumentFragment(); fragment.appendChild( div.lastChild ); // WebKit doesn't clone checked state correctly in fragments support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; // Check if a disconnected checkbox will retain its checked // value of true after appended to the DOM (IE6/7) support.appendChecked = input.checked; fragment.removeChild( input ); fragment.appendChild( div ); // Technique from Juriy Zaytsev // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/ // We only care about the case where non-standard event systems // are used, namely in IE. Short-circuiting here helps us to // avoid an eval call (in setAttribute) which can cause CSP // to go haywire. See: https://developer.mozilla.org/en/Security/CSP if ( div.attachEvent ) { for ( i in { submit: 1, change: 1, focusin: 1 }) { eventName = "on" + i; isSupported = ( eventName in div ); if ( !isSupported ) { div.setAttribute( eventName, "return;" ); isSupported = ( typeof div[ eventName ] === "function" ); } support[ i + "Bubbles" ] = isSupported; } } fragment.removeChild( div ); // Null elements to avoid leaks in IE fragment = select = opt = div = input = null; // Run tests that need a body at doc ready jQuery(function() { var container, outer, inner, table, td, offsetSupport, marginDiv, conMarginTop, style, html, positionTopLeftWidthHeight, paddingMarginBorderVisibility, paddingMarginBorder, body = document.getElementsByTagName("body")[0]; if ( !body ) { // Return for frameset docs that don't have a body return; } conMarginTop = 1; paddingMarginBorder = "padding:0;margin:0;border:"; positionTopLeftWidthHeight = "position:absolute;top:0;left:0;width:1px;height:1px;"; paddingMarginBorderVisibility = paddingMarginBorder + "0;visibility:hidden;"; style = "style='" + positionTopLeftWidthHeight + paddingMarginBorder + "5px solid #000;"; html = "
" + "" + "
"; container = document.createElement("div"); container.style.cssText = paddingMarginBorderVisibility + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px"; body.insertBefore( container, body.firstChild ); // Construct the test element div = document.createElement("div"); container.appendChild( div ); // Check if table cells still have offsetWidth/Height when they are set // to display:none and there are still other visible table cells in a // table row; if so, offsetWidth/Height are not reliable for use when // determining if an element has been hidden directly using // display:none (it is still safe to use offsets if a parent element is // hidden; don safety goggles and see bug #4512 for more information). // (only IE 8 fails this test) div.innerHTML = "
t
"; tds = div.getElementsByTagName( "td" ); isSupported = ( tds[ 0 ].offsetHeight === 0 ); tds[ 0 ].style.display = ""; tds[ 1 ].style.display = "none"; // Check if empty table cells still have offsetWidth/Height // (IE <= 8 fail this test) support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); // Check if div with explicit width and no margin-right incorrectly // gets computed margin-right based on width of container. For more // info see bug #3333 // Fails in WebKit before Feb 2011 nightlies // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right if ( window.getComputedStyle ) { div.innerHTML = ""; marginDiv = document.createElement( "div" ); marginDiv.style.width = "0"; marginDiv.style.marginRight = "0"; div.style.width = "2px"; div.appendChild( marginDiv ); support.reliableMarginRight = ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0; } if ( typeof div.style.zoom !== "undefined" ) { // Check if natively block-level elements act like inline-block // elements when setting their display to 'inline' and giving // them layout // (IE < 8 does this) div.innerHTML = ""; div.style.width = div.style.padding = "1px"; div.style.border = 0; div.style.overflow = "hidden"; div.style.display = "inline"; div.style.zoom = 1; support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 ); // Check if elements with layout shrink-wrap their children // (IE 6 does this) div.style.display = "block"; div.style.overflow = "visible"; div.innerHTML = "
"; support.shrinkWrapBlocks = ( div.offsetWidth !== 3 ); } div.style.cssText = positionTopLeftWidthHeight + paddingMarginBorderVisibility; div.innerHTML = html; outer = div.firstChild; inner = outer.firstChild; td = outer.nextSibling.firstChild.firstChild; offsetSupport = { doesNotAddBorder: ( inner.offsetTop !== 5 ), doesAddBorderForTableAndCells: ( td.offsetTop === 5 ) }; inner.style.position = "fixed"; inner.style.top = "20px"; // safari subtracts parent border width here which is 5px offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 ); inner.style.position = inner.style.top = ""; outer.style.overflow = "hidden"; outer.style.position = "relative"; offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 ); offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop ); if ( window.getComputedStyle ) { div.style.marginTop = "1%"; support.pixelMargin = ( window.getComputedStyle( div, null ) || { marginTop: 0 } ).marginTop !== "1%"; } if ( typeof container.style.zoom !== "undefined" ) { container.style.zoom = 1; } body.removeChild( container ); marginDiv = div = container = null; jQuery.extend( support, offsetSupport ); }); return support; })(); var rbrace = /^(?:\{.*\}|\[.*\])$/, rmultiDash = /([A-Z])/g; jQuery.extend({ cache: {}, // Please use with caution uuid: 0, // Unique for each copy of jQuery on the page // Non-digits removed to match rinlinejQuery expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), // The following elements throw uncatchable exceptions if you // attempt to add expando properties to them. noData: { "embed": true, // Ban all objects except for Flash (which handle expandos) "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", "applet": true }, hasData: function( elem ) { elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; return !!elem && !isEmptyDataObject( elem ); }, data: function( elem, name, data, pvt /* Internal Use Only */ ) { if ( !jQuery.acceptData( elem ) ) { return; } var privateCache, thisCache, ret, internalKey = jQuery.expando, getByName = typeof name === "string", // We have to handle DOM nodes and JS objects differently because IE6-7 // can't GC object references properly across the DOM-JS boundary isNode = elem.nodeType, // Only DOM nodes need the global jQuery cache; JS object data is // attached directly to the object so GC can occur automatically cache = isNode ? jQuery.cache : elem, // Only defining an ID for JS objects if its cache already exists allows // the code to shortcut on the same path as a DOM node with no cache id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey, isEvents = name === "events"; // Avoid doing any more work than we need to when trying to get data on an // object that has no data at all if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) { return; } if ( !id ) { // Only DOM nodes need a new unique ID for each element since their data // ends up in the global cache if ( isNode ) { elem[ internalKey ] = id = ++jQuery.uuid; } else { id = internalKey; } } if ( !cache[ id ] ) { cache[ id ] = {}; // Avoids exposing jQuery metadata on plain JS objects when the object // is serialized using JSON.stringify if ( !isNode ) { cache[ id ].toJSON = jQuery.noop; } } // An object can be passed to jQuery.data instead of a key/value pair; this gets // shallow copied over onto the existing cache if ( typeof name === "object" || typeof name === "function" ) { if ( pvt ) { cache[ id ] = jQuery.extend( cache[ id ], name ); } else { cache[ id ].data = jQuery.extend( cache[ id ].data, name ); } } privateCache = thisCache = cache[ id ]; // jQuery data() is stored in a separate object inside the object's internal data // cache in order to avoid key collisions between internal data and user-defined // data. if ( !pvt ) { if ( !thisCache.data ) { thisCache.data = {}; } thisCache = thisCache.data; } if ( data !== undefined ) { thisCache[ jQuery.camelCase( name ) ] = data; } // Users should not attempt to inspect the internal events object using jQuery.data, // it is undocumented and subject to change. But does anyone listen? No. if ( isEvents && !thisCache[ name ] ) { return privateCache.events; } // Check for both converted-to-camel and non-converted data property names // If a data property was specified if ( getByName ) { // First Try to find as-is property data ret = thisCache[ name ]; // Test for null|undefined property data if ( ret == null ) { // Try to find the camelCased property ret = thisCache[ jQuery.camelCase( name ) ]; } } else { ret = thisCache; } return ret; }, removeData: function( elem, name, pvt /* Internal Use Only */ ) { if ( !jQuery.acceptData( elem ) ) { return; } var thisCache, i, l, // Reference to internal data cache key internalKey = jQuery.expando, isNode = elem.nodeType, // See jQuery.data for more information cache = isNode ? jQuery.cache : elem, // See jQuery.data for more information id = isNode ? elem[ internalKey ] : internalKey; // If there is already no cache entry for this object, there is no // purpose in continuing if ( !cache[ id ] ) { return; } if ( name ) { thisCache = pvt ? cache[ id ] : cache[ id ].data; if ( thisCache ) { // Support array or space separated string names for data keys if ( !jQuery.isArray( name ) ) { // try the string as a key before any manipulation if ( name in thisCache ) { name = [ name ]; } else { // split the camel cased version by spaces unless a key with the spaces exists name = jQuery.camelCase( name ); if ( name in thisCache ) { name = [ name ]; } else { name = name.split( " " ); } } } for ( i = 0, l = name.length; i < l; i++ ) { delete thisCache[ name[i] ]; } // If there is no data left in the cache, we want to continue // and let the cache object itself get destroyed if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { return; } } } // See jQuery.data for more information if ( !pvt ) { delete cache[ id ].data; // Don't destroy the parent cache unless the internal data object // had been the only thing left in it if ( !isEmptyDataObject(cache[ id ]) ) { return; } } // Browsers that fail expando deletion also refuse to delete expandos on // the window, but it will allow it on all other JS objects; other browsers // don't care // Ensure that `cache` is not a window object #10080 if ( jQuery.support.deleteExpando || !cache.setInterval ) { delete cache[ id ]; } else { cache[ id ] = null; } // We destroyed the cache and need to eliminate the expando on the node to avoid // false lookups in the cache for entries that no longer exist if ( isNode ) { // IE does not allow us to delete expando properties from nodes, // nor does it have a removeAttribute function on Document nodes; // we must handle all of these cases if ( jQuery.support.deleteExpando ) { delete elem[ internalKey ]; } else if ( elem.removeAttribute ) { elem.removeAttribute( internalKey ); } else { elem[ internalKey ] = null; } } }, // For internal use only. _data: function( elem, name, data ) { return jQuery.data( elem, name, data, true ); }, // A method for determining if a DOM node can handle the data expando acceptData: function( elem ) { if ( elem.nodeName ) { var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; if ( match ) { return !(match === true || elem.getAttribute("classid") !== match); } } return true; } }); jQuery.fn.extend({ data: function( key, value ) { var parts, part, attr, name, l, elem = this[0], i = 0, data = null; // Gets all values if ( key === undefined ) { if ( this.length ) { data = jQuery.data( elem ); if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { attr = elem.attributes; for ( l = attr.length; i < l; i++ ) { name = attr[i].name; if ( name.indexOf( "data-" ) === 0 ) { name = jQuery.camelCase( name.substring(5) ); dataAttr( elem, name, data[ name ] ); } } jQuery._data( elem, "parsedAttrs", true ); } } return data; } // Sets multiple values if ( typeof key === "object" ) { return this.each(function() { jQuery.data( this, key ); }); } parts = key.split( ".", 2 ); parts[1] = parts[1] ? "." + parts[1] : ""; part = parts[1] + "!"; return jQuery.access( this, function( value ) { if ( value === undefined ) { data = this.triggerHandler( "getData" + part, [ parts[0] ] ); // Try to fetch any internally stored data first if ( data === undefined && elem ) { data = jQuery.data( elem, key ); data = dataAttr( elem, key, data ); } return data === undefined && parts[1] ? this.data( parts[0] ) : data; } parts[1] = value; this.each(function() { var self = jQuery( this ); self.triggerHandler( "setData" + part, parts ); jQuery.data( this, key, value ); self.triggerHandler( "changeData" + part, parts ); }); }, null, value, arguments.length > 1, null, false ); }, removeData: function( key ) { return this.each(function() { jQuery.removeData( this, key ); }); } }); function dataAttr( elem, key, data ) { // If nothing was found internally, try to fetch any // data from the HTML5 data-* attribute if ( data === undefined && elem.nodeType === 1 ) { var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); data = elem.getAttribute( name ); if ( typeof data === "string" ) { try { data = data === "true" ? true : data === "false" ? false : data === "null" ? null : jQuery.isNumeric( data ) ? +data : rbrace.test( data ) ? jQuery.parseJSON( data ) : data; } catch( e ) {} // Make sure we set the data so it isn't changed later jQuery.data( elem, key, data ); } else { data = undefined; } } return data; } // checks a cache object for emptiness function isEmptyDataObject( obj ) { for ( var name in obj ) { // if the public data object is empty, the private is still empty if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { continue; } if ( name !== "toJSON" ) { return false; } } return true; } function handleQueueMarkDefer( elem, type, src ) { var deferDataKey = type + "defer", queueDataKey = type + "queue", markDataKey = type + "mark", defer = jQuery._data( elem, deferDataKey ); if ( defer && ( src === "queue" || !jQuery._data(elem, queueDataKey) ) && ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) { // Give room for hard-coded callbacks to fire first // and eventually mark/queue something else on the element setTimeout( function() { if ( !jQuery._data( elem, queueDataKey ) && !jQuery._data( elem, markDataKey ) ) { jQuery.removeData( elem, deferDataKey, true ); defer.fire(); } }, 0 ); } } jQuery.extend({ _mark: function( elem, type ) { if ( elem ) { type = ( type || "fx" ) + "mark"; jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 ); } }, _unmark: function( force, elem, type ) { if ( force !== true ) { type = elem; elem = force; force = false; } if ( elem ) { type = type || "fx"; var key = type + "mark", count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 ); if ( count ) { jQuery._data( elem, key, count ); } else { jQuery.removeData( elem, key, true ); handleQueueMarkDefer( elem, type, "mark" ); } } }, queue: function( elem, type, data ) { var q; if ( elem ) { type = ( type || "fx" ) + "queue"; q = jQuery._data( elem, type ); // Speed up dequeue by getting out quickly if this is just a lookup if ( data ) { if ( !q || jQuery.isArray(data) ) { q = jQuery._data( elem, type, jQuery.makeArray(data) ); } else { q.push( data ); } } return q || []; } }, dequeue: function( elem, type ) { type = type || "fx"; var queue = jQuery.queue( elem, type ), fn = queue.shift(), hooks = {}; // If the fx queue is dequeued, always remove the progress sentinel if ( fn === "inprogress" ) { fn = queue.shift(); } if ( fn ) { // Add a progress sentinel to prevent the fx queue from being // automatically dequeued if ( type === "fx" ) { queue.unshift( "inprogress" ); } jQuery._data( elem, type + ".run", hooks ); fn.call( elem, function() { jQuery.dequeue( elem, type ); }, hooks ); } if ( !queue.length ) { jQuery.removeData( elem, type + "queue " + type + ".run", true ); handleQueueMarkDefer( elem, type, "queue" ); } } }); jQuery.fn.extend({ queue: function( type, data ) { var setter = 2; if ( typeof type !== "string" ) { data = type; type = "fx"; setter--; } if ( arguments.length < setter ) { return jQuery.queue( this[0], type ); } return data === undefined ? this : this.each(function() { var queue = jQuery.queue( this, type, data ); if ( type === "fx" && queue[0] !== "inprogress" ) { jQuery.dequeue( this, type ); } }); }, dequeue: function( type ) { return this.each(function() { jQuery.dequeue( this, type ); }); }, // Based off of the plugin by Clint Helfers, with permission. // http://blindsignals.com/index.php/2009/07/jquery-delay/ delay: function( time, type ) { time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; type = type || "fx"; return this.queue( type, function( next, hooks ) { var timeout = setTimeout( next, time ); hooks.stop = function() { clearTimeout( timeout ); }; }); }, clearQueue: function( type ) { return this.queue( type || "fx", [] ); }, // Get a promise resolved when queues of a certain type // are emptied (fx is the type by default) promise: function( type, object ) { if ( typeof type !== "string" ) { object = type; type = undefined; } type = type || "fx"; var defer = jQuery.Deferred(), elements = this, i = elements.length, count = 1, deferDataKey = type + "defer", queueDataKey = type + "queue", markDataKey = type + "mark", tmp; function resolve() { if ( !( --count ) ) { defer.resolveWith( elements, [ elements ] ); } } while( i-- ) { if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) || ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) || jQuery.data( elements[ i ], markDataKey, undefined, true ) ) && jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) { count++; tmp.add( resolve ); } } resolve(); return defer.promise( object ); } }); var rclass = /[\n\t\r]/g, rspace = /\s+/, rreturn = /\r/g, rtype = /^(?:button|input)$/i, rfocusable = /^(?:button|input|object|select|textarea)$/i, rclickable = /^a(?:rea)?$/i, rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, getSetAttribute = jQuery.support.getSetAttribute, nodeHook, boolHook, fixSpecified; jQuery.fn.extend({ attr: function( name, value ) { return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 ); }, removeAttr: function( name ) { return this.each(function() { jQuery.removeAttr( this, name ); }); }, prop: function( name, value ) { return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 ); }, removeProp: function( name ) { name = jQuery.propFix[ name ] || name; return this.each(function() { // try/catch handles cases where IE balks (such as removing a property on window) try { this[ name ] = undefined; delete this[ name ]; } catch( e ) {} }); }, addClass: function( value ) { var classNames, i, l, elem, setClass, c, cl; if ( jQuery.isFunction( value ) ) { return this.each(function( j ) { jQuery( this ).addClass( value.call(this, j, this.className) ); }); } if ( value && typeof value === "string" ) { classNames = value.split( rspace ); for ( i = 0, l = this.length; i < l; i++ ) { elem = this[ i ]; if ( elem.nodeType === 1 ) { if ( !elem.className && classNames.length === 1 ) { elem.className = value; } else { setClass = " " + elem.className + " "; for ( c = 0, cl = classNames.length; c < cl; c++ ) { if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) { setClass += classNames[ c ] + " "; } } elem.className = jQuery.trim( setClass ); } } } } return this; }, removeClass: function( value ) { var classNames, i, l, elem, className, c, cl; if ( jQuery.isFunction( value ) ) { return this.each(function( j ) { jQuery( this ).removeClass( value.call(this, j, this.className) ); }); } if ( (value && typeof value === "string") || value === undefined ) { classNames = ( value || "" ).split( rspace ); for ( i = 0, l = this.length; i < l; i++ ) { elem = this[ i ]; if ( elem.nodeType === 1 && elem.className ) { if ( value ) { className = (" " + elem.className + " ").replace( rclass, " " ); for ( c = 0, cl = classNames.length; c < cl; c++ ) { className = className.replace(" " + classNames[ c ] + " ", " "); } elem.className = jQuery.trim( className ); } else { elem.className = ""; } } } } return this; }, toggleClass: function( value, stateVal ) { var type = typeof value, isBool = typeof stateVal === "boolean"; if ( jQuery.isFunction( value ) ) { return this.each(function( i ) { jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); }); } return this.each(function() { if ( type === "string" ) { // toggle individual class names var className, i = 0, self = jQuery( this ), state = stateVal, classNames = value.split( rspace ); while ( (className = classNames[ i++ ]) ) { // check each className given, space seperated list state = isBool ? state : !self.hasClass( className ); self[ state ? "addClass" : "removeClass" ]( className ); } } else if ( type === "undefined" || type === "boolean" ) { if ( this.className ) { // store className if set jQuery._data( this, "__className__", this.className ); } // toggle whole className this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; } }); }, hasClass: function( selector ) { var className = " " + selector + " ", i = 0, l = this.length; for ( ; i < l; i++ ) { if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { return true; } } return false; }, val: function( value ) { var hooks, ret, isFunction, elem = this[0]; if ( !arguments.length ) { if ( elem ) { hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { return ret; } ret = elem.value; return typeof ret === "string" ? // handle most common string cases ret.replace(rreturn, "") : // handle cases where value is null/undef or number ret == null ? "" : ret; } return; } isFunction = jQuery.isFunction( value ); return this.each(function( i ) { var self = jQuery(this), val; if ( this.nodeType !== 1 ) { return; } if ( isFunction ) { val = value.call( this, i, self.val() ); } else { val = value; } // Treat null/undefined as ""; convert numbers to string if ( val == null ) { val = ""; } else if ( typeof val === "number" ) { val += ""; } else if ( jQuery.isArray( val ) ) { val = jQuery.map(val, function ( value ) { return value == null ? "" : value + ""; }); } hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; // If set returns undefined, fall back to normal setting if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { this.value = val; } }); } }); jQuery.extend({ valHooks: { option: { get: function( elem ) { // attributes.value is undefined in Blackberry 4.7 but // uses .value. See #6932 var val = elem.attributes.value; return !val || val.specified ? elem.value : elem.text; } }, select: { get: function( elem ) { var value, i, max, option, index = elem.selectedIndex, values = [], options = elem.options, one = elem.type === "select-one"; // Nothing was selected if ( index < 0 ) { return null; } // Loop through all the selected options i = one ? index : 0; max = one ? index + 1 : options.length; for ( ; i < max; i++ ) { option = options[ i ]; // Don't return options that are disabled or in a disabled optgroup if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { // Get the specific value for the option value = jQuery( option ).val(); // We don't need an array for one selects if ( one ) { return value; } // Multi-Selects return an array values.push( value ); } } // Fixes Bug #2551 -- select.val() broken in IE after form.reset() if ( one && !values.length && options.length ) { return jQuery( options[ index ] ).val(); } return values; }, set: function( elem, value ) { var values = jQuery.makeArray( value ); jQuery(elem).find("option").each(function() { this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; }); if ( !values.length ) { elem.selectedIndex = -1; } return values; } } }, attrFn: { val: true, css: true, html: true, text: true, data: true, width: true, height: true, offset: true }, attr: function( elem, name, value, pass ) { var ret, hooks, notxml, nType = elem.nodeType; // don't get/set attributes on text, comment and attribute nodes if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { return; } if ( pass && name in jQuery.attrFn ) { return jQuery( elem )[ name ]( value ); } // Fallback to prop when attributes are not supported if ( typeof elem.getAttribute === "undefined" ) { return jQuery.prop( elem, name, value ); } notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); // All attributes are lowercase // Grab necessary hook if one is defined if ( notxml ) { name = name.toLowerCase(); hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); } if ( value !== undefined ) { if ( value === null ) { jQuery.removeAttr( elem, name ); return; } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) { return ret; } else { elem.setAttribute( name, "" + value ); return value; } } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) { return ret; } else { ret = elem.getAttribute( name ); // Non-existent attributes return null, we normalize to undefined return ret === null ? undefined : ret; } }, removeAttr: function( elem, value ) { var propName, attrNames, name, l, isBool, i = 0; if ( value && elem.nodeType === 1 ) { attrNames = value.toLowerCase().split( rspace ); l = attrNames.length; for ( ; i < l; i++ ) { name = attrNames[ i ]; if ( name ) { propName = jQuery.propFix[ name ] || name; isBool = rboolean.test( name ); // See #9699 for explanation of this approach (setting first, then removal) // Do not do this for boolean attributes (see #10870) if ( !isBool ) { jQuery.attr( elem, name, "" ); } elem.removeAttribute( getSetAttribute ? name : propName ); // Set corresponding property to false for boolean attributes if ( isBool && propName in elem ) { elem[ propName ] = false; } } } } }, attrHooks: { type: { set: function( elem, value ) { // We can't allow the type property to be changed (since it causes problems in IE) if ( rtype.test( elem.nodeName ) && elem.parentNode ) { jQuery.error( "type property can't be changed" ); } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { // Setting the type on a radio button after the value resets the value in IE6-9 // Reset value to it's default in case type is set after value // This is for element creation var val = elem.value; elem.setAttribute( "type", value ); if ( val ) { elem.value = val; } return value; } } }, // Use the value property for back compat // Use the nodeHook for button elements in IE6/7 (#1954) value: { get: function( elem, name ) { if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { return nodeHook.get( elem, name ); } return name in elem ? elem.value : null; }, set: function( elem, value, name ) { if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { return nodeHook.set( elem, value, name ); } // Does not return so that setAttribute is also used elem.value = value; } } }, propFix: { tabindex: "tabIndex", readonly: "readOnly", "for": "htmlFor", "class": "className", maxlength: "maxLength", cellspacing: "cellSpacing", cellpadding: "cellPadding", rowspan: "rowSpan", colspan: "colSpan", usemap: "useMap", frameborder: "frameBorder", contenteditable: "contentEditable" }, prop: function( elem, name, value ) { var ret, hooks, notxml, nType = elem.nodeType; // don't get/set properties on text, comment and attribute nodes if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { return; } notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); if ( notxml ) { // Fix name and attach hooks name = jQuery.propFix[ name ] || name; hooks = jQuery.propHooks[ name ]; } if ( value !== undefined ) { if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { return ret; } else { return ( elem[ name ] = value ); } } else { if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { return ret; } else { return elem[ name ]; } } }, propHooks: { tabIndex: { get: function( elem ) { // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ var attributeNode = elem.getAttributeNode("tabindex"); return attributeNode && attributeNode.specified ? parseInt( attributeNode.value, 10 ) : rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? 0 : undefined; } } } }); // Add the tabIndex propHook to attrHooks for back-compat (different case is intentional) jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex; // Hook for boolean attributes boolHook = { get: function( elem, name ) { // Align boolean attributes with corresponding properties // Fall back to attribute presence where some booleans are not supported var attrNode, property = jQuery.prop( elem, name ); return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ? name.toLowerCase() : undefined; }, set: function( elem, value, name ) { var propName; if ( value === false ) { // Remove boolean attributes when set to false jQuery.removeAttr( elem, name ); } else { // value is true since we know at this point it's type boolean and not false // Set boolean attributes to the same name and set the DOM property propName = jQuery.propFix[ name ] || name; if ( propName in elem ) { // Only set the IDL specifically if it already exists on the element elem[ propName ] = true; } elem.setAttribute( name, name.toLowerCase() ); } return name; } }; // IE6/7 do not support getting/setting some attributes with get/setAttribute if ( !getSetAttribute ) { fixSpecified = { name: true, id: true, coords: true }; // Use this for any attribute in IE6/7 // This fixes almost every IE6/7 issue nodeHook = jQuery.valHooks.button = { get: function( elem, name ) { var ret; ret = elem.getAttributeNode( name ); return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ? ret.nodeValue : undefined; }, set: function( elem, value, name ) { // Set the existing or create a new attribute node var ret = elem.getAttributeNode( name ); if ( !ret ) { ret = document.createAttribute( name ); elem.setAttributeNode( ret ); } return ( ret.nodeValue = value + "" ); } }; // Apply the nodeHook to tabindex jQuery.attrHooks.tabindex.set = nodeHook.set; // Set width and height to auto instead of 0 on empty string( Bug #8150 ) // This is for removals jQuery.each([ "width", "height" ], function( i, name ) { jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { set: function( elem, value ) { if ( value === "" ) { elem.setAttribute( name, "auto" ); return value; } } }); }); // Set contenteditable to false on removals(#10429) // Setting to empty string throws an error as an invalid value jQuery.attrHooks.contenteditable = { get: nodeHook.get, set: function( elem, value, name ) { if ( value === "" ) { value = "false"; } nodeHook.set( elem, value, name ); } }; } // Some attributes require a special call on IE if ( !jQuery.support.hrefNormalized ) { jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { get: function( elem ) { var ret = elem.getAttribute( name, 2 ); return ret === null ? undefined : ret; } }); }); } if ( !jQuery.support.style ) { jQuery.attrHooks.style = { get: function( elem ) { // Return undefined in the case of empty string // Normalize to lowercase since IE uppercases css property names return elem.style.cssText.toLowerCase() || undefined; }, set: function( elem, value ) { return ( elem.style.cssText = "" + value ); } }; } // Safari mis-reports the default selected property of an option // Accessing the parent's selectedIndex property fixes it if ( !jQuery.support.optSelected ) { jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { get: function( elem ) { var parent = elem.parentNode; if ( parent ) { parent.selectedIndex; // Make sure that it also works with optgroups, see #5701 if ( parent.parentNode ) { parent.parentNode.selectedIndex; } } return null; } }); } // IE6/7 call enctype encoding if ( !jQuery.support.enctype ) { jQuery.propFix.enctype = "encoding"; } // Radios and checkboxes getter/setter if ( !jQuery.support.checkOn ) { jQuery.each([ "radio", "checkbox" ], function() { jQuery.valHooks[ this ] = { get: function( elem ) { // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified return elem.getAttribute("value") === null ? "on" : elem.value; } }; }); } jQuery.each([ "radio", "checkbox" ], function() { jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { set: function( elem, value ) { if ( jQuery.isArray( value ) ) { return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); } } }); }); var rformElems = /^(?:textarea|input|select)$/i, rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/, rhoverHack = /(?:^|\s)hover(\.\S+)?\b/, rkeyEvent = /^key/, rmouseEvent = /^(?:mouse|contextmenu)|click/, rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/, quickParse = function( selector ) { var quick = rquickIs.exec( selector ); if ( quick ) { // 0 1 2 3 // [ _, tag, id, class ] quick[1] = ( quick[1] || "" ).toLowerCase(); quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" ); } return quick; }, quickIs = function( elem, m ) { var attrs = elem.attributes || {}; return ( (!m[1] || elem.nodeName.toLowerCase() === m[1]) && (!m[2] || (attrs.id || {}).value === m[2]) && (!m[3] || m[3].test( (attrs[ "class" ] || {}).value )) ); }, hoverHack = function( events ) { return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" ); }; /* * Helper functions for managing events -- not part of the public interface. * Props to Dean Edwards' addEvent library for many of the ideas. */ jQuery.event = { add: function( elem, types, handler, data, selector ) { var elemData, eventHandle, events, t, tns, type, namespaces, handleObj, handleObjIn, quick, handlers, special; // Don't attach events to noData or text/comment nodes (allow plain objects tho) if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) { return; } // Caller can pass in an object of custom data in lieu of the handler if ( handler.handler ) { handleObjIn = handler; handler = handleObjIn.handler; selector = handleObjIn.selector; } // Make sure that the handler has a unique ID, used to find/remove it later if ( !handler.guid ) { handler.guid = jQuery.guid++; } // Init the element's event structure and main handler, if this is the first events = elemData.events; if ( !events ) { elemData.events = events = {}; } eventHandle = elemData.handle; if ( !eventHandle ) { elemData.handle = eventHandle = function( e ) { // Discard the second event of a jQuery.event.trigger() and // when an event is called after a page has unloaded return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : undefined; }; // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events eventHandle.elem = elem; } // Handle multiple events separated by a space // jQuery(...).bind("mouseover mouseout", fn); types = jQuery.trim( hoverHack(types) ).split( " " ); for ( t = 0; t < types.length; t++ ) { tns = rtypenamespace.exec( types[t] ) || []; type = tns[1]; namespaces = ( tns[2] || "" ).split( "." ).sort(); // If event changes its type, use the special event handlers for the changed type special = jQuery.event.special[ type ] || {}; // If selector defined, determine special event api type, otherwise given type type = ( selector ? special.delegateType : special.bindType ) || type; // Update special based on newly reset type special = jQuery.event.special[ type ] || {}; // handleObj is passed to all event handlers handleObj = jQuery.extend({ type: type, origType: tns[1], data: data, handler: handler, guid: handler.guid, selector: selector, quick: selector && quickParse( selector ), namespace: namespaces.join(".") }, handleObjIn ); // Init the event handler queue if we're the first handlers = events[ type ]; if ( !handlers ) { handlers = events[ type ] = []; handlers.delegateCount = 0; // Only use addEventListener/attachEvent if the special events handler returns false if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { // Bind the global event handler to the element if ( elem.addEventListener ) { elem.addEventListener( type, eventHandle, false ); } else if ( elem.attachEvent ) { elem.attachEvent( "on" + type, eventHandle ); } } } if ( special.add ) { special.add.call( elem, handleObj ); if ( !handleObj.handler.guid ) { handleObj.handler.guid = handler.guid; } } // Add to the element's handler list, delegates in front if ( selector ) { handlers.splice( handlers.delegateCount++, 0, handleObj ); } else { handlers.push( handleObj ); } // Keep track of which events have ever been used, for event optimization jQuery.event.global[ type ] = true; } // Nullify elem to prevent memory leaks in IE elem = null; }, global: {}, // Detach an event or set of events from an element remove: function( elem, types, handler, selector, mappedTypes ) { var elemData = jQuery.hasData( elem ) && jQuery._data( elem ), t, tns, type, origType, namespaces, origCount, j, events, special, handle, eventType, handleObj; if ( !elemData || !(events = elemData.events) ) { return; } // Once for each type.namespace in types; type may be omitted types = jQuery.trim( hoverHack( types || "" ) ).split(" "); for ( t = 0; t < types.length; t++ ) { tns = rtypenamespace.exec( types[t] ) || []; type = origType = tns[1]; namespaces = tns[2]; // Unbind all events (on this namespace, if provided) for the element if ( !type ) { for ( type in events ) { jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); } continue; } special = jQuery.event.special[ type ] || {}; type = ( selector? special.delegateType : special.bindType ) || type; eventType = events[ type ] || []; origCount = eventType.length; namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null; // Remove matching events for ( j = 0; j < eventType.length; j++ ) { handleObj = eventType[ j ]; if ( ( mappedTypes || origType === handleObj.origType ) && ( !handler || handler.guid === handleObj.guid ) && ( !namespaces || namespaces.test( handleObj.namespace ) ) && ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { eventType.splice( j--, 1 ); if ( handleObj.selector ) { eventType.delegateCount--; } if ( special.remove ) { special.remove.call( elem, handleObj ); } } } // Remove generic event handler if we removed something and no more handlers exist // (avoids potential for endless recursion during removal of special event handlers) if ( eventType.length === 0 && origCount !== eventType.length ) { if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { jQuery.removeEvent( elem, type, elemData.handle ); } delete events[ type ]; } } // Remove the expando if it's no longer used if ( jQuery.isEmptyObject( events ) ) { handle = elemData.handle; if ( handle ) { handle.elem = null; } // removeData also checks for emptiness and clears the expando if empty // so use it instead of delete jQuery.removeData( elem, [ "events", "handle" ], true ); } }, // Events that are safe to short-circuit if no handlers are attached. // Native DOM events should not be added, they may have inline handlers. customEvent: { "getData": true, "setData": true, "changeData": true }, trigger: function( event, data, elem, onlyHandlers ) { // Don't do events on text and comment nodes if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) { return; } // Event object or event type var type = event.type || event, namespaces = [], cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType; // focus/blur morphs to focusin/out; ensure we're not firing them right now if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { return; } if ( type.indexOf( "!" ) >= 0 ) { // Exclusive events trigger only for the exact event (no namespaces) type = type.slice(0, -1); exclusive = true; } if ( type.indexOf( "." ) >= 0 ) { // Namespaced trigger; create a regexp to match event type in handle() namespaces = type.split("."); type = namespaces.shift(); namespaces.sort(); } if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) { // No jQuery handlers for this event type, and it can't have inline handlers return; } // Caller can pass in an Event, Object, or just an event type string event = typeof event === "object" ? // jQuery.Event object event[ jQuery.expando ] ? event : // Object literal new jQuery.Event( type, event ) : // Just the event type (string) new jQuery.Event( type ); event.type = type; event.isTrigger = true; event.exclusive = exclusive; event.namespace = namespaces.join( "." ); event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null; ontype = type.indexOf( ":" ) < 0 ? "on" + type : ""; // Handle a global trigger if ( !elem ) { // TODO: Stop taunting the data cache; remove global events and always attach to document cache = jQuery.cache; for ( i in cache ) { if ( cache[ i ].events && cache[ i ].events[ type ] ) { jQuery.event.trigger( event, data, cache[ i ].handle.elem, true ); } } return; } // Clean up the event in case it is being reused event.result = undefined; if ( !event.target ) { event.target = elem; } // Clone any incoming data and prepend the event, creating the handler arg list data = data != null ? jQuery.makeArray( data ) : []; data.unshift( event ); // Allow special events to draw outside the lines special = jQuery.event.special[ type ] || {}; if ( special.trigger && special.trigger.apply( elem, data ) === false ) { return; } // Determine event propagation path in advance, per W3C events spec (#9951) // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) eventPath = [[ elem, special.bindType || type ]]; if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { bubbleType = special.delegateType || type; cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode; old = null; for ( ; cur; cur = cur.parentNode ) { eventPath.push([ cur, bubbleType ]); old = cur; } // Only add window if we got to document (e.g., not plain obj or detached DOM) if ( old && old === elem.ownerDocument ) { eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]); } } // Fire handlers on the event path for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) { cur = eventPath[i][0]; event.type = eventPath[i][1]; handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); if ( handle ) { handle.apply( cur, data ); } // Note that this is a bare JS function and not a jQuery handler handle = ontype && cur[ ontype ]; if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) { event.preventDefault(); } } event.type = type; // If nobody prevented the default action, do it now if ( !onlyHandlers && !event.isDefaultPrevented() ) { if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) && !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { // Call a native DOM method on the target with the same name name as the event. // Can't use an .isFunction() check here because IE6/7 fails that test. // Don't do default actions on window, that's where global variables be (#6170) // IE<9 dies on focus/blur to hidden element (#1486) if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) { // Don't re-trigger an onFOO event when we call its FOO() method old = elem[ ontype ]; if ( old ) { elem[ ontype ] = null; } // Prevent re-triggering of the same event, since we already bubbled it above jQuery.event.triggered = type; elem[ type ](); jQuery.event.triggered = undefined; if ( old ) { elem[ ontype ] = old; } } } } return event.result; }, dispatch: function( event ) { // Make a writable jQuery.Event from the native event object event = jQuery.event.fix( event || window.event ); var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []), delegateCount = handlers.delegateCount, args = [].slice.call( arguments, 0 ), run_all = !event.exclusive && !event.namespace, special = jQuery.event.special[ event.type ] || {}, handlerQueue = [], i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related; // Use the fix-ed jQuery.Event rather than the (read-only) native event args[0] = event; event.delegateTarget = this; // Call the preDispatch hook for the mapped type, and let it bail if desired if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { return; } // Determine handlers that should run if there are delegated events // Avoid non-left-click bubbling in Firefox (#3861) if ( delegateCount && !(event.button && event.type === "click") ) { // Pregenerate a single jQuery object for reuse with .is() jqcur = jQuery(this); jqcur.context = this.ownerDocument || this; for ( cur = event.target; cur != this; cur = cur.parentNode || this ) { // Don't process events on disabled elements (#6911, #8165) if ( cur.disabled !== true ) { selMatch = {}; matches = []; jqcur[0] = cur; for ( i = 0; i < delegateCount; i++ ) { handleObj = handlers[ i ]; sel = handleObj.selector; if ( selMatch[ sel ] === undefined ) { selMatch[ sel ] = ( handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel ) ); } if ( selMatch[ sel ] ) { matches.push( handleObj ); } } if ( matches.length ) { handlerQueue.push({ elem: cur, matches: matches }); } } } } // Add the remaining (directly-bound) handlers if ( handlers.length > delegateCount ) { handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) }); } // Run delegates first; they may want to stop propagation beneath us for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) { matched = handlerQueue[ i ]; event.currentTarget = matched.elem; for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) { handleObj = matched.matches[ j ]; // Triggered event must either 1) be non-exclusive and have no namespace, or // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) { event.data = handleObj.data; event.handleObj = handleObj; ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) .apply( matched.elem, args ); if ( ret !== undefined ) { event.result = ret; if ( ret === false ) { event.preventDefault(); event.stopPropagation(); } } } } } // Call the postDispatch hook for the mapped type if ( special.postDispatch ) { special.postDispatch.call( this, event ); } return event.result; }, // Includes some event props shared by KeyEvent and MouseEvent // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 *** props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), fixHooks: {}, keyHooks: { props: "char charCode key keyCode".split(" "), filter: function( event, original ) { // Add which for key events if ( event.which == null ) { event.which = original.charCode != null ? original.charCode : original.keyCode; } return event; } }, mouseHooks: { props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), filter: function( event, original ) { var eventDoc, doc, body, button = original.button, fromElement = original.fromElement; // Calculate pageX/Y if missing and clientX/Y available if ( event.pageX == null && original.clientX != null ) { eventDoc = event.target.ownerDocument || document; doc = eventDoc.documentElement; body = eventDoc.body; event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); } // Add relatedTarget, if necessary if ( !event.relatedTarget && fromElement ) { event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; } // Add which for click: 1 === left; 2 === middle; 3 === right // Note: button is not normalized, so don't use it if ( !event.which && button !== undefined ) { event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); } return event; } }, fix: function( event ) { if ( event[ jQuery.expando ] ) { return event; } // Create a writable copy of the event object and normalize some properties var i, prop, originalEvent = event, fixHook = jQuery.event.fixHooks[ event.type ] || {}, copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; event = jQuery.Event( originalEvent ); for ( i = copy.length; i; ) { prop = copy[ --i ]; event[ prop ] = originalEvent[ prop ]; } // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2) if ( !event.target ) { event.target = originalEvent.srcElement || document; } // Target should not be a text node (#504, Safari) if ( event.target.nodeType === 3 ) { event.target = event.target.parentNode; } // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8) if ( event.metaKey === undefined ) { event.metaKey = event.ctrlKey; } return fixHook.filter? fixHook.filter( event, originalEvent ) : event; }, special: { ready: { // Make sure the ready event is setup setup: jQuery.bindReady }, load: { // Prevent triggered image.load events from bubbling to window.load noBubble: true }, focus: { delegateType: "focusin" }, blur: { delegateType: "focusout" }, beforeunload: { setup: function( data, namespaces, eventHandle ) { // We only want to do this special case on windows if ( jQuery.isWindow( this ) ) { this.onbeforeunload = eventHandle; } }, teardown: function( namespaces, eventHandle ) { if ( this.onbeforeunload === eventHandle ) { this.onbeforeunload = null; } } } }, simulate: function( type, elem, event, bubble ) { // Piggyback on a donor event to simulate a different one. // Fake originalEvent to avoid donor's stopPropagation, but if the // simulated event prevents default then we do the same on the donor. var e = jQuery.extend( new jQuery.Event(), event, { type: type, isSimulated: true, originalEvent: {} } ); if ( bubble ) { jQuery.event.trigger( e, null, elem ); } else { jQuery.event.dispatch.call( elem, e ); } if ( e.isDefaultPrevented() ) { event.preventDefault(); } } }; // Some plugins are using, but it's undocumented/deprecated and will be removed. // The 1.7 special event interface should provide all the hooks needed now. jQuery.event.handle = jQuery.event.dispatch; jQuery.removeEvent = document.removeEventListener ? function( elem, type, handle ) { if ( elem.removeEventListener ) { elem.removeEventListener( type, handle, false ); } } : function( elem, type, handle ) { if ( elem.detachEvent ) { elem.detachEvent( "on" + type, handle ); } }; jQuery.Event = function( src, props ) { // Allow instantiation without the 'new' keyword if ( !(this instanceof jQuery.Event) ) { return new jQuery.Event( src, props ); } // Event object if ( src && src.type ) { this.originalEvent = src; this.type = src.type; // Events bubbling up the document may have been marked as prevented // by a handler lower down the tree; reflect the correct value. this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; // Event type } else { this.type = src; } // Put explicitly provided properties onto the event object if ( props ) { jQuery.extend( this, props ); } // Create a timestamp if incoming event doesn't have one this.timeStamp = src && src.timeStamp || jQuery.now(); // Mark it as fixed this[ jQuery.expando ] = true; }; function returnFalse() { return false; } function returnTrue() { return true; } // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html jQuery.Event.prototype = { preventDefault: function() { this.isDefaultPrevented = returnTrue; var e = this.originalEvent; if ( !e ) { return; } // if preventDefault exists run it on the original event if ( e.preventDefault ) { e.preventDefault(); // otherwise set the returnValue property of the original event to false (IE) } else { e.returnValue = false; } }, stopPropagation: function() { this.isPropagationStopped = returnTrue; var e = this.originalEvent; if ( !e ) { return; } // if stopPropagation exists run it on the original event if ( e.stopPropagation ) { e.stopPropagation(); } // otherwise set the cancelBubble property of the original event to true (IE) e.cancelBubble = true; }, stopImmediatePropagation: function() { this.isImmediatePropagationStopped = returnTrue; this.stopPropagation(); }, isDefaultPrevented: returnFalse, isPropagationStopped: returnFalse, isImmediatePropagationStopped: returnFalse }; // Create mouseenter/leave events using mouseover/out and event-time checks jQuery.each({ mouseenter: "mouseover", mouseleave: "mouseout" }, function( orig, fix ) { jQuery.event.special[ orig ] = { delegateType: fix, bindType: fix, handle: function( event ) { var target = this, related = event.relatedTarget, handleObj = event.handleObj, selector = handleObj.selector, ret; // For mousenter/leave call the handler if related is outside the target. // NB: No relatedTarget if the mouse left/entered the browser window if ( !related || (related !== target && !jQuery.contains( target, related )) ) { event.type = handleObj.origType; ret = handleObj.handler.apply( this, arguments ); event.type = fix; } return ret; } }; }); // IE submit delegation if ( !jQuery.support.submitBubbles ) { jQuery.event.special.submit = { setup: function() { // Only need this for delegated form submit events if ( jQuery.nodeName( this, "form" ) ) { return false; } // Lazy-add a submit handler when a descendant form may potentially be submitted jQuery.event.add( this, "click._submit keypress._submit", function( e ) { // Node name check avoids a VML-related crash in IE (#9807) var elem = e.target, form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; if ( form && !form._submit_attached ) { jQuery.event.add( form, "submit._submit", function( event ) { event._submit_bubble = true; }); form._submit_attached = true; } }); // return undefined since we don't need an event listener }, postDispatch: function( event ) { // If form was submitted by the user, bubble the event up the tree if ( event._submit_bubble ) { delete event._submit_bubble; if ( this.parentNode && !event.isTrigger ) { jQuery.event.simulate( "submit", this.parentNode, event, true ); } } }, teardown: function() { // Only need this for delegated form submit events if ( jQuery.nodeName( this, "form" ) ) { return false; } // Remove delegated handlers; cleanData eventually reaps submit handlers attached above jQuery.event.remove( this, "._submit" ); } }; } // IE change delegation and checkbox/radio fix if ( !jQuery.support.changeBubbles ) { jQuery.event.special.change = { setup: function() { if ( rformElems.test( this.nodeName ) ) { // IE doesn't fire change on a check/radio until blur; trigger it on click // after a propertychange. Eat the blur-change in special.change.handle. // This still fires onchange a second time for check/radio after blur. if ( this.type === "checkbox" || this.type === "radio" ) { jQuery.event.add( this, "propertychange._change", function( event ) { if ( event.originalEvent.propertyName === "checked" ) { this._just_changed = true; } }); jQuery.event.add( this, "click._change", function( event ) { if ( this._just_changed && !event.isTrigger ) { this._just_changed = false; jQuery.event.simulate( "change", this, event, true ); } }); } return false; } // Delegated event; lazy-add a change handler on descendant inputs jQuery.event.add( this, "beforeactivate._change", function( e ) { var elem = e.target; if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) { jQuery.event.add( elem, "change._change", function( event ) { if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { jQuery.event.simulate( "change", this.parentNode, event, true ); } }); elem._change_attached = true; } }); }, handle: function( event ) { var elem = event.target; // Swallow native change events from checkbox/radio, we already triggered them above if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { return event.handleObj.handler.apply( this, arguments ); } }, teardown: function() { jQuery.event.remove( this, "._change" ); return rformElems.test( this.nodeName ); } }; } // Create "bubbling" focus and blur events if ( !jQuery.support.focusinBubbles ) { jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { // Attach a single capturing handler while someone wants focusin/focusout var attaches = 0, handler = function( event ) { jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); }; jQuery.event.special[ fix ] = { setup: function() { if ( attaches++ === 0 ) { document.addEventListener( orig, handler, true ); } }, teardown: function() { if ( --attaches === 0 ) { document.removeEventListener( orig, handler, true ); } } }; }); } jQuery.fn.extend({ on: function( types, selector, data, fn, /*INTERNAL*/ one ) { var origFn, type; // Types can be a map of types/handlers if ( typeof types === "object" ) { // ( types-Object, selector, data ) if ( typeof selector !== "string" ) { // && selector != null // ( types-Object, data ) data = data || selector; selector = undefined; } for ( type in types ) { this.on( type, selector, data, types[ type ], one ); } return this; } if ( data == null && fn == null ) { // ( types, fn ) fn = selector; data = selector = undefined; } else if ( fn == null ) { if ( typeof selector === "string" ) { // ( types, selector, fn ) fn = data; data = undefined; } else { // ( types, data, fn ) fn = data; data = selector; selector = undefined; } } if ( fn === false ) { fn = returnFalse; } else if ( !fn ) { return this; } if ( one === 1 ) { origFn = fn; fn = function( event ) { // Can use an empty set, since event contains the info jQuery().off( event ); return origFn.apply( this, arguments ); }; // Use same guid so caller can remove using origFn fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); } return this.each( function() { jQuery.event.add( this, types, fn, data, selector ); }); }, one: function( types, selector, data, fn ) { return this.on( types, selector, data, fn, 1 ); }, off: function( types, selector, fn ) { if ( types && types.preventDefault && types.handleObj ) { // ( event ) dispatched jQuery.Event var handleObj = types.handleObj; jQuery( types.delegateTarget ).off( handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, handleObj.selector, handleObj.handler ); return this; } if ( typeof types === "object" ) { // ( types-object [, selector] ) for ( var type in types ) { this.off( type, selector, types[ type ] ); } return this; } if ( selector === false || typeof selector === "function" ) { // ( types [, fn] ) fn = selector; selector = undefined; } if ( fn === false ) { fn = returnFalse; } return this.each(function() { jQuery.event.remove( this, types, fn, selector ); }); }, bind: function( types, data, fn ) { return this.on( types, null, data, fn ); }, unbind: function( types, fn ) { return this.off( types, null, fn ); }, live: function( types, data, fn ) { jQuery( this.context ).on( types, this.selector, data, fn ); return this; }, die: function( types, fn ) { jQuery( this.context ).off( types, this.selector || "**", fn ); return this; }, delegate: function( selector, types, data, fn ) { return this.on( types, selector, data, fn ); }, undelegate: function( selector, types, fn ) { // ( namespace ) or ( selector, types [, fn] ) return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn ); }, trigger: function( type, data ) { return this.each(function() { jQuery.event.trigger( type, data, this ); }); }, triggerHandler: function( type, data ) { if ( this[0] ) { return jQuery.event.trigger( type, data, this[0], true ); } }, toggle: function( fn ) { // Save reference to arguments for access in closure var args = arguments, guid = fn.guid || jQuery.guid++, i = 0, toggler = function( event ) { // Figure out which function to execute var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i; jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 ); // Make sure that clicks stop event.preventDefault(); // and execute the function return args[ lastToggle ].apply( this, arguments ) || false; }; // link all the functions, so any of them can unbind this click handler toggler.guid = guid; while ( i < args.length ) { args[ i++ ].guid = guid; } return this.click( toggler ); }, hover: function( fnOver, fnOut ) { return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); } }); jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) { // Handle event binding jQuery.fn[ name ] = function( data, fn ) { if ( fn == null ) { fn = data; data = null; } return arguments.length > 0 ? this.on( name, null, data, fn ) : this.trigger( name ); }; if ( jQuery.attrFn ) { jQuery.attrFn[ name ] = true; } if ( rkeyEvent.test( name ) ) { jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks; } if ( rmouseEvent.test( name ) ) { jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks; } }); /*! * Sizzle CSS Selector Engine * Copyright 2011, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * More information: http://sizzlejs.com/ */ (function(){ var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, expando = "sizcache" + (Math.random() + '').replace('.', ''), done = 0, toString = Object.prototype.toString, hasDuplicate = false, baseHasDuplicate = true, rBackslash = /\\/g, rReturn = /\r\n/g, rNonWord = /\W/; // Here we check if the JavaScript engine is using some sort of // optimization where it does not always call our comparision // function. If that is the case, discard the hasDuplicate value. // Thus far that includes Google Chrome. [0, 0].sort(function() { baseHasDuplicate = false; return 0; }); var Sizzle = function( selector, context, results, seed ) { results = results || []; context = context || document; var origContext = context; if ( context.nodeType !== 1 && context.nodeType !== 9 ) { return []; } if ( !selector || typeof selector !== "string" ) { return results; } var m, set, checkSet, extra, ret, cur, pop, i, prune = true, contextXML = Sizzle.isXML( context ), parts = [], soFar = selector; // Reset the position of the chunker regexp (start from head) do { chunker.exec( "" ); m = chunker.exec( soFar ); if ( m ) { soFar = m[3]; parts.push( m[1] ); if ( m[2] ) { extra = m[3]; break; } } } while ( m ); if ( parts.length > 1 && origPOS.exec( selector ) ) { if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { set = posProcess( parts[0] + parts[1], context, seed ); } else { set = Expr.relative[ parts[0] ] ? [ context ] : Sizzle( parts.shift(), context ); while ( parts.length ) { selector = parts.shift(); if ( Expr.relative[ selector ] ) { selector += parts.shift(); } set = posProcess( selector, set, seed ); } } } else { // Take a shortcut and set the context if the root selector is an ID // (but not if it'll be faster if the inner selector is an ID) if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { ret = Sizzle.find( parts.shift(), context, contextXML ); context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; } if ( context ) { ret = seed ? { expr: parts.pop(), set: makeArray(seed) } : Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; if ( parts.length > 0 ) { checkSet = makeArray( set ); } else { prune = false; } while ( parts.length ) { cur = parts.pop(); pop = cur; if ( !Expr.relative[ cur ] ) { cur = ""; } else { pop = parts.pop(); } if ( pop == null ) { pop = context; } Expr.relative[ cur ]( checkSet, pop, contextXML ); } } else { checkSet = parts = []; } } if ( !checkSet ) { checkSet = set; } if ( !checkSet ) { Sizzle.error( cur || selector ); } if ( toString.call(checkSet) === "[object Array]" ) { if ( !prune ) { results.push.apply( results, checkSet ); } else if ( context && context.nodeType === 1 ) { for ( i = 0; checkSet[i] != null; i++ ) { if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { results.push( set[i] ); } } } else { for ( i = 0; checkSet[i] != null; i++ ) { if ( checkSet[i] && checkSet[i].nodeType === 1 ) { results.push( set[i] ); } } } } else { makeArray( checkSet, results ); } if ( extra ) { Sizzle( extra, origContext, results, seed ); Sizzle.uniqueSort( results ); } return results; }; Sizzle.uniqueSort = function( results ) { if ( sortOrder ) { hasDuplicate = baseHasDuplicate; results.sort( sortOrder ); if ( hasDuplicate ) { for ( var i = 1; i < results.length; i++ ) { if ( results[i] === results[ i - 1 ] ) { results.splice( i--, 1 ); } } } } return results; }; Sizzle.matches = function( expr, set ) { return Sizzle( expr, null, null, set ); }; Sizzle.matchesSelector = function( node, expr ) { return Sizzle( expr, null, null, [node] ).length > 0; }; Sizzle.find = function( expr, context, isXML ) { var set, i, len, match, type, left; if ( !expr ) { return []; } for ( i = 0, len = Expr.order.length; i < len; i++ ) { type = Expr.order[i]; if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { left = match[1]; match.splice( 1, 1 ); if ( left.substr( left.length - 1 ) !== "\\" ) { match[1] = (match[1] || "").replace( rBackslash, "" ); set = Expr.find[ type ]( match, context, isXML ); if ( set != null ) { expr = expr.replace( Expr.match[ type ], "" ); break; } } } } if ( !set ) { set = typeof context.getElementsByTagName !== "undefined" ? context.getElementsByTagName( "*" ) : []; } return { set: set, expr: expr }; }; Sizzle.filter = function( expr, set, inplace, not ) { var match, anyFound, type, found, item, filter, left, i, pass, old = expr, result = [], curLoop = set, isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); while ( expr && set.length ) { for ( type in Expr.filter ) { if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { filter = Expr.filter[ type ]; left = match[1]; anyFound = false; match.splice(1,1); if ( left.substr( left.length - 1 ) === "\\" ) { continue; } if ( curLoop === result ) { result = []; } if ( Expr.preFilter[ type ] ) { match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); if ( !match ) { anyFound = found = true; } else if ( match === true ) { continue; } } if ( match ) { for ( i = 0; (item = curLoop[i]) != null; i++ ) { if ( item ) { found = filter( item, match, i, curLoop ); pass = not ^ found; if ( inplace && found != null ) { if ( pass ) { anyFound = true; } else { curLoop[i] = false; } } else if ( pass ) { result.push( item ); anyFound = true; } } } } if ( found !== undefined ) { if ( !inplace ) { curLoop = result; } expr = expr.replace( Expr.match[ type ], "" ); if ( !anyFound ) { return []; } break; } } } // Improper expression if ( expr === old ) { if ( anyFound == null ) { Sizzle.error( expr ); } else { break; } } old = expr; } return curLoop; }; Sizzle.error = function( msg ) { throw new Error( "Syntax error, unrecognized expression: " + msg ); }; /** * Utility function for retreiving the text value of an array of DOM nodes * @param {Array|Element} elem */ var getText = Sizzle.getText = function( elem ) { var i, node, nodeType = elem.nodeType, ret = ""; if ( nodeType ) { if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { // Use textContent || innerText for elements if ( typeof elem.textContent === 'string' ) { return elem.textContent; } else if ( typeof elem.innerText === 'string' ) { // Replace IE's carriage returns return elem.innerText.replace( rReturn, '' ); } else { // Traverse it's children for ( elem = elem.firstChild; elem; elem = elem.nextSibling) { ret += getText( elem ); } } } else if ( nodeType === 3 || nodeType === 4 ) { return elem.nodeValue; } } else { // If no nodeType, this is expected to be an array for ( i = 0; (node = elem[i]); i++ ) { // Do not traverse comment nodes if ( node.nodeType !== 8 ) { ret += getText( node ); } } } return ret; }; var Expr = Sizzle.selectors = { order: [ "ID", "NAME", "TAG" ], match: { ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ }, leftMatch: {}, attrMap: { "class": "className", "for": "htmlFor" }, attrHandle: { href: function( elem ) { return elem.getAttribute( "href" ); }, type: function( elem ) { return elem.getAttribute( "type" ); } }, relative: { "+": function(checkSet, part){ var isPartStr = typeof part === "string", isTag = isPartStr && !rNonWord.test( part ), isPartStrNotTag = isPartStr && !isTag; if ( isTag ) { part = part.toLowerCase(); } for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { if ( (elem = checkSet[i]) ) { while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? elem || false : elem === part; } } if ( isPartStrNotTag ) { Sizzle.filter( part, checkSet, true ); } }, ">": function( checkSet, part ) { var elem, isPartStr = typeof part === "string", i = 0, l = checkSet.length; if ( isPartStr && !rNonWord.test( part ) ) { part = part.toLowerCase(); for ( ; i < l; i++ ) { elem = checkSet[i]; if ( elem ) { var parent = elem.parentNode; checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; } } } else { for ( ; i < l; i++ ) { elem = checkSet[i]; if ( elem ) { checkSet[i] = isPartStr ? elem.parentNode : elem.parentNode === part; } } if ( isPartStr ) { Sizzle.filter( part, checkSet, true ); } } }, "": function(checkSet, part, isXML){ var nodeCheck, doneName = done++, checkFn = dirCheck; if ( typeof part === "string" && !rNonWord.test( part ) ) { part = part.toLowerCase(); nodeCheck = part; checkFn = dirNodeCheck; } checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); }, "~": function( checkSet, part, isXML ) { var nodeCheck, doneName = done++, checkFn = dirCheck; if ( typeof part === "string" && !rNonWord.test( part ) ) { part = part.toLowerCase(); nodeCheck = part; checkFn = dirNodeCheck; } checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); } }, find: { ID: function( match, context, isXML ) { if ( typeof context.getElementById !== "undefined" && !isXML ) { var m = context.getElementById(match[1]); // Check parentNode to catch when Blackberry 4.6 returns // nodes that are no longer in the document #6963 return m && m.parentNode ? [m] : []; } }, NAME: function( match, context ) { if ( typeof context.getElementsByName !== "undefined" ) { var ret = [], results = context.getElementsByName( match[1] ); for ( var i = 0, l = results.length; i < l; i++ ) { if ( results[i].getAttribute("name") === match[1] ) { ret.push( results[i] ); } } return ret.length === 0 ? null : ret; } }, TAG: function( match, context ) { if ( typeof context.getElementsByTagName !== "undefined" ) { return context.getElementsByTagName( match[1] ); } } }, preFilter: { CLASS: function( match, curLoop, inplace, result, not, isXML ) { match = " " + match[1].replace( rBackslash, "" ) + " "; if ( isXML ) { return match; } for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { if ( elem ) { if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { if ( !inplace ) { result.push( elem ); } } else if ( inplace ) { curLoop[i] = false; } } } return false; }, ID: function( match ) { return match[1].replace( rBackslash, "" ); }, TAG: function( match, curLoop ) { return match[1].replace( rBackslash, "" ).toLowerCase(); }, CHILD: function( match ) { if ( match[1] === "nth" ) { if ( !match[2] ) { Sizzle.error( match[0] ); } match[2] = match[2].replace(/^\+|\s*/g, ''); // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); // calculate the numbers (first)n+(last) including if they are negative match[2] = (test[1] + (test[2] || 1)) - 0; match[3] = test[3] - 0; } else if ( match[2] ) { Sizzle.error( match[0] ); } // TODO: Move to normal caching system match[0] = done++; return match; }, ATTR: function( match, curLoop, inplace, result, not, isXML ) { var name = match[1] = match[1].replace( rBackslash, "" ); if ( !isXML && Expr.attrMap[name] ) { match[1] = Expr.attrMap[name]; } // Handle if an un-quoted value was used match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); if ( match[2] === "~=" ) { match[4] = " " + match[4] + " "; } return match; }, PSEUDO: function( match, curLoop, inplace, result, not ) { if ( match[1] === "not" ) { // If we're dealing with a complex expression, or a simple one if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { match[3] = Sizzle(match[3], null, null, curLoop); } else { var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); if ( !inplace ) { result.push.apply( result, ret ); } return false; } } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { return true; } return match; }, POS: function( match ) { match.unshift( true ); return match; } }, filters: { enabled: function( elem ) { return elem.disabled === false && elem.type !== "hidden"; }, disabled: function( elem ) { return elem.disabled === true; }, checked: function( elem ) { return elem.checked === true; }, selected: function( elem ) { // Accessing this property makes selected-by-default // options in Safari work properly if ( elem.parentNode ) { elem.parentNode.selectedIndex; } return elem.selected === true; }, parent: function( elem ) { return !!elem.firstChild; }, empty: function( elem ) { return !elem.firstChild; }, has: function( elem, i, match ) { return !!Sizzle( match[3], elem ).length; }, header: function( elem ) { return (/h\d/i).test( elem.nodeName ); }, text: function( elem ) { var attr = elem.getAttribute( "type" ), type = elem.type; // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) // use getAttribute instead to test this case return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); }, radio: function( elem ) { return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; }, checkbox: function( elem ) { return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; }, file: function( elem ) { return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; }, password: function( elem ) { return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; }, submit: function( elem ) { var name = elem.nodeName.toLowerCase(); return (name === "input" || name === "button") && "submit" === elem.type; }, image: function( elem ) { return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; }, reset: function( elem ) { var name = elem.nodeName.toLowerCase(); return (name === "input" || name === "button") && "reset" === elem.type; }, button: function( elem ) { var name = elem.nodeName.toLowerCase(); return name === "input" && "button" === elem.type || name === "button"; }, input: function( elem ) { return (/input|select|textarea|button/i).test( elem.nodeName ); }, focus: function( elem ) { return elem === elem.ownerDocument.activeElement; } }, setFilters: { first: function( elem, i ) { return i === 0; }, last: function( elem, i, match, array ) { return i === array.length - 1; }, even: function( elem, i ) { return i % 2 === 0; }, odd: function( elem, i ) { return i % 2 === 1; }, lt: function( elem, i, match ) { return i < match[3] - 0; }, gt: function( elem, i, match ) { return i > match[3] - 0; }, nth: function( elem, i, match ) { return match[3] - 0 === i; }, eq: function( elem, i, match ) { return match[3] - 0 === i; } }, filter: { PSEUDO: function( elem, match, i, array ) { var name = match[1], filter = Expr.filters[ name ]; if ( filter ) { return filter( elem, i, match, array ); } else if ( name === "contains" ) { return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; } else if ( name === "not" ) { var not = match[3]; for ( var j = 0, l = not.length; j < l; j++ ) { if ( not[j] === elem ) { return false; } } return true; } else { Sizzle.error( name ); } }, CHILD: function( elem, match ) { var first, last, doneName, parent, cache, count, diff, type = match[1], node = elem; switch ( type ) { case "only": case "first": while ( (node = node.previousSibling) ) { if ( node.nodeType === 1 ) { return false; } } if ( type === "first" ) { return true; } node = elem; /* falls through */ case "last": while ( (node = node.nextSibling) ) { if ( node.nodeType === 1 ) { return false; } } return true; case "nth": first = match[2]; last = match[3]; if ( first === 1 && last === 0 ) { return true; } doneName = match[0]; parent = elem.parentNode; if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) { count = 0; for ( node = parent.firstChild; node; node = node.nextSibling ) { if ( node.nodeType === 1 ) { node.nodeIndex = ++count; } } parent[ expando ] = doneName; } diff = elem.nodeIndex - last; if ( first === 0 ) { return diff === 0; } else { return ( diff % first === 0 && diff / first >= 0 ); } } }, ID: function( elem, match ) { return elem.nodeType === 1 && elem.getAttribute("id") === match; }, TAG: function( elem, match ) { return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match; }, CLASS: function( elem, match ) { return (" " + (elem.className || elem.getAttribute("class")) + " ") .indexOf( match ) > -1; }, ATTR: function( elem, match ) { var name = match[1], result = Sizzle.attr ? Sizzle.attr( elem, name ) : Expr.attrHandle[ name ] ? Expr.attrHandle[ name ]( elem ) : elem[ name ] != null ? elem[ name ] : elem.getAttribute( name ), value = result + "", type = match[2], check = match[4]; return result == null ? type === "!=" : !type && Sizzle.attr ? result != null : type === "=" ? value === check : type === "*=" ? value.indexOf(check) >= 0 : type === "~=" ? (" " + value + " ").indexOf(check) >= 0 : !check ? value && result !== false : type === "!=" ? value !== check : type === "^=" ? value.indexOf(check) === 0 : type === "$=" ? value.substr(value.length - check.length) === check : type === "|=" ? value === check || value.substr(0, check.length + 1) === check + "-" : false; }, POS: function( elem, match, i, array ) { var name = match[2], filter = Expr.setFilters[ name ]; if ( filter ) { return filter( elem, i, match, array ); } } } }; var origPOS = Expr.match.POS, fescape = function(all, num){ return "\\" + (num - 0 + 1); }; for ( var type in Expr.match ) { Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); } // Expose origPOS // "global" as in regardless of relation to brackets/parens Expr.match.globalPOS = origPOS; var makeArray = function( array, results ) { array = Array.prototype.slice.call( array, 0 ); if ( results ) { results.push.apply( results, array ); return results; } return array; }; // Perform a simple check to determine if the browser is capable of // converting a NodeList to an array using builtin methods. // Also verifies that the returned array holds DOM nodes // (which is not the case in the Blackberry browser) try { Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; // Provide a fallback method if it does not work } catch( e ) { makeArray = function( array, results ) { var i = 0, ret = results || []; if ( toString.call(array) === "[object Array]" ) { Array.prototype.push.apply( ret, array ); } else { if ( typeof array.length === "number" ) { for ( var l = array.length; i < l; i++ ) { ret.push( array[i] ); } } else { for ( ; array[i]; i++ ) { ret.push( array[i] ); } } } return ret; }; } var sortOrder, siblingCheck; if ( document.documentElement.compareDocumentPosition ) { sortOrder = function( a, b ) { if ( a === b ) { hasDuplicate = true; return 0; } if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { return a.compareDocumentPosition ? -1 : 1; } return a.compareDocumentPosition(b) & 4 ? -1 : 1; }; } else { sortOrder = function( a, b ) { // The nodes are identical, we can exit early if ( a === b ) { hasDuplicate = true; return 0; // Fallback to using sourceIndex (in IE) if it's available on both nodes } else if ( a.sourceIndex && b.sourceIndex ) { return a.sourceIndex - b.sourceIndex; } var al, bl, ap = [], bp = [], aup = a.parentNode, bup = b.parentNode, cur = aup; // If the nodes are siblings (or identical) we can do a quick check if ( aup === bup ) { return siblingCheck( a, b ); // If no parents were found then the nodes are disconnected } else if ( !aup ) { return -1; } else if ( !bup ) { return 1; } // Otherwise they're somewhere else in the tree so we need // to build up a full list of the parentNodes for comparison while ( cur ) { ap.unshift( cur ); cur = cur.parentNode; } cur = bup; while ( cur ) { bp.unshift( cur ); cur = cur.parentNode; } al = ap.length; bl = bp.length; // Start walking down the tree looking for a discrepancy for ( var i = 0; i < al && i < bl; i++ ) { if ( ap[i] !== bp[i] ) { return siblingCheck( ap[i], bp[i] ); } } // We ended someplace up the tree so do a sibling check return i === al ? siblingCheck( a, bp[i], -1 ) : siblingCheck( ap[i], b, 1 ); }; siblingCheck = function( a, b, ret ) { if ( a === b ) { return ret; } var cur = a.nextSibling; while ( cur ) { if ( cur === b ) { return -1; } cur = cur.nextSibling; } return 1; }; } // Check to see if the browser returns elements by name when // querying by getElementById (and provide a workaround) (function(){ // We're going to inject a fake input element with a specified name var form = document.createElement("div"), id = "script" + (new Date()).getTime(), root = document.documentElement; form.innerHTML = ""; // Inject it into the root element, check its status, and remove it quickly root.insertBefore( form, root.firstChild ); // The workaround has to do additional checks after a getElementById // Which slows things down for other browsers (hence the branching) if ( document.getElementById( id ) ) { Expr.find.ID = function( match, context, isXML ) { if ( typeof context.getElementById !== "undefined" && !isXML ) { var m = context.getElementById(match[1]); return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; } }; Expr.filter.ID = function( elem, match ) { var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); return elem.nodeType === 1 && node && node.nodeValue === match; }; } root.removeChild( form ); // release memory in IE root = form = null; })(); (function(){ // Check to see if the browser returns only elements // when doing getElementsByTagName("*") // Create a fake element var div = document.createElement("div"); div.appendChild( document.createComment("") ); // Make sure no comments are found if ( div.getElementsByTagName("*").length > 0 ) { Expr.find.TAG = function( match, context ) { var results = context.getElementsByTagName( match[1] ); // Filter out possible comments if ( match[1] === "*" ) { var tmp = []; for ( var i = 0; results[i]; i++ ) { if ( results[i].nodeType === 1 ) { tmp.push( results[i] ); } } results = tmp; } return results; }; } // Check to see if an attribute returns normalized href attributes div.innerHTML = ""; if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && div.firstChild.getAttribute("href") !== "#" ) { Expr.attrHandle.href = function( elem ) { return elem.getAttribute( "href", 2 ); }; } // release memory in IE div = null; })(); if ( document.querySelectorAll ) { (function(){ var oldSizzle = Sizzle, div = document.createElement("div"), id = "__sizzle__"; div.innerHTML = "

"; // Safari can't handle uppercase or unicode characters when // in quirks mode. if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { return; } Sizzle = function( query, context, extra, seed ) { context = context || document; // Only use querySelectorAll on non-XML documents // (ID selectors don't work in non-HTML documents) if ( !seed && !Sizzle.isXML(context) ) { // See if we find a selector to speed up var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { // Speed-up: Sizzle("TAG") if ( match[1] ) { return makeArray( context.getElementsByTagName( query ), extra ); // Speed-up: Sizzle(".CLASS") } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { return makeArray( context.getElementsByClassName( match[2] ), extra ); } } if ( context.nodeType === 9 ) { // Speed-up: Sizzle("body") // The body element only exists once, optimize finding it if ( query === "body" && context.body ) { return makeArray( [ context.body ], extra ); // Speed-up: Sizzle("#ID") } else if ( match && match[3] ) { var elem = context.getElementById( match[3] ); // Check parentNode to catch when Blackberry 4.6 returns // nodes that are no longer in the document #6963 if ( elem && elem.parentNode ) { // Handle the case where IE and Opera return items // by name instead of ID if ( elem.id === match[3] ) { return makeArray( [ elem ], extra ); } } else { return makeArray( [], extra ); } } try { return makeArray( context.querySelectorAll(query), extra ); } catch(qsaError) {} // qSA works strangely on Element-rooted queries // We can work around this by specifying an extra ID on the root // and working up from there (Thanks to Andrew Dupont for the technique) // IE 8 doesn't work on object elements } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { var oldContext = context, old = context.getAttribute( "id" ), nid = old || id, hasParent = context.parentNode, relativeHierarchySelector = /^\s*[+~]/.test( query ); if ( !old ) { context.setAttribute( "id", nid ); } else { nid = nid.replace( /'/g, "\\$&" ); } if ( relativeHierarchySelector && hasParent ) { context = context.parentNode; } try { if ( !relativeHierarchySelector || hasParent ) { return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); } } catch(pseudoError) { } finally { if ( !old ) { oldContext.removeAttribute( "id" ); } } } } return oldSizzle(query, context, extra, seed); }; for ( var prop in oldSizzle ) { Sizzle[ prop ] = oldSizzle[ prop ]; } // release memory in IE div = null; })(); } (function(){ var html = document.documentElement, matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; if ( matches ) { // Check to see if it's possible to do matchesSelector // on a disconnected node (IE 9 fails this) var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), pseudoWorks = false; try { // This should fail with an exception // Gecko does not error, returns false instead matches.call( document.documentElement, "[test!='']:sizzle" ); } catch( pseudoError ) { pseudoWorks = true; } Sizzle.matchesSelector = function( node, expr ) { // Make sure that attribute selectors are quoted expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); if ( !Sizzle.isXML( node ) ) { try { if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { var ret = matches.call( node, expr ); // IE 9's matchesSelector returns false on disconnected nodes if ( ret || !disconnectedMatch || // As well, disconnected nodes are said to be in a document // fragment in IE 9, so check for that node.document && node.document.nodeType !== 11 ) { return ret; } } } catch(e) {} } return Sizzle(expr, null, null, [node]).length > 0; }; } })(); (function(){ var div = document.createElement("div"); div.innerHTML = "
"; // Opera can't find a second classname (in 9.6) // Also, make sure that getElementsByClassName actually exists if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { return; } // Safari caches class attributes, doesn't catch changes (in 3.2) div.lastChild.className = "e"; if ( div.getElementsByClassName("e").length === 1 ) { return; } Expr.order.splice(1, 0, "CLASS"); Expr.find.CLASS = function( match, context, isXML ) { if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { return context.getElementsByClassName(match[1]); } }; // release memory in IE div = null; })(); function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; if ( elem ) { var match = false; elem = elem[dir]; while ( elem ) { if ( elem[ expando ] === doneName ) { match = checkSet[elem.sizset]; break; } if ( elem.nodeType === 1 && !isXML ){ elem[ expando ] = doneName; elem.sizset = i; } if ( elem.nodeName.toLowerCase() === cur ) { match = elem; break; } elem = elem[dir]; } checkSet[i] = match; } } } function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; if ( elem ) { var match = false; elem = elem[dir]; while ( elem ) { if ( elem[ expando ] === doneName ) { match = checkSet[elem.sizset]; break; } if ( elem.nodeType === 1 ) { if ( !isXML ) { elem[ expando ] = doneName; elem.sizset = i; } if ( typeof cur !== "string" ) { if ( elem === cur ) { match = true; break; } } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { match = elem; break; } } elem = elem[dir]; } checkSet[i] = match; } } } if ( document.documentElement.contains ) { Sizzle.contains = function( a, b ) { return a !== b && (a.contains ? a.contains(b) : true); }; } else if ( document.documentElement.compareDocumentPosition ) { Sizzle.contains = function( a, b ) { return !!(a.compareDocumentPosition(b) & 16); }; } else { Sizzle.contains = function() { return false; }; } Sizzle.isXML = function( elem ) { // documentElement is verified for cases where it doesn't yet exist // (such as loading iframes in IE - #4833) var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; return documentElement ? documentElement.nodeName !== "HTML" : false; }; var posProcess = function( selector, context, seed ) { var match, tmpSet = [], later = "", root = context.nodeType ? [context] : context; // Position selectors must be done after the filter // And so must :not(positional) so we move all PSEUDOs to the end while ( (match = Expr.match.PSEUDO.exec( selector )) ) { later += match[0]; selector = selector.replace( Expr.match.PSEUDO, "" ); } selector = Expr.relative[selector] ? selector + "*" : selector; for ( var i = 0, l = root.length; i < l; i++ ) { Sizzle( selector, root[i], tmpSet, seed ); } return Sizzle.filter( later, tmpSet ); }; // EXPOSE // Override sizzle attribute retrieval Sizzle.attr = jQuery.attr; Sizzle.selectors.attrMap = {}; jQuery.find = Sizzle; jQuery.expr = Sizzle.selectors; jQuery.expr[":"] = jQuery.expr.filters; jQuery.unique = Sizzle.uniqueSort; jQuery.text = Sizzle.getText; jQuery.isXMLDoc = Sizzle.isXML; jQuery.contains = Sizzle.contains; })(); var runtil = /Until$/, rparentsprev = /^(?:parents|prevUntil|prevAll)/, // Note: This RegExp should be improved, or likely pulled from Sizzle rmultiselector = /,/, isSimple = /^.[^:#\[\.,]*$/, slice = Array.prototype.slice, POS = jQuery.expr.match.globalPOS, // methods guaranteed to produce a unique set when starting from a unique set guaranteedUnique = { children: true, contents: true, next: true, prev: true }; jQuery.fn.extend({ find: function( selector ) { var self = this, i, l; if ( typeof selector !== "string" ) { return jQuery( selector ).filter(function() { for ( i = 0, l = self.length; i < l; i++ ) { if ( jQuery.contains( self[ i ], this ) ) { return true; } } }); } var ret = this.pushStack( "", "find", selector ), length, n, r; for ( i = 0, l = this.length; i < l; i++ ) { length = ret.length; jQuery.find( selector, this[i], ret ); if ( i > 0 ) { // Make sure that the results are unique for ( n = length; n < ret.length; n++ ) { for ( r = 0; r < length; r++ ) { if ( ret[r] === ret[n] ) { ret.splice(n--, 1); break; } } } } } return ret; }, has: function( target ) { var targets = jQuery( target ); return this.filter(function() { for ( var i = 0, l = targets.length; i < l; i++ ) { if ( jQuery.contains( this, targets[i] ) ) { return true; } } }); }, not: function( selector ) { return this.pushStack( winnow(this, selector, false), "not", selector); }, filter: function( selector ) { return this.pushStack( winnow(this, selector, true), "filter", selector ); }, is: function( selector ) { return !!selector && ( typeof selector === "string" ? // If this is a positional selector, check membership in the returned set // so $("p:first").is("p:last") won't return true for a doc with two "p". POS.test( selector ) ? jQuery( selector, this.context ).index( this[0] ) >= 0 : jQuery.filter( selector, this ).length > 0 : this.filter( selector ).length > 0 ); }, closest: function( selectors, context ) { var ret = [], i, l, cur = this[0]; // Array (deprecated as of jQuery 1.7) if ( jQuery.isArray( selectors ) ) { var level = 1; while ( cur && cur.ownerDocument && cur !== context ) { for ( i = 0; i < selectors.length; i++ ) { if ( jQuery( cur ).is( selectors[ i ] ) ) { ret.push({ selector: selectors[ i ], elem: cur, level: level }); } } cur = cur.parentNode; level++; } return ret; } // String var pos = POS.test( selectors ) || typeof selectors !== "string" ? jQuery( selectors, context || this.context ) : 0; for ( i = 0, l = this.length; i < l; i++ ) { cur = this[i]; while ( cur ) { if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { ret.push( cur ); break; } else { cur = cur.parentNode; if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) { break; } } } } ret = ret.length > 1 ? jQuery.unique( ret ) : ret; return this.pushStack( ret, "closest", selectors ); }, // Determine the position of an element within // the matched set of elements index: function( elem ) { // No argument, return index in parent if ( !elem ) { return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1; } // index in selector if ( typeof elem === "string" ) { return jQuery.inArray( this[0], jQuery( elem ) ); } // Locate the position of the desired element return jQuery.inArray( // If it receives a jQuery object, the first element is used elem.jquery ? elem[0] : elem, this ); }, add: function( selector, context ) { var set = typeof selector === "string" ? jQuery( selector, context ) : jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), all = jQuery.merge( this.get(), set ); return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? all : jQuery.unique( all ) ); }, andSelf: function() { return this.add( this.prevObject ); } }); // A painfully simple check to see if an element is disconnected // from a document (should be improved, where feasible). function isDisconnected( node ) { return !node || !node.parentNode || node.parentNode.nodeType === 11; } jQuery.each({ parent: function( elem ) { var parent = elem.parentNode; return parent && parent.nodeType !== 11 ? parent : null; }, parents: function( elem ) { return jQuery.dir( elem, "parentNode" ); }, parentsUntil: function( elem, i, until ) { return jQuery.dir( elem, "parentNode", until ); }, next: function( elem ) { return jQuery.nth( elem, 2, "nextSibling" ); }, prev: function( elem ) { return jQuery.nth( elem, 2, "previousSibling" ); }, nextAll: function( elem ) { return jQuery.dir( elem, "nextSibling" ); }, prevAll: function( elem ) { return jQuery.dir( elem, "previousSibling" ); }, nextUntil: function( elem, i, until ) { return jQuery.dir( elem, "nextSibling", until ); }, prevUntil: function( elem, i, until ) { return jQuery.dir( elem, "previousSibling", until ); }, siblings: function( elem ) { return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); }, children: function( elem ) { return jQuery.sibling( elem.firstChild ); }, contents: function( elem ) { return jQuery.nodeName( elem, "iframe" ) ? elem.contentDocument || elem.contentWindow.document : jQuery.makeArray( elem.childNodes ); } }, function( name, fn ) { jQuery.fn[ name ] = function( until, selector ) { var ret = jQuery.map( this, fn, until ); if ( !runtil.test( name ) ) { selector = until; } if ( selector && typeof selector === "string" ) { ret = jQuery.filter( selector, ret ); } ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { ret = ret.reverse(); } return this.pushStack( ret, name, slice.call( arguments ).join(",") ); }; }); jQuery.extend({ filter: function( expr, elems, not ) { if ( not ) { expr = ":not(" + expr + ")"; } return elems.length === 1 ? jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : jQuery.find.matches(expr, elems); }, dir: function( elem, dir, until ) { var matched = [], cur = elem[ dir ]; while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { if ( cur.nodeType === 1 ) { matched.push( cur ); } cur = cur[dir]; } return matched; }, nth: function( cur, result, dir, elem ) { result = result || 1; var num = 0; for ( ; cur; cur = cur[dir] ) { if ( cur.nodeType === 1 && ++num === result ) { break; } } return cur; }, sibling: function( n, elem ) { var r = []; for ( ; n; n = n.nextSibling ) { if ( n.nodeType === 1 && n !== elem ) { r.push( n ); } } return r; } }); // Implement the identical functionality for filter and not function winnow( elements, qualifier, keep ) { // Can't pass null or undefined to indexOf in Firefox 4 // Set to 0 to skip string check qualifier = qualifier || 0; if ( jQuery.isFunction( qualifier ) ) { return jQuery.grep(elements, function( elem, i ) { var retVal = !!qualifier.call( elem, i, elem ); return retVal === keep; }); } else if ( qualifier.nodeType ) { return jQuery.grep(elements, function( elem, i ) { return ( elem === qualifier ) === keep; }); } else if ( typeof qualifier === "string" ) { var filtered = jQuery.grep(elements, function( elem ) { return elem.nodeType === 1; }); if ( isSimple.test( qualifier ) ) { return jQuery.filter(qualifier, filtered, !keep); } else { qualifier = jQuery.filter( qualifier, filtered ); } } return jQuery.grep(elements, function( elem, i ) { return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep; }); } function createSafeFragment( document ) { var list = nodeNames.split( "|" ), safeFrag = document.createDocumentFragment(); if ( safeFrag.createElement ) { while ( list.length ) { safeFrag.createElement( list.pop() ); } } return safeFrag; } var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, rleadingWhitespace = /^\s+/, rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, rtagName = /<([\w:]+)/, rtbody = /]", "i"), // checked="checked" or checked rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, rscriptType = /\/(java|ecma)script/i, rcleanScript = /^\s*", "" ], legend: [ 1, "
", "
" ], thead: [ 1, "", "
" ], tr: [ 2, "", "
" ], td: [ 3, "", "
" ], col: [ 2, "", "
" ], area: [ 1, "", "" ], _default: [ 0, "", "" ] }, safeFragment = createSafeFragment( document ); wrapMap.optgroup = wrapMap.option; wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; wrapMap.th = wrapMap.td; // IE can't serialize and

Loading...

Please wait.
Downloads

Delete selected downloads?

How to delete selected downloads?

Selected downloads will be removed from queue. Already downloaded files remain on disk (this behavior can be changed via option DeleteCleanupDisk).

Selected downloads will be removed from queue. Already downloaded files will be deleted from disk (this behavior can be changed via option DeleteCleanupDisk).

Delete
DownloadsDeleteHelp
History

Delete selected history records?

How to delete selected history records?

Selected records will be deleted from history. All files remain on disk.

Selected records will be deleted from history. All files remain on disk (for failed downloads this behavior can be changed via option DeleteCleanupDisk).

Selected records will be deleted from history. For failed downloads (par-failure or unpack-failure) all downloaded files will be deleted (this behavior can be changed via option DeleteCleanupDisk).

Permanent deleting of hidden records may have an impact on duplicate check and is not recommended.

Delete
HistoryDeleteHelp
History

Download selected nzbs again?

All downloaded files will be deleted and the nzbs will be downloaded again from scratch.

Download Again
History

Mark selected history records as success?

Marking has an effect on duplicate handling and RSS.
Records marked as success considered successfully downloaded and processed. This is useful for downloads repaired or processed outside of NZBGet. Duplicates with higher duplicate scores may be downloaded in the future.

Mark Success
History

Mark selected history records as good?

Marking has an effect on duplicate handling and RSS.
For titles marked as good no more duplicates will be downloaded, even with higher duplicate score. Existing dupe-backups will be removed from history.

Mark Good
History

Mark selected history records as Bad?

Marking has an effect on duplicate handling and RSS.
If dupe-backups exist in the history the best of them will be moved to queue for download. Otherwise the title will be watched and downloaded when it becomes available.

Mark Bad
Messages

Clear Messages?

All log records will be deleted from screen buffer. The log-file remains on disk unchanged.

Clear
Configuration

Delete ?

Delete
Reset

Reset custom counter for all news servers?

Last reset on Fri Apr 04 2014 09:32:24.

Reset
Not yet implemented
Reload (soft-restart)

Reload NZBGet?

The configuration will be reloaded and the program will be reinitialized.

Reload

Reloading NZBGet

Stopping all activities and reloading...

Should this take too long:
  • Try to refresh the page in browser manually.
  • If you changed remote control settings (IP, Port, Password) update the URL in browser accordingly.
Shutdown

Shutdown NZBGet?

The program will be stopped. You will no longer be able to access or start it via web-interface. Make sure you know how to start the program again.

Shutdown
Files Submitted
Scan Completed
Speed Limit Changed
Saved
Pausing
Paused
Resumed
Deleted
Canceled
Moved
Merged
Splitted
Could not split. Check messages for errors.
Cannot split. Some of selected files are already (partially) downloaded.
Please select records first
Please select at least two records
Post-processing-downloads cannot be edited
URLs cannot be merged or paused
Sorted
Please select records first
Deleted
Cleared
Deleted
Returned to Queue
Post-Processing
Saved
Please select records first
Cannot mark URL-records
Marked
Cannot post-process URL- or hidden records
Cannot redownload hidden records
Fetching new items
No records selected
Fetching items
Nothing to save
No changes have been made
Could not save configuration in


Please check file permissions

Reload command has been sent

Please wait few seconds, then refresh the page.
Please select at least one section
Restoring settings...
Could not start update script
Debug
Incorrect period
Volume reset
Testing connection...
Connection successful
Please type a search string first
nzbget-16.4/webui/config.js0000644000175000017500000023146312630544544015507 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2012-2015 Andrey Prygunkov * * 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$ * */ /* * In this module: * 1) Loading of program options and post-processing scripts options; * 2) Settings tab; * 3) Function "Reload". */ /*** OPTIONS AND CONFIGS (FROM CONFIG FILES) **************************************/ var Options = (new function($) { 'use strict'; // Properties (public) this.options; this.postParamConfig; this.categories = []; this.restricted = false; // State var _this = this; var serverTemplateData = null; var serverValues; var loadComplete; var loadConfigError; var loadServerTemplateError; var shortScriptNames = []; var HIDDEN_SECTIONS = ['DISPLAY (TERMINAL)', 'POSTPROCESSING-PARAMETERS', 'POST-PROCESSING-PARAMETERS', 'POST-PROCESSING PARAMETERS']; var POSTPARAM_SECTIONS = ['POSTPROCESSING-PARAMETERS', 'POST-PROCESSING-PARAMETERS', 'POST-PROCESSING PARAMETERS']; this.init = function() { } this.update = function() { // RPC-function "config" returns CURRENT configurations settings loaded in NZBGet RPC.call('config', [], function(_options) { _this.options = _options; initCategories(); _this.restricted = _this.option('ControlPort') === '***'; // loading config templates and build list of post-processing parameters _this.postParamConfig = []; RPC.call('configtemplates', [false], function(data) { initPostParamConfig(data); RPC.next(); }, RPC.next); }); } this.cleanup = function() { serverTemplateData = null; serverValues = null; } this.option = function(name) { var opt = findOption(this.options, name); return opt ? opt.Value : null; } function initCategories() { _this.categories = []; for (var i=0; i < _this.options.length; i++) { var option = _this.options[i]; if ((option.Name.toLowerCase().substring(0, 8) === 'category') && (option.Name.toLowerCase().indexOf('.name') > -1)) { _this.categories.push(option.Value); } } } /*** LOADING CONFIG ********************************************************************/ this.loadConfig = function(callbacks) { loadComplete = callbacks.complete; loadConfigError = callbacks.configError; loadServerTemplateError = callbacks.serverTemplateError; // RPC-function "loadconfig" reads the configuration settings from NZBGet configuration file. // that's not neccessary the same settings returned by RPC-function "config". This could be the case, // for example, if the settings were modified but NZBGet was not restarted. RPC.call('loadconfig', [], serverValuesLoaded, loadConfigError); } function serverValuesLoaded(data) { serverValues = data; RPC.call('configtemplates', [true], serverTemplateLoaded, loadServerTemplateError); } function serverTemplateLoaded(data) { serverTemplateData = data; complete(); } function complete() { initShortScriptNames(serverTemplateData); if (serverTemplateData === null) { // the loading was cancelled and the data was discarded (via method "cleanup()") return; } var config = []; var serverConfig = readConfigTemplate(serverTemplateData[0].Template, undefined, HIDDEN_SECTIONS, '', ''); mergeValues(serverConfig.sections, serverValues); config.values = serverValues; config.push(serverConfig); // read scripts configs for (var i=1; i < serverTemplateData.length; i++) { var scriptName = serverTemplateData[i].Name; var scriptConfig = readConfigTemplate(serverTemplateData[i].Template, undefined, HIDDEN_SECTIONS, scriptName + ':'); scriptConfig.scriptName = scriptName; scriptConfig.id = scriptName.replace(/ |\/|\\|[\.|$|\:|\*]/g, '_'); scriptConfig.name = scriptName.substr(0, scriptName.lastIndexOf('.')) || scriptName; // remove file extension scriptConfig.name = scriptConfig.name.replace(/\\/, ' \\ ').replace(/\//, ' / '); scriptConfig.shortName = shortScriptName(scriptName); scriptConfig.shortName = scriptConfig.shortName.replace(/\\/, ' \\ ').replace(/\//, ' / '); scriptConfig.post = serverTemplateData[i].PostScript; scriptConfig.scan = serverTemplateData[i].ScanScript; scriptConfig.queue = serverTemplateData[i].QueueScript; scriptConfig.scheduler = serverTemplateData[i].SchedulerScript; scriptConfig.feed = serverTemplateData[i].FeedScript; mergeValues(scriptConfig.sections, serverValues); config.push(scriptConfig); } serverValues = null; loadComplete(config); } this.reloadConfig = function(_serverValues, _complete) { loadComplete = _complete; serverValues = _serverValues; complete(); } /*** PARSE CONFIG AND BUILD INTERNAL STRUCTURES **********************************************/ function readConfigTemplate(filedata, visiblesections, hiddensections, nameprefix) { var config = { description: '', nameprefix: nameprefix, sections: [] }; var section = null; var description = ''; var firstdescrline = ''; var data = filedata.split('\n'); for (var i=0, len=data.length; i < len; i++) { var line = data[i].replace(/\r+$/,''); // remove possible trailing CR-characters if (line.substring(0, 4) === '### ') { var section = {}; section.name = line.substr(4, line.length - 8).trim(); section.id = (nameprefix + section.name).replace(/ |\/|\\|[\.|$|\:|\*]/g, '_'); section.options = []; description = ''; section.hidden = !(hiddensections === undefined || (hiddensections.indexOf(section.name) == -1)) || (visiblesections !== undefined && (visiblesections.indexOf(section.name) == -1)); section.postparam = POSTPARAM_SECTIONS.indexOf(section.name) > -1; config.sections.push(section); } else if (line.substring(0, 2) === '# ' || line === '#') { if (description !== '') { description += ' '; } if (line[2] === ' ' && line[3] !== ' ' && description.substring(description.length-4, 4) != '\n \n ') { description += '\n'; } description += line.substr(1, 10000).trim(); var lastchar = description.substr(description.length - 1, 1); if (lastchar === '.' && firstdescrline === '') { firstdescrline = description; description = ''; } if ('.;:'.indexOf(lastchar) > -1 || line === '#') { description += '\n'; } } else if (line.indexOf('=') > -1) { if (!section) { // bad template file; create default section. section = {}; section.name = 'OPTIONS'; section.id = (nameprefix + section.name).replace(/ |\/|[\.|$|\:|\*]/g, '_'); section.options = []; description = ''; config.sections.push(section); } var option = {}; var enabled = line.substr(0, 1) !== '#'; option.caption = line.substr(enabled ? 0 : 1, line.indexOf('=') - (enabled ? 0 : 1)).trim(); option.name = (nameprefix != '' ? nameprefix : '') + option.caption; option.defvalue = line.substr(line.indexOf('=') + 1, 1000).trim(); option.value = null; option.sectionId = section.id; option.select = []; var pstart = firstdescrline.lastIndexOf('('); var pend = firstdescrline.lastIndexOf(')'); if (pstart > -1 && pend > -1 && pend === firstdescrline.length - 2) { var paramstr = firstdescrline.substr(pstart + 1, pend - pstart - 1); var params = paramstr.split(','); for (var pj=0; pj < params.length; pj++) { option.select.push(params[pj].trim()); } firstdescrline = firstdescrline.substr(0, pstart).trim() + '.'; } if (option.name.substr(nameprefix.length, 1000).indexOf('1.') > -1) { section.multi = true; section.multiprefix = option.name.substr(0, option.name.indexOf('1.')); } if (!section.multi || option.name.indexOf('1.') > -1) { section.options.push(option); } if (section.multi) { option.template = true; } option.description = firstdescrline + description; description = ''; firstdescrline = ''; } else { if (!section && firstdescrline !== '') { config.description = firstdescrline + description; } else if (section && section.options.length === 0) { section.description = firstdescrline + description; } description = ''; firstdescrline = ''; } } return config; } function findOption(options, name) { if (!options) { return null; } name = name.toLowerCase(); for (var i=0; i < options.length; i++) { var option = options[i]; if ((option.Name && option.Name.toLowerCase() === name) || (option.name && option.name.toLowerCase() === name)) { return option; } } return null; } this.findOption = findOption; function mergeValues(config, values) { // copy values for (var i=0; i < config.length; i++) { var section = config[i]; if (section.multi) { // multi sections (news-servers, scheduler) var subexists = true; for (var k=1; subexists; k++) { subexists = false; for (var m=0; m < section.options.length; m++) { var option = section.options[m]; if (option.name.indexOf('1.') > -1) { var name = option.name.replace(/1/, k); var val = findOption(values, name); if (val) { subexists = true; break; } } } if (subexists) { for (var m=0; m < section.options.length; m++) { var option = section.options[m]; if (option.template) { var name = option.name.replace(/1/, k); // copy option var newoption = $.extend({}, option); newoption.name = name; newoption.caption = option.caption.replace(/1/, k); newoption.template = false; newoption.multiid = k; newoption.value = null; section.options.push(newoption); var val = findOption(values, name); if (val) { newoption.value = val.Value; } } } } } } else { // simple sections for (var j=0; j < section.options.length; j++) { var option = section.options[j]; option.value = null; var val = findOption(values, option.name); if (val) { option.value = val.Value; } } } } } this.mergeValues = mergeValues; function initShortScriptNames(configTemplatesData) { for (var i=1; i < configTemplatesData.length; i++) { shortScriptNames[configTemplatesData[i].Name] = configTemplatesData[i].DisplayName; } } function shortScriptName(scriptName) { var shortName = shortScriptNames[scriptName]; return shortName ? shortName : scriptName; } this.shortScriptName = shortScriptName; function initPostParamConfig(data) { initShortScriptNames(data); // Create one big post-param section. It consists of one item for every post-processing script // and additionally includes all post-param options from post-param section of each script. var section = {}; section.id = 'PP-Parameters'; section.options = []; section.description = ''; section.hidden = false; section.postparam = true; _this.postParamConfig = [section]; for (var i=1; i < data.length; i++) { if (data[i].PostScript) { var scriptName = data[i].Name; var sectionId = (scriptName + ':').replace(/ |\/|[\.|$|\:|\*]/g, '_'); var option = {}; option.name = scriptName + ':'; option.caption = shortScriptName(scriptName); option.caption = option.caption.replace(/\\/, ' \\ ').replace(/\//, ' / '); option.defvalue = 'no'; option.description = (data[i].Template.trim().split('\n')[0].substr(1, 1000).trim() || 'Post-processing script ' + scriptName + '.'); option.value = null; option.sectionId = sectionId; option.select = ['yes', 'no']; section.options.push(option); var templateData = data[i].Template; var postConfig = readConfigTemplate(templateData, POSTPARAM_SECTIONS, undefined, scriptName + ':'); for (var j=0; j < postConfig.sections.length; j++) { var sec = postConfig.sections[j]; if (!sec.hidden) { for (var n=0; n < sec.options.length; n++) { var option = sec.options[n]; option.sectionId = sectionId; section.options.push(option); } } } } } } }(jQuery)); /*** SETTINGS TAB (UI) *********************************************************/ var Config = (new function($) { 'use strict'; // Controls var $ConfigNav; var $ConfigData; var $ConfigTabBadge; var $ConfigTabBadgeEmpty; var $ConfigContent; var $ConfigInfo; var $ConfigTitle; var $ConfigTable; var $ViewButton; var $LeaveConfigDialog; var $Body; // State var config = null; var values; var filterText = ''; var lastSection; var reloadTime; var updateTabInfo; var restored = false; var compactMode = false; var configSaved = false; var leaveTarget; this.init = function(options) { updateTabInfo = options.updateTabInfo; $Body = $('html, body'); $ConfigNav = $('#ConfigNav'); $ConfigData = $('#ConfigData'); $ConfigTabBadge = $('#ConfigTabBadge'); $ConfigTabBadgeEmpty = $('#ConfigTabBadgeEmpty'); $ConfigContent = $('#ConfigContent'); $ConfigInfo = $('#ConfigInfo'); $ConfigTitle = $('#ConfigTitle'); $ViewButton = $('#Config_ViewButton'); $LeaveConfigDialog = $('#LeaveConfigDialog'); $('#ConfigTable_filter').val(''); Util.show('#ConfigBackupSafariNote', $.browser.safari); $('#ConfigTable_filter').val(''); compactMode = UISettings.read('$Config_ViewCompact', 'no') == 'yes'; setViewMode(); $(window).bind('beforeunload', userLeavesPage); $ConfigNav.on('click', 'li > a', navClick); $ConfigTable = $('#ConfigTable'); $ConfigTable.fasttable( { filterInput: $('#ConfigTable_filter'), filterClearButton: $("#ConfigTable_clearfilter"), filterInputCallback: filterInput, filterClearCallback: filterClear }); } this.config = function() { return config; } this.show = function() { removeSaveBanner(); $('#ConfigSaved').hide(); $('#ConfigLoadInfo').show(); $('#ConfigLoadServerTemplateError').hide(); $('#ConfigLoadError').hide(); $ConfigContent.hide(); configSaved = false; } this.shown = function() { Options.loadConfig({ complete: buildPage, configError: loadConfigError, serverTemplateError: loadServerTemplateError }); } this.hide = function() { Options.cleanup(); config = null; $ConfigNav.children().not('.config-static').remove(); $ConfigData.children().not('.config-static').remove(); } function loadConfigError(message, resultObj) { $('#ConfigLoadInfo').hide(); $('#ConfigLoadError').show(); if (resultObj && resultObj.error && resultObj.error.message) { message = resultObj.error.message; } $('#ConfigLoadErrorText').text(message); } function loadServerTemplateError() { $('#ConfigLoadInfo').hide(); $('#ConfigLoadServerTemplateError').show(); var optConfigTemplate = Options.option('ConfigTemplate'); $('#ConfigLoadServerTemplateErrorEmpty').toggle(optConfigTemplate === ''); $('#ConfigLoadServerTemplateErrorNotFound').toggle(optConfigTemplate !== ''); $('#ConfigLoadServerTemplateErrorWebDir').text(Options.option('WebDir')); $('#ConfigLoadServerTemplateErrorConfigFile').text(Options.option('ConfigFile')); } function findOptionByName(name) { name = name.toLowerCase(); for (var k=0; k < config.length; k++) { var sections = config[k].sections; for (var i=0; i < sections.length; i++) { var section = sections[i]; for (var j=0; j < section.options.length; j++) { var option = section.options[j]; if (!option.template && ((option.Name && option.Name.toLowerCase() === name) || (option.name && option.name.toLowerCase() === name))) { return option; } } } } return null; } this.findOptionByName = findOptionByName; function findOptionById(formId) { for (var k=0; k < config.length; k++) { var sections = config[k].sections; for (var i=0; i < sections.length; i++) { var section = sections[i]; for (var j=0; j < section.options.length; j++) { var option = section.options[j]; if (option.formId === formId) { return option; } } } } return null; } function findSectionById(sectionId) { for (var k=0; k < config.length; k++) { var sections = config[k].sections; for (var i=0; i < sections.length; i++) { var section = sections[i]; if (section.id === sectionId) { return section; } } } return null; } /*** GENERATE HTML PAGE *****************************************************************/ function buildOptionsContent(section, extensionsec) { var html = ''; var lastmultiid = 1; var firstmultioption = true; var hasoptions = false; for (var i=0, op=0; i < section.options.length; i++) { if (i > 0 && extensionsec && Options.restricted) { // in restricted mode don't show any options for extension scripts, // option's content is hidden content anyway (***) break; } var option = section.options[i]; if (!option.template) { if (section.multi && option.multiid !== lastmultiid) { // new set in multi section html += buildMultiRowEnd(section, lastmultiid, true, true); lastmultiid = option.multiid; firstmultioption = true; } if (section.multi && firstmultioption) { html += buildMultiRowStart(section, option.multiid, option); firstmultioption = false; } html += buildOptionRow(option, section); hasoptions = true; op++; } } if (section.multi) { html += buildMultiRowEnd(section, lastmultiid, false, hasoptions); } return html; } this.buildOptionsContent = buildOptionsContent; function buildMultiSetContent(section, multiid) { var html = ''; var firstmultioption = true; var hasoptions = false; for (var i=0, op=0; i < section.options.length; i++) { var option = section.options[i]; if (!option.template && option.multiid === multiid) { if (firstmultioption) { html += buildMultiRowStart(section, multiid, option); firstmultioption = false; } html += buildOptionRow(option, section); hasoptions = true; op++; } } html += buildMultiRowEnd(section, multiid, true, hasoptions); return html; } function buildOptionRow(option, section) { var value = option.value; if (option.value === null) { value = option.defvalue; } option.formId = (option.name.indexOf(':') == -1 ? 'S_' : '') + option.name.replace(/ |\/|\\|[\.|$|\:|\*]/g, '_'); var caption = option.caption; if (section.multi) { caption = '' + caption.substring(0, caption.indexOf('.') + 1) + '' + caption.substring(caption.indexOf('.') + 1); } var html = '
'+ ''+ '
'; if (option.nocontent) { option.type = 'info'; html += '
'; } else if (option.select.length > 1) { option.type = 'switch'; html += '
'; var valfound = false; for (var j=0; j < option.select.length; j++) { var pvalue = option.select[j]; if (value && pvalue.toLowerCase() === value.toLowerCase()) { html += ''; valfound = true; } else { html += ''; } } if (!valfound) { html += ''; } html +='
'; } else if (option.select.length === 1) { option.type = 'numeric'; html += '
'+ ''+ ''+ option.select[0] +''+ '
'; } else if (option.name.toLowerCase() === 'serverpassword') { option.type = 'password'; html += ''; } else if (option.name.toLowerCase().indexOf('username') > -1 || option.name.toLowerCase().indexOf('password') > -1 || (option.name.indexOf('IP') > -1 && option.name.toLowerCase() !== 'authorizedip')) { option.type = 'text'; html += ''; } else if (option.editor) { option.type = 'text'; html += '
'; html += ''; html += ''; html += ''; html += '
'; } else { option.type = 'text'; html += ''; } if (option.description !== '') { var htmldescr = option.description; htmldescr = htmldescr.replace(/NOTE: do not forget to uncomment the next line.\n/, ''); // replace option references var exp = /\<([A-Z0-9\.]*)\>/ig; htmldescr = htmldescr.replace(exp, '$1'); htmldescr = htmldescr.replace(/&/g, '&'); // replace URLs exp = /(http:\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig; htmldescr = htmldescr.replace(exp, "$1"); // highlight first line htmldescr = htmldescr.replace(/\n/, '\n'); htmldescr = '' + htmldescr; htmldescr = htmldescr.replace(/\n/g, '
'); htmldescr = htmldescr.replace(/NOTE: /g, 'NOTE: '); htmldescr = htmldescr.replace(/INFO: /g, 'INFO: '); if (htmldescr.indexOf('INFO FOR DEVELOPERS:') > -1) { htmldescr = htmldescr.replace(/INFO FOR DEVELOPERS:
/g, ''); htmldescr += ''; } if (htmldescr.indexOf('MORE INFO:') > -1) { htmldescr = htmldescr.replace(/MORE INFO:
/g, ''); htmldescr += ''; } if (section.multi) { // replace strings like "TaskX.Command" and "Task1.Command" htmldescr = htmldescr.replace(new RegExp(section.multiprefix + '[X|1]\.', 'g'), ''); } html += '

' + htmldescr + '

'; } html += '
'; html += '
'; return html; } function buildMultiRowStart(section, multiid, option) { var name = option.caption; var setname = name.substr(0, name.indexOf('.')); var html = '
' + setname + '
'; return html; } function buildMultiRowEnd(section, multiid, hasmore, hasoptions) { var name = section.options[0].caption; var setname = name.substr(0, name.indexOf('1')); var html = ''; if (hasoptions) { html += '
'; html += ''; html += ' '; html += ' '; if (setname.toLowerCase() === 'feed') { html += ' '; } if (setname.toLowerCase() === 'server') { html += ' '; } html += '
'; html += '
'; } if (!hasmore) { var nextid = hasoptions ? multiid + 1 : 1; html += '
'; html += ''; html += '
'; } return html; } function buildPage(_config) { config = _config; extendConfig(); $ConfigNav.children().not('.config-static').remove(); $ConfigData.children().not('.config-static').remove(); $ConfigNav.append('
  • '); for (var k=0; k < config.length; k++) { if (k == 1) { $ConfigNav.append('
  • '); } var conf = config[k]; var added = false; for (var i=0; i < conf.sections.length; i++) { var section = conf.sections[i]; if (!section.hidden) { var html = $('
  • ' + section.name + '
  • '); $ConfigNav.append(html); var content = buildOptionsContent(section, k > 0); $ConfigData.append(content); added = true; } } if (!added) { var html = $('
  • ' + conf.name + '
  • '); $ConfigNav.append(html); } } notifyChanges(); $ConfigNav.append('
  • '); $ConfigNav.append('
  • SEARCH RESULTS
  • '); $ConfigNav.toggleClass('long-list', $ConfigNav.children().length > 20); showSection('Config-Info', false); if (filterText !== '') { filterInput(filterText); } $('#ConfigLoadInfo').hide(); $ConfigContent.show(); } function extendConfig() { for (var i=1; i < config.length; i++) { var conf = config[i]; var firstVisibleSection = null; var visibleSections = 0; for (var j=0; j < conf.sections.length; j++) { if (!conf.sections[j].hidden) { if (!firstVisibleSection) { firstVisibleSection = conf.sections[j]; } visibleSections++; } } // rename sections for (var j=0; j < conf.sections.length; j++) { var section = conf.sections[j]; section.name = conf.shortName.toUpperCase() + (visibleSections > 1 ? ' - ' + section.name.toUpperCase() + '' : ''); section.caption = conf.name.toUpperCase() + (visibleSections > 1 ? ' - ' + section.name.toUpperCase() + '' : ''); } if (!firstVisibleSection) { // create new section for virtual option "About". var section = {}; section.name = conf.shortName.toUpperCase(); section.caption = conf.name.toUpperCase(); section.id = conf.id + '_'; section.options = []; firstVisibleSection = section; conf.sections.push(section); } // create virtual option "About" with scripts description. var option = {}; var shortName = conf.scriptName.replace(/^.*[\\\/]/, ''); // leave only file name (remove path) shortName = shortName.substr(0, shortName.lastIndexOf('.')) || shortName; // remove file extension option.caption = 'About ' + shortName; option.name = conf.nameprefix + option.caption; option.value = ''; option.defvalue = ''; option.sectionId = firstVisibleSection.id; option.select = []; var description = conf.description; option.description = description !== '' ? description : 'No description available.'; option.nocontent = true; firstVisibleSection.options.unshift(option); } // register editors for certain options var conf = config[0]; for (var j=0; j < conf.sections.length; j++) { var section = conf.sections[j]; for (var k=0; k < section.options.length; k++) { var option = section.options[k]; var optname = option.name.toLowerCase(); if (optname.indexOf('scriptorder') > -1) { option.editor = { caption: 'Reorder', click: 'Config.editScriptOrder' }; } if (optname.indexOf('postscript') > -1) { option.editor = { caption: 'Choose', click: 'Config.editPostScript' }; } if (optname.indexOf('scanscript') > -1) { option.editor = { caption: 'Choose', click: 'Config.editScanScript' }; } if (optname.indexOf('queuescript') > -1) { option.editor = { caption: 'Choose', click: 'Config.editQueueScript' }; } if (optname.indexOf('feedscript') > -1) { option.editor = { caption: 'Choose', click: 'Config.editFeedScript' }; } if (optname.indexOf('task') > -1 && optname.indexOf('.param') > -1) { option.editor = { caption: 'Choose', click: 'Config.editSchedulerScript' }; } if (optname.indexOf('task') > -1 && optname.indexOf('.command') > -1) { option.onchange = Config.schedulerCommandChanged; } if (optname.indexOf('.filter') > -1) { option.editor = { caption: 'Change', click: 'Config.editFilter' }; } } } } function notifyChanges() { for (var k=0; k < config.length; k++) { var sections = config[k].sections; for (var i=0; i < sections.length; i++) { var section = sections[i]; for (var j=0; j < section.options.length; j++) { var option = section.options[j]; if (option.onchange && !option.template) { option.onchange(option); } } } } } function scrollOptionIntoView(optFormId) { var option = findOptionById(optFormId); // switch to tab and scroll the option into view showSection(option.sectionId, false); var element = $('#' + option.formId); var parent = $('html,body'); parent[0].scrollIntoView(true); var offsetY = 15; if ($('body').hasClass('navfixed')) { offsetY = 55; } parent.animate({ scrollTop: parent.scrollTop() + element.offset().top - parent.offset().top - offsetY }, { duration: 'slow', easing: 'swing' }); } this.switchClick = function(control) { var state = $(control).val().toLowerCase(); $('.btn', $(control).parent()).removeClass('btn-primary'); $(control).addClass('btn-primary'); // not for page Postprocess in download details if (config) { var optFormId = $(control).parent().attr('id'); var option = findOptionById(optFormId); if (option.onchange) { option.onchange(option); } } } function switchGetValue(control) { var state = $('.btn-primary', control).val(); return state; } function switchSetValue(control, value) { $('.btn', control).removeClass('btn-primary'); $('.btn@[value=' + value + ']', control).addClass('btn-primary'); } /*** CHANGE/ADD/REMOVE OPTIONS *************************************************************/ function navClick(event) { event.preventDefault(); var sectionId = $(this).attr('href').substr(1); showSection(sectionId, true); } function showSection(sectionId, animateScroll) { var link = $('a[href="#' + sectionId + '"]', $ConfigNav); $('li', $ConfigNav).removeClass('active'); link.closest('li').addClass('active'); $ConfigContent.removeClass('search'); Util.show($ViewButton, sectionId !== 'Config-Info'); $ConfigInfo.hide(); if (sectionId === 'Search') { search(); return; } lastSection = sectionId; if (sectionId === 'Config-Info') { $ConfigInfo.show(); $ConfigData.children().hide(); $ConfigTitle.text('INFO: SETTINGS'); return; } if (sectionId === 'Config-System') { $ConfigData.children().hide(); $('.config-system', $ConfigData).show(); markLastControlGroup(); $ConfigTitle.text('SYSTEM'); return; } $ConfigData.children().hide(); var opts = $('.' + sectionId, $ConfigData); opts.show(); markLastControlGroup(); var section = findSectionById(sectionId); $ConfigTitle.text(section.caption ? section.caption : section.name); $Body.animate({ scrollTop: 0 }, { duration: animateScroll ? 'slow' : 0, easing: 'swing' }); } this.deleteSet = function(control, setname, sectionId) { var multiid = parseInt($(control).attr('data-multiid')); $('#ConfigDeleteConfirmDialog_Option').text(setname + multiid); ConfirmDialog.showModal('ConfigDeleteConfirmDialog', function() { deleteOptionSet(setname, multiid, sectionId); }); } function deleteOptionSet(setname, multiid, sectionId) { // remove options from page, using a temporary div for slide effect var opts = $('.' + sectionId + '.multiid' + multiid, $ConfigData); var div = $('
    '); opts.first().before(div); div.append(opts); div.slideUp('normal', function() { div.remove(); }); // remove option set from config var section = findSectionById(sectionId); for (var j=0; j < section.options.length; j++) { var option = section.options[j]; if (!option.template && option.multiid === multiid) { section.options.splice(j, 1); j--; } } // reformat remaining sets (captions, input IDs, etc.) reformatSection(section, setname); section.modified = true; } function reformatSection(section, setname) { var hasOptions = false; var lastMultiId = 0; for (var j=0; j < section.options.length; j++) { var option = section.options[j]; if (!option.template) { if (option.multiid !== lastMultiId && option.multiid !== lastMultiId + 1) { reformatSet(section, setname, option.multiid, lastMultiId + 1); } lastMultiId = option.multiid; hasOptions = true; } } // update add-button var addButton = $('.config-add.' + section.id, $ConfigData); addButton.text('Add ' + (hasOptions ? 'another ' : '') + setname); } function reformatSet(section, setname, oldMultiId, newMultiId) { for (var j=0; j < section.options.length; j++) { var option = section.options[j]; if (!option.template && option.multiid == oldMultiId) { // reformat multiid var div = $('#' + setname + oldMultiId); div.attr('id', setname + newMultiId); // update captions $('.config-settitle.' + section.id + '.multiid' + oldMultiId, $ConfigData).text(setname + newMultiId); $('.' + section.id + '.multiid' + oldMultiId + ' .config-multicaption', $ConfigData).text(setname + newMultiId + '.'); $('.' + section.id + '.multiid' + oldMultiId + ' .config-delete', $ConfigData).text('Delete ' + setname + newMultiId); //update data id $('.' + section.id + '.multiid' + oldMultiId + ' .config-button', $ConfigData).attr('data-multiid', newMultiId); //update class $('.' + section.id + '.multiid' + oldMultiId, $ConfigData).removeClass('multiid' + oldMultiId).addClass('multiid' + newMultiId); // update input id var oldFormId = option.formId; option.formId = option.formId.replace(new RegExp(option.multiid), newMultiId); $('#' + oldFormId).attr('id', option.formId); // update label data-optid $('a[data-optid=' + oldFormId + ']').attr('data-optid', option.formId); // update editor id $('#' + oldFormId + '_Editor').attr('id', option.formId + '_Editor'); // update name option.name = option.name.replace(new RegExp(option.multiid), newMultiId); option.multiid = newMultiId; } } } this.addSet = function(setname, sectionId) { // find section var section = findSectionById(sectionId); // find max multiid var multiid = 0; for (var j=0; j < section.options.length; j++) { var option = section.options[j]; if (!option.template && option.multiid > multiid) { multiid = option.multiid; } } multiid++; // create new multi set var addedOptions = []; for (var j=0; j < section.options.length; j++) { var option = section.options[j]; if (option.template) { var name = option.name.replace(/1/, multiid); // copy option var newoption = $.extend({}, option); newoption.name = name; newoption.caption = option.caption.replace(/1/, multiid); newoption.template = false; newoption.multiid = multiid; section.options.push(newoption); addedOptions.push(newoption); } } section.modified = true; // visualize new multi set var html = buildMultiSetContent(section, multiid); var addButton = $('.config-add.' + section.id, $ConfigData); addButton.text('Add another ' + setname); // insert before add-button, using a temporary div for slide effect var div = $('
    ' + html + '
    '); div.hide(); addButton.parent().before(div); for (var j=0; j < addedOptions.length; j++) { var option = addedOptions[j]; if (option.onchange) { option.onchange(option); } } div.slideDown('normal', function() { var opts = div.children(); opts.detach(); div.after(opts); div.remove(); }); } this.moveSet = function(control, setname, sectionId, direction) { var id1 = parseInt($(control).attr('data-multiid')); var id2 = direction === 'down' ? id1 + 1 : id1 - 1; // swap options in two sets var opts1 = $('.' + sectionId + '.multiid' + (direction === 'down' ? id1 : id2), $ConfigData); var opts2 = $('.' + sectionId + '.multiid' + (direction === 'down' ? id2 : id1), $ConfigData); if (opts1.length === 0 || opts2.length === 0) { return; } opts1.first().before(opts2); // reformat remaining sets (captions, input IDs, etc.) var section = findSectionById(sectionId); reformatSet(section, setname, id2, 10000 + id2); reformatSet(section, setname, id1, id2); reformatSet(section, setname, 10000 + id2, id1); section.modified = true; } this.viewMode = function() { compactMode = !compactMode; UISettings.write('$Config_ViewCompact', compactMode ? 'yes' : 'no'); setViewMode(); } function setViewMode() { $('#Config_ViewCompact i').toggleClass('icon-ok', compactMode).toggleClass('icon-empty', !compactMode); $ConfigContent.toggleClass('hide-help-block', compactMode); } /*** OPTION SPECIFIC EDITORS *************************************************/ this.editScriptOrder = function(optFormId) { var option = findOptionById(optFormId); ScriptListDialog.showModal(option, config, null); } this.editPostScript = function(optFormId) { var option = findOptionById(optFormId); ScriptListDialog.showModal(option, config, 'post'); } this.editScanScript = function(optFormId) { var option = findOptionById(optFormId); ScriptListDialog.showModal(option, config, 'scan'); } this.editQueueScript = function(optFormId) { var option = findOptionById(optFormId); ScriptListDialog.showModal(option, config, 'queue'); } this.editFeedScript = function(optFormId) { var option = findOptionById(optFormId); ScriptListDialog.showModal(option, config, 'feed'); } this.editSchedulerScript = function(optFormId) { var option = findOptionById(optFormId); var command = getOptionValue(findOptionById(optFormId.replace(/Param/, 'Command'))); if (command !== 'Script') { alert('This button is to choose scheduler scripts when option TaskX.Command is set to "Script".'); return; } ScriptListDialog.showModal(option, config, 'scheduler'); } this.schedulerCommandChanged = function(option) { var command = getOptionValue(option); var btnId = option.formId.replace(/Command/, 'Param_Editor'); Util.show('#' + btnId, command === 'Script'); } /*** RSS FEEDS ********************************************************************/ this.editFilter = function(optFormId) { var option = findOptionById(optFormId); FeedFilterDialog.showModal( option.multiid, getOptionValue(findOptionByName('Feed' + option.multiid + '.Name')), getOptionValue(findOptionByName('Feed' + option.multiid + '.URL')), getOptionValue(findOptionByName('Feed' + option.multiid + '.Filter')), getOptionValue(findOptionByName('Feed' + option.multiid + '.Backlog')), getOptionValue(findOptionByName('Feed' + option.multiid + '.PauseNzb')), getOptionValue(findOptionByName('Feed' + option.multiid + '.Category')), getOptionValue(findOptionByName('Feed' + option.multiid + '.Priority')), getOptionValue(findOptionByName('Feed' + option.multiid + '.Interval')), getOptionValue(findOptionByName('Feed' + option.multiid + '.FeedScript')), function(filter) { var control = $('#' + option.formId); control.val(filter); }); } this.previewFeed = function(control, setname, sectionId) { var multiid = parseInt($(control).attr('data-multiid')); FeedDialog.showModal(multiid, getOptionValue(findOptionByName('Feed' + multiid + '.Name')), getOptionValue(findOptionByName('Feed' + multiid + '.URL')), getOptionValue(findOptionByName('Feed' + multiid + '.Filter')), getOptionValue(findOptionByName('Feed' + multiid + '.Backlog')), getOptionValue(findOptionByName('Feed' + multiid + '.PauseNzb')), getOptionValue(findOptionByName('Feed' + multiid + '.Category')), getOptionValue(findOptionByName('Feed' + multiid + '.Priority')), getOptionValue(findOptionByName('Feed' + multiid + '.Interval')), getOptionValue(findOptionByName('Feed' + multiid + '.FeedScript'))); } /*** TEST SERVER ********************************************************************/ var connecting = false; this.testConnection = function(control, setname, sectionId) { if (connecting) { return; } connecting = true; $('#Notif_Config_TestConnectionProgress').fadeIn(function() { var multiid = parseInt($(control).attr('data-multiid')); var timeout = Math.min(parseInt(getOptionValue(findOptionByName('ArticleTimeout'))), 10); RPC.call('testserver', [ getOptionValue(findOptionByName('Server' + multiid + '.Host')), parseInt(getOptionValue(findOptionByName('Server' + multiid + '.Port'))), getOptionValue(findOptionByName('Server' + multiid + '.Username')), getOptionValue(findOptionByName('Server' + multiid + '.Password')), getOptionValue(findOptionByName('Server' + multiid + '.Encryption')) === 'yes', getOptionValue(findOptionByName('Server' + multiid + '.Cipher')), timeout ], function(errtext) { $('#Notif_Config_TestConnectionProgress').fadeOut(function() { if (errtext == '') { Notification.show('#Notif_Config_TestConnectionOK'); } else { AlertDialog.showModal('Connection test failed', errtext); } }); connecting = false; }, function(message, resultObj) { $('#Notif_Config_TestConnectionProgress').fadeOut(function() { if (resultObj && resultObj.error && resultObj.error.message) { message = resultObj.error.message; } AlertDialog.showModal('Connection test failed', message); connecting = false; }); }); }); } /*** SAVE ********************************************************************/ function getOptionValue(option) { var control = $('#' + option.formId); if (option.type === 'switch') { return switchGetValue(control); } else { return control.val(); } } this.getOptionValue = getOptionValue; function setOptionValue(option, value) { var control = $('#' + option.formId); if (option.type === 'switch') { switchSetValue(control, value); } else { control.val(value); } } this.setOptionValue = setOptionValue; // Checks if there are obsolete or invalid options function invalidOptionsExist() { var hiddenOptions = ['ConfigFile', 'AppBin', 'AppDir', 'Version']; for (var i=0; i < Options.options.length; i++) { var option = Options.options[i]; var confOpt = findOptionByName(option.Name); if (!confOpt && hiddenOptions.indexOf(option.Name) === -1) { return true; } } return false; } function prepareSaveRequest(onlyUserChanges) { var modified = false; var request = []; for (var k=0; k < config.length; k++) { var sections = config[k].sections; for (var i=0; i < sections.length; i++) { var section = sections[i]; if (!section.postparam) { for (var j=0; j < section.options.length; j++) { var option = section.options[j]; if (!option.template && !(option.type === 'info')) { var oldValue = option.value; var newValue = getOptionValue(option); if (section.hidden) { newValue = oldValue; } if (newValue != null) { if (onlyUserChanges) { modified = modified || (oldValue != newValue && oldValue !== null); } else { modified = modified || (oldValue != newValue) || (option.value === null); } var opt = {Name: option.name, Value: newValue}; request.push(opt); } } modified = modified || section.modified; } } } } return modified || (!onlyUserChanges && invalidOptionsExist()) || restored ? request : []; } this.saveChanges = function() { $LeaveConfigDialog.modal('hide'); var serverSaveRequest = prepareSaveRequest(false); if (serverSaveRequest.length === 0) { Notification.show('#Notif_Config_Unchanged'); return; } showSaveBanner(); Util.show('#ConfigSaved_Reload, #ConfigReload', serverSaveRequest.length > 0); if (serverSaveRequest.length > 0) { $('#Notif_Config_Failed_Filename').text(Options.option('ConfigFile')); RPC.call('saveconfig', [serverSaveRequest], saveCompleted); } } function showSaveBanner() { $('#Config_Save').attr('disabled', 'disabled'); } function removeSaveBanner() { $('#Config_Save').removeAttr('disabled'); } function saveCompleted(result) { removeSaveBanner(); if (result) { $ConfigContent.fadeOut(function() { $('#ConfigSaved').fadeIn(); }); } else { Notification.show('#Notif_Config_Failed'); } configSaved = true; } this.canLeaveTab = function(target) { if (!config || prepareSaveRequest(true).length === 0 || configSaved) { return true; } leaveTarget = target; $LeaveConfigDialog.modal({backdrop: 'static'}); return false; } function userLeavesPage(e) { if (config && !configSaved && !UISettings.connectionError && prepareSaveRequest(true).length > 0) { return "Discard changes?"; } } this.discardChanges = function() { configSaved = true; $LeaveConfigDialog.modal('hide'); leaveTarget.click(); } this.scrollToOption = function(event, control) { event.preventDefault(); if ($(control).hasClass('option-name') && !$ConfigContent.hasClass('search')) { // Click on option title scrolls only from Search-page, not from regual pages return; } var optid = $(control).attr('data-optid'); if (!optid) { var optname = $(control).text(); var option = findOptionByName(optname); if (option) { optid = option.formId; } } if (optid) { scrollOptionIntoView(optid); } } this.showSpoiler = function(control) { $(control).hide(); $(control).next().show(); } function filterInput(value) { filterText = value; if (filterText.trim() !== '') { $('.ConfigSearch').show(); showSection('Search', true); } else { filterClear(); } } function filterClear() { filterText = ''; showSection(lastSection, true); $('.ConfigSearch').hide(); $ConfigTabBadge.hide(); $ConfigTabBadgeEmpty.show(); } var searcher = new FastSearcher(); function search() { $ConfigTabBadge.show(); $ConfigTabBadgeEmpty.hide(); $ConfigContent.addClass('search'); $ConfigData.children().hide(); searcher.compile(filterText); var total = 0; var available = 0; for (var k=0; k < config.length; k++) { var sections = config[k].sections; for (var i=0; i < sections.length; i++) { var section = sections[i]; if (!section.hidden) { for (var j=0; j < section.options.length; j++) { var option = section.options[j]; if (!option.template) { total++; if (filterOption(option)) { available++; var opt = $('#' + option.formId).closest('.control-group'); opt.show(); } } } } } } filterStaticPages(); markLastControlGroup(); $ConfigTitle.text('SEARCH RESULTS'); $Body.animate({ scrollTop: 0 }, { duration: 0 }); updateTabInfo($ConfigTabBadge, { filter: true, available: available, total: total}); } function filterOption(option) { return searcher.exec({ name: option.caption, description: option.description, value: (option.value === null ? '' : option.value), _search: ['name', 'description', 'value'] }); } function filterStaticPages() { $ConfigData.children().filter('.config-static').each(function(index, element) { var name = $('.control-label', element).text(); var description = $('.controls', element).text(); var found = searcher.exec({ name: name, description: description, value: '', _search: ['name', 'description'] }); Util.show(element, found); }); } function markLastControlGroup() { $ConfigData.children().removeClass('last-group'); $ConfigData.children().filter(':visible').last().addClass('last-group'); } /*** RELOAD ********************************************************************/ function restart(callback) { Refresher.pause(); $('#ConfigReloadInfoNotes').hide(); $('body').fadeOut(function() { $('#Navbar, #MainContent').hide(); $('#ConfigSaved').hide(); $('body').toggleClass('navfixed', false); $('body').show(); $('#ConfigReloadInfo').fadeIn(); reloadTime = new Date(); callback(); }); } this.reloadConfirm = function() { ConfirmDialog.showModal('ReloadConfirmDialog', Config.reload); } this.reload = function() { $('#ConfigReloadAction').text('Stopping all activities and reloading...'); restart(function() { RPC.call('reload', [], reloadCheckStatus); }); } function reloadCheckStatus() { RPC.call('status', [], function(status) { // OK, checking if it is a restarted instance if (status.UpTimeSec >= Status.status.UpTimeSec) { // the old instance is not restarted yet // waiting 0.5 sec. and retrying setTimeout(reloadCheckStatus, 500); reloadCheckNotes(); } else { // restarted successfully $('#ConfigReloadAction').text('Reloaded successfully. Refreshing the page...'); // refresh page document.location.reload(true); } }, function() { // Failure, waiting 0.5 sec. and retrying setTimeout(reloadCheckStatus, 500); reloadCheckNotes(); }); } function reloadCheckNotes() { // if reload takes more than 30 sec. show additional tips if (new Date() - reloadTime > 30000) { $('#ConfigReloadInfoNotes').show(1000); } } this.applyReloadedValues = function(values) { Options.reloadConfig(values, buildPage); restored = true; } /*** SHUTDOWN ********************************************************************/ this.shutdownConfirm = function() { ConfirmDialog.showModal('ShutdownConfirmDialog', Config.shutdown); } this.shutdown = function() { $('#ConfigReloadTitle').text('Shutdown NZBGet'); $('#ConfigReloadAction').text('Stopping all activities...'); restart(function() { RPC.call('shutdown', [], shutdownCheckStatus); }); } function shutdownCheckStatus() { RPC.call('version', [], function(version) { // the program still runs, waiting 0.5 sec. and retrying setTimeout(shutdownCheckStatus, 500); }, function() { // the program has been stopped $('#ConfigReloadTransmit').hide(); $('#ConfigReloadAction').text('The program has been stopped.'); }); } /*** UPDATE ********************************************************************/ this.checkUpdates = function() { UpdateDialog.showModal(); } }(jQuery)); /*** CHOOSE SCRIPT DIALOG *******************************************************/ var ScriptListDialog = (new function($) { 'use strict' // Controls var $ScriptListDialog; var $ScriptTable; var option; var config; var kind; var scriptList; var allScripts; var orderChanged; var orderMode; this.init = function() { $ScriptListDialog = $('#ScriptListDialog'); $('#ScriptListDialog_Save').click(save); $ScriptTable = $('#ScriptListDialog_ScriptTable'); $ScriptTable.fasttable( { pagerContainer: $('#ScriptListDialog_ScriptTable_pager'), headerCheck: $('#ScriptListDialog_ScriptTable > thead > tr:first-child'), infoEmpty: 'No scripts found. If you just changed option "ScriptDir", save settings and reload NZBGet.', pageSize: 1000 }); $ScriptTable.on('click', 'tbody div.check', function(event) { $ScriptTable.fasttable('itemCheckClick', this.parentNode.parentNode, event); }); $ScriptTable.on('click', 'thead div.check', function() { $ScriptTable.fasttable('titleCheckClick') }); $ScriptTable.on('mousedown', Util.disableShiftMouseDown); $ScriptListDialog.on('hidden', function() { // cleanup $ScriptTable.fasttable('update', []); }); } this.showModal = function(_option, _config, _kind) { option = _option; config = _config; kind = _kind; orderChanged = false; orderMode = option.name === 'ScriptOrder'; if (orderMode) { $('#ScriptListDialog_Title').text('Reorder scripts'); $('#ScriptListDialog_Instruction').text('Hover mouse over table elements for reorder buttons to appear.'); } else { $('#ScriptListDialog_Title').text('Choose scripts'); $('#ScriptListDialog_Instruction').html('Select scripts for option ' + option.name + '.'); } $ScriptTable.toggleClass('table-hidecheck', orderMode); $ScriptTable.toggleClass('table-check table-cancheck', !orderMode); $('#ScriptListDialog_OrderInfo').toggleClass('alert alert-info', !orderMode); Util.show('#ScriptListDialog_OrderInfo', orderMode, 'inline-block'); buildScriptList(); var selectedList = Util.parseCommaList(Config.getOptionValue(option)); updateTable(selectedList); $ScriptListDialog.modal({backdrop: 'static'}); } function updateTable(selectedList) { var reorderButtons = '
    '; var data = []; for (var i=0; i < scriptList.length; i++) { var scriptName = scriptList[i]; var scriptShortName = Options.shortScriptName(scriptName); var fields = ['
    ', '' + scriptShortName + '' + reorderButtons]; var item = { id: scriptName, fields: fields, }; data.push(item); if (!orderMode && selectedList && selectedList.indexOf(scriptName) > -1) { $ScriptTable.fasttable('checkRow', scriptName, true); } } $ScriptTable.fasttable('update', data); } function buildScriptList() { var orderList = Util.parseCommaList(Config.getOptionValue(Config.findOptionByName('ScriptOrder'))); var availableScripts = []; var availableAllScripts = []; for (var i=1; i < config.length; i++) { availableAllScripts.push(config[i].scriptName); if (!kind || config[i][kind]) { availableScripts.push(config[i].scriptName); } } availableScripts.sort(); availableAllScripts.sort(); scriptList = []; allScripts = []; // first add all scripts from orderList for (var i=0; i < orderList.length; i++) { var scriptName = orderList[i]; if (availableScripts.indexOf(scriptName) > -1) { scriptList.push(scriptName); } if (availableAllScripts.indexOf(scriptName) > -1) { allScripts.push(scriptName); } } // add all other scripts of this kind from script list for (var i=0; i < availableScripts.length; i++) { var scriptName = availableScripts[i]; if (scriptList.indexOf(scriptName) === -1) { scriptList.push(scriptName); } } // add all other scripts of other kinds from script list for (var i=0; i < availableAllScripts.length; i++) { var scriptName = availableAllScripts[i]; if (allScripts.indexOf(scriptName) === -1) { allScripts.push(scriptName); } } return scriptList; } function save(e) { e.preventDefault(); if (!orderMode) { var selectedList = ''; var checkedRows = $ScriptTable.fasttable('checkedRows'); for (var i=0; i < scriptList.length; i++) { var scriptName = scriptList[i]; if (checkedRows[scriptName]) { selectedList += (selectedList == '' ? '' : ', ') + scriptName; } } var control = $('#' + option.formId); control.val(selectedList); } if (orderChanged) { var scriptOrderOption = Config.findOptionByName('ScriptOrder'); var control = $('#' + scriptOrderOption.formId); // preserving order of scripts of other kinds which were not visible in the dialog var orderList = []; for (var i=0; i < allScripts.length; i++) { var scriptName = allScripts[i]; if (orderList.indexOf(scriptName) === -1) { if (scriptList.indexOf(scriptName) > -1) { orderList = orderList.concat(scriptList); } else { orderList.push(scriptName); } } } control.val(orderList.join(', ')); } $ScriptListDialog.modal('hide'); } this.move = function(control, direction) { var index = parseInt($('span', $(control).closest('tr')).attr('data-index')); if ((index === 0 && (direction === 'up' || direction === 'top')) || (index === scriptList.length-1 && (direction === 'down' || direction === 'bottom'))) { return; } switch (direction) { case 'up': case 'down': { var newIndex = direction === 'up' ? index - 1 : index + 1; var tmp = scriptList[newIndex]; scriptList[newIndex] = scriptList[index]; scriptList[index] = tmp; break; } case 'top': case 'bottom': { var tmp = scriptList[index]; scriptList.splice(index, 1); if (direction === 'top') { scriptList.unshift(tmp); } else { scriptList.push(tmp); } break; } } if (!orderChanged && !orderMode) { $('#ScriptListDialog_OrderInfo').fadeIn(500); } orderChanged = true; updateTable(); } }(jQuery)); /*** BACKUP/RESTORE SETTINGS *******************************************************/ var ConfigBackupRestore = (new function($) { 'use strict' // State var settings; var filename; this.init = function(options) { $('#Config_RestoreInput')[0].addEventListener('change', restoreSelectHandler, false); } /*** BACKUP ********************************************************************/ this.backupSettings = function() { var settings = ''; for (var i=0; i < Config.config().values.length; i++) { var option = Config.config().values[i]; if (option.Value !== null) { settings += settings==='' ? '' : '\n'; settings += option.Name + '=' + option.Value; } } var pad = function(arg) { return (arg < 10 ? '0' : '') + arg } var dt = new Date(); var datestr = dt.getFullYear() + pad(dt.getMonth() + 1) + pad(dt.getDate()) + '-' + pad(dt.getHours()) + pad(dt.getMinutes()) + pad(dt.getSeconds()); var filename = 'nzbget-' + datestr + '.conf'; if (window.Blob) { var blob = new Blob([settings], {type: "text/plain;charset=utf-8"}); if (navigator.msSaveBlob) { navigator.msSaveBlob(blob, filename); } else { var URL = window.URL || window.webkitURL || window; var object_url = URL.createObjectURL(blob); var save_link = document.createElement('a'); save_link.href = object_url; save_link.download = filename; var event = document.createEvent('MouseEvents'); event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); save_link.dispatchEvent(event); } } else { alert('Unfortunately your browser doesn\'t support access to local file system.\n\n'+ 'To backup settings you can manually save file "nzbget.conf" (' + Options.option('ConfigFile')+ ').'); } } /*** RESTORE ********************************************************************/ this.restoreSettings = function() { if (!window.FileReader) { alert("Unfortunately your browser doesn't support FileReader API."); return; } var testreader = new FileReader(); if (!testreader.readAsBinaryString && !testreader.readAsDataURL) { alert("Unfortunately your browser doesn't support neither \"readAsBinaryString\" nor \"readAsDataURL\" functions of FileReader API."); return; } var inp = $('#Config_RestoreInput'); // Reset file input control (needed for IE10) inp.wrap('
    ').closest('form').get(0).reset(); inp.unwrap(); inp.click(); } function restoreSelectHandler(event) { if (!event.target.files) { alert("Unfortunately your browser doesn't support direct access to local files."); return; } if (event.target.files.length > 0) { restoreFromFile(event.target.files[0]); } } function restoreFromFile(file) { var reader = new FileReader(); reader.onload = function (event) { if (reader.readAsBinaryString) { settings = event.target.result; } else { var base64str = event.target.result.replace(/^data:[^,]+,/, ''); settings = atob(base64str); } filename = file.name; if (settings.indexOf('MainDir=') < 0) { alert('File ' + filename + ' is not a valid NZBGet backup.'); return; } RestoreSettingsDialog.showModal(Config.config(), restoreExecute); }; if (reader.readAsBinaryString) { reader.readAsBinaryString(file); } else { reader.readAsDataURL(file); } } function restoreExecute(selectedSections) { $('#Notif_Config_Restoring').show(); setTimeout(function() { var values = restoreValues(selectedSections); Config.applyReloadedValues(values); $('#Notif_Config_Restoring').hide(); $('#SettingsRestoredDialog').modal({backdrop: 'static'}); }, 50); } function restoreValues(selectedSections) { var config = Config.config(); var values = config.values; settings = settings.split('\n'); for (var i=0; i < settings.length; i++) { var optstr = settings[i]; var ind = optstr.indexOf('='); var option = { Name: optstr.substr(0, ind).trim(), Value: optstr.substr(ind+1, 100000).trim() }; settings[i] = option; } function removeValue(name) { var name = name.toLowerCase(); for (var i=0; i < values.length; i++) { if (values[i].Name.toLowerCase() === name) { values.splice(i, 1); return true; } } return false; } function addValue(name) { var name = name.toLowerCase(); for (var i=0; i < settings.length; i++) { if (settings[i].Name.toLowerCase() === name) { values.push(settings[i]); return true; } } return false; } function restoreOption(option) { if (!option.template && !option.multiid) { removeValue(option.name); addValue(option.name); } else if (option.template) { // delete all multi-options for (var j=1; ; j++) { var optname = option.name.replace('1', j); if (!removeValue(optname)) { break; } } // add all multi-options for (var j=1; ; j++) { var optname = option.name.replace('1', j); if (!addValue(optname)) { break; } } } } for (var k=0; k < config.length; k++) { var conf = config[k]; for (var i=0; i < conf.sections.length; i++) { var section = conf.sections[i]; if (!section.hidden && selectedSections[section.id]) { for (var m=0; m < section.options.length; m++) { restoreOption(section.options[m]); } } } } return values; } }(jQuery)); /*** RESTORE SETTINGS DIALOG *******************************************************/ var RestoreSettingsDialog = (new function($) { 'use strict' // Controls var $RestoreSettingsDialog; var $SectionTable; // State var config; var restoreClick; this.init = function() { $RestoreSettingsDialog = $('#RestoreSettingsDialog'); $('#RestoreSettingsDialog_Restore').click(restore); $SectionTable = $('#RestoreSettingsDialog_SectionTable'); $SectionTable.fasttable( { pagerContainer: $('#RestoreSettingsDialog_SectionTable_pager'), headerCheck: $('#RestoreSettingsDialog_SectionTable > thead > tr:first-child'), infoEmpty: 'No sections found.', pageSize: 1000 }); $SectionTable.on('click', 'tbody div.check', function(event) { $SectionTable.fasttable('itemCheckClick', this.parentNode.parentNode, event); }); $SectionTable.on('click', 'thead div.check', function() { $SectionTable.fasttable('titleCheckClick') }); $SectionTable.on('mousedown', Util.disableShiftMouseDown); $RestoreSettingsDialog.on('hidden', function() { // cleanup $SectionTable.fasttable('update', []); }); } this.showModal = function(_config, _restoreClick) { config = _config; restoreClick = _restoreClick; updateTable(); $RestoreSettingsDialog.modal({backdrop: 'static'}); } function updateTable() { var data = []; for (var k=0; k < config.length; k++) { var conf = config[k]; for (var i=0; i < conf.sections.length; i++) { var section = conf.sections[i]; if (!section.hidden) { var fields = ['
    ', '' + section.name + '']; var item = { id: section.id, fields: fields, }; data.push(item); } } } $SectionTable.fasttable('update', data); } function restore(e) { e.preventDefault(); var selectedSections = []; var checkedRows = $SectionTable.fasttable('checkedRows'); var checkedCount = $SectionTable.fasttable('checkedCount'); if (checkedCount === 0) { Notification.show('#Notif_Config_RestoreSections'); return; } checkedRows = $.extend({}, checkedRows); // clone $RestoreSettingsDialog.modal('hide'); setTimeout(function() { restoreClick(checkedRows); }, 0); } }(jQuery)); /*** UPDATE DIALOG *******************************************************/ var UpdateDialog = (new function($) { 'use strict' // Controls var $UpdateDialog; var $UpdateProgressDialog; var $UpdateProgressDialog_Log; // State var VersionInfo; var PackageInfo; var UpdateInfo; var lastUpTimeSec; var installing = false; var logReceived = false; this.init = function() { $UpdateDialog = $('#UpdateDialog'); $('#UpdateDialog_InstallStable,#UpdateDialog_InstallTesting,#UpdateDialog_InstallDevel').click(install); $UpdateProgressDialog = $('#UpdateProgressDialog'); $UpdateProgressDialog_Log = $('#UpdateProgressDialog_Log'); $UpdateDialog.on('hidden', resumeRefresher); $UpdateProgressDialog.on('hidden', resumeRefresher); } function resumeRefresher() { if (!installing) { Refresher.resume(); } } this.showModal = function() { $('#UpdateDialog_Install').hide(); $('#UpdateDialog_CheckProgress').show(); $('#UpdateDialog_CheckFailed').hide(); $('#UpdateDialog_Versions').hide(); $('#UpdateDialog_UpdateAvail').hide(); $('#UpdateDialog_UpdateNotAvail').hide(); $('#UpdateDialog_UpdateNoInfo').hide(); $('#UpdateDialog_InstalledInfo').show(); $('#UpdateDialog_VerInstalled').text(Options.option('Version')); PackageInfo = {}; VersionInfo = {}; UpdateInfo = {}; installing = false; Refresher.pause(); $UpdateDialog.modal({backdrop: 'static'}); RPC.call('readurl', ['http://nzbget.net/info/nzbget-version.json?nocache=' + new Date().getTime(), 'version info'], loadedUpstreamInfo, error); } function error(e) { $('#UpdateDialog_CheckProgress').hide(); $('#UpdateDialog_CheckFailed').show(); } function parseJsonP(jsonp) { var p = jsonp.indexOf('{'); var obj = JSON.parse(jsonp.substr(p, 10000)); return obj; } function loadedUpstreamInfo(data) { VersionInfo = parseJsonP(data); if (VersionInfo['devel-version']) { loadPackageInfo(); } else { loadGitVerData(); } } function loadGitVerData() { // fetching devel version number from svn viewer RPC.call('readurl', ['https://github.com/nzbget/nzbget', 'git revision info'], function(gitRevData) { RPC.call('readurl', ['https://raw.githubusercontent.com/nzbget/nzbget/develop/configure.ac', 'git branch info'], function(gitBranchData) { var html = document.createElement('DIV'); html.innerHTML = gitRevData; html = html.textContent || html.innerText || ''; html = html.replace(/(?:\r\n|\r|\n)/g, ' '); var rev = html.match(/([0-9\,]*)\s*commits/); if (rev && rev.length > 1) { rev = rev[1].replace(',', ''); var ver = gitBranchData.match(/AC_INIT\(nzbget, (.*), .*/); if (ver && ver.length > 1) { VersionInfo['devel-version'] = ver[1] + '-r' + rev; } } loadPackageInfo(); }, error); }, error); } function loadPackageInfo() { $.get('package-info.json', loadedPackageInfo, 'html').fail(loadedAll); } function loadedPackageInfo(data) { PackageInfo = parseJsonP(data); if (PackageInfo['update-info-link']) { RPC.call('readurl', [PackageInfo['update-info-link'], 'update info'], loadedUpdateInfo, loadedAll); } else if (PackageInfo['update-info-script']) { RPC.call('checkupdates', [], loadedUpdateInfo, loadedAll); } else { loadedAll(); } } function loadedUpdateInfo(data) { UpdateInfo = parseJsonP(data); loadedAll(); } function formatTesting(str) { return str.replace('-testing-', '-'); } function revision(version) { var rev = version.match(/.*r(\d+)/); return rev && rev.length > 1 ? parseInt(rev[1]) : 0; } function vernumber(version) { var ver = version.match(/([\d.]+).*/); return ver && ver.length > 1 ? parseFloat(ver[1]) : 0; } function loadedAll() { var installedVersion = Options.option('Version'); $('#UpdateDialog_CheckProgress').hide(); $('#UpdateDialog_Versions').show(); $('#UpdateDialog_InstalledInfo').show(); $('#UpdateDialog_CurStable').text(VersionInfo['stable-version'] ? VersionInfo['stable-version'] : 'no data'); $('#UpdateDialog_CurTesting').text(VersionInfo['testing-version'] ? formatTesting(VersionInfo['testing-version']) : 'no data'); $('#UpdateDialog_CurDevel').text(VersionInfo['devel-version'] ? formatTesting(VersionInfo['devel-version']) : 'no data'); $('#UpdateDialog_CurNotesStable').attr('href', VersionInfo['stable-release-notes']); $('#UpdateDialog_CurNotesTesting').attr('href', VersionInfo['testing-release-notes']); $('#UpdateDialog_CurNotesDevel').attr('href', VersionInfo['devel-release-notes']); Util.show('#UpdateDialog_CurNotesStable', VersionInfo['stable-release-notes']); Util.show('#UpdateDialog_CurNotesTesting', VersionInfo['testing-release-notes']); Util.show('#UpdateDialog_CurNotesDevel', VersionInfo['devel-release-notes']); $('#UpdateDialog_AvailStable').text(UpdateInfo['stable-version'] ? UpdateInfo['stable-version'] : 'not available'); $('#UpdateDialog_AvailTesting').text(UpdateInfo['testing-version'] ? formatTesting(UpdateInfo['testing-version']) : 'not available'); $('#UpdateDialog_AvailDevel').text(UpdateInfo['devel-version'] ? formatTesting(UpdateInfo['devel-version']) : 'not available'); $('#UpdateDialog_AvailNotesStable').attr('href', UpdateInfo['stable-package-info']); $('#UpdateDialog_AvailNotesTesting').attr('href', UpdateInfo['testing-package-info']); $('#UpdateDialog_AvailNotesDevel').attr('href', UpdateInfo['devel-package-info']); Util.show('#UpdateDialog_AvailNotesStableBlock', UpdateInfo['stable-package-info']); Util.show('#UpdateDialog_AvailNotesTestingBlock', UpdateInfo['testing-package-info']); Util.show('#UpdateDialog_AvailNotesDevelBlock', UpdateInfo['devel-package-info']); var installedRev = revision(installedVersion); var installedVer = vernumber(installedVersion); var installedStable = installedRev === 0 && installedVersion.indexOf('testing') === -1; var canInstallStable = UpdateInfo['stable-version'] && ((installedStable && installedVer < vernumber(UpdateInfo['stable-version'])) || (!installedStable && installedVer <= vernumber(UpdateInfo['stable-version']))); var canInstallTesting = UpdateInfo['testing-version'] && ((installedStable && installedVer < vernumber(UpdateInfo['testing-version'])) || (!installedStable && (installedRev === 0 || installedRev < revision(UpdateInfo['testing-version'])))); var canInstallDevel = UpdateInfo['devel-version'] && ((installedStable && installedVer < vernumber(UpdateInfo['devel-version'])) || (!installedStable && (installedRev === 0 || installedRev < revision(UpdateInfo['devel-version'])))); Util.show('#UpdateDialog_InstallStable', canInstallStable); Util.show('#UpdateDialog_InstallTesting', canInstallTesting); Util.show('#UpdateDialog_InstallDevel', canInstallDevel); var hasUpdateSource = PackageInfo['update-info-link'] || PackageInfo['update-info-script']; var hasUpdateInfo = UpdateInfo['stable-version'] || UpdateInfo['testing-version'] || UpdateInfo['devel-version']; var canUpdate = canInstallStable || canInstallTesting || canInstallDevel; Util.show('#UpdateDialog_UpdateAvail', canUpdate); Util.show('#UpdateDialog_UpdateNotAvail', hasUpdateInfo && !canUpdate); Util.show('#UpdateDialog_UpdateNoInfo', !hasUpdateSource); Util.show('#UpdateDialog_CheckFailed', hasUpdateSource && !hasUpdateInfo); $('#UpdateDialog_AvailRow').toggleClass('hide', !hasUpdateInfo); } function install(e) { e.preventDefault(); var kind = $(this).attr('data-kind'); var script = PackageInfo['install-script']; var info = PackageInfo['install-' + kind + '-info']; if (!script) { alert('Something is wrong with the package configuration file "package-info.json".'); return; } RPC.call('status', [], function(status) { lastUpTimeSec = status.UpTimeSec; RPC.call('startupdate', [kind], updateStarted); }); } function updateStarted(started) { if (!started) { Notification.show('#Notif_StartUpdate_Failed'); return; } installing = true; $UpdateDialog.fadeOut(250, function() { $UpdateProgressDialog_Log.text(''); $UpdateProgressDialog.fadeIn(250, function() { $UpdateDialog.modal('hide'); $UpdateProgressDialog.modal({backdrop: 'static'}); updateLog(); }); }); } function updateLog() { RPC.call('logupdate', [0, 100], function(data) { logReceived = logReceived || data.length > 0; if (logReceived && data.length === 0) { terminated(); } else { updateLogTable(data); setTimeout(updateLog, 500); } }, terminated); } function terminated() { // rpc-failure: the program has been terminated. Waiting for new instance. setLogContentAndScroll($UpdateProgressDialog_Log.html() + '\n' + 'NZBGet has been terminated. Waiting for restart...'); setTimeout(checkStatus, 500); } function setLogContentAndScroll(html) { var scroll = $UpdateProgressDialog_Log.prop('scrollHeight') - $UpdateProgressDialog_Log.prop('scrollTop') === $UpdateProgressDialog_Log.prop('clientHeight'); $UpdateProgressDialog_Log.html(html); if (scroll) { $UpdateProgressDialog_Log.scrollTop($UpdateProgressDialog_Log.prop('scrollHeight')); } } function updateLogTable(messages) { var html = ''; for (var i=0; i < messages.length; i++) { var message = messages[i]; var text = Util.textToHtml(message.Text); if (message.Kind === 'ERROR') { text = '' + text + ''; } html = html + text + '\n'; } setLogContentAndScroll(html); } function checkStatus() { RPC.call('status', [], function(status) { // OK, checking if it is a restarted instance if (status.UpTimeSec >= lastUpTimeSec) { // the old instance is not restarted yet // waiting 0.5 sec. and retrying if ($('#UpdateProgressDialog').is(':visible')) { setTimeout(checkStatus, 500); } } else { // restarted successfully, refresh page setLogContentAndScroll($UpdateProgressDialog_Log.html() + '\n' + 'Successfully started. Refreshing the page...'); setTimeout(function() { document.location.reload(true); }, 1000); } }, function() { // Failure, waiting 0.5 sec. and retrying if ($('#UpdateProgressDialog').is(':visible')) { setTimeout(checkStatus, 500); } }); } }(jQuery)); nzbget-16.4/webui/feed.js0000644000175000017500000006104112630544544015136 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2013-2015 Andrey Prygunkov * * 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$ * */ /* * In this module: * 1) Feeds menu; * 2) Feed view/preview dialog; * 3) Feed filter dialog. */ /*** FEEDS **********************************************/ var Feeds = (new function($) { 'use strict'; this.init = function() { } this.redraw = function() { var menu = $('#RssMenu'); var menuItemTemplate = $('.feed-menu-template', menu); menuItemTemplate.removeClass('feed-menu-template').removeClass('hide').addClass('feed-menu'); var insertPos = $('#RssMenu_Divider', menu); $('.feed-menu', menu).remove(); for (var i=1; ;i++) { var url = Options.option('Feed' + i + '.URL'); if (url === null) { break; } if (url.trim() !== '') { var item = menuItemTemplate.clone(); var name = Options.option('Feed' + i + '.Name'); var a = $('span', item); a.text(name !== '' ? name : 'Feed' + i); a.attr('data-id', i); a.click(viewFeed); var im = $('button', item); im.click(fetchFeed); im.attr('data-id', i); insertPos.before(item); } } Util.show('#RssMenuBlock', $('.feed-menu', menu).length > 0); } function viewFeed() { var id = parseInt($(this).attr('data-id')); FeedDialog.showModal(id); } function fetchFeed() { var id = parseInt($(this).attr('data-id')); RPC.call('fetchfeed', [id], function() { Notification.show('#Notif_Feeds_Fetch'); }); } this.fetchAll = function() { RPC.call('fetchfeed', [0], function() { Notification.show('#Notif_Feeds_Fetch'); }); } }(jQuery)); /*** FEEDS VIEW / PREVIEW DIALOG **********************************************/ var FeedDialog = (new function($) { 'use strict'; // Controls var $FeedDialog; var $ItemTable; // State var items = null; var pageSize = 100; var curFilter = 'ALL'; var filenameMode = false; var tableInitialized = false; this.init = function() { $FeedDialog = $('#FeedDialog'); $ItemTable = $('#FeedDialog_ItemTable'); $ItemTable.fasttable( { filterInput: '#FeedDialog_ItemTable_filter', pagerContainer: '#FeedDialog_ItemTable_pager', headerCheck: '#FeedDialog_ItemTable > thead > tr:first-child', pageSize: pageSize, hasHeader: true, renderCellCallback: itemsTableRenderCellCallback }); $ItemTable.on('click', UISettings.rowSelect ? 'tbody tr' : 'tbody div.check', function(event) { $ItemTable.fasttable('itemCheckClick', UISettings.rowSelect ? this : this.parentNode.parentNode, event); }); $ItemTable.on('click', 'thead div.check', function() { $ItemTable.fasttable('titleCheckClick') }); $ItemTable.on('mousedown', Util.disableShiftMouseDown); $FeedDialog.on('hidden', function() { // cleanup $ItemTable.fasttable('update', []); // resume updates Refresher.resume(); }); TabDialog.extend($FeedDialog); if (UISettings.setFocus) { $FeedDialog.on('shown', function() { //$('#FeedDialog_Name').focus(); }); } } this.showModal = function(id, name, url, filter, backlog, pauseNzb, category, priority, interval, feedscript) { Refresher.pause(); $ItemTable.fasttable('update', []); enableAllButtons(); $FeedDialog.restoreTab(); $('#FeedDialog_ItemTable_filter').val(''); $('#FeedDialog_ItemTable_pagerBlock').hide(); items = null; curFilter = 'ALL'; filenameMode = false; tableInitialized = false; $('#FeedDialog_Toolbar .badge').text('?'); updateFilterButtons(undefined, undefined, undefined, false); tableInitialized = false; $FeedDialog.modal({backdrop: 'static'}); $FeedDialog.maximize({mini: UISettings.miniTheme}); $('.loading-block', $FeedDialog).show(); if (name === undefined) { var name = Options.option('Feed' + id + '.Name'); $('#FeedDialog_Title').text(name !== '' ? name : 'Feed'); RPC.call('viewfeed', [id, false], itemsLoaded, feedFailure); } else { $('#FeedDialog_Title').text(name !== '' ? name : 'Feed Preview'); var feedBacklog = backlog === 'yes'; var feedPauseNzb = pauseNzb === 'yes'; var feedCategory = category; var feedPriority = parseInt(priority); var feedInterval = parseInt(interval); var feedScript = feedscript; RPC.call('previewfeed', [id, name, url, filter, feedBacklog, feedPauseNzb, feedCategory, feedPriority, feedInterval, feedScript, false, 0, ''], itemsLoaded, feedFailure); } } function feedFailure(res) { $FeedDialog.modal('hide'); AlertDialog.showModal('Error', res); } function disableAllButtons() { $('#FeedDialog .modal-footer .btn').attr('disabled', 'disabled'); setTimeout(function() { $('#FeedDialog_Transmit').show(); }, 500); } function enableAllButtons() { $('#FeedDialog .modal-footer .btn').removeAttr('disabled'); $('#FeedDialog_Transmit').hide(); } function itemsLoaded(itemsArr) { $('.loading-block', $FeedDialog).hide(); items = itemsArr; updateTable(); $('.modal-inner-scroll', $FeedDialog).scrollTop(100).scrollTop(0); } function updateTable() { var countNew = 0; var countFetched = 0; var countBacklog = 0; var differentFilenames = false; var data = []; for (var i=0; i < items.length; i++) { var item = items[i]; var age = Util.formatAge(item.Time + UISettings.timeZoneCorrection*60*60); var size = (item.SizeMB > 0 || item.SizeLo > 0 || item.SizeHi > 0) ? Util.formatSizeMB(item.SizeMB, item.SizeLo) : ''; var status; switch (item.Status) { case 'UNKNOWN': status = 'UNKNOWN'; break; case 'BACKLOG': status = 'BACKLOG'; countBacklog +=1; break; case 'FETCHED': status = 'FETCHED'; countFetched +=1; break; case 'NEW': status = 'NEW'; countNew +=1; break; default: status = 'internal error(' + item.Status + ')'; } if (!(curFilter === item.Status || curFilter === 'ALL')) { continue; } differentFilenames = differentFilenames || (item.Filename !== item.Title); var itemName = filenameMode ? item.Filename : item.Title; var name = Util.textToHtml(itemName); name = name.replace(/\./g, '.').replace(/_/g, '_'); var fields; if (!UISettings.miniTheme) { fields = ['
    ', status, name, item.Category, age, size]; } else { var info = '
    ' + name + '' + ' ' + status; fields = [info]; } var item = { id: item.URL, item: item, fields: fields, data: { status: item.Status, name: itemName, category: item.Category, age: age, size: size, _search: true } }; data.push(item); } $ItemTable.fasttable('update', data); $ItemTable.fasttable('setCurPage', 1); Util.show('#FeedDialog_ItemTable_pagerBlock', data.length > pageSize); updateFilterButtons(countNew, countFetched, countBacklog, differentFilenames); } function itemsTableRenderCellCallback(cell, index, item) { if (index > 3) { cell.className = 'text-right'; } } function updateFilterButtons(countNew, countFetched, countBacklog, differentFilenames) { if (countNew != undefined) { $('#FeedDialog_Badge_ALL,#FeedDialog_Badge_ALL2').text(countNew + countFetched + countBacklog); $('#FeedDialog_Badge_NEW,#FeedDialog_Badge_NEW2').text(countNew); $('#FeedDialog_Badge_FETCHED,#FeedDialog_Badge_FETCHED2').text(countFetched); $('#FeedDialog_Badge_BACKLOG,#FeedDialog_Badge_BACKLOG2').text(countBacklog); } $('#FeedDialog_Toolbar .btn').removeClass('btn-inverse'); $('#FeedDialog_Badge_' + curFilter + ',#FeedDialog_Badge_' + curFilter + '2').closest('.btn').addClass('btn-inverse'); $('#FeedDialog_Toolbar .badge').removeClass('badge-active'); $('#FeedDialog_Badge_' + curFilter + ',#FeedDialog_Badge_' + curFilter + '2').addClass('badge-active'); if (differentFilenames != undefined && !tableInitialized) { Util.show('#FeedDialog .FeedDialog-names', differentFilenames); tableInitialized = true; } $('#FeedDialog_Titles,#FeedDialog_Titles2').toggleClass('btn-inverse', !filenameMode); $('#FeedDialog_Filenames,#FeedDialog_Filenames2').toggleClass('btn-inverse', filenameMode); $('#FeedDialog_ItemTable_Name').text(filenameMode ? 'Filename' : 'Title'); } this.fetch = function() { var checkedRows = $ItemTable.fasttable('checkedRows'); var checkedCount = $ItemTable.fasttable('checkedCount'); if (checkedCount === 0) { Notification.show('#Notif_FeedDialog_Select'); return; } disableAllButtons(); var fetchItems = []; for (var i = 0; i < items.length; i++) { var item = items[i]; if (checkedRows[item.URL]) { fetchItems.push(item); } } fetchNextItem(fetchItems); } function fetchNextItem(fetchItems) { if (fetchItems.length > 0) { var name = fetchItems[0].Filename; if (name.substr(name.length-4, 4).toLowerCase() !== '.nzb') { name += '.nzb'; } RPC.call('append', [name, fetchItems[0].URL, fetchItems[0].AddCategory, fetchItems[0].Priority, false, false, fetchItems[0].DupeKey, fetchItems[0].DupeScore, fetchItems[0].DupeMode], function() { fetchItems.shift(); fetchNextItem(fetchItems); }) } else { $FeedDialog.modal('hide'); Notification.show('#Notif_FeedDialog_Fetched'); } } this.filter = function(type) { curFilter = type; updateTable(); } this.setFilenameMode = function(mode) { filenameMode = mode; updateTable(); } }(jQuery)); /*** FEED FILTER DIALOG **********************************************/ var FeedFilterDialog = (new function($) { 'use strict'; // Controls var $FeedFilterDialog; var $ItemTable; var $Splitter; var $FilterInput; var $FilterBlock; var $FilterLines; var $FilterNumbers; var $PreviewBlock; var $ModalBody; var $LoadingBlock; var $CHAutoRematch; var $RematchIcon; // State var items = null; var pageSize = 100; var curFilter = 'ALL'; var filenameMode = false; var tableInitialized = false; var saveCallback; var splitStartPos; var feedId; var feedName; var feedUrl; var feedFilter; var feedBacklog; var feedPauseNzb; var feedCategory; var feedPriority; var feedInterval; var feedScript; var cacheTimeSec; var cacheId; var updating; var updateTimerIntitialized = false; var autoUpdate = false; var splitRatio; var firstUpdate; var lineNo; var showLines; this.init = function() { $FeedFilterDialog = $('#FeedFilterDialog'); $Splitter = $('#FeedFilterDialog_Splitter'); $Splitter.mousedown(splitterMouseDown); $('#FeedFilterDialog_Save').click(save); $FilterInput = $('#FeedFilterDialog_FilterInput'); $FilterBlock = $('#FeedFilterDialog_FilterBlock'); $FilterLines = $('#FeedFilterDialog_FilterLines'); $FilterNumbers = $('#FeedFilterDialog_FilterNumbers'); $PreviewBlock = $('#FeedFilterDialog_PreviewBlock'); $ModalBody = $('.modal-body', $FeedFilterDialog); $LoadingBlock = $('.loading-block', $FeedFilterDialog); $CHAutoRematch = $('#FeedFilterDialog_CHAutoRematch'); $RematchIcon = $('#FeedFilterDialog_RematchIcon'); autoUpdate = UISettings.read('$FeedFilterDialog_AutoRematch', '1') == '1'; updateRematchState(); initLines(); $ItemTable = $('#FeedFilterDialog_ItemTable'); $ItemTable.fasttable( { filterInput: '', pagerContainer: '#FeedFilterDialog_ItemTable_pager', headerCheck: '', pageSize: pageSize, hasHeader: true, renderCellCallback: itemsTableRenderCellCallback }); $ItemTable.on('mousedown', Util.disableShiftMouseDown); $FilterInput.keypress(filterKeyPress); $FeedFilterDialog.on('hidden', function() { // cleanup $ItemTable.fasttable('update', []); $(window).off('resize', windowResized); // resume updates Refresher.resume(); }); TabDialog.extend($FeedFilterDialog); if (UISettings.setFocus) { $FeedFilterDialog.on('shown', function() { $FilterInput.focus(); }); } } this.showModal = function(id, name, url, filter, backlog, pauseNzb, category, priority, interval, feedscript, _saveCallback) { saveCallback = _saveCallback; Refresher.pause(); $ItemTable.fasttable('update', []); $FeedFilterDialog.restoreTab(); $(window).on('resize', windowResized); splitterRestore(); $('#FeedFilterDialog_ItemTable_pagerBlock').hide(); $FilterInput.val(filter.replace(/\s*%\s*/g, '\n')); items = null; firstUpdate = true; curFilter = 'ALL'; filenameMode = false; tableInitialized = false; $('#FeedFilterDialog_Toolbar .badge').text('?'); updateFilterButtons(undefined, undefined, undefined, false); tableInitialized = false; $FeedFilterDialog.modal({backdrop: 'static'}); $FeedFilterDialog.maximize({mini: UISettings.miniTheme}); updateLines(); $LoadingBlock.show(); $('#FeedFilterDialog_Title').text(name !== '' ? name : 'Feed Preview'); feedId = id; feedName = name; feedUrl = url; feedFilter = filter; feedBacklog = backlog === 'yes'; feedPauseNzb = pauseNzb === 'yes'; feedCategory = category; feedPriority = parseInt(priority); feedInterval = parseInt(interval); feedScript = feedscript; cacheId = '' + Math.random()*10000000; cacheTimeSec = 60*10; // 10 minutes if (url !== '') { RPC.call('previewfeed', [feedId, name, url, filter, feedBacklog, feedPauseNzb, feedCategory, feedPriority, feedInterval, feedScript, true, cacheTimeSec, cacheId], itemsLoaded, feedFailure); } else { $LoadingBlock.hide(); } } this.rematch = function() { updateFilter(); } function updateFilter() { if (feedUrl == '') { return; } tableInitialized = false; updating = true; var filter = $FilterInput.val().replace(/\n/g, '%'); RPC.call('previewfeed', [feedId, feedName, feedUrl, filter, feedBacklog, feedPauseNzb, feedCategory, feedPriority, feedInterval, feedScript, true, cacheTimeSec, cacheId], itemsLoaded, feedFailure); setTimeout(function() { if (updating) { $LoadingBlock.show(); } }, 500); } function feedFailure(msg, result) { updating = false; var filter = $FilterInput.val().replace(/\n/g, ' % '); if (firstUpdate && filter === feedFilter) { $FeedFilterDialog.modal('hide'); } $LoadingBlock.hide(); AlertDialog.showModal('Error', result ? result.error.message : msg); } function itemsLoaded(itemsArr) { updating = false; $LoadingBlock.hide(); items = itemsArr; updateTable(); if (firstUpdate) { $('.modal-inner-scroll', $FeedFilterDialog).scrollTop(100).scrollTop(0); } firstUpdate = false; if (!updateTimerIntitialized) { setupUpdateTimer(); updateTimerIntitialized = true; } } function updateTable() { var countAccepted = 0; var countRejected = 0; var countIgnored = 0; var differentFilenames = false; var filter = $FilterInput.val().split('\n'); var data = []; for (var i=0; i < items.length; i++) { var item = items[i]; var age = Util.formatAge(item.Time + UISettings.timeZoneCorrection*60*60); var size = (item.SizeMB > 0 || item.SizeLo > 0 || item.SizeHi > 0) ? Util.formatSizeMB(item.SizeMB, item.SizeLo) : ''; var status; switch (item.Match) { case 'ACCEPTED': var addInfo = [item.AddCategory !== feedCategory ? 'category: ' + item.AddCategory : null, item.Priority !== feedPriority ? DownloadsUI.buildPriorityText(item.Priority) : null, item.PauseNzb !== feedPauseNzb ? (item.PauseNzb ? 'paused' : 'unpaused') : null, item.DupeScore != 0 ? 'dupe-score: ' + item.DupeScore : null, item.DupeKey !== '' ? 'dupe-key: ' + item.DupeKey : null, item.DupeMode !== 'SCORE' ? 'dupe-mode: ' + item.DupeMode.toLowerCase() : null]. filter(function(e){return e}).join('; '); status = 'ACCEPTED'; countAccepted += 1; break; case 'REJECTED': status = 'REJECTED'; countRejected += 1; break; case 'IGNORED': status = 'IGNORED'; countIgnored += 1; break; default: status = 'internal error(' + item.Match + ')'; break; } if (!(curFilter === item.Match || curFilter === 'ALL')) { continue; } differentFilenames = differentFilenames || (item.Filename !== item.Title); var itemName = filenameMode ? item.Filename : item.Title; var name = Util.textToHtml(itemName); name = name.replace(/\./g, '.').replace(/_/g, '_'); var rule = ''; if (item.Rule > 0) { rule = ' ' + item.Rule + ' '; } var fields; if (!UISettings.miniTheme) { fields = [status, rule, name, item.Category, age, size]; } else { var info = '' + name + '' + ' ' + status; fields = [info]; } var dataItem = { id: item.URL, item: item, fields: fields, data: { match: item.Match, rule: item.Rule, title: itemName, category: item.Category, age: age, size: size, _search: true } }; data.push(dataItem); } $ItemTable.fasttable('update', data); Util.show('#FeedFilterDialog_ItemTable_pagerBlock', data.length > pageSize); updateFilterButtons(countAccepted, countRejected, countIgnored, differentFilenames); } function itemsTableRenderCellCallback(cell, index, item) { if (index > 3) { cell.className = 'text-right'; } } function updateFilterButtons(countAccepted, countRejected, countIgnored, differentFilenames) { if (countAccepted != undefined) { $('#FeedFilterDialog_Badge_ALL,#FeedFilterDialog_Badge_ALL2').text(countAccepted + countRejected + countIgnored); $('#FeedFilterDialog_Badge_ACCEPTED,#FeedFilterDialog_Badge_ACCEPTED2').text(countAccepted); $('#FeedFilterDialog_Badge_REJECTED,#FeedFilterDialog_Badge_REJECTED2').text(countRejected); $('#FeedFilterDialog_Badge_IGNORED,#FeedFilterDialog_Badge_IGNORED2').text(countIgnored); } $('#FeedFilterDialog_Toolbar .FeedFilterDialog-filter .btn').removeClass('btn-inverse'); $('#FeedFilterDialog_Badge_' + curFilter + ',#FeedFilterDialog_Badge_' + curFilter + '2').closest('.btn').addClass('btn-inverse'); $('#FeedFilterDialog_Toolbar .badge').removeClass('badge-active'); $('#FeedFilterDialog_Badge_' + curFilter + ',#FeedFilterDialog_Badge_' + curFilter + '2').addClass('badge-active'); if (differentFilenames != undefined && !tableInitialized) { Util.show('#FeedFilterDialog .FeedFilterDialog-names', differentFilenames); tableInitialized = true; } $('#FeedFilterDialog_Titles,#FeedFilterDialog_Titles2').toggleClass('btn-inverse', !filenameMode); $('#FeedFilterDialog_Filenames,#FeedFilterDialog_Filenames2').toggleClass('btn-inverse', filenameMode); $('#FeedFilterDialog_ItemTable_Name').text(filenameMode ? 'Filename' : 'Title'); } this.filter = function(type) { curFilter = type; updateTable(); } this.setFilenameMode = function(mode) { filenameMode = mode; updateTable(); } function save(e) { e.preventDefault(); $FeedFilterDialog.modal('hide'); var filter = $FilterInput.val().replace(/\n/g, ' % '); saveCallback(filter); } function setupUpdateTimer() { // Create a timer which gets reset upon every keyup event. // Perform filter only when the timer's wait is reached (user finished typing or paused long enough to elapse the timer). // Do not perform the filter if the query has not changed. var timer; var lastFilter = $FilterInput.val(); $FilterInput.keyup(function() { var timerCallback = function() { var value = $FilterInput.val(); if (value != lastFilter) { lastFilter = value; if (autoUpdate) { updateFilter(); } } }; // Reset the timer clearTimeout(timer); timer = setTimeout(timerCallback, 500); return false; }); } this.autoRematch = function() { autoUpdate = !autoUpdate; UISettings.write('$FeedFilterDialog_AutoRematch', autoUpdate ? '1' : '0'); updateRematchState(); if (autoUpdate) { updateFilter(); } } function updateRematchState() { Util.show($CHAutoRematch, autoUpdate); $RematchIcon.toggleClass('icon-process', !autoUpdate); $RematchIcon.toggleClass('icon-process-auto', autoUpdate); } function filterKeyPress(event) { if (event.which == 37) { event.preventDefault(); alert('Percent character (%) cannot be part of a filter because it is used\nas line separator when saving filter into configuration file.'); } } /*** SPLITTER ***/ function splitterMouseDown(e) { e.stopPropagation(); e.preventDefault(); splitStartPos = e.pageX; $(document).bind("mousemove", splitterMouseMove).bind("mouseup", splitterMouseUp); $ModalBody.css('cursor', 'col-resize'); $FilterInput.css('cursor', 'col-resize'); } function splitterMouseMove(e) { var newPos = e.pageX; var right = $PreviewBlock.position().left + $PreviewBlock.width(); newPos = newPos < 150 ? 150 : newPos; newPos = newPos > right - 150 ? right - 150 : newPos; splitterMove(newPos - splitStartPos); splitStartPos = newPos; } function splitterMouseUp(e) { $ModalBody.css('cursor', ''); $FilterInput.css('cursor', ''); $(document).unbind("mousemove", splitterMouseMove).unbind("mouseup", splitterMouseUp); splitterSave(); } function splitterMove(delta) { $FilterBlock.css('width', parseInt($FilterBlock.css('width')) + delta); $PreviewBlock.css('left', parseInt($PreviewBlock.css('left')) + delta); $Splitter.css('left', parseInt($Splitter.css('left')) + delta); } function splitterSave() { if (!UISettings.miniTheme) { splitRatio = parseInt($FilterBlock.css('width')) / $(window).width(); UISettings.write('$FeedFilterDialog_SplitRatio', splitRatio); } } function splitterRestore() { if (!UISettings.miniTheme) { var oldSplitRatio = parseInt($FilterBlock.css('width')) / $(window).width(); splitRatio = UISettings.read('$FeedFilterDialog_SplitRatio', oldSplitRatio); windowResized(); } } function windowResized() { if (!UISettings.miniTheme) { var oldWidth = parseInt($FilterBlock.css('width')); var winWidth = $(window).width(); var newWidth = Math.round(winWidth * splitRatio); var right = winWidth - 30; newWidth = newWidth > right - 150 ? right - 150 : newWidth; newWidth = newWidth < 150 ? 150 : newWidth; splitterMove(newWidth - oldWidth); } } /*** LINE SELECTION ***/ this.selectRule = function(rule) { selectTextareaLine($FilterInput[0], rule); } function selectTextareaLine(tarea, lineNum) { lineNum--; // array starts at 0 var lines = tarea.value.split("\n"); // calculate start/end var startPos = 0, endPos = tarea.value.length; for (var x = 0; x < lines.length; x++) { if (x == lineNum) { break; } startPos += (lines[x].length+1); } var endPos = lines[lineNum].length+startPos; if (typeof(tarea.selectionStart) != "undefined") { tarea.focus(); tarea.selectionStart = startPos; tarea.selectionEnd = endPos; } } /*** LINE NUMBERS ***/ // Idea and portions of code from LinedTextArea plugin by Alan Williamson // http://files.aw20.net/jquery-linedtextarea/jquery-linedtextarea.html function initLines() { showLines = !UISettings.miniTheme; if (showLines) { lineNo = 1; $FilterInput.scroll(updateLines); } } function updateLines() { if (!UISettings.miniTheme && showLines) { var domTextArea = $FilterInput[0]; var scrollTop = domTextArea.scrollTop; var clientHeight = domTextArea.clientHeight; $FilterNumbers.css('margin-top', (-1*scrollTop) + "px"); lineNo = fillOutLines(scrollTop + clientHeight, lineNo); } } function fillOutLines(h, lineNo) { while ($FilterNumbers.height() - h <= 0) { $FilterNumbers.append("
    " + lineNo + "
    "); lineNo++; } return lineNo; } }(jQuery)); nzbget-16.4/webui/edit.js0000644000175000017500000016435412630544544015173 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2012-2015 Andrey Prygunkov * * 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$ * */ /* * In this module: * 1) Download edit dialog; * 2) Download multi edit dialog (edit multiple items); * 3) Download merge dialog; * 4) Download split dialog; * 5) History edit dialog. */ /*** DOWNLOAD EDIT DIALOG ************************************************************/ var DownloadsEditDialog = (new function($) { 'use strict'; // Controls var $DownloadsEditDialog; var $DownloadsFileTable; var $DownloadsEdit_ParamData; var $ServStatsTable; // State var curGroup; var notification = null; var postParams = []; var lastPage; var lastFullscreen; var logFilled; var files; var refreshTimer = 0; var showing; var oldCategory; this.init = function() { $DownloadsEditDialog = $('#DownloadsEditDialog'); $DownloadsEdit_ParamData = $('#DownloadsEdit_ParamData'); $('#DownloadsEdit_Save').click(saveChanges); $('#DownloadsEdit_Pause').click(itemPause); $('#DownloadsEdit_Resume').click(itemResume); $('#DownloadsEdit_Delete').click(itemDelete); $('#DownloadsEdit_CancelPP').click(itemCancelPP); $('#DownloadsEdit_Param, #DownloadsEdit_Log, #DownloadsEdit_File, #DownloadsEdit_Dupe').click(tabClick); $('#DownloadsEdit_Back').click(backClick); $('#DownloadsEdit_Category').change(categoryChange); LogTab.init('Downloads'); $DownloadsFileTable = $('#DownloadsEdit_FileTable'); $DownloadsFileTable.fasttable( { filterInput: '#DownloadsEdit_FileTable_filter', pagerContainer: '#DownloadsEdit_FileTable_pager', headerCheck: '#DownloadsEdit_FileTable > thead > tr:first-child', pageSize: 10000, hasHeader: true, renderCellCallback: fileTableRenderCellCallback }); $ServStatsTable = $('#DownloadsEdit_ServStatsTable'); $ServStatsTable.fasttable( { filterInput: '#DownloadsEdit_ServStatsTable_filter', pagerContainer: '#DownloadsEdit_ServStatsTable_pager', pageSize: 100, maxPages: 3, hasHeader: true, renderCellCallback: EditUI.servStatsTableRenderCellCallback }); $DownloadsFileTable.on('click', UISettings.rowSelect ? 'tbody tr' : 'tbody div.check', function(event) { $DownloadsFileTable.fasttable('itemCheckClick', UISettings.rowSelect ? this : this.parentNode.parentNode, event); }); $DownloadsFileTable.on('click', 'thead div.check', function() { $DownloadsFileTable.fasttable('titleCheckClick') }); $DownloadsFileTable.on('mousedown', Util.disableShiftMouseDown); $DownloadsEditDialog.on('hidden', function() { // cleanup LogTab.reset('Downloads'); $DownloadsFileTable.fasttable('update', []); $DownloadsEdit_ParamData.empty(); clearTimeout(refreshTimer); // resume updates Refresher.resume(); }); TabDialog.extend($DownloadsEditDialog); if (UISettings.setFocus) { $DownloadsEditDialog.on('shown', function() { if ($('#DownloadsEdit_NZBName').is(":visible")) { $('#DownloadsEdit_NZBName').focus(); } }); } } this.showModal = function(nzbid, allGroups, area) { var group = null; // find Group object for (var i=0; i 0 ? Util.formatTimeHMS((group.RemainingSizeMB-group.PausedSizeMB)*1024/(Status.status.DownloadRate/1024)) : ''); var completion = group.SuccessArticles + group.FailedArticles > 0 ? Util.round0(group.SuccessArticles * 100.0 / (group.SuccessArticles + group.FailedArticles)) + '%' : '--'; if (group.FailedArticles > 0 && completion === '100%') { completion = '99.9%'; } var table = ''; //table += 'Age' + age + ''; table += 'Total' + size + ''; table += 'Paused' + pausedSize + ''; table += 'Unpaused' + remaining + ''; //table += 'Size (total/remaining/paused)4.10 / 4.10 / 0.00 GB'; //table += 'Active downloads' + group.ActiveDownloads + ''; //table += 'Estimated time' + estimated + ''; table += 'Health (critical/current)' + Math.floor(group.CriticalHealth / 10) + '% / ' + Math.floor(group.Health / 10) + '%'; table += 'Files (total/remaining/pars)' + group.FileCount + ' / ' + group.RemainingFileCount + ' / ' + group.RemainingParCount + ''; table += '' + (group.ServerStats.length > 0 ? '' : '') + 'Articles (total/completion)' + (group.ServerStats.length > 0 ? ' ' : '') + '' + group.TotalArticles + ' / ' + completion + ''; $('#DownloadsEdit_Statistics').html(table); $('#DownloadsEdit_ServStats').click(tabClick); EditUI.fillServStats($ServStatsTable, group); $ServStatsTable.fasttable('setCurPage', 1); $('#DownloadsEdit_Title').html(Util.formatNZBName(group.NZBName) + (group.Kind === 'URL' ? ' URL' : '')); $('#DownloadsEdit_NZBName').attr('value', group.NZBName); $('#DownloadsEdit_NZBName').attr('readonly', group.postprocess); $('#DownloadsEdit_URL').attr('value', group.URL); // Priority var v = $('#DownloadsEdit_Priority'); DownloadsUI.fillPriorityCombo(v); v.val(group.MaxPriority); if (v.val() != group.MaxPriority) { v.append(''); } v.attr('disabled', 'disabled'); // Category v = $('#DownloadsEdit_Category'); DownloadsUI.fillCategoryCombo(v); v.val(group.Category); if (v.val() != group.Category) { v.append($('').text(group.Category)); } // duplicate settings $('#DownloadsEdit_DupeKey').val(group.DupeKey); $('#DownloadsEdit_DupeScore').val(group.DupeScore); $('#DownloadsEdit_DupeMode').val(group.DupeMode); $DownloadsFileTable.fasttable('update', []); var postParamConfig = ParamTab.createPostParamConfig(); Util.show('#DownloadsEdit_NZBNameReadonly', group.postprocess); Util.show('#DownloadsEdit_CancelPP', group.postprocess); Util.show('#DownloadsEdit_Delete', !group.postprocess); Util.show('#DownloadsEdit_Pause', group.Kind === 'NZB' && !group.postprocess); Util.show('#DownloadsEdit_Resume', false); Util.show('#DownloadsEdit_Save', !group.postprocess); Util.show('#DownloadsEdit_StatisticsGroup', group.Kind === 'NZB'); Util.show('#DownloadsEdit_File', group.Kind === 'NZB'); Util.show('#DownloadsEdit_URLGroup', group.Kind === 'URL'); $('#DownloadsEdit_CategoryGroup').toggleClass('control-group-last', group.Kind === 'URL'); var dupeCheck = Options.option('DupeCheck') === 'yes'; Util.show('#DownloadsEdit_Dupe', dupeCheck); var postParam = postParamConfig[0].options.length > 0 && group.Kind === 'NZB'; var postLog = group.MessageCount > 0; Util.show('#DownloadsEdit_Param', postParam); Util.show('#DownloadsEdit_Log', postLog); if (group.postprocess) { $('#DownloadsEdit_NZBName').attr('disabled', 'disabled'); $('#DownloadsEdit_Priority').attr('disabled', 'disabled'); $('#DownloadsEdit_Category').attr('disabled', 'disabled'); $('#DownloadsEdit_Close').addClass('btn-primary'); $('#DownloadsEdit_Close').text('Close'); } else { $('#DownloadsEdit_NZBName').removeAttr('disabled'); $('#DownloadsEdit_Priority').removeAttr('disabled'); $('#DownloadsEdit_Category').removeAttr('disabled'); $('#DownloadsEdit_Close').removeClass('btn-primary'); $('#DownloadsEdit_Close').text('Cancel'); if (group.RemainingSizeHi == group.PausedSizeHi && group.RemainingSizeLo == group.PausedSizeLo && group.Kind === 'NZB') { $('#DownloadsEdit_Resume').show(); $('#DownloadsEdit_Pause').hide(); } } if (postParam) { postParams = ParamTab.buildPostParamTab($DownloadsEdit_ParamData, postParamConfig, curGroup.Parameters); } EditUI.buildDNZBLinks(curGroup.Parameters, 'DownloadsEdit_DNZB'); enableAllButtons(); $('#DownloadsEdit_GeneralTab').show(); $('#DownloadsEdit_ParamTab').hide(); $('#DownloadsEdit_ServStatsTab').hide(); $('#DownloadsEdit_LogTab').hide(); $('#DownloadsEdit_FileTab').hide(); $('#DownloadsEdit_DupeTab').hide(); $('#DownloadsEdit_Back').hide(); $('#DownloadsEdit_BackSpace').show(); $DownloadsEditDialog.restoreTab(); $('#DownloadsEdit_FileTable_filter').val(''); $DownloadsFileTable.fasttable('setCurPage', 1); $DownloadsFileTable.fasttable('applyFilter', ''); LogTab.reset('Downloads'); files = null; logFilled = false; notification = null; oldCategory = curGroup.Category; if (area === 'backup') { showing = true; $('#DownloadsEdit_ServStats').trigger('click'); } showing = false; $DownloadsEditDialog.modal({backdrop: 'static'}); } function completed() { $DownloadsEditDialog.modal('hide'); Refresher.update(); if (notification) { Notification.show(notification); notification = null; } } function tabClick(e) { e.preventDefault(); $('#DownloadsEdit_Back').fadeIn(showing ? 0 : 500); $('#DownloadsEdit_BackSpace').hide(); var tab = '#' + $(this).attr('data-tab'); lastPage = $(tab); lastFullscreen = ($(this).attr('data-fullscreen') === 'true') && !UISettings.miniTheme; $('#DownloadsEdit_FileBlock').removeClass('modal-inner-scroll'); $('#DownloadsEdit_FileBlock').css('top', ''); if (UISettings.miniTheme && files === null) { $('#DownloadsEdit_FileBlock').css('min-height', $DownloadsEditDialog.height()); } if (UISettings.miniTheme && !logFilled) { $('#DownloadsEdit_LogBlock').css('min-height', $DownloadsEditDialog.height()); } $DownloadsEditDialog.switchTab($('#DownloadsEdit_GeneralTab'), lastPage, e.shiftKey || !UISettings.slideAnimation || showing ? 0 : 500, {fullscreen: lastFullscreen, mini: UISettings.miniTheme, complete: function() { if (!UISettings.miniTheme) { $('#DownloadsEdit_FileBlock').css('top', $('#DownloadsEdit_FileBlock').position().top); $('#DownloadsEdit_FileBlock').addClass('modal-inner-scroll'); } else { $('#DownloadsEdit_FileBlock').css('min-height', ''); $('#DownloadsEdit_LogBlock').css('min-height', ''); } }}); if (tab === '#DownloadsEdit_LogTab' && !logFilled && (curGroup.postprocess || curGroup.MessageCount > 0)) { LogTab.fill('Downloads', curGroup); logFilled = true; } if (tab === '#DownloadsEdit_FileTab' && files === null) { fillFiles(); } if (tab === '#DownloadsEdit_ServStatsTab') { scheduleRefresh(); } } function backClick(e) { e.preventDefault(); $('#DownloadsEdit_Back').fadeOut(500, function() { $('#DownloadsEdit_BackSpace').show(); }); $('#DownloadsEdit_FileBlock').removeClass('modal-inner-scroll'); $('#DownloadsEdit_FileBlock').css('top', ''); $DownloadsEditDialog.switchTab(lastPage, $('#DownloadsEdit_GeneralTab'), e.shiftKey || !UISettings.slideAnimation ? 0 : 500, {fullscreen: lastFullscreen, mini: UISettings.miniTheme, back: true}); clearTimeout(refreshTimer); } function disableAllButtons() { $('#DownloadsEditDialog .modal-footer .btn').attr('disabled', 'disabled'); setTimeout(function() { $('#DownloadsEdit_Transmit').show(); }, 500); } function enableAllButtons() { $('#DownloadsEditDialog .modal-footer .btn').removeAttr('disabled'); $('#DownloadsEdit_Transmit').hide(); } function saveChanges(e) { e.preventDefault(); disableAllButtons(); notification = null; saveName(); } function saveName() { var name = $('#DownloadsEdit_NZBName').val(); name !== curGroup.NZBName && !curGroup.postprocess ? RPC.call('editqueue', ['GroupSetName', 0, name, [curGroup.NZBID]], function() { notification = '#Notif_Downloads_Saved'; savePriority(); }) :savePriority(); } function savePriority() { var priority = parseInt($('#DownloadsEdit_Priority').val()); priority !== curGroup.MaxPriority ? RPC.call('editqueue', ['GroupSetPriority', 0, ''+priority, [curGroup.NZBID]], function() { notification = '#Notif_Downloads_Saved'; saveCategory(); }) : saveCategory(); } function saveCategory() { var category = $('#DownloadsEdit_Category').val(); category !== curGroup.Category ? RPC.call('editqueue', ['GroupSetCategory', 0, category, [curGroup.NZBID]], function() { notification = '#Notif_Downloads_Saved'; saveDupeKey(); }) : saveDupeKey(); } function itemPause(e) { e.preventDefault(); disableAllButtons(); notification = '#Notif_Downloads_Paused'; RPC.call('editqueue', ['GroupPause', 0, '', [curGroup.NZBID]], completed); } function itemResume(e) { e.preventDefault(); disableAllButtons(); notification = '#Notif_Downloads_Resumed'; RPC.call('editqueue', ['GroupResume', 0, '', [curGroup.NZBID]], function() { if (Options.option('ParCheck') === 'force') { completed(); } else { RPC.call('editqueue', ['GroupPauseExtraPars', 0, '', [curGroup.NZBID]], completed); } }); } function itemDelete(e) { e.preventDefault(); DownloadsUI.deleteConfirm(doItemDelete, false, curGroup.Kind === 'NZB', curGroup.Kind === 'URL'); } function doItemDelete(command) { disableAllButtons(); notification = '#Notif_Downloads_Deleted'; RPC.call('editqueue', [command, 0, '', [curGroup.NZBID]], completed); } function itemCancelPP(e) { e.preventDefault(); disableAllButtons(); notification = '#Notif_Downloads_PostCanceled'; RPC.call('editqueue', ['PostDelete', 0, '', [curGroup.NZBID]], completed); } function categoryChange() { var category = $('#DownloadsEdit_Category').val(); ParamTab.reassignParams(postParams, oldCategory, category); oldCategory = category; } /*** TAB: POST-PROCESSING PARAMETERS **************************************************/ function saveParam() { if (curGroup.Kind === 'URL') { completed(); return; } var paramList = ParamTab.prepareParamRequest(postParams); saveNextParam(paramList); } function saveNextParam(paramList) { if (paramList.length > 0) { RPC.call('editqueue', ['GroupSetParameter', 0, paramList[0], [curGroup.NZBID]], function() { notification = '#Notif_Downloads_Saved'; paramList.shift(); saveNextParam(paramList); }) } else { saveFiles(); } } /*** TAB: DUPLICATE SETTINGS **************************************************/ function saveDupeKey() { var value = $('#DownloadsEdit_DupeKey').val(); value !== curGroup.DupeKey ? RPC.call('editqueue', ['GroupSetDupeKey', 0, value, [curGroup.NZBID]], function() { notification = '#Notif_Downloads_Saved'; saveDupeScore(); }) :saveDupeScore(); } function saveDupeScore() { var value = $('#DownloadsEdit_DupeScore').val(); value != curGroup.DupeScore ? RPC.call('editqueue', ['GroupSetDupeScore', 0, value, [curGroup.NZBID]], function() { notification = '#Notif_Downloads_Saved'; saveDupeMode(); }) :saveDupeMode(); } function saveDupeMode() { var value = $('#DownloadsEdit_DupeMode').val(); value !== curGroup.DupeMode ? RPC.call('editqueue', ['GroupSetDupeMode', 0, value, [curGroup.NZBID]], function() { notification = '#Notif_Downloads_Saved'; saveParam(); }) :saveParam(); } /*** TAB: FILES *************************************************************************/ function fillFiles() { $('.loading-block', $DownloadsEditDialog).show(); RPC.call('listfiles', [0, 0, curGroup.NZBID], filesLoaded); } function filesLoaded(fileArr) { $('.loading-block', $DownloadsEditDialog).hide(); files = fileArr; var data = []; for (var i=0; i < files.length; i++) { var file = files[i]; if (!file.status) { file.status = file.Paused ? (file.ActiveDownloads > 0 ? 'pausing' : 'paused') : (file.ActiveDownloads > 0 ? 'downloading' : 'queued'); } var FileSizeMB = (file.FileSizeHi * 4096) + (file.FileSizeLo / 1024 / 1024); var RemainingSizeMB = (file.RemainingSizeHi * 4096) + (file.RemainingSizeLo / 1024 / 1024); var age = Util.formatAge(file.PostTime + UISettings.timeZoneCorrection*60*60); var size = Util.formatSizeMB(FileSizeMB, file.FileSizeLo); if (FileSizeMB !== RemainingSizeMB || file.FileSizeLo !== file.RemainingSizeLo) { size = '(' + Util.round0((file.FileSizeHi > 0 ? RemainingSizeMB / FileSizeMB : file.RemainingSizeLo / file.FileSizeLo) * 100) + '%) ' + size; } var status; switch (file.status) { case 'downloading': case 'pausing': status = '' + file.status + ''; break; case 'paused': status = 'paused'; break; case 'queued': status = 'queued'; break; case 'deleted': status = 'deleted'; break; default: status = 'internal error(' + file.status + ')'; } var name = Util.textToHtml(file.Filename); var fields; if (!UISettings.miniTheme) { var info = name; fields = ['
    ', status, info, age, size]; } else { var info = '
    ' + name + '' + ' ' + (file.status === 'queued' ? '' : status); fields = [info]; } var item = { id: file.ID, file: file, fields: fields, data: { status: file.status, name: file.Filename, age: age, size: size, _search: true } }; data.push(item); } $DownloadsFileTable.fasttable('update', data); } function fileTableRenderCellCallback(cell, index, item) { if (index > 2) { cell.className = 'text-right'; } } this.editActionClick = function(action) { if (files.length == 0) { return; } var checkedRows = $DownloadsFileTable.fasttable('checkedRows'); var checkedCount = $DownloadsFileTable.fasttable('checkedCount'); if (checkedCount === 0) { Notification.show('#Notif_Edit_Select'); return; } for (var i = 0; i < files.length; i++) { var file = files[i]; file.moved = false; } var editIDList = []; var splitError = false; for (var i = 0; i < files.length; i++) { var n = i; if (action === 'down' || action === 'top') { // iterate backwards in the file list n = files.length-1-i; } var file = files[n]; if (checkedRows[file.ID]) { editIDList.push(file.ID); switch (action) { case 'pause': file.status = 'paused'; file.editAction = action; break; case 'resume': file.status = 'queued'; file.editAction = action; break; case 'delete': file.status = 'deleted'; file.editAction = action; break; case 'top': if (!file.moved) { files.splice(n, 1); files.unshift(file); file.moved = true; file.editMoved = true; i--; } break; case 'up': if (!file.moved && i > 0) { files.splice(i, 1); files.splice(i-1, 0, file); file.moved = true; file.editMoved = true; } break; case 'down': if (!file.moved && i > 0) { files.splice(n, 1); files.splice(n+1, 0, file); file.moved = true; file.editMoved = true; } break; case 'bottom': if (!file.moved) { files.splice(i, 1); files.push(file); file.moved = true; file.editMoved = true; i--; } break; case 'split': if (file.ActiveDownloads > 0 || file.Progress > 0) { splitError = true; } break; } } } if (action === 'split') { if (splitError) { Notification.show('#Notif_Downloads_SplitNotPossible'); } else { DownloadsSplitDialog.showModal(curGroup, editIDList); } } filesLoaded(files); } function saveFilesActions(actions, commands) { if (actions.length === 0 || !files || files.length === 0) { saveFileOrder(); return; } var action = actions.shift(); var command = commands.shift(); var IDs = []; for (var i = 0; i < files.length; i++) { var file = files[i]; if (file.editAction === action) { IDs.push(file.ID); } } if (IDs.length > 0) { RPC.call('editqueue', [command, 0, '', IDs], function() { notification = '#Notif_Downloads_Saved'; saveFilesActions(actions, commands); }) } else { saveFilesActions(actions, commands); } } function saveFiles() { saveFilesActions(['pause', 'resume', 'delete'], ['FilePause', 'FileResume', 'FileDelete']); } function saveFileOrder() { if (!files || files.length === 0) { completed(); return; } var IDs = []; var hasMovedFiles = false; for (var i = 0; i < files.length; i++) { var file = files[i]; IDs.push(file.ID); hasMovedFiles |= file.editMoved; } if (hasMovedFiles) { RPC.call('editqueue', ['FileReorder', 0, '', IDs], function() { notification = '#Notif_Downloads_Saved'; completed(); }) } else { completed(); } } /*** TAB: PER-SERVER STATUSTICS *****************************************************************/ function scheduleRefresh() { refreshTimer = setTimeout(updateServStats, UISettings.refreshInterval * 1000); } function updateServStats() { RPC.call('listgroups', [], groups_loaded); } function groups_loaded(groups) { for (var i=0, il=groups.length; i < il; i++) { var group = groups[i]; if (group.NZBID === curGroup.NZBID) { curGroup.ServerStats = group.ServerStats; EditUI.fillServStats($ServStatsTable, group); scheduleRefresh(); break; } } } }(jQuery)); /*** COMMON FUNCTIONS FOR EDIT DIALOGS ************************************************************/ var EditUI = (new function($) { 'use strict' this.buildDNZBLinks = function(parameters, prefix) { $('.' + prefix).hide(); var hasItems = false; for (var i=0; i < parameters.length; i++) { var param = parameters[i]; if (param.Name.substr(0, 6) === '*DNZB:') { var linkName = param.Name.substr(6, 100); var $paramLink = $('#' + prefix + '_' + linkName); if($paramLink.length > 0) { $paramLink.attr('href', param.Value); $paramLink.show(); hasItems = true; } } } Util.show('#' + prefix + '_Section', hasItems); } /*** TAB: SERVER STATISTICS **************************************************/ this.fillServStats = function(table, editItem) { var data = []; for (var i=0; i < Status.status.NewsServers.length; i++) { var server = Status.status.NewsServers[i]; var name = Options.option('Server' + server.ID + '.Name'); if (name === null || name === '') { var host = Options.option('Server' + server.ID + '.Host'); var port = Options.option('Server' + server.ID + '.Port'); name = (host === null ? '' : host) + ':' + (port === null ? '119' : port); } var articles = '--'; var artquota = '--'; var success = '--'; var failures = '--'; for (var j=0; j < editItem.ServerStats.length; j++) { var stat = editItem.ServerStats[j]; if (stat.ServerID === server.ID && stat.SuccessArticles + stat.FailedArticles > 0) { articles = stat.SuccessArticles + stat.FailedArticles; artquota = Util.round0(articles * 100.0 / (editItem.SuccessArticles + editItem.FailedArticles)) + '%'; success = Util.round0(stat.SuccessArticles * 100.0 / articles) + '%'; failures = Util.round0(stat.FailedArticles * 100.0 / articles) + '%'; if (stat.FailedArticles > 0 && failures === '0%') { success = '99.9%'; failures = '0.1%'; } success = '' + success + ''; failures = '' + failures + ''; break; } } var fields = [server.ID + '. ' + name, articles, artquota, success, failures]; var item = { id: server.ID, fields: fields, }; data.push(item); } table.fasttable('update', data); } this.servStatsTableRenderCellCallback = function (cell, index, item) { if (index > 0) { cell.className = 'text-right'; } } }(jQuery)); /*** PARAM TAB FOR EDIT DIALOGS ************************************************************/ var ParamTab = (new function($) { 'use strict' this.buildPostParamTab = function(configData, postParamConfig, parameters) { var postParams = $.extend(true, [], postParamConfig); Options.mergeValues(postParams, parameters); var content = Config.buildOptionsContent(postParams[0]); configData.empty(); configData.append(content); configData.addClass('retain-margin'); var lastClass = ''; var lastDiv = null; for (var i=0; i < configData.children().length; i++) { var div = $(configData.children()[i]); var divClass = div.attr('class'); if (divClass != lastClass && lastClass != '') { lastDiv.addClass('wants-divider'); } lastDiv = div; lastClass = divClass; } return postParams; } this.createPostParamConfig = function() { var postParamConfig = Options.postParamConfig; defineBuiltinParams(postParamConfig); return postParamConfig; } function defineBuiltinParams(postParamConfig) { if (postParamConfig.length == 0) { postParamConfig.push({category: 'P', postparam: true, options: []}); } if (!Options.findOption(postParamConfig[0].options, '*Unpack:')) { postParamConfig[0].options.unshift({name: '*Unpack:Password', value: '', defvalue: '', select: [], caption: 'Password', sectionId: '_Unpack_', description: 'Unpack-password for encrypted archives.'}); postParamConfig[0].options.unshift({name: '*Unpack:', value: '', defvalue: 'yes', select: ['yes', 'no'], caption: 'Unpack', sectionId: '_Unpack_', description: 'Unpack rar and 7-zip archives.'}); } } this.prepareParamRequest = function(postParams) { var request = []; for (var i=0; i < postParams.length; i++) { var section = postParams[i]; for (var j=0; j < section.options.length; j++) { var option = section.options[j]; if (!option.template && !section.hidden) { var oldValue = option.value; var newValue = Config.getOptionValue(option); if (oldValue != newValue && !((oldValue === null || oldValue === '') && newValue === option.defvalue)) { var opt = option.name + '=' + newValue; request.push(opt); } } } } return request; } function buildCategoryScriptList(category) { var scriptList = []; for (var i=0; i < Options.categories.length; i++) { if (category === Options.categories[i]) { scriptList = Util.parseCommaList(Options.option('Category' + (i + 1) + '.PostScript')); if (scriptList.length === 0) { scriptList = Util.parseCommaList(Options.option('PostScript')); } if (Options.option('Category' + (i + 1) + '.Unpack') === 'yes') { scriptList.push('*Unpack'); } return scriptList; } } // empty category or category not found scriptList = Util.parseCommaList(Options.option('PostScript')); if (Options.option('Unpack') === 'yes') { scriptList.push('*Unpack'); } return scriptList; } this.reassignParams = function(postParams, oldCategory, newCategory) { var oldScriptList = buildCategoryScriptList(oldCategory); var newScriptList = buildCategoryScriptList(newCategory); for (var i=0; i < postParams.length; i++) { var section = postParams[i]; for (var j=0; j < section.options.length; j++) { var option = section.options[j]; if (!option.template && !section.hidden && option.name.substr(option.name.length - 1, 1) === ':') { console.log(option.name); var scriptName = option.name.substr(0, option.name.length-1); if (oldScriptList.indexOf(scriptName) > -1 && newScriptList.indexOf(scriptName) === -1) { Config.setOptionValue(option, 'no'); } else if (oldScriptList.indexOf(scriptName) === -1 && newScriptList.indexOf(scriptName) > -1) { Config.setOptionValue(option, 'yes'); } } } } } }(jQuery)); /*** LOG TAB FOR EDIT DIALOGS ************************************************************/ var LogTab = (new function($) { 'use strict' this.init = function(name) { var recordsPerPage = UISettings.read('ItemLogRecordsPerPage', 10); $('#' + name + 'LogRecordsPerPage').val(recordsPerPage); var $LogTable = $('#' + name + 'Edit_LogTable'); $LogTable.fasttable( { filterInput: '#' + name + 'Edit_LogTable_filter', pagerContainer: '#' + name + 'Edit_LogTable_pager', pageSize: recordsPerPage, maxPages: 3, hasHeader: true, renderCellCallback: logTableRenderCellCallback }); } this.reset = function(name) { var $LogTable = $('#' + name + 'Edit_LogTable'); $LogTable.fasttable('update', []); $LogTable.fasttable('setCurPage', 1); $LogTable.fasttable('applyFilter', ''); $('#' + name + 'Edit_LogTable_filter').val(''); } this.fill = function(name, item) { function logLoaded(log) { $('#' + name + 'EditDialog .loading-block').hide(); var $LogTable = $('#' + name + 'Edit_LogTable'); var data = []; for (var i=0; i < log.length; i++) { var message = log[i]; var kind; switch (message.Kind) { case 'INFO': kind = 'info'; break; case 'DETAIL': kind = 'detail'; break; case 'WARNING': kind = 'warning'; break; case 'ERROR': kind = 'error'; break; case 'DEBUG': kind = 'debug'; break; } var text = Util.textToHtml(message.Text); var time = Util.formatDateTime(message.Time + UISettings.timeZoneCorrection*60*60); var fields; if (!UISettings.miniTheme) { fields = [kind, time, text]; } else { var info = kind + ' ' + time + ' ' + text; fields = [info]; } var item = { id: message, fields: fields, data: { kind: message.Kind, time: time, text: message.Text, _search: true } }; data.unshift(item); } $LogTable.fasttable('update', data); } var recordsPerPage = UISettings.read('ItemLogRecordsPerPage', 10); $('#' + name + 'LogRecordsPerPage').val(recordsPerPage); $('#' + name + 'EditDialog .loading-block').show(); RPC.call('loadlog', [item.NZBID, 0, 10000], logLoaded); } function logTableRenderCellCallback(cell, index, item) { if (index === 0) { cell.width = '65px'; } } this.recordsPerPageChange = function(name) { var val = $('#' + name + 'LogRecordsPerPage').val(); UISettings.write('ItemLogRecordsPerPage', val); var $LogTable = $('#' + name + 'Edit_LogTable'); $LogTable.fasttable('setPageSize', val); } }(jQuery)); /*** DOWNLOAD MULTI EDIT DIALOG ************************************************************/ var DownloadsMultiDialog = (new function($) { 'use strict' // Controls var $DownloadsMultiDialog; // State var multiIDList; var notification = null; var oldPriority; var oldCategory; this.init = function() { $DownloadsMultiDialog = $('#DownloadsMultiDialog'); $('#DownloadsMulti_Save').click(saveChanges); $DownloadsMultiDialog.on('hidden', function () { Refresher.resume(); }); if (UISettings.setFocus) { $DownloadsMultiDialog.on('shown', function () { if ($('#DownloadsMulti_Priority').is(":visible")) { $('#DownloadsMulti_Priority').focus(); } }); } } this.showModal = function(nzbIdList, allGroups) { var groups = []; multiIDList = []; for (var i=0; i -1) { groups.push(gr); multiIDList.push(gr.NZBID); } } if (groups.length == 0) { return; } Refresher.pause(); var FileSizeMB = 0, FileSizeLo = 0; var RemainingSizeMB = 0, RemainingSizeLo = 0; var PausedSizeMB = 0, PausedSizeLo = 0; var FileCount = 0, RemainingFileCount = 0, RemainingParCount = 0; var paused = true; var Priority = groups[0].MaxPriority; var PriorityDiff = false; var Category = groups[0].Category; var CategoryDiff = false; for (var i=0; i 0 ? Util.formatTimeHMS((RemainingSizeMB-PausedSizeMB)*1024/(Status.status.DownloadRate/1024)) : ''); var table = ''; table += 'Total' + size + ''; table += 'Paused' + unpausedSize + ''; table += 'Unpaused' + remaining + ''; table += 'Estimated time' + estimated + ''; table += 'Files (total/remaining/pars)' + FileCount + ' / ' + RemainingFileCount + ' / ' + RemainingParCount + ''; $('#DownloadsMulti_Statistics').html(table); $('#DownloadsMulti_Title').text('Multiple records (' + groups.length + ')'); // Priority var v = $('#DownloadsMulti_Priority'); DownloadsUI.fillPriorityCombo(v); v.val(Priority); if (v.val() != Priority) { v.append(''); v.val(Priority); } if (PriorityDiff) { v.append(''); } oldPriority = v.val(); $('#DownloadsMulti_Priority').removeAttr('disabled'); // Category var v = $('#DownloadsMulti_Category'); DownloadsUI.fillCategoryCombo(v); v.val(Category); if (v.val() != Category) { v.append($('').text(Category)); v.val(Category); } if (CategoryDiff) { v.append(''); } oldCategory = v.val(); enableAllButtons(); $('#DownloadsMulti_GeneralTabLink').tab('show'); notification = null; $DownloadsMultiDialog.modal({backdrop: 'static'}); } function enableAllButtons() { $('#DownloadsMulti .modal-footer .btn').removeAttr('disabled'); $('#DownloadsMulti_Transmit').hide(); } function disableAllButtons() { $('#DownloadsMulti .modal-footer .btn').attr('disabled', 'disabled'); setTimeout(function() { $('#DownloadsMulti_Transmit').show(); }, 500); } function saveChanges(e) { e.preventDefault(); disableAllButtons(); savePriority(); } function savePriority() { var priority = $('#DownloadsMulti_Priority').val(); (priority !== oldPriority && priority !== '') ? RPC.call('editqueue', ['GroupSetPriority', 0, priority, multiIDList], function() { notification = '#Notif_Downloads_Saved'; saveCategory(); }) : saveCategory(); } function saveCategory() { var category = $('#DownloadsMulti_Category').val(); (category !== oldCategory && category !== '') ? RPC.call('editqueue', ['GroupApplyCategory', 0, category, multiIDList], function() { notification = '#Notif_Downloads_Saved'; completed(); }) : completed(); } function completed() { $DownloadsMultiDialog.modal('hide'); Refresher.update(); if (notification) { Notification.show(notification); } } }(jQuery)); /*** DOWNLOAD MERGE DIALOG ************************************************************/ var DownloadsMergeDialog = (new function($) { 'use strict' // Controls var $DownloadsMergeDialog; // State var mergeEditIDList; this.init = function() { $DownloadsMergeDialog = $('#DownloadsMergeDialog'); $('#DownloadsMerge_Merge').click(merge); $DownloadsMergeDialog.on('hidden', function () { Refresher.resume(); }); if (UISettings.setFocus) { $DownloadsMergeDialog.on('shown', function () { $('#DownloadsMerge_Merge').focus(); }); } } this.showModal = function(nzbIdList, allGroups) { Refresher.pause(); mergeEditIDList = []; $('#DownloadsMerge_Files').empty(); for (var i = 0; i < allGroups.length; i++) { var group = allGroups[i]; if (nzbIdList.indexOf(group.NZBID) > -1) { mergeEditIDList.push(group.NZBID); var html = '
    ' + Util.formatNZBName(group.NZBName) + '
    '; $('#DownloadsMerge_Files').append(html); } } $DownloadsMergeDialog.modal({backdrop: 'static'}); } function merge() { RPC.call('editqueue', ['GroupMerge', 0, '', mergeEditIDList], completed); } function completed() { $DownloadsMergeDialog.modal('hide'); Refresher.update(); Notification.show('#Notif_Downloads_Merged'); } }(jQuery)); /*** DOWNLOAD SPLIT DIALOG ************************************************************/ var DownloadsSplitDialog = (new function($) { 'use strict' // Controls var $DownloadsSplitDialog; // State var splitEditIDList; this.init = function() { $DownloadsSplitDialog = $('#DownloadsSplitDialog'); $('#DownloadsSplit_Split').click(split); $DownloadsSplitDialog.on('hidden', function () { Refresher.resume(); }); if (UISettings.setFocus) { $DownloadsSplitDialog.on('shown', function () { $('#DownloadsSplit_Merge').focus(); }); } } this.showModal = function(group, editIDList) { Refresher.pause(); splitEditIDList = editIDList; var groupName = group.NZBName + ' (' + editIDList[0] + (editIDList.length > 1 ? '-' + editIDList[editIDList.length-1] : '') + ')'; $('#DownloadsSplit_NZBName').attr('value', groupName); $DownloadsSplitDialog.modal({backdrop: 'static'}); } function split() { var groupName = $('#DownloadsSplit_NZBName').val(); RPC.call('editqueue', ['FileSplit', 0, groupName, splitEditIDList], completed); } function completed(result) { $('#DownloadsEditDialog').modal('hide'); $DownloadsSplitDialog.modal('hide'); Refresher.update(); Notification.show(result ? '#Notif_Downloads_Splitted' : '#Notif_Downloads_SplitError'); } }(jQuery)); /*** EDIT HISTORY DIALOG *************************************************************************/ var HistoryEditDialog = (new function() { 'use strict' // Controls var $HistoryEditDialog; var $HistoryEdit_ParamData; var $ServStatsTable; // State var curHist; var notification = null; var postParams = []; var lastPage; var lastFullscreen; var saveCompleted; var logFilled; this.init = function() { $HistoryEditDialog = $('#HistoryEditDialog'); $HistoryEdit_ParamData = $('#HistoryEdit_ParamData'); $('#HistoryEdit_Save').click(saveChanges); $('#HistoryEdit_Delete').click(itemDelete); $('#HistoryEdit_Return, #HistoryEdit_ReturnURL').click(itemReturn); $('#HistoryEdit_Reprocess').click(itemReprocess); $('#HistoryEdit_Redownload').click(itemRedownload); $('#HistoryEdit_Param, #HistoryEdit_Dupe, #HistoryEdit_Log').click(tabClick); $('#HistoryEdit_Back').click(backClick); $('#HistoryEdit_MarkSuccess').click(itemSuccess); $('#HistoryEdit_MarkGood').click(itemGood); $('#HistoryEdit_MarkBad').click(itemBad); LogTab.init('History'); $ServStatsTable = $('#HistoryEdit_ServStatsTable'); $ServStatsTable.fasttable( { filterInput: '#HistoryEdit_ServStatsTable_filter', pagerContainer: '#HistoryEdit_ServStatsTable_pager', pageSize: 100, maxPages: 3, hasHeader: true, renderCellCallback: EditUI.servStatsTableRenderCellCallback }); $HistoryEditDialog.on('hidden', function () { $HistoryEdit_ParamData.empty(); LogTab.reset('History'); // resume updates Refresher.resume(); }); TabDialog.extend($HistoryEditDialog); } this.showModal = function(hist) { Refresher.pause(); curHist = hist; var status = ''; if (hist.Kind === 'NZB') { if (hist.DeleteStatus === '' || hist.DeleteStatus === 'HEALTH') { status = 'health: ' + Math.floor(hist.Health / 10) + '%'; } if (hist.MarkStatus !== 'NONE') { status += ' ' + buildStatus(hist.MarkStatus, 'Mark: '); } else if (hist.DeleteStatus === 'NONE') { var exParStatus = hist.ExParStatus === 'RECIPIENT' ? ' ' + '' + buildStatus(hist.ExParStatus, 'ExPar: ') + '' : hist.ExParStatus === 'DONOR' ? ' ' + '' + buildStatus(hist.ExParStatus, 'ExPar: ') + '' : ''; status += ' ' + buildStatus(hist.ParStatus, 'Par: ') + exParStatus + ' ' + (Options.option('Unpack') == 'yes' || hist.UnpackStatus != 'NONE' ? buildStatus(hist.UnpackStatus, 'Unpack: ') : '') + ' ' + (hist.MoveStatus === "FAILURE" ? buildStatus(hist.MoveStatus, 'Move: ') : ''); } else { status += ' ' + buildStatus('DELETED-' + hist.DeleteStatus, 'Delete: '); } for (var i=0; i' + (hist.Kind === 'DUP' ? 'hidden' : hist.Kind) + ''); } $('#HistoryEdit_NZBName').val(hist.Name); if (hist.Kind !== 'DUP') { // Category var v = $('#HistoryEdit_Category'); DownloadsUI.fillCategoryCombo(v); v.val(hist.Category); if (v.val() != hist.Category) { v.append($('').text(hist.Category)); } } if (hist.Kind === 'NZB') { $('#HistoryEdit_Path').val(hist.FinalDir !== '' ? hist.FinalDir : hist.DestDir); var size = Util.formatSizeMB(hist.FileSizeMB, hist.FileSizeLo); var completion = hist.SuccessArticles + hist.FailedArticles > 0 ? Util.round0(hist.SuccessArticles * 100.0 / (hist.SuccessArticles + hist.FailedArticles)) + '%' : '--'; if (hist.FailedArticles > 0 && completion === '100%') { completion = '99.9%'; } var time = Util.formatTimeHMS(hist.DownloadTimeSec + hist.PostTotalTimeSec); var table = ''; table += 'Total '+ '' + '' + size + ''; table += 'Files (total/remaining)' + hist.FileCount + ' / ' + hist.RemainingFileCount + ''; table += '' + (hist.ServerStats.length > 0 ? '' : '') + 'Articles (total/completion)' + (hist.ServerStats.length > 0 ? ' ' : '') + '' + hist.TotalArticles + ' / ' + completion + ''; $('#HistoryEdit_Statistics').html(table); $('#HistoryEdit_ServStats').click(tabClick); EditUI.fillServStats($ServStatsTable, hist); $ServStatsTable.fasttable('setCurPage', 1); $('#HistoryEdit_TimeStats').click(tabClick); fillTimeStats(); } $('#HistoryEdit_DupeKey').val(hist.DupeKey); $('#HistoryEdit_DupeScore').val(hist.DupeScore); $('#HistoryEdit_DupeMode').val(hist.DupeMode); $('#HistoryEdit_DupeBackup').prop('checked', hist.DeleteStatus === 'DUPE'); $('#HistoryEdit_DupeBackup').prop('disabled', !(hist.DeleteStatus === 'DUPE' || hist.DeleteStatus === 'MANUAL')); Util.show($('#HistoryEdit_DupeBackup').closest('.control-group'), hist.Kind === 'NZB'); $('#HistoryEdit_DupeMode').closest('.control-group').toggleClass('last-group', hist.Kind !== 'NZB'); Util.show('#HistoryEdit_Return', hist.RemainingFileCount > 0); Util.show('#HistoryEdit_ReturnURL', hist.Kind === 'URL'); Util.show('#HistoryEdit_Redownload', hist.Kind === 'NZB'); Util.show('#HistoryEdit_PathGroup, #HistoryEdit_StatisticsGroup, #HistoryEdit_Reprocess', hist.Kind === 'NZB'); Util.show('#HistoryEdit_CategoryGroup', hist.Kind !== 'DUP'); Util.show('#HistoryEdit_DupGroup', hist.Kind === 'DUP'); var dupeCheck = Options.option('DupeCheck') === 'yes'; Util.show('#HistoryEdit_MarkSuccess', dupeCheck && ((hist.Kind === 'NZB' && hist.MarkStatus !== 'SUCCESS') || (hist.Kind === 'DUP' && hist.DupStatus !== 'SUCCESS')) && hist.Status.substr(0, 7) !== 'SUCCESS'); Util.show('#HistoryEdit_MarkGood', dupeCheck && ((hist.Kind === 'NZB' && hist.MarkStatus !== 'GOOD') || (hist.Kind === 'DUP' && hist.DupStatus !== 'GOOD'))); Util.show('#HistoryEdit_MarkBad', dupeCheck && hist.Kind !== 'URL'); Util.show('#HistoryEdit_Dupe', dupeCheck); $('#HistoryEdit_CategoryGroup').toggleClass('control-group-last', hist.Kind === 'URL'); Util.show('#HistoryEdit_URLGroup', hist.Kind === 'URL'); $('#HistoryEdit_URL').attr('value', hist.URL); var postParamConfig = ParamTab.createPostParamConfig(); var postParam = hist.Kind === 'NZB' && postParamConfig[0].options.length > 0; Util.show('#HistoryEdit_Param', postParam); if (postParam) { postParams = ParamTab.buildPostParamTab($HistoryEdit_ParamData, postParamConfig, curHist.Parameters); } var postLog = hist.MessageCount > 0; Util.show('#HistoryEdit_Log', postLog); EditUI.buildDNZBLinks(curHist.Parameters ? curHist.Parameters : [], 'HistoryEdit_DNZB'); enableAllButtons(); $('#HistoryEdit_GeneralTab').show(); $('#HistoryEdit_ParamTab').hide(); $('#HistoryEdit_ServStatsTab').hide(); $('#HistoryEdit_TimeStatsTab').hide(); $('#HistoryEdit_DupeTab').hide(); $('#HistoryEdit_LogTab').hide(); $('#HistoryEdit_Back').hide(); $('#HistoryEdit_BackSpace').show(); $HistoryEditDialog.restoreTab(); LogTab.reset('History'); logFilled = false; notification = null; $HistoryEditDialog.modal({backdrop: 'static'}); } function buildStatus(status, prefix) { switch (status) { case 'SUCCESS': case 'GOOD': case 'RECIPIENT': case 'DONOR': return '' + prefix + status + ''; case 'FAILURE': return '' + prefix + 'failure'; case 'BAD': return '' + prefix + status + ''; case 'REPAIR_POSSIBLE': return '' + prefix + 'repairable'; case 'MANUAL': // PAR-MANUAL case 'SPACE': case 'PASSWORD': return '' + prefix + status + ''; case 'DELETED-DUPE': case 'DELETED-MANUAL': case 'DELETED-COPY': case 'DELETED-GOOD': case 'DELETED-SUCCESS': return '' + prefix + status.substr(8).toLowerCase() + ''; case 'DELETED-HEALTH': return '' + prefix + 'health'; case 'DELETED-BAD': return '' + prefix + 'bad'; case 'DELETED-SCAN': return '' + prefix + 'scan'; case 'SCAN_SKIPPED': return '' + prefix + 'skipped'; case 'NONE': return '' + prefix + 'none'; default: return '' + prefix + status + ''; } } function fillTimeStats() { var hist = curHist; var downloaded = Util.formatSizeMB(hist.DownloadedSizeMB, hist.DownloadedSizeLo); var speed = hist.DownloadTimeSec > 0 ? Util.formatSpeed((hist.DownloadedSizeMB > 1024 ? hist.DownloadedSizeMB * 1024.0 * 1024.0 : hist.DownloadedSizeLo) / hist.DownloadTimeSec) : '--'; var table = ''; table += 'Downloaded size' + downloaded + ''; table += 'Download speed' + speed + ''; table += 'Total time' + Util.formatTimeHMS(hist.DownloadTimeSec + hist.PostTotalTimeSec) + ''; table += 'Download time' + Util.formatTimeHMS(hist.DownloadTimeSec) + ''; table += 'Verification time ' + Util.formatTimeHMS(hist.ParTimeSec - hist.RepairTimeSec) + ''; table += 'Repair time' + Util.formatTimeHMS(hist.RepairTimeSec) + ''; table += 'Unpack time' + Util.formatTimeHMS(hist.UnpackTimeSec) + ''; table += hist.ExtraParBlocks > 0 ? 'Received extra par-blocks' + hist.ExtraParBlocks + '' : hist.ExtraParBlocks < 0 ? 'Donated par-blocks' + - hist.ExtraParBlocks + '' : ''; $('#HistoryEdit_TimeStatsTable tbody').html(table); } function tabClick(e) { e.preventDefault(); $('#HistoryEdit_Back').fadeIn(500); $('#HistoryEdit_BackSpace').hide(); var tab = '#' + $(this).attr('data-tab'); lastPage = $(tab); lastFullscreen = ($(this).attr('data-fullscreen') === 'true') && !UISettings.miniTheme; $HistoryEditDialog.switchTab($('#HistoryEdit_GeneralTab'), lastPage, e.shiftKey || !UISettings.slideAnimation ? 0 : 500, {fullscreen: lastFullscreen, mini: UISettings.miniTheme}); if (tab === '#HistoryEdit_LogTab' && !logFilled && curHist.MessageCount > 0) { LogTab.fill('History', curHist); logFilled = true; } } function backClick(e) { e.preventDefault(); $('#HistoryEdit_Back').fadeOut(500, function() { $('#HistoryEdit_BackSpace').show(); }); $HistoryEditDialog.switchTab(lastPage, $('#HistoryEdit_GeneralTab'), e.shiftKey || !UISettings.slideAnimation ? 0 : 500, {fullscreen: lastFullscreen, mini: UISettings.miniTheme, back: true}); } function disableAllButtons() { $('#HistoryEditDialog .modal-footer .btn').attr('disabled', 'disabled'); setTimeout(function() { $('#HistoryEdit_Transmit').show(); }, 500); } function enableAllButtons() { $('#HistoryEditDialog .modal-footer .btn').removeAttr('disabled'); $('#HistoryEdit_Transmit').hide(); } function itemDelete(e) { e.preventDefault(); HistoryUI.deleteConfirm(doItemDelete, curHist.Kind === 'NZB', curHist.Kind === 'DUP', curHist.ParStatus === 'FAILURE' || curHist.UnpackStatus === 'FAILURE', false); } function doItemDelete(command) { disableAllButtons(); notification = '#Notif_History_Deleted'; RPC.call('editqueue', [command, 0, '', [curHist.ID]], completed); } function itemReturn(e) { e.preventDefault(); disableAllButtons(); notification = '#Notif_History_Returned'; RPC.call('editqueue', ['HistoryReturn', 0, '', [curHist.ID]], completed); } function itemRedownload(e) { e.preventDefault(); if (curHist.SuccessArticles > 0) { ConfirmDialog.showModal('HistoryEditRedownloadConfirmDialog', doItemRedownload, function () { HistoryUI.confirmMulti(false); }); } else { doItemRedownload(); } } function doItemRedownload() { disableAllButtons(); notification = '#Notif_History_Returned'; RPC.call('editqueue', ['HistoryRedownload', 0, '', [curHist.ID]], completed); } function itemReprocess(e) { e.preventDefault(); disableAllButtons(); saveCompleted = reprocess; saveDupeKey(); } function reprocess() { notification = '#Notif_History_Reprocess'; RPC.call('editqueue', ['HistoryProcess', 0, '', [curHist.ID]], completed); } function completed() { $HistoryEditDialog.modal('hide'); Refresher.update(); if (notification) { Notification.show(notification); notification = null; } } function saveChanges(e) { e.preventDefault(); disableAllButtons(); notification = null; saveCompleted = completed; saveName(); } function saveName() { var name = $('#HistoryEdit_NZBName').val(); name !== curHist.Name && !curHist.postprocess ? RPC.call('editqueue', ['HistorySetName', 0, name, [curHist.ID]], function() { notification = '#Notif_History_Saved'; saveCategory(); }) :saveCategory(); } function saveCategory() { var category = $('#HistoryEdit_Category').val(); category !== curHist.Category && curHist.Kind !== 'DUP' ? RPC.call('editqueue', ['HistorySetCategory', 0, category, [curHist.ID]], function() { notification = '#Notif_History_Saved'; saveDupeKey(); }) : saveDupeKey(); } function itemSuccess(e) { e.preventDefault(); ConfirmDialog.showModal('HistoryEditSuccessConfirmDialog', doItemSuccess, function () { HistoryUI.confirmMulti(false); }); } function doItemSuccess() { disableAllButtons(); notification = '#Notif_History_Marked'; RPC.call('editqueue', ['HistoryMarkSuccess', 0, '', [curHist.ID]], completed); } function itemGood(e) { e.preventDefault(); ConfirmDialog.showModal('HistoryEditGoodConfirmDialog', doItemGood, function () { HistoryUI.confirmMulti(false); }); } function doItemGood() { disableAllButtons(); notification = '#Notif_History_Marked'; RPC.call('editqueue', ['HistoryMarkGood', 0, '', [curHist.ID]], completed); } function itemBad(e) { e.preventDefault(); ConfirmDialog.showModal('HistoryEditBadConfirmDialog', doItemBad, function () { HistoryUI.confirmMulti(false); }); } function doItemBad() { disableAllButtons(); notification = '#Notif_History_Marked'; RPC.call('editqueue', ['HistoryMarkBad', 0, '', [curHist.ID]], completed); } /*** TAB: POST-PROCESSING PARAMETERS **************************************************/ function saveParam() { if (curHist.Kind !== 'NZB') { saveCompleted(); return; } var paramList = ParamTab.prepareParamRequest(postParams); saveNextParam(paramList); } function saveNextParam(paramList) { if (paramList.length > 0) { RPC.call('editqueue', ['HistorySetParameter', 0, paramList[0], [curHist.ID]], function() { notification = '#Notif_History_Saved'; paramList.shift(); saveNextParam(paramList); }) } else { saveCompleted(); } } /*** TAB: DUPLICATE SETTINGS **************************************************/ function saveDupeKey() { var value = $('#HistoryEdit_DupeKey').val(); value !== curHist.DupeKey ? RPC.call('editqueue', ['HistorySetDupeKey', 0, value, [curHist.ID]], function() { notification = '#Notif_History_Saved'; saveDupeScore(); }) :saveDupeScore(); } function saveDupeScore() { var value = $('#HistoryEdit_DupeScore').val(); value != curHist.DupeScore ? RPC.call('editqueue', ['HistorySetDupeScore', 0, value, [curHist.ID]], function() { notification = '#Notif_History_Saved'; saveDupeMode(); }) :saveDupeMode(); } function saveDupeMode() { var value = $('#HistoryEdit_DupeMode').val(); value !== curHist.DupeMode ? RPC.call('editqueue', ['HistorySetDupeMode', 0, value, [curHist.ID]], function() { notification = '#Notif_History_Saved'; saveDupeBackup(); }) :saveDupeBackup(); } function saveDupeBackup() { var canChange = curHist.DeleteStatus === 'DUPE' || curHist.DeleteStatus === 'MANUAL'; var oldValue = curHist.DeleteStatus === 'DUPE'; var value = $('#HistoryEdit_DupeBackup').is(':checked'); canChange && value !== oldValue ? RPC.call('editqueue', ['HistorySetDupeBackup', 0, value ? "YES" : "NO", [curHist.ID]], function() { notification = '#Notif_History_Saved'; saveParam(); }) :saveParam(); } }(jQuery)); nzbget-16.4/webui/history.js0000644000175000017500000003434012630544544015736 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2012-2015 Andrey Prygunkov * * 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$ * */ /* * In this module: * 1) History tab; * 2) Functions for html generation for history, also used from other modules (edit dialog). */ /*** HISTORY TAB AND EDIT HISTORY DIALOG **********************************************/ var History = (new function($) { 'use strict'; // Controls var $HistoryTable; var $HistoryTabBadge; var $HistoryTabBadgeEmpty; var $HistoryRecordsPerPage; // State var history; var notification = null; var updateTabInfo; var curFilter = 'ALL'; var activeTab = false; var showDup = false; this.init = function(options) { updateTabInfo = options.updateTabInfo; $HistoryTable = $('#HistoryTable'); $HistoryTabBadge = $('#HistoryTabBadge'); $HistoryTabBadgeEmpty = $('#HistoryTabBadgeEmpty'); $HistoryRecordsPerPage = $('#HistoryRecordsPerPage'); var recordsPerPage = UISettings.read('HistoryRecordsPerPage', 10); $HistoryRecordsPerPage.val(recordsPerPage); $('#HistoryTable_filter').val(''); $HistoryTable.fasttable( { filterInput: $('#HistoryTable_filter'), filterClearButton: $("#HistoryTable_clearfilter"), pagerContainer: $('#HistoryTable_pager'), infoContainer: $('#HistoryTable_info'), headerCheck: $('#HistoryTable > thead > tr:first-child'), pageSize: recordsPerPage, maxPages: UISettings.miniTheme ? 1 : 5, pageDots: !UISettings.miniTheme, fillFieldsCallback: fillFieldsCallback, filterCallback: filterCallback, renderCellCallback: renderCellCallback, updateInfoCallback: updateInfo }); $HistoryTable.on('click', 'a', editClick); $HistoryTable.on('click', UISettings.rowSelect ? 'tbody tr' : 'tbody div.check', function(event) { $HistoryTable.fasttable('itemCheckClick', UISettings.rowSelect ? this : this.parentNode.parentNode, event); }); $HistoryTable.on('click', 'thead div.check', function() { $HistoryTable.fasttable('titleCheckClick') }); $HistoryTable.on('mousedown', Util.disableShiftMouseDown); } this.applyTheme = function() { $HistoryTable.fasttable('setPageSize', UISettings.read('HistoryRecordsPerPage', 10), UISettings.miniTheme ? 1 : 5, !UISettings.miniTheme); } this.show = function() { activeTab = true; this.redraw(); } this.hide = function() { activeTab = false; } this.update = function() { if (!history) { $('#HistoryTable_Category').css('width', DownloadsUI.calcCategoryColumnWidth()); initFilterButtons(); } RPC.call('history', [showDup], loaded); } function loaded(curHistory) { history = curHistory; prepare(); RPC.next(); } function prepare() { for (var j=0, jl=history.length; j < jl; j++) { var hist = history[j]; if (hist.Status === 'DELETED/MANUAL' || hist.Status === 'DELETED/GOOD' || hist.Status === 'DELETED/SUCCESS' || hist.Status === 'DELETED/COPY') { hist.FilterKind = 'DELETED'; } else if (hist.Status === 'DELETED/DUPE') { hist.FilterKind = 'DUPE'; } else if (hist.Status.substring(0, 7) === 'SUCCESS') { hist.FilterKind = 'SUCCESS'; } else { hist.FilterKind = 'FAILURE'; } } } var SEARCH_FIELDS = ['name', 'status', 'priority', 'category', 'age', 'size', 'time']; this.redraw = function() { var data = []; for (var i=0; i < history.length; i++) { var hist = history[i]; var kind = hist.Kind; hist.status = HistoryUI.buildStatusText(hist); hist.name = hist.Name; hist.size = kind === 'URL' ? '' : Util.formatSizeMB(hist.FileSizeMB); hist.sizemb = hist.FileSizeMB; hist.sizegb = hist.FileSizeMB / 1024; hist.time = Util.formatDateTime(hist.HistoryTime + UISettings.timeZoneCorrection*60*60); hist.category = kind !== 'DUP' ? hist.Category : ''; hist.dupe = DownloadsUI.buildDupeText(hist.DupeKey, hist.DupeScore, hist.DupeMode); var age_sec = kind === 'NZB' ? new Date().getTime() / 1000 - (hist.MinPostTime + UISettings.timeZoneCorrection*60*60) : 0; hist.age = kind === 'NZB' ? Util.formatAge(hist.MinPostTime + UISettings.timeZoneCorrection*60*60) : ''; hist.agem = Util.round0(age_sec / 60); hist.ageh = Util.round0(age_sec / (60*60)); hist.aged = Util.round0(age_sec / (60*60*24)); hist._search = SEARCH_FIELDS; var item = { id: hist.ID, data: hist, }; data.push(item); } $HistoryTable.fasttable('update', data); Util.show($HistoryTabBadge, history.length > 0); Util.show($HistoryTabBadgeEmpty, history.length === 0 && UISettings.miniTheme); } function fillFieldsCallback(item) { var hist = item.data; var status = HistoryUI.buildStatus(hist); var name = '' + Util.textToHtml(Util.formatNZBName(hist.Name)) + ''; var dupe = DownloadsUI.buildDupe(hist.DupeKey, hist.DupeScore, hist.DupeMode); var category = ''; if (hist.Kind !== 'DUP') { var category = Util.textToHtml(hist.Category); } if (hist.Kind === 'URL') { name += ' URL'; } else if (hist.Kind === 'DUP') { name += ' hidden'; } if (!UISettings.miniTheme) { item.fields = ['
    ', status, item.data.time, name + dupe, category, item.data.age, item.data.size]; } else { var info = '
    ' + name + '' + dupe + ' ' + status + ' ' + item.data.time + ''; if (category) { info += ' ' + category + ''; } if (hist.Kind === 'NZB') { info += ' ' + item.data.size + ''; } item.fields = [info]; } } function renderCellCallback(cell, index, item) { if (index === 2) { cell.className = 'text-center'; } else if (index === 5 || index === 6) { cell.className = 'text-right'; } } this.recordsPerPageChange = function() { var val = $HistoryRecordsPerPage.val(); UISettings.write('HistoryRecordsPerPage', val); $HistoryTable.fasttable('setPageSize', val); } function updateInfo(stat) { updateTabInfo($HistoryTabBadge, stat); if (activeTab) { updateFilterButtons(); } } this.actionClick = function(action) { var checkedRows = $HistoryTable.fasttable('checkedRows'); var checkedCount = $HistoryTable.fasttable('checkedCount'); if (checkedCount === 0) { Notification.show('#Notif_History_Select'); return; } var hasNzb = false; var hasUrl = false; var hasDup = false; var hasFailed = false; for (var i = 0; i < history.length; i++) { var hist = history[i]; if (checkedRows[hist.ID]) { hasNzb |= hist.Kind === 'NZB'; hasUrl |= hist.Kind === 'URL'; hasDup |= hist.Kind === 'DUP'; hasFailed |= hist.ParStatus === 'FAILURE' || hist.UnpackStatus === 'FAILURE'; } } switch (action) { case 'DELETE': notification = '#Notif_History_Deleted'; HistoryUI.deleteConfirm(historyAction, hasNzb, hasDup, hasFailed, true); break; case 'REPROCESS': if (hasUrl || hasDup) { Notification.show('#Notif_History_CantReprocess'); return; } notification = '#Notif_History_Reprocess'; historyAction('HistoryProcess'); break; case 'REDOWNLOAD': if (hasDup) { Notification.show('#Notif_History_CantRedownload'); return; } notification = '#Notif_History_Returned'; ConfirmDialog.showModal('HistoryEditRedownloadConfirmDialog', function () { historyAction('HistoryRedownload') }, function () { HistoryUI.confirmMulti(checkedCount > 1); }); break; case 'MARKSUCCESS': case 'MARKGOOD': case 'MARKBAD': if (hasUrl) { Notification.show('#Notif_History_CantMark'); return; } notification = '#Notif_History_Marked'; ConfirmDialog.showModal(action === 'MARKSUCCESS' ? 'HistoryEditSuccessConfirmDialog' : action === 'MARKGOOD' ? 'HistoryEditGoodConfirmDialog' : 'HistoryEditBadConfirmDialog', function () // action { historyAction(action === 'MARKSUCCESS' ? 'HistoryMarkSuccess' : action === 'MARKGOOD' ? 'HistoryMarkGood' :'HistoryMarkBad'); }, function (_dialog) // init { HistoryUI.confirmMulti(checkedCount > 1); } ); break; } } function historyAction(command) { Refresher.pause(); var ids = []; var checkedRows = $HistoryTable.fasttable('checkedRows'); for (var id in checkedRows) { ids.push(parseInt(id)); } RPC.call('editqueue', [command, 0, '', ids], function() { editCompleted(); }); } function editCompleted() { Refresher.update(); if (notification) { Notification.show(notification); notification = null; } } function editClick(e) { e.preventDefault(); e.stopPropagation(); var histid = $(this).attr('histid'); $(this).blur(); var hist = null; // find history object for (var i=0; i' + statusText + ''; } this.deleteConfirm = function(actionCallback, hasNzb, hasDup, hasFailed, multi) { var dupeCheck = Options.option('DupeCheck') === 'yes'; var cleanupDisk = Options.option('DeleteCleanupDisk') === 'yes'; var dialog = null; function init(_dialog) { dialog = _dialog; HistoryUI.confirmMulti(multi); $('#HistoryDeleteConfirmDialog_Hide', dialog).prop('checked', true); Util.show($('#HistoryDeleteConfirmDialog_Options', dialog), hasNzb && dupeCheck); Util.show($('#HistoryDeleteConfirmDialog_Simple', dialog), !(hasNzb && dupeCheck)); Util.show($('#HistoryDeleteConfirmDialog_DeleteWillCleanup', dialog), hasNzb && hasFailed && cleanupDisk); Util.show($('#HistoryDeleteConfirmDialog_DeleteCanCleanup', dialog), hasNzb && hasFailed && !cleanupDisk); Util.show($('#HistoryDeleteConfirmDialog_DeleteNoCleanup', dialog), !(hasNzb && hasFailed)); Util.show($('#HistoryDeleteConfirmDialog_DupAlert', dialog), !hasNzb && dupeCheck && hasDup); Util.show('#ConfirmDialog_Help', hasNzb && dupeCheck); }; function action() { var hide = $('#HistoryDeleteConfirmDialog_Hide', dialog).is(':checked'); var command = hasNzb && hide ? 'HistoryDelete' : 'HistoryFinalDelete'; actionCallback(command); } ConfirmDialog.showModal('HistoryDeleteConfirmDialog', action, init); } this.confirmMulti = function(multi) { if (multi === undefined || !multi) { var html = $('#ConfirmDialog_Text').html(); html = html.replace(/records/g, 'record'); html = html.replace(/nzbs/g, 'nzb'); $('#ConfirmDialog_Text').html(html); } } }(jQuery)); nzbget-16.4/osx/0000755000175000017500000000000012630544544013371 5ustar andreasandreasnzbget-16.4/osx/RPC.h0000644000175000017500000000215512630544544014171 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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 #import "WebClient.h" @interface RPC : WebClient { } - (id)initWithMethod:(NSString*)method receiver:(id)receiver success:(SEL)successCallback failure:(SEL)failureCallback; + (void)setRpcUrl:(NSString*)url; @end nzbget-16.4/osx/RPC.m0000644000175000017500000000341112630544544014172 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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 #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 nzbget-16.4/osx/PFMoveApplication.h0000644000175000017500000000041412630544544017061 0ustar andreasandreas// // 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 void PFMoveToApplicationsFolderIfNecessary(void); nzbget-16.4/osx/WebClient.h0000644000175000017500000000244112630544544015417 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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 @interface WebClient : NSObject { id _receiver; SEL _successCallback; SEL _failureCallback; NSURLConnection *connection; NSMutableData *data; NSInteger failureCode; NSDictionary *responseHeaderFields; } - (id)initWithURLString:(NSString*)urlStr receiver:(id)receiver success:(SEL)successCallback failure:(SEL)failureCallback; - (void)start; - (void)success; - (void)failure; @end nzbget-16.4/osx/PFMoveApplication.m0000644000175000017500000004320212630544544017070 0ustar andreasandreas// // 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 #import // 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); } nzbget-16.4/osx/NZBGet.xcodeproj/0000755000175000017500000000000012630544544016456 5ustar andreasandreasnzbget-16.4/osx/NZBGet.xcodeproj/project.pbxproj0000644000175000017500000005101212630544544021531 0ustar andreasandreas// !$*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 */; }; F2A6E11117C8E42300D910CB /* statusicon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F2A6E10F17C8E42300D910CB /* statusicon@2x.png */; }; F2BBD9C613E083160037473A /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = F2FC0F8B13BF595700D834E3 /* Credits.rtf */; }; F2C040481A18E946003EAB32 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F2C040471A18E946003EAB32 /* IOKit.framework */; }; 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 */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; 256AC3F00F4B6AF500CF3369 /* App_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = App_Prefix.pch; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; 29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 8D1107310486CEB800E47090 /* NZBGet-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "NZBGet-Info.plist"; sourceTree = ""; }; 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 = ""; }; F20FC6DF17C6B9FC00C392AC /* PFMoveApplication.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PFMoveApplication.m; sourceTree = ""; }; 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 = ""; }; F21D369A13BF387F00E6D821 /* PreferencesDialog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PreferencesDialog.m; sourceTree = ""; }; F26CBA89136DE86A00DCB596 /* MainApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MainApp.h; sourceTree = ""; }; F26CBA8A136DE86A00DCB596 /* MainApp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MainApp.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; F26D959217C0E81800E58E5D /* Welcome.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; name = Welcome.rtf; path = Resources/Welcome.rtf; sourceTree = ""; }; F26D959417C0E86300E58E5D /* MainApp.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainApp.xib; sourceTree = ""; }; F26D959617C0E87E00E58E5D /* PreferencesDialog.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PreferencesDialog.xib; sourceTree = ""; }; F26D959817C0E88700E58E5D /* WelcomeDialog.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = WelcomeDialog.xib; sourceTree = ""; }; F26D959A17C0E89D00E58E5D /* Localizable.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = Localizable.strings; path = Resources/Localizable.strings; sourceTree = ""; }; F29ABB2317BFC03D0023A423 /* DaemonController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DaemonController.h; sourceTree = ""; }; F29ABB2417BFC03D0023A423 /* DaemonController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DaemonController.m; sourceTree = ""; }; F2A55D3617C4CA9000D6FFE1 /* daemon */ = {isa = PBXFileReference; lastKnownFileType = folder; name = daemon; path = Resources/daemon; sourceTree = ""; }; F2A55D3A17C4CAF800D6FFE1 /* tools */ = {isa = PBXFileReference; lastKnownFileType = folder; name = tools; path = Resources/tools; sourceTree = ""; }; F2A6E10F17C8E42300D910CB /* statusicon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "statusicon@2x.png"; path = "Images/statusicon@2x.png"; sourceTree = ""; }; F2C040471A18E946003EAB32 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/System/Library/Frameworks/IOKit.framework; sourceTree = DEVELOPER_DIR; }; F2CD856417C254A90019D2CA /* RPC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RPC.h; sourceTree = ""; }; F2CD856517C254A90019D2CA /* RPC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RPC.m; sourceTree = ""; }; F2CD856917C282800019D2CA /* WebClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WebClient.h; sourceTree = ""; }; F2CD856A17C282800019D2CA /* WebClient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WebClient.m; sourceTree = ""; }; F2D2A2EE13CBA680000824B4 /* WelcomeDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WelcomeDialog.h; sourceTree = ""; }; F2D2A2EF13CBA680000824B4 /* WelcomeDialog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WelcomeDialog.m; sourceTree = ""; }; F2F9804917C9081D004623D6 /* licenses */ = {isa = PBXFileReference; lastKnownFileType = folder; name = licenses; path = Resources/licenses; sourceTree = ""; }; F2FC0F8B13BF595700D834E3 /* Credits.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = Credits.rtf; path = Resources/Credits.rtf; sourceTree = ""; }; F2FCD73A13BB9CE900FC81F5 /* mainicon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = mainicon.icns; path = Images/mainicon.icns; sourceTree = ""; }; F2FCD7D813BBDAED00FC81F5 /* statusicon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = statusicon.png; path = Images/statusicon.png; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 8D11072E0486CEB800E47090 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( F2C040481A18E946003EAB32 /* IOKit.framework in Frameworks */, 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 = ""; }; 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 = ""; }; 29B97315FDCFA39411CA2CEA /* Other Sources */ = { isa = PBXGroup; children = ( 256AC3F00F4B6AF500CF3369 /* App_Prefix.pch */, ); name = "Other Sources"; sourceTree = ""; }; 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 = ""; }; 29B97323FDCFA39411CA2CEA /* Frameworks */ = { isa = PBXGroup; children = ( F2C040471A18E946003EAB32 /* IOKit.framework */, 29B97324FDCFA39411CA2CEA /* AppKit.framework */, 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */, 29B97325FDCFA39411CA2CEA /* Foundation.framework */, F20FC6E317C6BC8D00C392AC /* Security.framework */, ); name = Frameworks; sourceTree = ""; }; F20FC6E017C6BA0100C392AC /* LetsMove */ = { isa = PBXGroup; children = ( F20FC6DE17C6B9FC00C392AC /* PFMoveApplication.h */, F20FC6DF17C6B9FC00C392AC /* PFMoveApplication.m */, ); name = LetsMove; sourceTree = ""; }; F21D343013BE339300E6D821 /* Preferences */ = { isa = PBXGroup; children = ( F26D959617C0E87E00E58E5D /* PreferencesDialog.xib */, F21D369913BF387F00E6D821 /* PreferencesDialog.h */, F21D369A13BF387F00E6D821 /* PreferencesDialog.m */, ); name = Preferences; sourceTree = ""; }; F26CBA8C136DE87500DCB596 /* Main */ = { isa = PBXGroup; children = ( F26D959417C0E86300E58E5D /* MainApp.xib */, F26CBA89136DE86A00DCB596 /* MainApp.h */, F26CBA8A136DE86A00DCB596 /* MainApp.m */, ); name = Main; sourceTree = ""; }; 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 = ""; }; F2D2A2F213CBA7C8000824B4 /* WelcomeDialog */ = { isa = PBXGroup; children = ( F26D959817C0E88700E58E5D /* WelcomeDialog.xib */, F2D2A2EE13CBA680000824B4 /* WelcomeDialog.h */, F2D2A2EF13CBA680000824B4 /* WelcomeDialog.m */, ); name = WelcomeDialog; sourceTree = ""; }; F2D370AE13C0859A002C0573 /* Images */ = { isa = PBXGroup; children = ( F2FCD73A13BB9CE900FC81F5 /* mainicon.icns */, F2A6E10F17C8E42300D910CB /* statusicon@2x.png */, F2FCD7D813BBDAED00FC81F5 /* statusicon.png */, ); name = Images; path = Resources; sourceTree = ""; }; /* 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 */, 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 */, 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 */; } nzbget-16.4/osx/MainApp.m0000644000175000017500000005054212630544544015102 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2014 Andrey Prygunkov * * 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]; NSImage* icon = [NSImage imageNamed:@"statusicon.png"]; [icon setTemplate:YES]; [statusItem setImage:icon]; } 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; BOOL preventSleep = NO; 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); preventSleep = YES; } 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]; if (speed >= 1024 * 1024 * 10) { info1 = [NSString stringWithFormat:NSLocalizedString(@"Status.DownloadingMB10", nil), speed / 1024 / 1024]; } else if (speed >= 1024 * 1024) { info1 = [NSString stringWithFormat:NSLocalizedString(@"Status.DownloadingMB", nil), (float)speed / 1024.0 / 1024.0]; } else { info1 = [NSString stringWithFormat:NSLocalizedString(@"Status.DownloadingKB", nil), speed / 1024]; } preventSleep = YES; 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]]; } } if (preventSleep != preventingSleep) { [self updateSleepState:preventSleep]; } [info1Item setTitle:info1]; [info2Item setHidden:info2 == nil]; if (info2 != nil) { [info2Item setTitle:info2]; } } - (void)updateSleepState:(BOOL)preventSleep { if (preventSleep) { sleepID = 0; NSString* reason = NSLocalizedString(@"Status.PreventSleep", nil); IOPMAssertionCreateWithName(kIOPMAssertionTypePreventUserIdleSystemSleep, kIOPMAssertionLevelOn, (__bridge CFStringRef)reason, &sleepID); } else if (sleepID != 0) { IOPMAssertionRelease(sleepID); } preventingSleep = preventSleep; } - (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 nzbget-16.4/osx/PreferencesDialog.h0000644000175000017500000000226412630544544017127 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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 @interface PreferencesDialog : NSWindowController { IBOutlet NSButton *autostartButton; IBOutlet NSButton *showStatusIconButton; IBOutlet NSTextField *generalText; IBOutlet NSTextField *appearanceText; IBOutlet NSButton *autoShowWebUI; } - (IBAction)autostartButtonClicked:(id)sender; @end nzbget-16.4/osx/Resources/0000755000175000017500000000000012630544544015343 5ustar andreasandreasnzbget-16.4/osx/Resources/licenses/0000755000175000017500000000000012630544544017150 5ustar andreasandreasnzbget-16.4/osx/Resources/licenses/license-bootstrap.txt0000644000175000017500000002367512630544544023363 0ustar andreasandreas Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS nzbget-16.4/osx/Resources/licenses/license-jquery-MIT.txt0000644000175000017500000000211312630544544023274 0ustar andreasandreasCopyright 2013 jQuery Foundation and other contributors http://jquery.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. nzbget-16.4/osx/Resources/licenses/license-jquery-GPL.txt0000644000175000017500000004313112630544544023272 0ustar andreasandreas GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. nzbget-16.4/osx/Resources/Localizable.strings0000644000175000017500000000772712630544544021214 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2014 Andrey Prygunkov * * 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$ * */ "Status.DownloadingKB"="Downloading at %i KB/s"; "Status.DownloadingMB"="Downloading at %1.1f MB/s"; "Status.DownloadingMB10"="Downloading at %i MB/s"; "Status.Post-Processing"="Post-Processing"; "Status.Fetching NZBs"="Fetching NZBs"; "Status.Fetching Feeds"="Fetching Feeds"; "Status.Paused"="Paused"; "Status.Idle"="Idle"; "Status.Left"="%@ left"; "Status.NoConnection"="Downloader Not Responding"; "Status.Restarting"="Restarting"; "Status.PreventSleep"="NZBGet is downloading or post-processing"; "Left.Days"="%i days"; "Left.Days.Hours"="%id %ih"; "Left.Hours"="%i hours"; "Left.Hours.Minutes"="%ih %02im"; "Left.Minutes"="%im"; "Left.Minutes.Seconds"="%im %02is"; "Left.Seconds"="%is"; "Menu.LinkHomePage"="http://nzbget.net"; "Menu.LinkDownloads"="http://nzbget.net/download"; "Menu.LinkForum"="http://nzbget.net/forum"; "ShowWebUINoConnection.MessageText"="Could not establish connection to downloader (background process)."; "ShowWebUINoConnection.InformativeText"="Try to restart the program via Troubleshooting-menu."; "RestartNoConnection.MessageText"="Could not establish connection to downloader (background process)."; "RestartNoConnection.InformativeText"="Try to restart the program in recovery mode via Troubleshooting-menu."; "Restarted.MessageText"="The program has been successfully restarted."; "Restarted.InformativeText"=""; "Restarted.WebUI"="Show Web-Interface"; "Restarted.OK"="OK"; "RestartedRecoveryMode.MessageText"="The program has been successfully restarted and is now working on address http://127.0.0.1:6789 without password."; "RestartedRecoveryMode.InformativeText"="To access the program via address, port and password defined in settings the program must be restarted in normal mode using Troubleshooting-menu. A soft-restart via web-interface is not sufficient!"; "FactoryResetted.MessageText"="The program has been reset to factory defaults."; "FactoryResetted.InformativeText"=""; "FactoryReset.MessageText"="Reset to factory defaults?"; "FactoryReset.InformativeText"="All settings will be reset to defaults. The download queue, history, statistics, log-file, default incoming nzb-directory and default scripts-directory will be erased. "; "FactoryReset.Cancel"="Cancel"; "FactoryReset.Reset"="Erase and Reset"; "AlreadyRunning.MessageText"="NZBGet is already running."; "AlreadyRunning.InformativeTextWithIcon" = "You can control NZBGet using web-interface or icon in menubar."; "AlreadyRunning.InformativeTextWithoutIcon" = "You can control NZBGet using web-interface. The icon in menubar is currently not displayed because the option 'Show in menubar' was unselected in preferences."; "AlreadyRunning.Quit"="Quit NZBGet"; "AlreadyRunning.Preferences"="Open Preferences"; "AlreadyRunning.WebUI"="Show Web-Interface"; "CantShowInFinder.MessageText"="Cannot open %@."; "CantShowInFinder.InformativeTextWithOption"="The path doesn't exist or refers to a volume not mounted at the moment. Check option %@."; "CantShowInFinder.InformativeTextForCategory"="The path doesn't exist or refers to a volume not mounted at the moment. Please also note that destination directories for categories are created upon first use."; nzbget-16.4/osx/Resources/Images/0000755000175000017500000000000012630544544016550 5ustar andreasandreasnzbget-16.4/osx/Resources/Images/mainicon.icns0000644000175000017500000104074312630544544021234 0ustar andreasandreasicnsATOC Xic08\ic13\ic12ic07Elil32l8mkic11 is32s8mkic14Wic08\PNG  IHDR\rf pHYs  @IDATx ]Gu.Z=inI-ɲ&[%Y-xc3 f ! {N $~77<&| vl`060&,0a LX`xYR#灧FJx!Umq 9] ըOƇUZVU\qXOqR)Pc2T]Vs.1 T'8t9EfzgjᖳG:NmUW 8FN1fmfz6q#ű6n6wZANkѪe ٸ^zc]^|:4QsѬb=eIBeAղ}8vLy{ἦY;prԾZ^,( yaX`0X>Hkٰc9?d$:̫m^9Pyա%u|$ǒ#LZ:qYσ3 j9u㘯S!L©'pv"_iګVlr^UփtlUGOx|$$M!rZ݋v ˟ssgΜd+9SV˱ؖBT_ JbϭTSّy%J$kAv\Evm'|r( * *8(䐢(hw@A2R>~wqnjzO >9ϙwH+,\k5uJI8}c G N"+z. cD"$G!7)R9+ؔ.ʘsx4'Xuoo~]wmA ޡ0T@LzIc9ҧIUw 󃎞tvܹ˹ qSpykMEqiN)HJCđsfbR,NZɳ*uGLfw*Lp T'u۲y_Wӟf㾃n 3  {L]*zwW?wxrzӧO77/{})̟wnj7/,)/R E|C$<+Y ZvNFƈԤĪ *ɪx&*Mц7lٲλ|s5 9T^;@y**#ke|{{bȝ]@; A U?%+)ڨS+un`Vt8[YpQp~{/s <T`YA8P Z$IZSMB\?+T # L^z O!*^ }rzO?g_w+YhYTA[v x i:D ȗFT3-axڵk>O7_n]ذaCش9lݺ51^жx@NVpx8c s 4#,\bA7&xJ0 ĝԁו= #P\-`#]ı¶S,Sv'V^׿~(A@|Wrfz`c)2k?%Nȝ|k|cogzU/^/};:;gonI96ɩO MR(0+PaVo-qܡ8A9VX.p嗇sO x2iBUDhtQ1 ٳoxß?\'P`> 0B:. Cu7@Rej$ +~f[g+w_hg ɿ1%|V {B%q<$L9 xWֶ6l ?p=*~ǎP\XcWExv\tE/ƥp/$;ǽh-!㠬|UZzٿ7na#~  ev ku9dNuk5Q\'z|6_=aZ񑟊/ݜ'~9\FGxx 1ps+;;w ?-߾%(|smo2k /| Œ]aڴinDGU`3c2Kxa-HK]]o{۾CmEBjj41a=n7s'+T?w~ttG?z<~Wzy-FݲEQ8'/fGuräIÿ˷¿AԶ٤6 $rZ6rr%p_v8,].yJ [BbIqoꪫ # @5**yuH\T y9?^+?Snp x{G 8dTZ5-Iq26{ ko o osyP~> ;m{&j- *Ԁ7ӽ86B1FkvT'ړBpOSW_27{q3l)ǎfX7 n  49ݿo߮ɟ|ӟn@A@0 puI5 w\hJf μk hϯSy-hH<8{du@1#E$Š/뇿φo}[6ӷCiǩ8aO!F@XcǠ^X?>iO LŎ=zWQmAW n901f?^bNc(%/ x n@8PyT)6Jˬ^7Yկ~s[$4,ͩu',;r`ת l7s}sww]gςZNGa+{؆nw{AmR2(X0xы^^?v*8 =ihcPabշ΀$OK65==_{C@A0 p?Dsu)(T1fU0_Z ۪g[? 0; |"q@sD=7潒SO=5E/ |`ћm"Xc0(sz;|xw_߻AI_JP1)JӀgD V)Sy9=Iݧ r|_pm3a‘sǑ=k"ʁox~C @>;ٳ*Dr/3.\$;朔_K,7 <[怈+"$%\tER p=fz+ ) |ի@I|o۶uz?րFv. HH'?K|7>Fs 9/Sj[K1Op`ৰѝtXpx=򄖱/<%Kw!,[ 4S̤fr##Z8|Z~$ꯃ!q6J{v͝;<p䑵_ZŊ6)WÚN]Vp#3б9߲x)#(>iilK=%|@9RuS+>|'P7KA&EF "ϝ<k巻(:ޗ  Wp25wC@=tさ[q/@`ۛZ vt8Akn)߂񱝁oxf)dPȔv.Q'X׿_z'˷m'MVy&w<8kL'X>o.tnM_`/NѶ@qм#~Ӑobre/eGk!A:v'!剫h#lҐU~u/Ǧ;t~3 Y3`P03Ѳ[m_vlcb\_-k5=miH`.B@p90NJ`~p釀 P&sz?A|O%x}p-SӜDvɦ[FM )QkۯyÜ#e>f :,Ea8v_>|r†M Ұـ |ORa1&!VMf{YwhC݌` `$l,38#Lv˹[G|7-x ˕{eSRug..lˡpίRo͛W;964iXfz&1\M/a >&[D:v} k'ٽ F!n&^!_b찃` ܈H4Gx9P$ >"lˁFQhԼos緕曮WѿݩeӭJ7-۶?? ~0Dr`^y*`H:f(܇> dXjU~V"MUE.YΟOO+AjSK圦Nu60A+? NW?}y=_q ^ WEJ--er6xÏ=/彡g"5#4&>)nGp8.C;% C b3̒{%/yQ䓃nY;N<$Lб|>;WPOH<k~tG24KGv6 |~+_r򗿌$4džASB[ghD7{¡u. 7

    jeZ뾮ްо#S vN;m=TDA;\^H b:b2P U[@\Tc3HsgMvܬ~ ) 3-éw?sMv@vYx(|0ɱ9h9Hp<gŮ0ж/ 5c^UaG 8 fN̸u|? @ŋqsE`<#L Օ_[+Ɲ6:5ݬl2͜0!7mJY} {U^<iR_ZN i\$x֥~~ e!'|lIplxh%YV}!w҃>>n|ڿC#aڛ܎{hZ:ơ' cm,X[]aq{|O+WV07z08p7/׊1+y_γ'E͌qo3QC,`+"ƫ^~߿k˖-@O@+Ax)"m?M[of7-H)oDV|&,9n<2$^%<n5v:bg GMVwN=;fAZ|FX}VE&z)!֖=+&gX@ZЈ4^@*o_ Ǘ/ޏW>]H3v{m7͟ϼ_=}ak6|> .:ꉞ3?I}N]#=a=a/!qYw7.=~t~6,q&5f̘>/|\<-_#/HR#'kX^ꀙRBӁP+?V??{!^Z m~= X`|5Է#̏Zbg(w~=?~r짓ː.N.Qغe+a\ɞBsu Cj(-kƸi*^|TGzO'!UǠsyUq Tmr-gl@.eo;1[5?lr2@;O<?'E32@ UrYXfҰJA{W_x3,te@J#-xx}W;6&cA[TlN"Ǖl fyqHD?%Rꛝ'ej2!N2 b*cWu@V5U[c ~CP! Zlj*bmSd :P_9`cŏ.iH"BN~>^xQ_yRO2uӿ/H]F~D(K![ʳ|DL&A>|u#m?S<95>⬒CUv{8ԍ0+YQS[+_k7`㟥;r|UN&;˖mrS[dԁ(2+2rK弮^F~Jc$ {l}}K}еz/~Х@ex# `@_Ƀ@~/ Jc3 F~<<߶x;oqg9N ԙ Ԟ资_N/_y^>np_ӓֆ' |m7Ol>x-0pz7MgBJ OWY@#a%!y`Fl27My[#i }g7!\gmxj G7 wH-y0 z ePg<mlc,bo]P~!iLToCk76]FKn+t)#sv? r2V-ngA;cA>EMs?J8tڡhE(F8?ݽpgP~PrdF|ppߢrWD[rhgm4с2:'T"^>(N3mcL&1P΍6&'/v1 4)AjW$-p@xbɚIG<歂9'ރI'|%\22@ϹOg1XِDTcE(a)0f?fij| 0n7o[Y83ްOdLwOHOuJt>'hE#>1arfk, ր96G5r_U=N[!7/n'6r#2rHS{Sхgogm^y5|!wqmϝ5u͋P!}?t$J6E[<LJs~ a["@s|C=v퀓}K1HX"`ҙ /ȚlR\%B>Rч??ڋ.!ɄO|CcSfyp1pI'}ɐ.@>tP둙IQHioa&}?@'6 qշŢG^D3?o{|wo)B_19>)Mi-q\ (Y2ƼUXӰBt2}sD*!+BzVGR:=F4+?L}X\֔*UWDw>y" ތn H>~FmŔd*s %ȃB RP(W˦͘Wyt aҺu¾=x.vg`@=>ibٟ,:թl<J]KPF"t1D@' j'")fz>¤,L*PVGQ*\$ u:?~PŞ@@i?yx詠%4}>EB+#[c x؉ 72O믿 7G 1c CCy_{+4nG}(Lc$2WEb^HUo:1QhXH#-g]=+sz7u[` `يXqGL;GnT rа֙(1c_41I˰ȂF}4D2^̺p* 7!WJ-S~yἕXvK<ǧ|@>A«oK+W~;y rS($әUbb5;ȑyZ`[Ps=*:E= [Dp{~ԀMLSAo$v̓Knƞ)%:v[sj'T5d.s:0 b} 9 )QU<ˤ$Z# b9316~IV"pX ԙآl?˭6yFa[KñJ8c]=#t;^aK2@gEwa\'dۅ^xEkk, uĝ^"y Hl|$%.o(EoouoB8H@(]08AC?_ξCڱLN('T @MVԻVzc!kϢIDRiYX|,[Yÿ xx)">!Ñd(G$F%dۘ 4Y=FJh.}C:>b+a_LrLg.jq}[JvŶ _Yg~y+{678 %}-: r* =4$$1'kLvϽESL2ŀFŨ(ƃ:*g|޹R3s jRUS OL 3HoWZK>m`lLc66he퉲.YlEeG0`BE9,Idҙe᨟x 1ZÓSL)$IFmСXpUW\~I%')ʬ>202b!6YOOTҰȖtRt^# Yؿ\^傟(3jl֣xYs9t'ovoC72T7ZP}@@rF8#'c|;I7M@IDATD8I< gdF0 ZMRlar>Ou(J *D8؉Ӷ|fh"ԡloEw .*vw=L#RdL k3ή]'m =@9/ zq=ag3RTWv.P&Z*چu ˩A1n=%Zdb{sU=z¡'Kҍzv&},CLjvTڰi+b@*Z<"pB#:t6襔5*  2C%V !lrBѽHՏӐ+HSL$H 5>hP,[ P8;i4iQ[^'.N%#2.CALqĥ]針Ų }2^׽砹>''ku ȐIPS y?rf^SM7!/ƌ~aYK*oNR9v[ŗp-|GQmX8y&⍜[%B2I'`L<չ%財UAp)W*{E䖨#f݊K7u;>_?hr<.|}Vz_պZN*t"m~sE뮻 =?pubw9㷣Ln;?\J2&MKD^H-ExXƱNړm6ytOvisz#1y3]"N?5! m$[Ag3HGudQ2/5=ѵ]|l30𑠞zD?k= @:!l4$0ț04iJy-C.9f Dʖei"ʄJN,|lcbb5O;y<4p#MF]wM;Q;db^FsԽ%u*{eQYy9iG s\c1/ =?/,G8sl|9r嗴aU7dN23B 2O(L 7bU80Oݛ({b 0x$j$]eA#pA\%MX,V=UgFR'uy_Qi@713B9s_%M'#zL7QmU'>Ch+hd`IV";C'9gҖ?7X?mqͅ|!^/MUj9i8TP*TGBvF;Wq?sA_ IcWؿF,Pmr$/2G;@ a>:0LN9"&:/[N v&:!q<dH2sF(dkM%Y{2Fb 9*ƤcʎG V9g̘Wt/QJ@NCvμEK/t1Wq X$Q"{[L2\)NQ˩ԇMjkm8җ'%?kDm} 5rîdR_5٥e&'m -c+oi^qYAUb&H %2Q$NL|%OGP]7 jS \| o(_$#kZ9)qdK,[&Ү 0#۾{C vŹ?t5&%gSV QpZս 0D*&:=iG'##6*`D8)0H__`g l `ot7ةQ¦̕Kx!_W\ױn܇L(!bq 4 3|H՟,yJJd2TRcB;cҥA` dƲށ_U$MS%D.Ix^b,WE(v+!ٯ[mՄ>~ GBY;ȂomP>S-":.W%‚,#%pbSL&iIo˓Q^ƈ1rVʠa^D}, ŏN˔18֩[nDL <k@1 &ɲӖ@1/$ae.1ц:wܥ6}m@ 6P9ԭqH$ H*WKf-s^Gj\$2G6T.ڥ:癨ЮcE;ὅM\՞2 ʳސtT&vLI*dƒJUgÑ-yF[3!@~g9bڨk؟XXlA?1pb_C>\ ,4u/ ̗̙N ~$oQa:]2H6LɤNzPVNdKi@Y zk0x"K9x([8%DѷXyqx0]H{q(B釲l2&%: dm؁ɪLL2!6"Ķ)T"e$*>tLϫ=B` >~b 7=g'= %?De S 3e*ژ?l'@"߷=0—mp1:dSʔji*9FQ,rh/%pZdN o1a+1 G,qESdx&p$&xk97֛gYc2c@I၍,ӲD"ʠeT2)İz *-o% a#-;poy@p袋:O)dN[IuA*(< E p'- #7Z&dZ'i`**"JK%L\գ vX俥;Az/\*\ygXR+Oy`cވ-O9!Q,PY,]LީTrQ&h16 i{gvV(&J1 Er MdsDN[zsW*x ?KV`wsXgr &gݙ$p>KfFk\٨38#=P2IH e2,ӧˈKBUUGXm Zrx:q]~eJ;'eEBubÑ$2":l9I@]5 ོ?4wd|2V6L^*M^Y#7V6fF EuΕ̘Ě4)&| y=/&6YKq"k/$KIBY̍)0"q$1:?"n#2Qv^ꌪH|ֆ*l$=$w[KO. n 熚- U0O9o: r&bl`ƌeSlb|g)0FT㷞q'on+%E F2RQ.oz*MR؊tȅ͑"1 /xԷΛ} hR#6.-]4y_-\Ad`4MYὑ'?d$yFKzd&a\& l]Q]F> D2 Ey\ YCV&*UWG0y}8$|:_X=i3Ps|ӦMLj!լA @0뎖 Nl9X4A \pC2Wc~ cC(+-g-*k!' 3DLQ"%9w}lD)*"Փص@2b$gϞc&l,>T#hBB|(b̓#8 ېWƺR@V3Svo[g'I8< 5*.)QK?3dFQ hcNje'43>aA-#Gu͚Dn2 P/L07ޖ]3;C:ёJ&Q68[99ŵ``C`s~n_'#5<*;+'AU@x,7 8?Zȧ ͛o ˒D] -J'4LB{īP|x~`h!u0~8L'gT@u'@Tr`F&b61}:h-<#G oA6Pn9މoET5 5E*ѩ"6S_;]2!P:\LQlԿ_cSK+qDe*%~Ë,٢]ћT3Vp{ lŸO#tؒl(u~P\<_RVqΐ12o<{B`?d? )'5M("R K:v6%Sljf홍;8spvڢlD<Qȉ$a / m*k_!֥EK҈WA6Ŵ㷐ʜJ~ oaUf1)WQ엵!E 4]4/ST`\l]"uMC? ro/4`K1uAzlHkʜmLӧdcg,nu֟D3KwK@(V9՟&iO TD)DW-)EVs[uGC4#::%Y]ﲖ1qR-|JFNIJOb@Ѱ(T:6.dDwh?->Tv ]NA NN;? #n-N L*UxԊ E8pw.xP.d'[DrGlAa$}Uzx*!6Ry)(mYPaAixe&$By& 5ɒ&XrՊW%+\я:q΅{)3hLezǐMȫ`"ӧO+{ԫ,,q1XSD:V^<o,~H{zÇ0uT! C$襬W~H:&^X)@.<:F$p@sȼE<<@ҶT&<&qvv7;zӀ>IK$!E;TW)Wԋ;SF5M@JrzpD!@٨4}ɡsڂCa%dE"1lxjLzyxR{YM̆ tɆmܺk캲Fxqn,- krE=|є_ ia vFs1 VgiruE%[.)h_UP8*28կqڢxfrr%jNA|1Z)泲ASFN/kh/x>ǨEY+UE}핫W}?z9].\nݢ |1gS6S #` ?NVYxPq}M-.):Y<9Da ^'e!Q| W8Zυh39:=2V8 .;t`y^QҷƁg|pw`կE+W@8(aABmEn8d:Kc3[`{>o*)iuHyk%6m[KUHyޤ8uvN7]g̅EC^]xm4ޏssKe[ TN>`cK@vїL̷$/ik"E:"G\ŏA/ޘ,PF0'g4\IV nO>t,1Px "x'3 x\rY=:EhI1z3X+[oΡu1f^̮ёZ?0%E?:noѧto ޞ^IZ P*;3 `_2pѽ)AIAttɌP{l86^tfJYh[Y̝H&ECf=ټpfKgFDz&y!i {X)OZʐY{ʲY%%(}[E)s,{@(kбu=6JU:렿1XciB 6%W~:~>>U{%VZWXkn~w}NmA,k适|kb4wbQo=i b?举Lb25wzw>T5`bpgsq5=*5i)ƗٱlJq6(]g=J좭‚sp6c>P(/%so &ᧄ[HpHfL9d.<IjQmo{C^X%MDՇģؿ)Xƪ^9-/ۦ,V}lmٌ/5?V)Yp D AH@Egb5?+W"(8E Pflyym6[5[[Q?Fal@ w@Pm P+VG/?-r[8W݇AVR0yqHAp|v} ;YjI6tmkdthRgꀀ "{,Eb^&E(bX-ݻw7'G@Y`z#09$zeklF1O>el;yp?&2&x[`Kf;80n4QR(FOvo4W݋LK+{(R84|k=Δ@ D#v@MD15jpL rk鐬lml^ӹ3,yYh=p\'rC[W K#5sf41<>ezzMȩR11}<\y?#Ku ȟ2_}5(σ̮ ws8-K{=;|qb;P1b8FP$6MwcFNp o/J xlK}‚>,M3hY%ʀ> D3}čA +[]~[;0*tMl]WRcO80?vU+V0-[A 9Rb"klx׮]fCڒAF_d3#lLUB (2 UsZ{HuhUflH@w% V Ap@ FiK%ç ۀʊ1.X`= mSi1 P-[.&rZl88i#%8 {MOȣQlS 2SXų $t7a jyԛGJ,"jWJW<NC刮xoSLݰ=چ@O(o6E67m޼ 2H'-БP.ĐCeo|%2xRs9@#$v7BOgUCg[%~R+u1Ț@w#g|YLuey¸[,(|CFQk!>R JŖj?n)[u)˗x"Č5 @O`?#:6_`G&I] ]9fPFBVDt0~`ʕOAb{@>p?1 ni࢖lwCOd!EJ D4tB8@dǸ&$r>?nŌσ[QKyG" F. Z$[^Sz1{>D{,Qu<KTA}~Ж0=l/E @d  uf$Ca'6: (l9}By? Л?㴟AI4d&iiifyJtA3R*HՔFwdc\SaY4dT ZjHq%Ka+9J -@z DzǂCؾm;4]3:gv)0V2ZŚq_TH#:J$o,/1F׏긏?I⣯w)iqpd:+k>/DR4!)-@o] gUonūӷҁpP71.\| {$kSV: @w 7/~:;i<2wހo O5ӻ8泄  J ںF.R6Vp3"LEfH ä=1?Nc|t~p?}r N\SQ;>*OL*-fX@3J| o~)Q!m6mڴIP$/; b.B9y d3d汈".@Ȁe/=g㪰!!fz K(vj f:73:'02c|z\Gp1:8W^l;!SR]Ikt FHRUb)#0QHj~:sd2[! - ?A%(Ji3P&lG"[nnc#‘V&? 2T #H7P M)؝K)t'zQ^JR[]ØG1or$ zL/}~vL?"z)[ba"BI^z*|F+_^ќa2~>~sMrB[oIq e>b\gyHJn®لu.N~Tļˀpy(B޺T]EbJV3&L,ZBL+h%Z a>|q.qƿ}dlF6ڔ 3X`Ll-ce^q[chT iXfo78 ]r㗍[[]P*ܿyEF 5 ϗ0 ڠ,͓dl +b ˉhYG4b)+)=UT`+^w'O@φF\/h)N-׊TUfϹKgˮV)ر^wo i:lY_=mܸiRi-f s?Mqa̟r6eѸ#Am3҈f^`qSOG/C Ss1k'@c_z >@Rh/ Ї9s2\Sڜ쏌40/qU vĤ̂?w?~3alNnjpC>o{kޘg{<t_0$TJcƍXYy +Xe\7Lp`/6gȏwEЙ]+@\GǗJwܹcm -$?[+wV\j6ƅ`ݐ=[ : '@g4LfSSȎG K/[gVg"c2V;UT7\R"3lLJ|A;خY3k'Nlwl{y:)8iVuaYXkb1D?dY{? x6,[M,pJgXV-@KvqgdM@ ]9W*K$ճ.c| -lY>~J׋PwG_Pp@,t0+9qIyHdd0O3*s:/<8bY_=53-Lx017wMf83xSģòq( Z`Q{J+B*- ºZkĂ'"A62ʹ @ˀɬrpȩNLei0tN;VNU!'´F[ul'΋:.XC]bmd.s #\J,*4bXKr d\Cjs @ns@d:ֶgʕRx D3J W7][72.u}YPH cGz00GO:fb7*(&4</Ø_Nl%s3UPDE1e7ONӌ'米^:\!0F 7DX*<G @HjYw@@u؇Mbz#1h\'A( 퐺#[U 2f /e0K#G?:3-MaVw,U5/N-5gg"mHr 'Nҕ2K<҇G2t!h4/+S*Ȳ؉23/b˕<ҽT|zۚ }YjoV=[Tr n_K;  _/Hڈ͐F:mUw QyesG o_0Etl8IDAT^OvK./_aJ4 PT-8Kbdi:\DK\Hb)ɉ7 IM<@=γ2uږ0Sػab#$)NX7`Qk3xL~&'cfFPSijgGB!ElOJ!rRVvעtu3[j@v/]G$A7q _N@#? \PN:JxP|&`N_%K~LuS698dzjhn _j! MA8`NءcFAF gg,[LarzODym-\B?E+#sd dH#ϟ / 3Vv٤q*T i:;NYxn@e3/-zrQ3ç"I*5 }\@$R)[-ZqGю Ғѐ73,e -=ߙȗ@YN03i;)YY/)18)*LYޤBzbey&&>EgMpƵHq|+/`Kje @%GrBP?!>c|MOZfk-iT6>{ cP0~H|qxK;Wڭs,Q.˔+e4A0JL#=,%ee|Tsø{)a5FLJ莫LnD73%u3֘IkCbQeNK3ד(ƫlj3ڼicxP gn4zj.` xMy_zGN@vE;S) KON#u*,8͙+r{oFc@ |=bThxY?A7 1U%Ci-f6*#^vV4Y|[g,tBNo'a#_c:կ]V+lDHg(Ye\eF#O,M8Aow}w\݈>뷲 n&W0q`xN{ FIUtlZn7h@^}SQ` g??tG4ѽx:?`oP<*iB<.W֩Ihp:>Rz7b H%oS۞ve'⹂V\Yݱ$\*5CNGLGY9 I Y4j7l Ey@\ 13 gRva f=9e4m瞻!U?mGnS6&# GT*H嵒7çoƳ'*WQ+'fvK6UYÛO߆ԉ W ­GT8ݔsp[網i/()ǘJgB,[Q1x[3^;^˛}(ܯ}@B o}ހMo~ ʎrۢ1H E8P񬜇<)<-y՛7oZd^:_ NLS9]Q&knC lBNuVvlċ|q(xPg2Og8pOt&0X-Cք`, iΑNv3iSq. ͯ SZ>`s0bm'Ɇ;K M{i$-AHvhk7GP@۠(T=T"T Dgk=ifkA-f sc|4xo+ P&w9uf *SeD˗Xf%2΅3:CZkFk[Ei!>n֭=rÏF'p>ƟܦX$CJqct@JB ukl3g΂kr/o մh(w 4p}o_RR)@½cq#Nnd` A24fi$LBhf۴P7">]i-/ح T& މăD,C5DlY-njNe%~+)+za98}[׮C#žryr5F#.1iZ0w(I'1Yzy:Zjdin ͡⏥U94}`jDPPh LyNڧ H h Tg%^?!4ܷ-LpJ@C(꜁T3xqF2)r$I/uq1T!캐ЋUͯ/m~k~N 6.H£Uׇ&6|KR>_/i=1_ԲW;f8OŏnBi;#2/D=/a\Rpy.6Dk|k_t2pg_[p0{:JojatY9zrU˸BĽ#X)n I_$0S=3%9ILTz<1#̑f8RTZ&IS(#n馓ȘexYTf/AO X60wo߼Vgifk[Uv|>M9k~hK4ňy ñ.r(%!kMz $41F5ڵ;44ԇq&&LR׎ }(p V) 0p _F*2e4,0V RXA,^ϴhlfE,xsuCD\%(1g,{7Ɗr /}dMSh<ׂP߇q'Cus:;:/~/c?O‘O4 XxĎ"@"k,FQ` OoCts}lf2M6dD }f=jJpV(iH'L +q@/Ff2vZKsd2HTW^hgdĽ2:R,_*?%.rNùAcnj&lFy`m'zgm+j3eJ#  ӵ,B s^veJ+|N(&*0Ǝgjyͳ/ =< B!HGTb eb\WQ)WE,dYeʎ4<'Rm"?ؘp xxT,c׾vCtIsb!ZOoP R t6۱搸<2 ln3t3vi)bAؾm;6[~ 0U g{M#=ۻ]qsfeEJC$?4bhGy/0?x1TvvGS.d6E`!@r@KtEs]P_#o蹑Oo뮰 3T^lwd{1?ES~=m&7~A#f_Wq: s9S(SoL~ϺvP?(; eM53}{%ḆvUTeqp?a;: h3'r(ŧFbyӲByA%rmS&,]4:ufh/".Zۊ3{vNyHe,s]*2,DKr.˒4XHg2L<$_G[AxavL~w[oҺ8ֶm@-1=ݸɽ{nE4~ϧhgCO;JRJS#Ho jll1n؋h" > d˛1G~|= hD 1rTs~a`ҥN2mWb?iz:.Bsw^pΠs“{>=]I9n$=vNTFG>S\Vqɑ>RyMΥ35o ?pq_jغm3 |_U?k*b싫VsO2TWƮ/ XXPo8+M Ӊ086s9ֵv ܍!iV",pƍ86380M0oϼGp H39#(Kfv^³d"̖?[;r:q&6.} s #8$0oaƩZh5TK. ?Tӊ:'v b2Q<2Z@˩寐)b-x+䷔>b} ='ތ/P'0W]RZ͗0 ^wUwm۷oQ~94~jF)B$\K@ՖhJ޺uk[}}FHo*vy;i1~j|ذaaIC U᪖v=sտ:wL6i(#%,M+A+QQNZ#z7y]J\C!KS계SjN 0A;wNj J6;;߳^inϣ8IzPrjAcJOo 2uژIt7l{:6Y©  hW/bcUcxQ϶ٯfR ;]x;LKdW꼪i1IJnP$uvQ^YI_ Z*|HWtf=_vCxN 9v駩?,&rvZɯt+]P)?>{3Fhb6 0Θ1# >/JeT=(a~oÖgOσb|dR JN쀧vᦉ C뇙Ӏ\lY>ۤaC(Tgri MȀ\o @i_=jNP hSPJ׌ts_x鳯L5ҏ'H(.oE;/ Ob ģw¶a.w`_vyQ脿[m8zƳ'~2 Y;f<g>c? 2trO%xs?f$ki'EI=PTu9Ž&3^{+Wͬf] DL8K3~=ט L8 _xW4_v9>dD]JkK#d b$ާq?[o 2v#Ο j^ǯa[k!,V,'N}(+@hvkb4qΝ;pń^Ç0bGTR!tq0" p7|[0EVn 2: YA㲁Z7*\0aA˒NSP6~š0/|zMaj43~c<'wo-Lk^f.;M\l|8hύ_'`% s_;6E:%Ӌ8)ॡFY?lY*מ]/q\|aTϽ O-s6N6 tAĔCKm;G}?5 _ýދQk|N"Q8; __v]?;wXWS{|<Uv:Kc*S&Lh3#'h$AK,ns '-Wܝr*‰n߇?}=z.i4=O|yi`uD~փb_~ugQqگOiQ;f즈3YʒTNx*'lj>w^*Fv/@'>BM]7?Pi_4|&sy8^28N**ꩧ?1~6ѳ 2)un [7y5 -'1@84o8#aD1pwol8muW:;t6#ȏu?gϚ*qOtlU_I˕) xqtOhI,gy>֯]oNox6xڪp~avaច@S7&|`+_j86Bٽ'$nX?+/No^GOvl,@+7Ѱ/Ĉ#2+$Nqt@Ua΃sa.Du*^>qMxb7Uon}KM5DJ‚ q/f΁M^ v7Yb]wKї?G}G~Z 3hľ>6G5''!eu[O݁!--3 !'cGVP:++¼{v|(6kh̏bq$9k9$%a;5as 0øe>^~n Ѩ :3.? Q>ȯ_k~<v~eEx: !WI]6zؾہ{;Ͼz;p!Qo sW.0@z,󪏆=x@cuc0絜̮cpw>яK3;w/D`^… |ԧ~v: t 9.Z2`e;RNNNh86g68ik p1kq,F츃O9K|| 'w$SM`F@n^}ʵaH͐`}$ܟy[xI1ݷ kѝ׉>M6Ʊ$vx-F18V"?c} wCjiF>PtKL@4L`0]uU7TW7ϏssV>7D I2G8ap>FX(_B#ݸg~4w9 K {`mx;a8wv&rG} :vw߰e˖5@5G~XT" R#x pY4tذ/z ߎ[}Q%ؾJ#N2įp5WYyBXwm;og5+G\5;44|bGػomt@8Qq~o)/)?צ 3 v/4#h7xK_O~X2vM@`G~yĀf~,c')rXy!=:>¼ۧə>NMM!~mMMKk٥K».Y~"9 B<" :h/Q?G# gTaI )]kc pf|̙3S3 h<8tvrfHwS!&<<ĭ{2|m {s8[}[jK|GU 0(BKv6m` L4=\3Uט29vؖd+mn-aHːeMƏCA{,9w ?2oޗHpėШ/O\AF/(za 2  N|_@N0__~^Dä1Ri/cwGirNhhh]zYCN)+3XH=wϯ\3PGO|gm=>p`}|<}08ޠ;,|sw}` /çσS~2ŷޅ~dPT$V.pџ&w\(n!4y/mZ4p9' ΃;ý[ m7k_y ,7#}Ϟ=PNGqʯ> _>G|^ndHw+P8a hZ`0 {#: CNqi@`9 04@JI`^x`~Cxի_Lkqk H"əQH"ygq݋q†Yg.>_ǝ1xF8_xGr.8=GGa s> yKst:1 P;\իW`ѢE" =pOF[|tKIi'DO,- أ$lN6 h gq fB\mla.l~\VJB7ܷWC~ЩnMՃC./xpfkBM@'n6M`ڠqàsov8#:~rFOƮO'}6GMD#KYY\v`#d r  hI@8xgM>`qǟ,#?S8C0G<}i{aʔ)^7Zˑ tH^ c:9EWl*}/~aРAf:K;Yv,Wms{QQ_}yPAZU*20V4opB9B p&ـfZ$]‘q}FkF@7]$ j,7/k^0>) ʦr8E<: AH囃ӄV/bnf' xp| :eնvYw@:cXvMwuրGl!h])s46GG|=.3s={9y2z9k2zAJf'N9\.jގƞXh6@Hg6+AΞ0~kX|p2G7aH%7s}2eJKå^&e$$hbu Dgt6s6b=x14hBr VZqi?8gFźa>`*Z9޶QE OF|/7|OWbRD<^)=lqE'fhY G 8٧~~+~Tv!>@ZO1+."/_p~*䤰r4%N%C)h:hϋ )R? oHy N4?`>#Nða'Θ>/1WBZL֧7)׎Njˇc be%jrmၘ`Yox=YxiK.嫱fԜиu}pX_dW0⯂iei'G|:^M5~6RFAd j Hf hr+0G0Su6 /!9$H+hĖa0_ۋxBܿueC.#רx~ik'2Kz0NV$oc@399B mp3^|) g ec!+x܁4 e'S}|n*9T07aUPx4# Ac_A[k?qԨQ/׈.@8 gްa0؁"h2t&gZ'N( ^i @ 2-8Б5;4:fج ˃"Ι@{a`x/ɗjtTFy94x4 Ie;!7;+9P39sg@<Ѱ]C{%z^l>kp1wi4 /=4J/]ƞJ?aOVh\4| s'@/(G!`WG%x8f f2F <ѦM.h[FtE'8Qqўv T@"x~'nF2b9 s* ĠˇX64">І!> 8&qJ>}}oñ.|c;6C߂N߃Qܑ S+ ŧg 4$ +h{w2*( O M E3CBnĹ3r9MUr)NY$'i 20 a '!*Q*WŦwdJ#dP;<6pN`@gnX4qFȀ;U,30F,d(4".cQ2.\Px^.g2U ._`Ii 9.,hb t_E}U%0ES#7|Uъxg|a]LWTF#=]s<@G!c@;@Qw`1i 2fs-ҕ],Ki:$oW!7@W0i E&_i W99$ $c'V>;;@86 Ta&T3MtS=`npQIz 󼬟i<:<9x`=>q֔b^. >9C#<*,dMTKE$=x6!֨J-'!*^azVLId\Ai,V|Lc(;5r% Jt\rD'Ӆ+-i!bPJ'^u4_ThǨuc,f h+XdN/y<]0O#~$!7>EHWi/mNKU ہ:}wӊ|8D@JY/xir=c=$@1Q]*)3z@{J:=P@ǨsC2{:sZJgx׭jǚyw!w;[{F=u} g@)40 hhUp+m2IENDB`ic13\PNG  IHDR\rf pHYs%%IR$@IDATx ]Gu.Z=inI-ɲ&[%Y-xc3 f ! {N $~77<&| vl`060&,0a LX`xYR#灧FJx!Umq 9] ըOƇUZVU\qXOqR)Pc2T]Vs.1 T'8t9EfzgjᖳG:NmUW 8FN1fmfz6q#ű6n6wZANkѪe ٸ^zc]^|:4QsѬb=eIBeAղ}8vLy{ἦY;prԾZ^,( yaX`0X>Hkٰc9?d$:̫m^9Pyա%u|$ǒ#LZ:qYσ3 j9u㘯S!L©'pv"_iګVlr^UփtlUGOx|$$M!rZ݋v ˟ssgΜd+9SV˱ؖBT_ JbϭTSّy%J$kAv\Evm'|r( * *8(䐢(hw@A2R>~wqnjzO >9ϙwH+,\k5uJI8}c G N"+z. cD"$G!7)R9+ؔ.ʘsx4'Xuoo~]wmA ޡ0T@LzIc9ҧIUw 󃎞tvܹ˹ qSpykMEqiN)HJCđsfbR,NZɳ*uGLfw*Lp T'u۲y_Wӟf㾃n 3  {L]*zwW?wxrzӧO77/{})̟wnj7/,)/R E|C$<+Y ZvNFƈԤĪ *ɪx&*Mц7lٲλ|s5 9T^;@y**#ke|{{bȝ]@; A U?%+)ڨS+un`Vt8[YpQp~{/s <T`YA8P Z$IZSMB\?+T # L^z O!*^ }rzO?g_w+YhYTA[v x i:D ȗFT3-axڵk>O7_n]ذaCش9lݺ51^жx@NVpx8c s 4#,\bA7&xJ0 ĝԁו= #P\-`#]ı¶S,Sv'V^׿~(A@|Wrfz`c)2k?%Nȝ|k|cogzU/^/};:;gonI96ɩO MR(0+PaVo-qܡ8A9VX.p嗇sO x2iBUDhtQ1 ٳoxß?\'P`> 0B:. Cu7@Rej$ +~f[g+w_hg ɿ1%|V {B%q<$L9 xWֶ6l ?p=*~ǎP\XcWExv\tE/ƥp/$;ǽh-!㠬|UZzٿ7na#~  ev ku9dNuk5Q\'z|6_=aZ񑟊/ݜ'~9\FGxx 1ps+;;w ?-߾%(|smo2k /| Œ]aڴinDGU`3c2Kxa-HK]]o{۾CmEBjj41a=n7s'+T?w~ttG?z<~Wzy-FݲEQ8'/fGuräIÿ˷¿AԶ٤6 $rZ6rr%p_v8,].yJ [BbIqoꪫ # @5**yuH\T y9?^+?Snp x{G 8dTZ5-Iq26{ ko o osyP~> ;m{&j- *Ԁ7ӽ86B1FkvT'ړBpOSW_27{q3l)ǎfX7 n  49ݿo߮ɟ|ӟn@A@0 puI5 w\hJf μk hϯSy-hH<8{du@1#E$Š/뇿φo}[6ӷCiǩ8aO!F@XcǠ^X?>iO LŎ=zWQmAW n901f?^bNc(%/ x n@8PyT)6Jˬ^7Yկ~s[$4,ͩu',;r`ת l7s}sww]gςZNGa+{؆nw{AmR2(X0xы^^?v*8 =ihcPabշ΀$OK65==_{C@A0 p?Dsu)(T1fU0_Z ۪g[? 0; |"q@sD=7潒SO=5E/ |`ћm"Xc0(sz;|xw_߻AI_JP1)JӀgD V)Sy9=Iݧ r|_pm3a‘sǑ=k"ʁox~C @>;ٳ*Dr/3.\$;朔_K,7 <[怈+"$%\tER p=fz+ ) |ի@I|o۶uz?րFv. HH'?K|7>Fs 9/Sj[K1Op`ৰѝtXpx=򄖱/<%Kw!,[ 4S̤fr##Z8|Z~$ꯃ!q6J{v͝;<p䑵_ZŊ6)WÚN]Vp#3б9߲x)#(>iilK=%|@9RuS+>|'P7KA&EF "ϝ<k巻(:ޗ  Wp25wC@=tさ[q/@`ۛZ vt8Akn)߂񱝁oxf)dPȔv.Q'X׿_z'˷m'MVy&w<8kL'X>o.tnM_`/NѶ@qм#~Ӑobre/eGk!A:v'!剫h#lҐU~u/Ǧ;t~3 Y3`P03Ѳ[m_vlcb\_-k5=miH`.B@p90NJ`~p釀 P&sz?A|O%x}p-SӜDvɦ[FM )QkۯyÜ#e>f :,Ea8v_>|r†M Ұـ |ORa1&!VMf{YwhC݌` `$l,38#Lv˹[G|7-x ˕{eSRug..lˡpίRo͛W;964iXfz&1\M/a >&[D:v} k'ٽ F!n&^!_b찃` ܈H4Gx9P$ >"lˁFQhԼos緕曮WѿݩeӭJ7-۶?? ~0Dr`^y*`H:f(܇> dXjU~V"MUE.YΟOO+AjSK圦Nu60A+? NW?}y=_q ^ WEJ--er6xÏ=/彡g"5#4&>)nGp8.C;% C b3̒{%/yQ䓃nY;N<$Lб|>;WPOH<k~tG24KGv6 |~+_r򗿌$4džASB[ghD7{¡u. 7

    jeZ뾮ްо#S vN;m=TDA;\^H b:b2P U[@\Tc3HsgMvܬ~ ) 3-éw?sMv@vYx(|0ɱ9h9Hp<gŮ0ж/ 5c^UaG 8 fN̸u|? @ŋqsE`<#L Օ_[+Ɲ6:5ݬl2͜0!7mJY} {U^<iR_ZN i\$x֥~~ e!'|lIplxh%YV}!w҃>>n|ڿC#aڛ܎{hZ:ơ' cm,X[]aq{|O+WV07z08p7/׊1+y_γ'E͌qo3QC,`+"ƫ^~߿k˖-@O@+Ax)"m?M[of7-H)oDV|&,9n<2$^%<n5v:bg GMVwN=;fAZ|FX}VE&z)!֖=+&gX@ZЈ4^@*o_ Ǘ/ޏW>]H3v{m7͟ϼ_=}ak6|> .:ꉞ3?I}N]#=a=a/!qYw7.=~t~6,q&5f̘>/|\<-_#/HR#'kX^ꀙRBӁP+?V??{!^Z m~= X`|5Է#̏Zbg(w~=?~r짓ː.N.Qغe+a\ɞBsu Cj(-kƸi*^|TGzO'!UǠsyUq Tmr-gl@.eo;1[5?lr2@;O<?'E32@ UrYXfҰJA{W_x3,te@J#-xx}W;6&cA[TlN"Ǖl fyqHD?%Rꛝ'ej2!N2 b*cWu@V5U[c ~CP! Zlj*bmSd :P_9`cŏ.iH"BN~>^xQ_yRO2uӿ/H]F~D(K![ʳ|DL&A>|u#m?S<95>⬒CUv{8ԍ0+YQS[+_k7`㟥;r|UN&;˖mrS[dԁ(2+2rK弮^F~Jc$ {l}}K}еz/~Х@ex# `@_Ƀ@~/ Jc3 F~<<߶x;oqg9N ԙ Ԟ资_N/_y^>np_ӓֆ' |m7Ol>x-0pz7MgBJ OWY@#a%!y`Fl27My[#i }g7!\gmxj G7 wH-y0 z ePg<mlc,bo]P~!iLToCk76]FKn+t)#sv? r2V-ngA;cA>EMs?J8tڡhE(F8?ݽpgP~PrdF|ppߢrWD[rhgm4с2:'T"^>(N3mcL&1P΍6&'/v1 4)AjW$-p@xbɚIG<歂9'ރI'|%\22@ϹOg1XِDTcE(a)0f?fij| 0n7o[Y83ްOdLwOHOuJt>'hE#>1arfk, ր96G5r_U=N[!7/n'6r#2rHS{Sхgogm^y5|!wqmϝ5u͋P!}?t$J6E[<LJs~ a["@s|C=v퀓}K1HX"`ҙ /ȚlR\%B>Rч??ڋ.!ɄO|CcSfyp1pI'}ɐ.@>tP둙IQHioa&}?@'6 qշŢG^D3?o{|wo)B_19>)Mi-q\ (Y2ƼUXӰBt2}sD*!+BzVGR:=F4+?L}X\֔*UWDw>y" ތn H>~FmŔd*s %ȃB RP(W˦͘Wyt aҺu¾=x.vg`@=>ibٟ,:թl<J]KPF"t1D@' j'")fz>¤,L*PVGQ*\$ u:?~PŞ@@i?yx詠%4}>EB+#[c x؉ 72O믿 7G 1c CCy_{+4nG}(Lc$2WEb^HUo:1QhXH#-g]=+sz7u[` `يXqGL;GnT rа֙(1c_41I˰ȂF}4D2^̺p* 7!WJ-S~yἕXvK<ǧ|@>A«oK+W~;y rS($әUbb5;ȑyZ`[Ps=*:E= [Dp{~ԀMLSAo$v̓Knƞ)%:v[sj'T5d.s:0 b} 9 )QU<ˤ$Z# b9316~IV"pX ԙآl?˭6yFa[KñJ8c]=#t;^aK2@gEwa\'dۅ^xEkk, uĝ^"y Hl|$%.o(EoouoB8H@(]08AC?_ξCڱLN('T @MVԻVzc!kϢIDRiYX|,[Yÿ xx)">!Ñd(G$F%dۘ 4Y=FJh.}C:>b+a_LrLg.jq}[JvŶ _Yg~y+{678 %}-: r* =4$$1'kLvϽESL2ŀFŨ(ƃ:*g|޹R3s jRUS OL 3HoWZK>m`lLc66he퉲.YlEeG0`BE9,Idҙe᨟x 1ZÓSL)$IFmСXpUW\~I%')ʬ>202b!6YOOTҰȖtRt^# Yؿ\^傟(3jl֣xYs9t'ovoC72T7ZP}@@rF8#'c|;I7M@IDATD8I< gdF0 ZMRlar>Ou(J *D8؉Ӷ|fh"ԡloEw .*vw=L#RdL k3ή]'m =@9/ zq=ag3RTWv.P&Z*چu ˩A1n=%Zdb{sU=z¡'Kҍzv&},CLjvTڰi+b@*Z<"pB#:t6襔5*  2C%V !lrBѽHՏӐ+HSL$H 5>hP,[ P8;i4iQ[^'.N%#2.CALqĥ]針Ų }2^׽砹>''ku ȐIPS y?rf^SM7!/ƌ~aYK*oNR9v[ŗp-|GQmX8y&⍜[%B2I'`L<չ%財UAp)W*{E䖨#f݊K7u;>_?hr<.|}Vz_պZN*t"m~sE뮻 =?pubw9㷣Ln;?\J2&MKD^H-ExXƱNړm6ytOvisz#1y3]"N?5! m$[Ag3HGudQ2/5=ѵ]|l30𑠞zD?k= @:!l4$0ț04iJy-C.9f Dʖei"ʄJN,|lcbb5O;y<4p#MF]wM;Q;db^FsԽ%u*{eQYy9iG s\c1/ =?/,G8sl|9r嗴aU7dN23B 2O(L 7bU80Oݛ({b 0x$j$]eA#pA\%MX,V=UgFR'uy_Qi@713B9s_%M'#zL7QmU'>Ch+hd`IV";C'9gҖ?7X?mqͅ|!^/MUj9i8TP*TGBvF;Wq?sA_ IcWؿF,Pmr$/2G;@ a>:0LN9"&:/[N v&:!q<dH2sF(dkM%Y{2Fb 9*ƤcʎG V9g̘Wt/QJ@NCvμEK/t1Wq X$Q"{[L2\)NQ˩ԇMjkm8җ'%?kDm} 5rîdR_5٥e&'m -c+oi^qYAUb&H %2Q$NL|%OGP]7 jS \| o(_$#kZ9)qdK,[&Ү 0#۾{C vŹ?t5&%gSV QpZս 0D*&:=iG'##6*`D8)0H__`g l `ot7ةQ¦̕Kx!_W\ױn܇L(!bq 4 3|H՟,yJJd2TRcB;cҥA` dƲށ_U$MS%D.Ix^b,WE(v+!ٯ[mՄ>~ GBY;ȂomP>S-":.W%‚,#%pbSL&iIo˓Q^ƈ1rVʠa^D}, ŏN˔18֩[nDL <k@1 &ɲӖ@1/$ae.1ц:wܥ6}m@ 6P9ԭqH$ H*WKf-s^Gj\$2G6T.ڥ:癨ЮcE;ὅM\՞2 ʳސtT&vLI*dƒJUgÑ-yF[3!@~g9bڨk؟XXlA?1pb_C>\ ,4u/ ̗̙N ~$oQa:]2H6LɤNzPVNdKi@Y zk0x"K9x([8%DѷXyqx0]H{q(B釲l2&%: dm؁ɪLL2!6"Ķ)T"e$*>tLϫ=B` >~b 7=g'= %?De S 3e*ژ?l'@"߷=0—mp1:dSʔji*9FQ,rh/%pZdN o1a+1 G,qESdx&p$&xk97֛gYc2c@I၍,ӲD"ʠeT2)İz *-o% a#-;poy@p袋:O)dN[IuA*(< E p'- #7Z&dZ'i`**"JK%L\գ vX俥;Az/\*\ygXR+Oy`cވ-O9!Q,PY,]LީTrQ&h16 i{gvV(&J1 Er MdsDN[zsW*x ?KV`wsXgr &gݙ$p>KfFk\٨38#=P2IH e2,ӧˈKBUUGXm Zrx:q]~eJ;'eEBubÑ$2":l9I@]5 ོ?4wd|2V6L^*M^Y#7V6fF EuΕ̘Ě4)&| y=/&6YKq"k/$KIBY̍)0"q$1:?"n#2Qv^ꌪH|ֆ*l$=$w[KO. n 熚- U0O9o: r&bl`ƌeSlb|g)0FT㷞q'on+%E F2RQ.oz*MR؊tȅ͑"1 /xԷΛ} hR#6.-]4y_-\Ad`4MYὑ'?d$yFKzd&a\& l]Q]F> D2 Ey\ YCV&*UWG0y}8$|:_X=i3Ps|ӦMLj!լA @0뎖 Nl9X4A \pC2Wc~ cC(+-g-*k!' 3DLQ"%9w}lD)*"Փص@2b$gϞc&l,>T#hBB|(b̓#8 ېWƺR@V3Svo[g'I8< 5*.)QK?3dFQ hcNje'43>aA-#Gu͚Dn2 P/L07ޖ]3;C:ёJ&Q68[99ŵ``C`s~n_'#5<*;+'AU@x,7 8?Zȧ ͛o ˒D] -J'4LB{īP|x~`h!u0~8L'gT@u'@Tr`F&b61}:h-<#G oA6Pn9މoET5 5E*ѩ"6S_;]2!P:\LQlԿ_cSK+qDe*%~Ë,٢]ћT3Vp{ lŸO#tؒl(u~P\<_RVqΐ12o<{B`?d? )'5M("R K:v6%Sljf홍;8spvڢlD<Qȉ$a / m*k_!֥EK҈WA6Ŵ㷐ʜJ~ oaUf1)WQ엵!E 4]4/ST`\l]"uMC? ro/4`K1uAzlHkʜmLӧdcg,nu֟D3KwK@(V9՟&iO TD)DW-)EVs[uGC4#::%Y]ﲖ1qR-|JFNIJOb@Ѱ(T:6.dDwh?->Tv ]NA NN;? #n-N L*UxԊ E8pw.xP.d'[DrGlAa$}Uzx*!6Ry)(mYPaAixe&$By& 5ɒ&XrՊW%+\я:q΅{)3hLezǐMȫ`"ӧO+{ԫ,,q1XSD:V^<o,~H{zÇ0uT! C$襬W~H:&^X)@.<:F$p@sȼE<<@ҶT&<&qvv7;zӀ>IK$!E;TW)Wԋ;SF5M@JrzpD!@٨4}ɡsڂCa%dE"1lxjLzyxR{YM̆ tɆmܺk캲Fxqn,- krE=|є_ ia vFs1 VgiruE%[.)h_UP8*28կqڢxfrr%jNA|1Z)泲ASFN/kh/x>ǨEY+UE}핫W}?z9].\nݢ |1gS6S #` ?NVYxPq}M-.):Y<9Da ^'e!Q| W8Zυh39:=2V8 .;t`y^QҷƁg|pw`կE+W@8(aABmEn8d:Kc3[`{>o*)iuHyk%6m[KUHyޤ8uvN7]g̅EC^]xm4ޏssKe[ TN>`cK@vїL̷$/ik"E:"G\ŏA/ޘ,PF0'g4\IV nO>t,1Px "x'3 x\rY=:EhI1z3X+[oΡu1f^̮ёZ?0%E?:noѧto ޞ^IZ P*;3 `_2pѽ)AIAttɌP{l86^tfJYh[Y̝H&ECf=ټpfKgFDz&y!i {X)OZʐY{ʲY%%(}[E)s,{@(kбu=6JU:렿1XciB 6%W~:~>>U{%VZWXkn~w}NmA,k适|kb4wbQo=i b?举Lb25wzw>T5`bpgsq5=*5i)ƗٱlJq6(]g=J좭‚sp6c>P(/%so &ᧄ[HpHfL9d.<IjQmo{C^X%MDՇģؿ)Xƪ^9-/ۦ,V}lmٌ/5?V)Yp D AH@Egb5?+W"(8E Pflyym6[5[[Q?Fal@ w@Pm P+VG/?-r[8W݇AVR0yqHAp|v} ;YjI6tmkdthRgꀀ "{,Eb^&E(bX-ݻw7'G@Y`z#09$zeklF1O>el;yp?&2&x[`Kf;80n4QR(FOvo4W݋LK+{(R84|k=Δ@ D#v@MD15jpL rk鐬lml^ӹ3,yYh=p\'rC[W K#5sf41<>ezzMȩR11}<\y?#Ku ȟ2_}5(σ̮ ws8-K{=;|qb;P1b8FP$6MwcFNp o/J xlK}‚>,M3hY%ʀ> D3}čA +[]~[;0*tMl]WRcO80?vU+V0-[A 9Rb"klx׮]fCڒAF_d3#lLUB (2 UsZ{HuhUflH@w% V Ap@ FiK%ç ۀʊ1.X`= mSi1 P-[.&rZl88i#%8 {MOȣQlS 2SXų $t7a jyԛGJ,"jWJW<NC刮xoSLݰ=چ@O(o6E67m޼ 2H'-БP.ĐCeo|%2xRs9@#$v7BOgUCg[%~R+u1Ț@w#g|YLuey¸[,(|CFQk!>R JŖj?n)[u)˗x"Č5 @O`?#:6_`G&I] ]9fPFBVDt0~`ʕOAb{@>p?1 ni࢖lwCOd!EJ D4tB8@dǸ&$r>?nŌσ[QKyG" F. Z$[^Sz1{>D{,Qu<KTA}~Ж0=l/E @d  uf$Ca'6: (l9}By? Л?㴟AI4d&iiifyJtA3R*HՔFwdc\SaY4dT ZjHq%Ka+9J -@z DzǂCؾm;4]3:gv)0V2ZŚq_TH#:J$o,/1F׏긏?I⣯w)iqpd:+k>/DR4!)-@o] gUonūӷҁpP71.\| {$kSV: @w 7/~:;i<2wހo O5ӻ8泄  J ںF.R6Vp3"LEfH ä=1?Nc|t~p?}r N\SQ;>*OL*-fX@3J| o~)Q!m6mڴIP$/; b.B9y d3d汈".@Ȁe/=g㪰!!fz K(vj f:73:'02c|z\Gp1:8W^l;!SR]Ikt FHRUb)#0QHj~:sd2[! - ?A%(Ji3P&lG"[nnc#‘V&? 2T #H7P M)؝K)t'zQ^JR[]ØG1or$ zL/}~vL?"z)[ba"BI^z*|F+_^ќa2~>~sMrB[oIq e>b\gyHJn®لu.N~Tļˀpy(B޺T]EbJV3&L,ZBL+h%Z a>|q.qƿ}dlF6ڔ 3X`Ll-ce^q[chT iXfo78 ]r㗍[[]P*ܿyEF 5 ϗ0 ڠ,͓dl +b ˉhYG4b)+)=UT`+^w'O@φF\/h)N-׊TUfϹKgˮV)ر^wo i:lY_=mܸiRi-f s?Mqa̟r6eѸ#Am3҈f^`qSOG/C Ss1k'@c_z >@Rh/ Ї9s2\Sڜ쏌40/qU vĤ̂?w?~3alNnjpC>o{kޘg{<t_0$TJcƍXYy +Xe\7Lp`/6gȏwEЙ]+@\GǗJwܹcm -$?[+wV\j6ƅ`ݐ=[ : '@g4LfSSȎG K/[gVg"c2V;UT7\R"3lLJ|A;خY3k'Nlwl{y:)8iVuaYXkb1D?dY{? x6,[M,pJgXV-@KvqgdM@ ]9W*K$ճ.c| -lY>~J׋PwG_Pp@,t0+9qIyHdd0O3*s:/<8bY_=53-Lx017wMf83xSģòq( Z`Q{J+B*- ºZkĂ'"A62ʹ @ˀɬrpȩNLei0tN;VNU!'´F[ul'΋:.XC]bmd.s #\J,*4bXKr d\Cjs @ns@d:ֶgʕRx D3J W7][72.u}YPH cGz00GO:fb7*(&4</Ø_Nl%s3UPDE1e7ONӌ'米^:\!0F 7DX*<G @HjYw@@u؇Mbz#1h\'A( 퐺#[U 2f /e0K#G?:3-MaVw,U5/N-5gg"mHr 'Nҕ2K<҇G2t!h4/+S*Ȳ؉23/b˕<ҽT|zۚ }YjoV=[Tr n_K;  _/Hڈ͐F:mUw QyesG o_0Etl8IDAT^OvK./_aJ4 PT-8Kbdi:\DK\Hb)ɉ7 IM<@=γ2uږ0Sػab#$)NX7`Qk3xL~&'cfFPSijgGB!ElOJ!rRVvעtu3[j@v/]G$A7q _N@#? \PN:JxP|&`N_%K~LuS698dzjhn _j! MA8`NءcFAF gg,[LarzODym-\B?E+#sd dH#ϟ / 3Vv٤q*T i:;NYxn@e3/-zrQ3ç"I*5 }\@$R)[-ZqGю Ғѐ73,e -=ߙȗ@YN03i;)YY/)18)*LYޤBzbey&&>EgMpƵHq|+/`Kje @%GrBP?!>c|MOZfk-iT6>{ cP0~H|qxK;Wڭs,Q.˔+e4A0JL#=,%ee|Tsø{)a5FLJ莫LnD73%u3֘IkCbQeNK3ד(ƫlj3ڼicxP gn4zj.` xMy_zGN@vE;S) KON#u*,8͙+r{oFc@ |=bThxY?A7 1U%Ci-f6*#^vV4Y|[g,tBNo'a#_c:կ]V+lDHg(Ye\eF#O,M8Aow}w\݈>뷲 n&W0q`xN{ FIUtlZn7h@^}SQ` g??tG4ѽx:?`oP<*iB<.W֩Ihp:>Rz7b H%oS۞ve'⹂V\Yݱ$\*5CNGLGY9 I Y4j7l Ey@\ 13 gRva f=9e4m瞻!U?mGnS6&# GT*H嵒7çoƳ'*WQ+'fvK6UYÛO߆ԉ W ­GT8ݔsp[網i/()ǘJgB,[Q1x[3^;^˛}(ܯ}@B o}ހMo~ ʎrۢ1H E8P񬜇<)<-y՛7oZd^:_ NLS9]Q&knC lBNuVvlċ|q(xPg2Og8pOt&0X-Cք`, iΑNv3iSq. ͯ SZ>`s0bm'Ɇ;K M{i$-AHvhk7GP@۠(T=T"T Dgk=ifkA-f sc|4xo+ P&w9uf *SeD˗Xf%2΅3:CZkFk[Ei!>n֭=rÏF'p>ƟܦX$CJqct@JB ukl3g΂kr/o մh(w 4p}o_RR)@½cq#Nnd` A24fi$LBhf۴P7">]i-/ح T& މăD,C5DlY-njNe%~+)+za98}[׮C#žryr5F#.1iZ0w(I'1Yzy:Zjdin ͡⏥U94}`jDPPh LyNڧ H h Tg%^?!4ܷ-LpJ@C(꜁T3xqF2)r$I/uq1T!캐ЋUͯ/m~k~N 6.H£Uׇ&6|KR>_/i=1_ԲW;f8OŏnBi;#2/D=/a\Rpy.6Dk|k_t2pg_[p0{:JojatY9zrU˸BĽ#X)n I_$0S=3%9ILTz<1#̑f8RTZ&IS(#n馓ȘexYTf/AO X60wo߼Vgifk[Uv|>M9k~hK4ňy ñ.r(%!kMz $41F5ڵ;44ԇq&&LR׎ }(p V) 0p _F*2e4,0V RXA,^ϴhlfE,xsuCD\%(1g,{7Ɗr /}dMSh<ׂP߇q'Cus:;:/~/c?O‘O4 XxĎ"@"k,FQ` OoCts}lf2M6dD }f=jJpV(iH'L +q@/Ff2vZKsd2HTW^hgdĽ2:R,_*?%.rNùAcnj&lFy`m'zgm+j3eJ#  ӵ,B s^veJ+|N(&*0Ǝgjyͳ/ =< B!HGTb eb\WQ)WE,dYeʎ4<'Rm"?ؘp xxT,c׾vCtIsb!ZOoP R t6۱搸<2 ln3t3vi)bAؾm;6[~ 0U g{M#=ۻ]qsfeEJC$?4bhGy/0?x1TvvGS.d6E`!@r@KtEs]P_#o蹑Oo뮰 3T^lwd{1?ES~=m&7~A#f_Wq: s9S(SoL~ϺvP?(; eM53}{%ḆvUTeqp?a;: h3'r(ŧFbyӲByA%rmS&,]4:ufh/".Zۊ3{vNyHe,s]*2,DKr.˒4XHg2L<$_G[AxavL~w[oҺ8ֶm@-1=ݸɽ{nE4~ϧhgCO;JRJS#Ho jll1n؋h" > d˛1G~|= hD 1rTs~a`ҥN2mWb?iz:.Bsw^pΠs“{>=]I9n$=vNTFG>S\Vqɑ>RyMΥ35o ?pq_jغm3 |_U?k*b싫VsO2TWƮ/ XXPo8+M Ӊ086s9ֵv ܍!iV",pƍ86380M0oϼGp H39#(Kfv^³d"̖?[;r:q&6.} s #8$0oaƩZh5TK. ?Tӊ:'v b2Q<2Z@˩寐)b-x+䷔>b} ='ތ/P'0W]RZ͗0 ^wUwm۷oQ~94~jF)B$\K@ՖhJ޺uk[}}FHo*vy;i1~j|ذaaIC U᪖v=sտ:wL6i(#%,M+A+QQNZ#z7y]J\C!KS계SjN 0A;wNj J6;;߳^inϣ8IzPrjAcJOo 2uژIt7l{:6Y©  hW/bcUcxQ϶ٯfR ;]x;LKdW꼪i1IJnP$uvQ^YI_ Z*|HWtf=_vCxN 9v駩?,&rvZɯt+]P)?>{3Fhb6 0Θ1# >/JeT=(a~oÖgOσb|dR JN쀧vᦉ C뇙Ӏ\lY>ۤaC(Tgri MȀ\o @i_=jNP hSPJ׌ts_x鳯L5ҏ'H(.oE;/ Ob ģw¶a.w`_vyQ脿[m8zƳ'~2 Y;f<g>c? 2trO%xs?f$ki'EI=PTu9Ž&3^{+Wͬf] DL8K3~=ט L8 _xW4_v9>dD]JkK#d b$ާq?[o 2v#Ο j^ǯa[k!,V,'N}(+@hvkb4qΝ;pń^Ç0bGTR!tq0" p7|[0EVn 2: YA㲁Z7*\0aA˒NSP6~š0/|zMaj43~c<'wo-Lk^f.;M\l|8hύ_'`% s_;6E:%Ӌ8)ॡFY?lY*מ]/q\|aTϽ O-s6N6 tAĔCKm;G}?5 _ýދQk|N"Q8; __v]?;wXWS{|<Uv:Kc*S&Lh3#'h$AK,ns '-Wܝr*‰n߇?}=z.i4=O|yi`uD~փb_~ugQqگOiQ;f즈3YʒTNx*'lj>w^*Fv/@'>BM]7?Pi_4|&sy8^28N**ꩧ?1~6ѳ 2)un [7y5 -'1@84o8#aD1pwol8muW:;t6#ȏu?gϚ*qOtlU_I˕) xqtOhI,gy>֯]oNox6xڪp~avaច@S7&|`+_j86Bٽ'$nX?+/No^GOvl,@+7Ѱ/Ĉ#2+$Nqt@Ua΃sa.Du*^>qMxb7Uon}KM5DJ‚ q/f΁M^ v7Yb]wKї?G}G~Z 3hľ>6G5''!eu[O݁!--3 !'cGVP:++¼{v|(6kh̏bq$9k9$%a;5as 0øe>^~n Ѩ :3.? Q>ȯ_k~<v~eEx: !WI]6zؾہ{;Ͼz;p!Qo sW.0@z,󪏆=x@cuc0絜̮cpw>яK3;w/D`^… |ԧ~v: t 9.Z2`e;RNNNh86g68ik p1kq,F츃O9K|| 'w$SM`F@n^}ʵaH͐`}$ܟy[xI1ݷ kѝ׉>M6Ʊ$vx-F18V"?c} wCjiF>PtKL@4L`0]uU7TW7ϏssV>7D I2G8ap>FX(_B#ݸg~4w9 K {`mx;a8wv&rG} :vw߰e˖5@5G~XT" R#x pY4tذ/z ߎ[}Q%ؾJ#N2įp5WYyBXwm;og5+G\5;44|bGػomt@8Qq~o)/)?צ 3 v/4#h7xK_O~X2vM@`G~yĀf~,c')rXy!=:>¼ۧə>NMM!~mMMKk٥K».Y~"9 B<" :h/Q?G# gTaI )]kc pf|̙3S3 h<8tvrfHwS!&<<ĭ{2|m {s8[}[jK|GU 0(BKv6m` L4=\3Uט29vؖd+mn-aHːeMƏCA{,9w ?2oޗHpėШ/O\AF/(za 2  N|_@N0__~^Dä1Ri/cwGirNhhh]zYCN)+3XH=wϯ\3PGO|gm=>p`}|<}08ޠ;,|sw}` /çσS~2ŷޅ~dPT$V.pџ&w\(n!4y/mZ4p9' ΃;ý[ m7k_y ,7#}Ϟ=PNGqʯ> _>G|^ndHw+P8a hZ`0 {#: CNqi@`9 04@JI`^x`~Cxի_Lkqk H"əQH"ygq݋q†Yg.>_ǝ1xF8_xGr.8=GGa s> yKst:1 P;\իW`ѢE" =pOF[|tKIi'DO,- أ$lN6 h gq fB\mla.l~\VJB7ܷWC~ЩnMՃC./xpfkBM@'n6M`ڠqàsov8#:~rFOƮO'}6GMD#KYY\v`#d r  hI@8xgM>`qǟ,#?S8C0G<}i{aʔ)^7Zˑ tH^ c:9EWl*}/~aРAf:K;Yv,Wms{QQ_}yPAZU*20V4opB9B p&ـfZ$]‘q}FkF@7]$ j,7/k^0>) ʦr8E<: AH囃ӄV/bnf' xp| :eնvYw@:cXvMwuրGl!h])s46GG|=.3s={9y2z9k2zAJf'N9\.jގƞXh6@Hg6+AΞ0~kX|p2G7aH%7s}2eJKå^&e$$hbu Dgt6s6b=x14hBr VZqi?8gFźa>`*Z9޶QE OF|/7|OWbRD<^)=lqE'fhY G 8٧~~+~Tv!>@ZO1+."/_p~*䤰r4%N%C)h:hϋ )R? oHy N4?`>#Nða'Θ>/1WBZL֧7)׎Njˇc be%jrmၘ`Yox=YxiK.嫱fԜиu}pX_dW0⯂iei'G|:^M5~6RFAd j Hf hr+0G0Su6 /!9$H+hĖa0_ۋxBܿueC.#רx~ik'2Kz0NV$oc@399B mp3^|) g ec!+x܁4 e'S}|n*9T07aUPx4# Ac_A[k?qԨQ/׈.@8 gްa0؁"h2t&gZ'N( ^i @ 2-8Б5;4:fج ˃"Ι@{a`x/ɗjtTFy94x4 Ie;!7;+9P39sg@<Ѱ]C{%z^l>kp1wi4 /=4J/]ƞJ?aOVh\4| s'@/(G!`WG%x8f f2F <ѦM.h[FtE'8Qqўv T@"x~'nF2b9 s* ĠˇX64">І!> 8&qJ>}}oñ.|c;6C߂N߃Qܑ S+ ŧg 4$ +h{w2*( O M E3CBnĹ3r9MUr)NY$'i 20 a '!*Q*WŦwdJ#dP;<6pN`@gnX4qFȀ;U,30F,d(4".cQ2.\Px^.g2U ._`Ii 9.,hb t_E}U%0ES#7|Uъxg|a]LWTF#=]s<@G!c@;@Qw`1i 2fs-ҕ],Ki:$oW!7@W0i E&_i W99$ $c'V>;;@86 Ta&T3MtS=`npQIz 󼬟i<:<9x`=>q֔b^. >9C#<*,dMTKE$=x6!֨J-'!*^azVLId\Ai,V|Lc(;5r% Jt\rD'Ӆ+-i!bPJ'^u4_ThǨuc,f h+XdN/y<]0O#~$!7>EHWi/mNKU ہ:}wӊ|8D@JY/xir=c=$@1Q]*)3z@{J:=P@ǨsC2{:sZJgx׭jǚyw!w;[{F=u} g@)40 hhUp+m2IENDB`ic12PNG  IHDR@@iq pHYs%%IR$IDATx[ypEv3c4[%lKdI6f 揀1X]HHH*)v!kCPM*;1Ky1l[ز-K)>F4h~}=fd8[Vt^%OIoP%)e]pʃxYIM 'f856 9.6= ò/pTah0MIV`!N2vqLdkQ<%2άNF= 93999'\hѢyYYY)))Ȏ zF=ޡ!POOO/eA\ _'2m4{rwlh [ocwO?:A͂0e` KZr˷l+W\A^y7dϮ=ob"?hld)8A )w~NٸA0?~졇@0r&hbOEnݺ|qb?Y>j#3$ VS2"޹ jeA풒rwY;nCFk Sgn/?$o9+؞%Q<`~ qSYiDJA~}VcȆ?C]jIhM0^K67o_ϒ~0OkN,edQ/# ʏPd0x6iAɜ9s$...qٲe.]\]]9k,|ä`2p*ٳg_|Y?&nW~,La=rE#2Y*P$?'2S/ [vu?##cC b e@ nϿG 8y7ɕG.I $FRLBZ0^36;paIx* bKk=MII)V,7<4sLkKQX5U?穧`ooe+OPcY2:U_iձ 5Q!-(ǧ`_j֜;%3#C s.ܼy~_Ϝ9è6EEE?2jٳgowpLud%ώg,X0%<9$H6" ż $vmVTTEG* o)Fzy?O6%ٓZҶIu2o801z0afG_P|uℊ=ЕʝEPN~zeeªUDNUWK3b D`CS݉ce8b4Fx8sfj%<^=(B mVysׯ_ѣb}D0`d?W_T+[FRjOLB 6EAK# h Oʾ-. + -8Qݝfgԝ+plܸA ٤LA/somj5&X#!`Zu2jQ 1i΃ Mե'dxpH"G BзPTM6݉4ɇmAz3zd,r Ix!)_MDeym%Np##+0,",#m6jϖ4DG}d>[=ȡ7Fʟ `*F2,Y(ԿK.aa jFX@L "7W=jnk1G92DSgNl^ N$>..q 쏯gBcP$hPFϟ W Ws\R-YtıJ4I -sb8C 1F  %ߙ!8nUlrĹ.d$(ذar/,bHj50*1F1\%|4jhFyU(:"yu@As2f1 EONO\ldN4| /?xڛ@;G]I3M:WOil / /` PL6:CU [i;#ӘmL HXT+ZOc+]6f0j#SzWlSit.GG@Naqq6u"*4ݚ@ ?nۣ«UWè㙪jAaNS/u~!z8yLlrR=Jf8f6ᱡ%s3\`,Ft񄘋4JCC$6< "〒#&(w+V{gۥPj$vDqBB`lH^K~YÉX +W!* l4! ߻>!(ۥÅ&~a A_@[K_/4#aRJ7ZZZ3||"YjRū&di!!NMd>(Iwj毮ɸb Cb[XweD'瞺/hmo#Q+萃R2ԊP%/ ^R1QzuKi8M $ w.a5\iW~a|O^# qe`sscǗp -0IyMF9+RFQ|;:7p O3i!ғjIrr>0wPd"k0&\U;#%~hh"AdJhģfZ fYW€b02EZ';lW0(q*8S!<.>!ݝ֦z;;:/_\[-< 9@v dCFa]p۽cxкZYW&<g "}S q\BWJѿ3%!Q+QJC藴͈)EB9}4> >:rh;pC&rc\0oF^>/>ꗍ. +JL=MMG%2in e+oA{ ƘZ`C``:WK'w^PPЅ/FJR43Ȼz--o%tvK|K!&)Tٻw:]?Wlu\#h @ Np ʅy@HQ]d033Wp+aN85*r>>pUf/ >~l' 2].,'qݤS3q@~T|::o=FKS2CMfI:H"ǛY>ax|u~K]ũ;vx>/oF[kk O㊦AH"܊m$W]y^KFPpƫa"Jp8a+hFO \p<̈mn~էnWpQ+WCߋ /6;lAˊs0QK-P,C-tRsl}IG>Tɗi'n7*4@po94!FO?&91HӦdH,sO co^^Rߋȯ9S!kS5=Ջv++32\/],~"늿)Ru?Qi9;ql]Xd˖G%-c_=}{<S ⎠7C[.Cj sڝX2{ n/NhGF\6g|&_) sڗׅN/t nb!±LGYVU #h򽇾/eҜ,ۻW!VX& sq$.M%jNɶ ^-r q{z3K7J7_x|tϬ^p!9:2W`{a"hzyE}wjJONCrfz& 3gHyJN]#ؖ5;x rtC{[`r =PLi1Ncńv"ɂRRSʋ !8GLeWTF˭koʥd?C3ᚫ L{}Rs#3 b#g\_{?=#pθ_sUhs#ЈI J*$!˞75LK0x "%[degKii*@tvvIOw455Ʌ T;f ^Cŷĥh=U^˯#PB Po᠖BKρ$g %;tX^fX7OÖe= t7m36vC&&54.j 'bexLB&E &S՜2 N -`F#L0cx?Sc%N@\:s{gh$dj3l'z*P̎3lׇKtF륛)M. 2ʬz OAha:pYiIpCݬd^\3h0 c&M?IENDB`ic07ElPNG  IHDR>a pHYs  @IDATx ]Uk5T! ! sH Vv@[š[_v'E[l d `H}pp˺ 6\g3tQ_B]՛;(xG\0YAPMqpf|| u5۷|=9q̙qS|o_Aî BETK^mU \6DZ@nP*TQŋX~ 7ܰX[[[{J^ЅPp(&N Vpkhqph01bz0}֙g5rCvR ykfW$l&SNAC*WQӭN^@d#,]|p_|0tIvօ@ * n3(hMkv.~Vգ]|(Z7r1NK6mڴB"!WjXo$I),C|sR'O-P:y]4z$n׬^.pۗۻw >E|j2{~˯BSiGlڸ1,}|v@ 0f̘0~pC=4̘1#pI)S,v)`TUaOXđM8jEt򽹚'x^qŏe6 6qAp2k#x7'bҎK2|r[?|u}'9==jWRdki%=߇MM͡'g~h/W>-\v{[ze*i 쫹?-e~8=81J^ z6z9c& 0`Bbvպ6R+q"\L 6O?=s9OTiaA_G}UpI%{o;e,Y|jpmG19l|T>#/Q"u?^k_ٗNUtw*P-CM.?~zSXdI7aS+i4%?IA0zd1I˗PU1 -[YZ ^pA%m#Ҋ@Y. I*r/WIo*uo}%s&3c5o w0}tv0Pu.%J _Uȶ g% Oip*U+(\taAB[{[!@Wзi˦3g; 6M3TiԘ3}9?Wax.3Ua 0+>;H5yO[LI|^sx*h'.]7+Gcs9m\p23W\4&{4ڏw"g*pZgB㳢Zl<'E93 g7;Ɔ1ƋQO}GO=ԯhEM+&HQWW@ Q| (+s5tt3BzzCScc ?BCsCn&B]L n 'SŏB"2UքviЦe'-s=ۣ>&LM )# oԟ#V|F=*XO>}|K+ SU-ؖc$< /b8~ t&g5c/(1:dq*/SW(gXq睿 KKјvN+ >LS-F;h-P ,'\I#wqH>5| ȟ}C|>+E]USԚ8i?Z{B^35/Z;'^ ؜)0fO9ٕ+̶`( /hał X&J} wǤh ˗yHm/\|pJM׃=ƞ͡H{yY2v G59kDB|{I"6l ?3fsxc_mMI@BcKc:-&(:'[!?/t+,C]m}۵<т0aVsN8$"vy+̧HVP|no||tUW_p~?"׿?y.IaMfCzM{uaKKl7O6yADpܱΛpmwu7B㳂@ɽr#%3棬ut9 a{e]Q^).Kߡ h;3+}8Rָ w#vE"We>jkÙg+*: fpW<4|_WC`2 J)Ye>{;C#K# zu_?\я Wm88ʮjl"7.B,$V:| Wݫd-+ -y[z_\Ҿkzp}l2D7TrdQ-~v`1֯_-SqgT? ]PXzsaWEvcIOˑs4OqVTk@G_*Ō2G&e=,ڨ͢ 0o1ؕZpBy.szçA&\ZâtFj_,4v>7!W[š:\ TԥLʆ5wYUN{:/@z&W!j:t˺"gMJaJ0- {}ȴi ]?`#g3)6uP }3㎏fWI[mpH\MMW̧24&yvgpNQ+^R O\Zm$>e% K|)jD3^{mUl̋YomRy=\*Мx/IM!:* +uKs/{޹oX%z7M7FoxPUAps$5L tJJmДl%H#n -:8i`79h39YT?ujss{lhF> qez1hI"օdMWZ}>Y('X :vpQd>gΘ: '{P?^I*I2?M-9vLvcw驩`*`㗂ڱ 1G|^}z:%sm+%*p[ޏeJ39g\W,u<|44.pO H'JeB,ѧx3n"j}??e]R:\X{̚WF0|`!ʓ&YtG4Hxr8CDwwO'?!PE 425Η囵VoŪ BtʠI7X!>66{&kO^lٿE'o~sGXtIѸ#<7Avc:l B!OеVK=`c{0(kLt26gI;BӮFԆCM gq&f`T3g;뤅92?VA)̥`'{뮻} X!VXeZbḃvv){:0]r;bfL'x#h0?}"zV\֬[c!,XeAO~򓏈 Ldk I<5@ gL>CNXB'43L׏Mm`0#2?KȐQls 1WicXB 8hf?'uHWpz4dV͛7/dg*;E;/T>7R&^ DWgm< ?.t6wMn mwו2ѝY 3,- Mi6dRmFAvS,ciElJhq[ǎ O>H|2܏*xϜR n+Q7}s̙vYA%DOfiɒo7'2?Q!&LLs?K48'-܈KW+yOޭ$w 'PlO Gxت:V $.*e*hS')"^: G}SN-oyΣfnK[4^臍7fAM{u*؅b#'465\wQdI:d"P\<GM f,E p/ev`+lBGa 0xq' Q)1\Wp:j;Bhf0&ZҎǩlqw}S6J #5&RW~ʳp +k jExv3v'+ |ao0.P2T`Y)6lwoL~Φ9)>hO^Hx|exjB1+lGYknK|_{q2i) xHA]yաN @d> +S =VO\5tId|ht9* !ɳ|Ay,7jb[.i@~8^NrKxrJ:V_,z:6im axG}6o<ć" -\:u^&9g 鉞\MUxu>&Tf~xRBaԱiF~cS#FF3|NpBe$ݱu- hOVtaED,QR ÝQHT DBz:ġ%9g*۱>o$2Cg?@4 t4p5ݧ=!76I @R֥F$<^)ECg Gц%UUPA-rf)D CIA;~4x(3᠘URG0l68QX#/Ba%P `RcLfdݲ6JD_5ӑeZ&W؁W71` +?ipU&bwkh UrajHH+h^,x FR8I uHyƲN=.o5^WLǚ>WlTImZZebk<)6LrRӱ44x`Q+ޥNjTpS5V?ԾV:]6l#HNkALJ\I 90Q0r,B(eֲr([4ph73r!*?"3 #_֬ՠ6I"dRVah6MQ * f7@\ zYv]r~q?fys#pZV3aK!Jg8` LT_BSN`ڱ6? Z:e7鲬\b-Zz$7-xwOtAJVMUit o>7zFY `dR4:)zwz>nJQZPC1 DAs6iLYf#$k,ݔE7wl73A0U+D,8/ʦ'2VYa!GȺޡѯ\ r .DYҤ?:`Lx= mܱUi)EI ɈJgAŋIS7q7δM" x"1 G% |NK7-rRPi.mA}]Gs9!Nc|*T9F_32ǰ=ykyeFLRT aH2IԏE#/!/%z^{P66H6lk(W4b 2UŔsIlsM_c(Ӏm kxr~Fa2Ji.Hh|gUF^(-u[J9θug!COb)By' ۖ{VK:m#|2^ISJ&@`f6E=M:%l#`bI*44WMQOpdpnY2d+ӻx`.۩lLH^I8)/`mشGH!ȳL/@a9ei; Ь?]ԋ|A*eP'hyJ1Dȅsߤ"zT A3:tP#qފq\ 1?/ (/ G?E^Fd-[$:)x6MJXlh'MD7LUK<Q#ILpc(8oTbv TZf g]F^Xy!`7h$ؕiRGXt5Ŝ"fb1^o"$Xox!( (4)^lkS@,ԋ,xs'UduV( )(VM([W֤J/RGYWlCf,eTR Yf6m0𢡄2P{gᕶ7Ab9X @Z &MѲX@ő2ûkkC"}Q9I/2Lb֨:cB:ao;z)GapZ $M*'i|! .fzB 8ip}'AFZRd$% 8XA Mʁt900r #k<"~Q,o,q%&a+vOd HUC`(2UpצsCR CX#ӡCZ#2!sw;+Jd0EI6:cCn޻yAQQog"`~8iMCL9sڕ;@:>pkcPqNaz;:;v%%p$A | + 1lFw*DU$̟6Tr$cxEzY ߂NTe{2nx@GaKu/M2t(}nWgg[>O@^6f&v01Tg/)aZ,˷9UJ~' =1F}F?C FX>ҟ/Ōlz1H[?ZsyR,yٴ@[Uӧ,r.` mEM.{*O,LGoh1AM%2Mhosp| {r1Wpa6] HRr@mQ]2LD-缥7Բ@C7[FR+g$.ɶ@塸TK Я2rN=96<>ۖ:|O|-0b ;UŨH_O+L=ǹ w/bmd]A_{eˬT*J'k`|6=m9tn`ǯ%Z̈́HsJ T!AOIJ'BOsWq/@/o HQC! 0<,Rk>FХO/"ZS@CP6v_=HYE-!h}4@P~.jW99Ab+?-yVY ^yfy%t\Q|<ޱ_,lڼIApV<Gnذq ej5]G2(9$ĽUdHJ 2QuBPKX>yI\"ƫ%qPi)ESd,4/G$]A|ڎ':{uyﭸ_|'+u k$o4@5`>[q쀴o,2To.x\xS.MYũHKW pDK?=6mh+4]}#<)%4. Y%;vJέ:O2tgZFI8XdqgLT?qZ܌X*흳9_N x,#7hi8xNSԼl1@:hwjLq(.5@*VZyOKK0FHAe@1yǍ7x]O: xQ;M-hXu#횪9Z*ڿb q"6b<Ρm.Az!:zHj_py~d.GQ _JOPiF)Tk%|zIGΦhöBŷeHxHe"h"5> pOyr\dIhnn&H>/?ab&DJ ]in@)24o7`t_~!.zX|V'=+o"C؈\?":}? >M-zyϿ` hb&s \g5@ hM]xؔb]KDήfM<yva"K A3%)!l c@\!h걹 22Ҝ_h 6$mM$aJ+ up Xk+=@SM쳛o>63݁ vdPs==P 6UԄkHa~BPzKGyzTxus.4JIqXe +W^0<ۭOpĽP=W֯ R| :?+[L``.iwޤ(}$E s>xҺ^8QCTpySy)_9M|c|'BxͤT(2uXr4l;~V:-;X6Gnvb_uSLBDs7j !B'Ε:AJ$PG}S@T`O#Sq:^V1|sLARlYڏ-)^&U94vK&c+[ü9mڊ#@Zy+~'>lU8k&:6H(oFP)u] Q&J=v@'|< Ǖ#6@2L`QHCFpIsj WwH]8s? p87\ĶR\U'ׯ#3-zOF/a>?;z?<2`# WOzL`җ.[-OէL.|B+ᔚSo5R!)Íjb4c93h41#c*3;dYaqX4j#M Ig[CvR{ZR3Ȣ^eX!n{_O^763^.ۓ8m5 M햟r,*ϨC@pAMl&F%F@R"!pp$~=^m#{Massoz9P-V"vc%N-À-.magM<`bxV4տC|42kxP exw|..._)˿p!G|1j};}#p,}a֭uޫjwpjHhF YO*<.Z{pMzdO~ Nژ-%^Q b2xa&iCa?jׯ%Hď~Щslwڵ?{-M+S.D ek#ZK84 x:u!̧ƏV󂑾Rϋ[bN#aS_Kf}h#=7& tG'e)#i2޽pZiZaUhI; <"Kg.kZ&cvg{(KCviZo|?^{|ILunzw{AU::bX]ĕż$]PSĽ) "옦8#:Fj*P$m8$4T=)w{=YZƶrچ5:jHƟc4 @Y| ᥗ^\7oڸ&!%>]>0q|Wχǜ~v$^d a f`hHѵ0$gSO;G!^: ZN|%*9,mwm"_Aǿt7eM}r G@.!k_NaP>~MY_E'X5rLp V6zmøTݧV4[cEaq-KpG͏I&tj[֟݇+_B΢3?1Ɂf6ۤz x0PUv[+W4&L8pa}͖0lc}+A )2I)m>=}|/4'Q!PZtUmmNP '>)5$7)ezY_XbarZLTaWHKeqE{@:AHkkkBgglW_d6N jcĘ+׻ri^jl؊x!829_?aLU~(H8i;<+(hJޤkLKU n= 8Q&~ٌA1KC wv7)BKiibt]*6[ȔS&!c"dt\,l :h߿9| Bn pme_k$տY}~> Be2m.~zӥv1i韇r%|87<]\n~k8 74A&--lNr|ZpicOn=Wv~!MxT!>s!vm솏#Bs6@F^d :<ҥ'WL2eSK-x7 ~=ъP|s)}C?xk_M=֭< =ŋ?7#^ Koё0U~͒lIQ.@!GuGƛ;pҘ;Hm8iׄ7aeh@K/- W]uix7yCtQo>]g! ٍ8 ąj]5~yt/0xî0Mlpڞuay2v#/>o>33Z5^S=7`{߻.p =m9N"rʫޛa >QJ a uxʟ>sdˢ@Hd0vlKM7!S4UU[z7 ׍2ÔClfuܸp:S=LhbJoQ~4ZwԧgV~&4FM"FGgWwfzh2[4V6Ɵf D{+}a'&Mh?ּv_`Aa ;R35J/ݡ>dG2Qsrx tq8쳯%@QfMXs{{G8cn飔s^NЭEbuo}kkjBmjtŔυc~Gᆥz2mm ':GBeuU-s30|MF izB 0OЬ%z׻n4G8!. ?d%'A3Z}κoM%21_;W-z; \6ᴖӵu~O8꾩)\Ҫ=3I|=kCf_mPY;:j̇j-YFFzncG#3j;a'̞ڴLYn46Q>Ymw=f@@19?҃/.X6z EyzwЭrL%qw9}r|7x`/ꨌ|d;çu{1gO>325a"8ξO<9^pm 1 3K۾ qRb Bp)AhqƊw;&tǤ >;rjm :;z>aʔ)z?^?F;}gp wjUVu $zv[wh:kDS}e}hn3کRe]` 2I‡օ?яµ^%X}˗ IDATo*3 |O P+޴i?S[~K}t}`ʫhB 6#g ƹs~/z<}F?-@X7I6m8V~:(cv> (9JS4"hE`eUkFGiiW6E=n  /&L`e,3#?0^Z/?*R,^znz>⺞ޞGdG|{G}Έlګ}f5Mĸ1ъ|ӵx6KgQ`{(+$PIg}0}W5ZI LMZ21]4̘1 o}XT)p@o7l{,YsE;$a/|cՃ`0̥x0(߰". .0 GAͰ/dhNGX,mԩȀV-'(bp搆dTGmԵMp6߮k,RWy<; <>;RxG;C5&@A{G;G|?<(?>,+-c±c-+)CQ&#%&(&%#(Q ض ѡoodeeeQgā ƴpv}櫎ćƔ^ݬŀǀŭ`ÛŀšnՌЂǀЏ  Ž‚i̜Ҁʀt ܂ Ķ58'&*òƱ Ƴ ƶϬ$.-/;ʀ қ CŰ qԔCǀtNÀՎEʀljWǀՈ Gǫz SɁ Iǀ}XkЁ ΓǀАgD 0j́ǁͣ`7 o;]ӋԞVBO)6NxʇxG;C5'@Dȁ֎B<)?>,,.cȱc.++CQ&#%&())(&%#(R ض ѡiidJ57875KeQR78778 7789Zw}B8778H^ݭ@876776678D`N87646565678Tnv87665444678Ύ ¯@887755333356788B‚iˁ8764431/./.0134568t X987653310 11446789\>887653210 01245688>8764310.,+,+,0024477888663310.,0244688664210..1243688763( 57788 @887552$46689A qk:877541! 35678:mtN876543!34568V K98875543 4456789Lz S8 766554445678zXk9876565&567889gD 0jc98767567789d`8p;]8:8:8VBO )6OwN8;98 9;8OxG;C5&@AyD8Dz?<)?>,,-c±c-+)CQ&#%&(&%#(Sl8mk*00*,|}, ,, fg fi ,),*|**0000**},*,) gk hj )) *Ȇ**00*ic11 PNG  IHDR szz pHYs%%IR$IDATX W}PT?]X`W>Wa,bVۘcJFt:8vZcN:F#юNk:iN4cFȌbe7"Bvzs߾Thz{XKРE;Fk>d:ac66f }`/x 3 P( k׮+W\p٩V5^S5׮_;GךOK39pV_Y^^)5=mVg{'ݺuCC}Bq=ϟOK.%"'=={&>>o/td,H޺u;;vvu8꾨S`)/$%N]54~AKeoG:~M6yjkkYb}F>85,@W'Hʀa3 B IΔ(bԫtZ"i[f=z_666 > TUU޾}{ec%н4,YעQň9AD#43W(NMM;pcǎΜ9Kdz6oc@S5CXoT7`6A&Cd0l`ݡo2_/|=nwF` @IOO9t^=G)꣆5I%CNlI G(O=SMƙ5{6ZK_@O.I. ;o?&= IXGGǹ'#Oj-|FTV$ 0@RHſ[(vP5uTB}" l;ãpw]mabD7%c2J9a< !LAg:=;٩O1X5|~Gm20 Rq(grX&i,ATZt `޳_I7n< 0wP(9߉x"I /P;tP N%&Jde5ޗ'V碗]'Odڵ2%l'l'LUs-  o%%%c  _'W^5_{Q,%"eeeQ#LvMW#jDþ?Һߤ(]%kϟ?/0l`ZZZmmm|GC=,ũD*F 8x\yŊnjbRb ^"38c^<8Nks-C[cs15gl dҷ IENDB`is32ww H:;;:J;;:9;;h;;:89;;mc<:8755689;96569;?;;96322357;;;;953 67;;A;83 0:Adz>:8309>|bA;=:9409=;B C;>;:9:>;@ 5wC;:Cw1X q!SyxS X ww ƿ ƨh΀ƴmĮ܀ç Ʊ Ʈ ɂ ŔfȀdAƑBGĀD 7ww4X q#SyxS"X ww F7887I887688h8875 688la97532 469c<8530358<8852/--/15788852/ 1578>850 -6>dx;750,6;{bA8:751-6:8B C8;8767<8@ 5w@87@w1X q!SyxS Xs8mk45465868ic14W‰PNG  IHDRx pHYs%%IR$@IDATxyy=XXqHK$E+:Ce'DRvNc')J%?$8v-ّeR$JdQExH AMy띝(ӻ3OoBW] t%Е@W] t%Е@W] t%Е@W] t%Е@W] t%Е@W] t%Е@W] t%Е@W] %0p:\Dw ] t%Puy+8Kn4NrߕN:u~PBW*ӽV!tJ] N%շtI^9uZ] |M] t%Е@W] t%pJ@7"9%.'q :uEwG']]z] 4%p7.'MAv9J֛t-_kӵ\JX:fWu o9xu}a8K2~"艰vkxu%pZꢺJ8K8gN^ mvELDkv~m~1Y>vkxeY6Lf'J-R]XW( lXft\cxSXCwKF3/M9:ɬ3<g|tNpE=2e+Xz/^7ˏNvNcVZ^:V~v:~WD Le+IJXfc2Mex.RJp8mroЇTwNp<3e+c(sM&}z7m+4A;ʹ.c֝)7=]=w+I.s] L垞nSϴ!20^fM//qu+ytl 6w,Npe:)3e+it+ˬgh}h M^ m6^X6+Znh4ylga/C?~.m+mwxW'M F⤹.#u L羝TMwL~gۆy~R ÷!KYs/MMYGw%pҕ6 '݅t:K`:qiM 7it-mIfiø!:vVi:Ӆ-78:m6Axز^2.pЌӦ;x+ץ]Jd,7֓ݣ!6nXC7m4帆o;Myƹ4u,e%^Lě`פgeKۮnkM=ʶӘJ6;ؕZ0;J`:T: /yi&`i˓D}땶x3n?9&K6&^+q d,o6Rx|B߼R-RE8%0D:] LQӹUd 7hܺ7/7,u&Y$4*yxO9: cJ[% GtC[V7:ong]ǵ|RA[nYVy^Gw%p%pq'EJ T\?yo%M25RiB9R(/L:F<1,-KuyӁMgUM[VB;Rϼ2^3NIqJrtc^ϬqvSʌJ^O%/u;+T0Dg\ LuM& )ĒW :oy@`~I;eS2dNϰggt\y)SMgdڐƁ%Y m2Me 9zMpl%>eMM`WUnD+r+IJ`{k2&K9xm:5uLne ,q;˸MYt &nL7u76&t|22|WlDz>vmu<ӥܸ!:nIcX/L&w%0i LAH'J TS?yo%M2GO怱U:rlK=W[7:@34ϰWpdBl&ڼo妁%m h>дq8^?>&l ]OƷNxw+i Hx%0}O h&tެch~Y^3lsJ~'.F(:^?|tx"7ϺSoBl<;Ng8%m=x)/7D8І6c^&|_, |*㕺.iJ_Rû[(5 '/M5Ѕ t9Fɷ eghy z6^d%8 1$.xIRf҉CVpx9/2~?qԅ&Xnh^ yI/!' S'prCwQS;%G /5 /m=ۨ  ]{g]v^lE_pyϟ;GaY3gΜ5441 فH~0C҈0::Z fxTa#G9|!e!N?w{9k׮/-[ݴiӞx`}FTI7he}`֬YcJ+2>g Bad87%t:@B.mYxJ<`?u[] %@cӅ&gr6<ˁv4g}CŸ3߸t\NvX8Zp"ΫK\v ʹ/\dB9rȹϗ}|y;Gjp`P<^G*#4I!0&|`  .A M8T}#$W!W^ye;g裏y)M.' ʃ>RB/xt( *ӵ} 'V&^|:׏6߰kd2t+(tѕ%0}OVSoُWؖи+:^e_ EPj4>+Xnݺ+W^ti>/^xYgi`y,?oޜ9siYsqEo>ܳ,J"D|Lˇ:cr5iq@~}:v޽oΝ{9,:"{<#{zճ=~V>v rono;\N)kҥ7|ɡ eIGǡO%+mtZڊ3 ˞&G̟ 2;Vrk8ɗrk/r0ii>k,^h΂ fY/xF,_|dϖ"e7[Y<֐; z1F@qy9H,$84ǘB%pt-%"Ԋ=$1$zVhV)[iR"whH`-pxLq.u^QG۷y;7n|jOoڭ];w/޶}pv5oip!7t&m>Ӓ28Xu qc|2 &ϐp]n,LưT0iNrE#[눐| B+7fB8%(R0 "qcVP\e Di`K/>۟o~-_k]ˆ<8@ 8s;e;v`KF'cy `8Wm>X,xХ%Lfa%v]vw@{SKMf9/i;aKڸ!:8j%GxIROOxo98]l9s.53 i|̙D1Kd.-t=ZI*i7a% #8 BN5lМNB<lBA  =|}lW6>wcg>[r,Q68 e ״!-~,/ix)KvY]w?K6Xq%]}WL7it|␯`"_t7pцek.XL_8`x>ovhˁߨӉSY'M߃0]}A#FjLR[9 e} FHržٌ^;DGG|J)haĠBh1Ue5eة#}!zRP:i)^}ٳ{m۶xg?;q7^w+ #{u $&0w츁m;}i"z&Mk⦥ AhPΧR>~g@ jp%d@?%,qơ:ah=Nح7 t ƾsm袳_,-[~+;oZT+їnR+CpRByAzM1WC;x= =`<:=[-Cs|ߓZKiV" |,DI .D*ъޕ4f |rz/|-_zA&s~j]߹]Ys_ }XH-[^o;~~vQ tY/$Jf>Nzq =ۉ40NddCҘD*5'$Qp@ EC{⁧4*ˆDl8ȑƕ$qdG90PgtP+㥭/ر] ^xɍlo޺uAYǭN:lnt4wX&h'NAo:&Mz`Jg<~^G%@]_Υ AXǼ~rmtꖗѻү;Mkbx /WׯYfv5gcczLɓ&O/IES x*꜐T2Vö )*+:;nVS!4Ep=Mlbzq ,Y9qI~;d$/Թ Tк=PntR~^-2#U6}xie>ܐRU՚yѧ>5;zjl^%N9hݗXf~΁{$Z_q, dd~q:)TF?2eJ/y>H2n7nhu];lNta5C??n oxk׮h#ө5=(d'™^rPHtҶZsBH=m"D8Se5\!_Irz&pSCW*)|]r!|-啧qS r("MSƟk41Sa父ް:f*l3 )9nbNI?>_9[ǝf'"Kh|.á'!ku%Ш-tYo@o#|J2ƭgm4m9Kq^ l|Gg_Lgh&c\U Vd‰т^G:rca ҏt0LL fV.Qufl@)=DH\q_#ץlkS"gO+4bMA`t-QJhCG=CS[LcR SZ8S-7 6o'o2{u\G10RYֱ7ϴ%HL@NKOm<'Y@ Է).@oƳd>Y׼8;wC%lïFZ7S+V[c/7M˯o| [~sY.Y4۱3 bP`T/Et'wI޳G͌@!ף(UxvVD~Cy'u "&O7Y5,Oxq~IÎ:"5B[50N:r#e4"/i%nj{<)>Ф3MmP7O?}_ַcd{T7cf@|;}CٛX˃Bl %,I&[JxOoS ]'/r22 mN3$N#{!os<ǟiR yZvM7u{Kt je(E {5ѱK4iSJY.l|WEÅgy=)6o!҃aTȸ!wp'7+$q3Əy=r&':ݣTrd~|,KSHbJ3 xg=!w~KyOZc 5N8$D?#:*$?Rҝw>o|ㅧ~z>|Xfk'^Q7ӆM$@KT0;WV_:wyLJ^7vMTpZ%Bhig=.Pb<9`iػ[gضS2C0#`m$!J1u}"Zri} ^̞Z\w=?RwrNH;1|YIja/XHJ7'2?FcVYίUz!3>9"!yPu&Js>R׾o&Qʌ{ puy m^`t\ye%>iWxI2:< K VmT<1ބ8Yxp;zxv%n3rFz6:G躟/h钥+Iϰ>F꒳Sۙ.#ZIkb7dADؗ6 [;NDi fHR;~upq2+pHU4B3.ߩY3BVXNJ1|[пU̖S`I7da+L?8Iԋ(2E(Yl6r=`AB*9٠k9fl-ݛI=#GG8祗^裏>?eS: |0:Onݶ퀄#PYzAx::vܼ}%}#@Jh4zO9|`//7f9ar`)S9KM?mS4jj=3Z{֒Y& `ngЊS$Yݖ 擽1b9i+ڦVzI;*Mһfծ;:vV;"B>YR_іAHЉ`:{բEŋU眳Zti:9Zbx(g| &W_bk6K&nƝ$Y@gT錘u{i&`vcS慫qŁ{~q>-9#)Eg öN@g$:d2Ih2K.Tmc/ƭcOh6q ͷox8Y3g͜7lJ<߶vݺryD5R}FbZE7EINņ~^`aBByYL3i}/K?RqƁ߾}{Me2S&g޸ѱiyډ:Xt 2̒653 xt,ӗWTru VTUb5t*F4#RAIZ~~AK;8:~"^DgN.m[ 8[#SOTduDk4spӦMO|'>Gd5G,X0ztν| P7iT !/fh5u:XN1fKz4Kv#iCgg?KV*?7of/]t_-F>L1Ƶ8֜A,S۩?|`\Dr|Je2Z/"/%B'uzE;Wm*Ǿ=mr;^(~WOSLH0l(@LvDV*xVLf_JN{G>t;(afE`:4c|Y$@kG#Z۠!D ?Nɿ>_CI6kYw8YO4fR[(g$t;wXӭQlݲy_%fN)<{ł@KǏc,d\!x['vЏߦ^ȷbtK4iۉ%myvvMho;×xF(hK/=[nY{o뮻z+VG+zш:/.?XP[S/>l&4j+n='H!&9N9{-ҳ>*w+xe+r8mM3'3wس {w(q: 0CNc=SpgU ::[(}٩QuBG~9P[˅/-7ZR38~ԍS*iସcG:ԩٺus}'??ȣ a9%#ŁCw'[ظ;Mh_BR%TtxQ,=2SAwIPOtY(Jw3Xvř#g><@~Gj,Q}o?gMZv=/ӸѠ;pvqӌ&L2w j %*NB"k!!qȩ  70h\5žu,cMP=ӕn>>:崷q=\OsAh 0Sq$^p՚kW qRto)vln&UEf .>)sP#cv ^ks/(NQq^" |ʴng+<`襯|+_~ޥxk@&FTq*w tĹ}L#$SJy%0{k/ߤeƛl^s8vwd'!p o|o"%je?ik'GQFU`Ud~ G q+i'z''ӧo3 W.S P@9r#\E]T_&R%\R-:XLmK?CؐĿ_&Qݓ^tH=^F'9[ sX=IO}Q0[ַ_b}ˆty[qڥwg|`ór!7Dde@xM~J\%{6'N@aXpsv5緳sxۗ/ o|֔y̞4<{u?e4)x֮N֝P[mF^Sw^]#lBqoXz@b=8!@qPK{zrtYO$'|f 6lyFXG-%u+᳹sSHXDc_ljن C=:Ȉjʡk+mwO&yvNi~w q"oWdmM㝠uufYml:7f' FjA=ch{v:7.r#6T~+=#|n>rX@p> 9<|'RQ_CPVd~%l4.x<ɾ0ʍk3:/ !Ա#ifk , wg!3o po\veՕW^UBeúHȦNl. ao:kʞ1Dẍ́Xž{v>?~h.:u,3#`o>59kpi_%4xO` 9&;S@[y7y84д ><;|wᕴɳxo~Cp׽aUk]VfX*jF.(xGk}yAjVG VON'4Ls?'ǿ%zm'0g8P1yp>(i7͸XcMWǔTDnDvE=Ԙ`Hm2M׿ ȵ Qj&!cx#;!gqj=6/bSڀK.D^T]W ׭]W6ūWBLF;.eTOʟ$:ȊAn{Bd7La-[7=|ӟꯞNt4 ww94r>!iC,KT:[6w *s vf@.Ɓ>0H eڸ@?|wv%/:hڒu?w]uUoT?MVM\Kxe*R -W zJVq֫&QN|}1:{սf/Uw{k` KlNJejX,$nP+mܸoz@bq%͝ [v0D܇jwG\#N5VƓ;pȍ߸=q`^f m+ў-oqBgVMor<͔^mM],$DdE#+R@#f}+](vH|0g]^l7_\&)_˅y}[/"bdsmZx-!ʵ,H+خۋɷ~{A[ifuZM٤ta؟:^zk?Q}ёp'mBLx:E:m ;mjStȁd }Y6z?z_4;8xw )& @m/ ktq@K3rmh+xn@ tvrʿtC߯SY>'ޥwh[F`4Θd:vft0N$ϩ1ޥ|߯oW5 ߎǍrVtky:ɔt"^|j"2[xٟ7g2iauSix@ 6H0$͟W;8J !2i km^w3mGǧzoݺu ?я^[?5\W;=uLfw8kRvu Z x 4 B;%mQETs렱eO=J{gy&6{aWóئId$daDʏi|Y+y,"Dzv& y4;쵔@sHa[9=7M2\ m?nUDu3kz#Z8=%kN@Ĩ[yb$v$dx0D\L֐I{*AϜMs#JIxb~xvT cw_?``63@qg^:hrQybrp*y,%ͱDt,2m3ބ(vƁ7h֠~߫B:FMQrp{ë .n:9^=H9JV gA ?gۮYYHc\:mD\N:83bўhLkc2"=3~bmN(GZu)EpxLu fAM H2< Wp+-M#`W_S=kWjB::-.xIxRM:`)^ң.MAoLΘf{/}ݥ\ϜgLS 04?|HUrMlS@}WL?J9I gmqC 77ˡ?uwϯ_[_x%ⳜMA*, $O)$/Ρ#a&}2kU8OL(t0ǯ#1{7OV,|;QZtjYςm^_M!ՓlcC$t@HtFEK&QS\Zh - n'UF.#C#Є8{;&N G46^çYiFZK,:c>,ӈxu׺xן~an"MS{f6YԮ{`s8X9J fs؝pt:?Pgi$M#^u 8Ĉ$HxHUfbu瞻˫o~[ue:E져y9zWX%i1 I/{f]{5\<ț#=fΟL #Z”mBrM~2NOJ`:7':3MLpgoҳTg1/|#~~V$wDV[A>OU=x>5 B8٣7J }B>A/)x44?W_- 4+zQE63H6DRB)w:E:aPO6:8h+iDTr _hT8&GGvᗝתojԯcMSFZQe]%}PG ҈U5TqLyows_N{?Ki\0EUl rCL%rj|;_ wJG!%{̝u X<8'uO>J<Z I Еw=BVRQ&,E$FJ)Z XR#<49r^c6nƧp[JdYo DCz Vj(tQ)}cl$ױNW` 'jyD^:_|z$f_&QvAYe\iJv|z\rՐlYbE<:쁁v yτA2 +Y ؔۮ;jͿK/ךe/k?k-ɣlGy%;sJT^ dm2N)L7t?^)KF q%^duwW_ ׾騁Ha#JʃӲoyQK „:8v+c)\uqib:fX4HX# x6b5k 螮 Puw_ez, W=31>@5cɞbL_Q':PKFd7>O\ι>E,`eLQ'~Zv萾) Qu7ڬ/q5T:MynF tFLA`Mty`OY/8|~1篕3U ѝ}XrΒS> w*wi#F!gDnr %PP;Ӫ>]_u: |yFKJ@!z<nԈ_v{$ڰt|p/EսDg@5t`-2.,{8`)0 w5@ @x%@<&\fWUPΎu$0@Dsn/P7)n-R~ƛ߃36W=px"*^te [5x$򯼩a`-a/sd/S%V|1xSޤKo@h$dƂ+eM<<ڷDZ0 :v>Ooz>S28CjW*>N54S,%ԋHqy?ҳ˿g ɢ-QҮIP. vXW3@TG'Mܢ͊:d8C{Ua3}Oiy"͞goU7}[^r[cA$FԤ}+WfOXf%{VT`B&mR~ewuV窻JG#5O_cz350 #RɐEi3rH MC؈+]Z.t%*vmWZ)zzWt{ok ps?s 3>t煷ھ}|- Y|Nt :G)~ʨG ͫ#dP#vRՂuUY~ n|v6xTMlw(yƁ3No6Y? A)[ B#ӷ/G|lt\t?m#cD=LSUW1WE豬9=[DӔ|F!l{>45::4GO4j~[qTc=^iԭ 6Mp[lX=1b"+u|MIo`U[w{>I F@Τl \k$n6d6Y3(Mty`UqC;~;C;rΟVG}hW_u^C=oOML$Tp'A>^]L(|I|F3i _vcd|TWt@tBAJ j'm:VwFP.t%pʖk%r j+8QqtǛ:CyۆݻئtjpX.n;l'NNSFPkdW-\y 7,ٿoKns#su8y dm6^?g?7gĵN"n&i____+N5zxsJ(Ʋ D^њ*(r򞆹 Ooj]_۫G}$F!|(+?l&pIVqV{67^9:ȧX.2u+S>hKMJf֎ff~]TN3T^uUyp&_7kFn-X8W6a3 Td2E僋^\G,hZD%KkkWoT;ꬡBQs1!2Ⱦ89sPIߖUFFrSWXj4z"c=}_9"qҌB0\[D'O5Z]ymJW7 #569ЕiU> X\uh~ꑾ50c:> ǡLH{|MsaaTr6s N^F>jً]ǵ.`%+i*=ة-wQ|ؘZv"|" g8,yqJwOݼ)hx> Nq\_f ~˿4}͋vڣS@CYV6"8t]Uw%}ojS1"aB sxCSgmfiѣ[wýa1'\ov;je 9,!g/ZT{J y̝ :۟bS<8?;:TSq]J/ҕih0\YlM#a HWgɇDݣl^=۶uk%[˖.Sա#bm&GA?BX$I^B5S![Тk׮`Æ G!! CB_dSKi&)7 l66O{L4o6!<;}xͣ8p>cAwyÿ;[or3L ]n,EE`^ɿB7>sYv#? zۏTWiԯѣs J ] t%+ :]$cêKF fPwq! nF]tc6@  |]sv)el2̨åcU8ysYևiA6@#*+, u6Qggtkҥi× BDvyctt-IhN?Dmٵk׈V)`7 -~)MqZ4gZh|%gqӏ~jϴz߳q[}}{~Gߧ/u28qMv=Ȫim@?K7?|TYȪ +-n$F4Lo=Ry+!{N] t%p%@@P袣 Ћ¦ip _NQo%Q|FtuAf3KR kd /9 <IyYtv8bMhbsڵkG-trT[Š3'fOu~:Qn  99(3o:~ ΟP| KR`|+qW5kqz?p<?w/W$^󣓐[!,(g~Bpu-u犘G%$ ] t%pJ-l -jm7XmcOHCu_xo%Q{嗪Eg/ (޸Vm `"XM!j`-F>>6ڬ5"N@+C_8ʙ?vI @vm8txv%7W`ho39s^38V\XdG,GjDsEˢ>zPuwS_}:60Ius2)Y)5gE~.:+N\ uQpx͡^Sc<.9wΝ;ӱ%Kz##v!FUP:*hQt (Z5kz: հ7#Y7dǚ0{-gJ(X&M9oKO*A;;|wpv@>eK ZUMwr1ˑ*Ye:' RXBM:JYYOvwT_1qR ;\)ဆl]0RV#L[ ] >%@Q_Yy$ffևj?Q \̬<6m-_qe `V+M ~1Zr@qR;͘vu˗/?ܷ}40L5T:ʄ8%~oLL"k;vuvvm?L}b{??|o-Fe?row+F(uEg!bg[JMlRs=~VG G=}h8Foz:tjth9NJP>kv<-5V|y{ir\5m;;'hP$EOqE"c6WR(B!JB/z}kZ+Q h@fqf{z_֩ntvןgɬ̬,ly]|/q9ŁB9l#|w mPvzz0TPf0˰}[oE]PA!6i s\Xx^>"T(>B J.RRϸizk  SVd ̘b<+WI&Zg?hg?'q>aށ\yAkSOzDya^ayVb׫);;CҴ Jfqe1jt˟ddcyv=p-=s1$ j|z' n3v$)i.7l s@n C&3 Db0`"s] b>9< θO^7N}K_:λ.wVFܘ`t* F~]ZUpn;43&~ p p[k-I_<{{> p2qt"0m;J#R~oI@4tK8qWUj'Y5M,0x|vëE3OWSo- ϒrS+Iw=ZxNZ߁qqq6 Xw~ +| l_Yc Nv:"ܡ;(6@+],m0o;vLNgDz<jm|ҘʑN\%!ʊ8a8U1<=6BO,B^J$<> v[x!L!)`ʋ(AA=Gycƣ>,TdTt:S,h!L('k"^Xa#ΊMc>_.~6\[n +mOo#352Jyhhœsfϓstt}_>?7h*6h~gόWkEXgQV~% >9^\tr=xuw W}`#;xi}h1:6Gg,A>ZN]^& ì)ǻ0сԽ3qq%qM)_ohnn!#LCra(ܚ V+*238?<_Ww߽?|>bZ{,p,5vG%+IwMiЦAr5+`8v~|bs vŻ[裏>Ήfa)+uj̀]KH2  :p;_7 l"}xd5+8ffU5'zZܺ-nq MTs|~|5u] L_d!OC0zDn!l,k@؆Azڱ];]̀fILs0K"1^6*ݰkއzx_ϰQݻ&)THZBeukilAj!<4аA0:cf77͸Ի?>뻕6lc`s ҧ̓*e??UPg?NZ([6>0nc LT SW5>W,wWP~IkLׯZ;\܃ya24> oA:|mp=.!{}㫢{(='h((&8fj>pN~+_}ytlLIHNJDW,yŢgxYeZ҆MW`1<.v'3[?遷F^ҝwˈkgb#duG/R/~7i-7s|ߓ_\}}"Ḫj窩/T Zjzxx9I+ny Dx#^?E W?[D}0wW}3<&8/ vln|e(NdQOdٰٻw X&`3Ɵf'Ked7:1bM/񚳊8(+x`JꆋX }9lضr__;wq vYzqD`*0by UP};5>II7mvj2QRapfjjvo,z,ujV%Q_0UŗR =!zQ;B6=cwk+>ni,Ge2 Ըyx/SGz,EW"s5ObE2U9e',?8nU?رcm˿w)a=Dל +D\ k?as$ _?Ǐb)/7ʫ14Þ1 y܀>.W)IlNNf\uc:艠4F? k9~dqЍ F%#WH+ 1 v͈m1t61ѽ]f R-٩QB>^D7 xMp^&@.UgaMq7ᎁl` &|۠"L  Ţ`F'௞1ʑCA4Mf@Taors vG?^g^,-$шj$[ ]+]}_y5x'>l'%Κٍs?W=:j+7 1aQd/&&=ƥLG62aM'ɴ'FO'bS_K@YkSmQ\se=$wlà;S/T00]mm.MKY\G*&O[~IY6; Lg!MHES-lx3㍇"1<8Q`wQB莭:(+5ؓC@6p+x{{WWwȑc-X#Gs59=Og=ɰ옸Җ׿^?s5y;IT$q輚k~G{vGZh @P.b+H鋹8'܆A?#+~5 +}6}7|(kR8RїC-yu#q P:A{xo=l왳6}@ɞ蟫MjHףAS.US%x9u~ ] nd^NYbPx`+we.K]ezLU8C=0sqlK?.웴O o>HrF]I]z6]%i!F9(`b"l֋DZw=?࿀EXL,/1Emp&"ۀ-^e-nĹI*[q)[mƗٹ?aj1}qsc ]pN5@;qL;IQ0~Bw(!x(MnA;xbuL:C7s\\rpWtl<˃e֠ x  b6_:t65%>VXmY&.l@u_'?=?fS(ܒc։j-g,KxRL2nR,S"9<~Of)a\a"܋5?gf] E}YuӨ~oAӸϝwlߎ< J$nݞuK~3lS[n2Ge[Q݈eJbJQXbrJKnūeP: '-YF5(k+1w?|kÈ#Ekѭs+rfxÓOl&_췙N"fr%cO Xs36H16˸/]S8^NtV/<'*UhS1.G;gLC#,0E  {& 081G?}M@ܻgxhg{Mؿk(RHѦU6FJtJgҁFqv:G3~Μ9;tfN?Ƃi%Iq@\wZmA 1&xa8c?g?w= n/ N/pp =VߺzilìQGRsī9S{/Tg;S잆oAQYK"c"dK",t;[D_n$#Ygү@ %()=u1A$HnwLUG7W:$p0I_OY2:W'b[(/[s-N\2F:H)Hv1^kpAe;ڝPO!!rr' `v#Ai7n ͟};F߽n=>!fC2r֓p/5Y+{tvY68~~4{-Gc?Z6fQZ_}*/&i52xxCyVANfc8o3kC4Clf|Vl3tPTwrt7Yjct^&o®; Ֆ3[yf Wh-w4q .x}v@$)eivU=Q* [hzd;U[neWM7j\@+Titi'%V* +<<򤱜:HW̫, [v3o|㣏-ySԭ~kȟ]Sd 0k a'P8qWbhr! gĦ3hk(nWCo<]J*.󝖶ӕNx`wuӐY̞rm R2׈d(ſu`iC2'Bbyb'qvⶋl9ImJb @G#Tx'o AL(P,k+g? ںu.|"rԩ.2愀pY/g\ #Jtw$^$'c2$n6:pL[,9< u\ėcˬ X} k4ƚV-p?yGyIFB6a Z5xÅsVCs [~Tֳmk`r":˹gݖ,4,:/ڮ"KDq dN D3B> 8)%h@ZiV&IW%Xp!ģ`Q"IL&Ȣ,KsJegx%_'q4 PW ^<ʼ0UMN^o]X E|%Jnv0 qg!ƅ͹];wv~~^MɜDSLkQLC#VtA0#\n_q/JC?6!?ǟ~v}3sm7&3X-K&(Nc{_|Ǻ:sL9}6Ӹ*iP'S{(>AuZJkysTbiE1]rI *2Ae "M8i&|qkSh-8]% 3$mfjdלN2\2;S͔5;`[jv= {5\ ?< F_}e͌ 0Y\qV<x~;r৩FD'XE @6C<kg\$@]h}o}[oGpK'nfc{ίMUj떭?׾U|6<BNPāTa~`:jqqt2ǻw:ZԌ -K=0^*~y& + k@gbػ&AeJmAYbQ0AƏrrҏ4uI1Ig-,/{ata[)j)sQvv;B}qbu!~s ]+ jHi⪿ڴiӎ}я~^zi>&L@@ hn"g$(*qѯ[̡dIРO\08s'?6lݲy|3?=!+с3Q@p`fxàwW}r<'tS} iЈ~TuʍJ%,:wG7:D/‘xrd`=lj1BD;ɳ9;2LBېmX QFK5G3$ǏXWIW7VK5E$n&-ijݡ5; `SLo#jPKp0[N/rr7n 4e'ROgzhqA?A9ݳwx.}00i!1feU6(q_9 @'|c6gDolF;z匒Ill8Ws?#MTI叓F7`x:f(-.m<"^I^iͶd-YnX6YZ2h,bbǺn[e';uڭzd@ӶZ%o"me=עm]+[$Cu_m>gf\ (( (޻w_`%gPGFn/I8O{,?wٽ zg!b:# rPf kr@\DgC%L\ƚ X[@O铟|m[w"8L0oc|4_QOb¶l~Ꮿf?4Aa6TK 竳aˠUBMObK%8.-T33Cbh#*D R zBFXH.vlm KidCU &'"Gj xrj樤ջOTΓF2|h= ;򭀟<\m COկ5FAM#IwOM ;DS?>ᑉwy Џ@l%3)5Ę-5 b4+D[SJ1aq! @?|{QV"Fl˺Ǥ4$rc#ØTԁgS.`*<{ªl#-ym \:g:v{n_t4R:eMBfg55ҍߠ{dPWj@˒qQۑ_WxHQk =gE*-J^qPLLa춉R6sݖڷJ| XVc MpMpz`p6Ea | Їݨz~~x!Xo~D5T Il:ڗAʉWl,pJtoa_lP8ɹ\uT&va²MvQd)nò<;vƥOo%u֬ ZDUV¡$yRz6#Vk[/dV*McGwZt^[%(gt\~;U?l@6Kܵ,,7I'Inm ߺ ,ѲűBb/!Lԙgx@Z %y VYHtLN*#4XqΝ;7{+6Aq yzz@Ml5 1Jp ^,#$?g|RnUn6 Exv/ nB!Ɔ~5yŮ-dUWk35&P[`f!,@M-&(]#Fp';~B{{欿)Ɂ]Ao._<& 9t|??z`sl}C40f̃9W pT3U#ˡrzI+>,ʽyꩧP?uϛlMj1j$SgrbzV?}fAC}6F@kUuW k>C1Un<ҍAk2;:(n$fvۆ M"\^xt ~cnhCm棝Llqk_v5֖^ -ZH} 8~K|R>i!_# _-'Ӝ33X?߀na>O<}mqn6qcm ^8Q B94oYYqʳ2}XT2 j>gC4 /~Fިn4;u8 vV}1s l7OW÷&cj 3=,[pq೦ -vE+_vka$V%80д<`whAW\XdYeP iůҐ 5{n)O͖ _iu,#ܞs# tyѧ-~ob?h`@&ؚٞ[۹s׍M7ݴ׈ic-qQ-2fh6Z_SzO9# ǃy&JPEC*myfg~}((oTDak3F>?`m`!O>]=WZ?nAvrMՙNƲ`MZkPkg׭t x& :TGneob]6%$퉿XYx,D?Ixd ']GRi lF!Wo?Q :Yo3XI\?Bjɲt7.Onu҈dy>lSO;@cb67cDbC8ijAPLM1+%$\:]UF?nc0^;v<೿;Y!>75Cag8i֢(Z>7γV'^9Yw,1AN$z"jVIAvq^.Jgi`]\!J;:ZتT .&nՕ~/mh~V}:x`w^|ӃG*#I &MQΞ^~x5>1=u=ChS 7E.c!g!//_(_,Ը&AYkH@u Zy-:4 TI|ߏlC>~lqӦm ٱvH12 /ۻ<&{tٸLWlK>l0vۈ-߶{Ϟ`Mt%@jK8CÇ,^3X]WXҌ-.7+GsXgwMxN/in)CM2ֹ'KS,ƤґB9gm:Ǹ[hMwS\/鴩QOߴ"~FDgz;,zf:!Ȗ2C׶hIM?ZK0,:p>ރG tO}pu*'!?Q?o= !Ŏ tB/nx v#m&iQL,JA^ ȑx4Y1f^ъU?dT)XʼnE=z·~-}\sGu[F&l}fd )<իX$(teUNvY&ȟ\:`&$n!u$&FxG[mV> 1s%5ցBa hCHgV&Q61r KIT0@#'X$ ̙3Kx5wS?N}2?iQ9m͸YcvCOwE0ǖ8hQl9 qc=Ү̌^P:CiGTr>ƹrptyO޼c=lhR;5$PRq7,tw 9+"\Ull6238[ 4TMmf|ϐ"G/}V~ 5_vfցHǧq^G.KG6-(Ԋ6$z0uQ3zYm.ld"6lγt%V% E4% xn ,s+g`/lN kΟNiHcf`~yPmW}ݬ_g !@: O3΁0 |&/JUgjfv?+g'(`A'V3g_ ʼ:<0Mn1:?]5)$C `<ɠ4ݮu#k$p3c:Lʀׇ %H!XQmmtbLWr: V.|+W2.tuЩjb/p'? }6/gߞV##7>ld\YHfp{xFԃ lݶmÏ<·4.:q*֠` ˁ%{Ubfx%Iy$\:Xo+!O;^QlWlCffb5yC'{fgf𡵟TcU?^Yǀ>=/0iD ^$s=ɟɛk_8h!\Kuj!Ecy^9En5(K<: ULߛ{,' ԁP2F25(1==QNV?ًܼHQ[6&gn畿g e͒J69M n:l6h͎$-!tII8';Н`@5UR$ך*%e(do }i:*KʆyZhX.ZϽBmIƧƷ>&-o q+.CA'lQ 7QtqUhoKΣc<x9ΔcHӸ"8\jpҮxPA3jM+ԉ+UuҩIɦِkPֶܖe궞E6V*#yL.1>6/Ć}ַo'%g{_/6h ߷_/=gVƠ?YA:-H. xY`Ic'\ێVx|kCvu5^NukոQ\bϗ(dž5[OD%W#MR~1{e?RL].b*JVV<\㴠iN̅VmjA:CͪN6S :!?p?_M< = ⩮ jmIkCf$$JJюg(B%i%#-4? 2,OĀǿh~vBrub= $7p&\HbX><7'cPkbt&`]K[aG8c93ƚqi5y=S/RvωzZu-D>1E ~gŠ:4b sȁl-)*诎_H\[,nH VT&MZ+_BKi7~H2.ƭ!¦.mI$b$ Ͳv]om*!|wC`=eP ̞31t `&?W, _Ň_p 4 EvdÇ܍웚ڀ4qGc3*:f S1UeFWPAcLXH\N\0c?E!Mk\<eX)ć!k {"A <^{s 6r1c.E:;KGH>hЗAkTh՗F4?dH&- T/f-ݷ`Y& p Q\i[vFzmevt*_njtZWxZ7?`{|^۟|v&i}> 3đm; >= vsA 0$W,Q ƃAcᗙ[Eg,X&|c{/Ue^q*F5A%NE*C&smзFB~p6˹et4ASf&6*C*_5Mt (ud0=ڮ)V&!ZNNV=v):388)4JŦimд\ԑ ",*âBMVQ֊o$Ehmy{CѷvDdZ͢[J"`@ BuiIvdδȟ e8GV.rBlNg2[g)b S#Kӳ́Ls*5}ci%Rce0=c۷oy p9kLcR<$ UnbAD6{zQ|/vnk8H9_E ㏃??DPQa$G<]-kWՋ,ne[ kKn Kٯfc,WN=u"g<:r=]wtw.5yI& j$݆ 69hi#ћ괜NۺtbU/Fh q Zǹ0BE3wW7 YDVQ1Rqf,+~S'Wʷ 9 3dzw1`$_l"_5Ø:]L\̘,:?|ls۾}{ۃJ1 `0c8'čgL\i ۬w,sJ,n^D5ݭ45dD\RQBG7[L6kh K'[Nf(Eu!٦ vScɺx z3N"QMQ3Kdbb+\6 L>gth3pUӋcf`z[NKx={kX|s9H­!?&!N燂s 9b"m9 3w^)^NP,G1Cwe"=oѣlk k:L͂7jB` JSv*f/R:m|x5o̐JR[}.lB76Wb#@H`V57^pV4dt`EAx׺~okm(dko艹0NjҠ%C]hcekl)Kv5ei\)4F|vḿ=0aXH=9y'Y@'{' !w~DZ}.O Gx.n1Dm`v`aU`%,DW̼鈳*6 wܿ[h4*AI"7'qKr{Ϝ>S`[r&b=BcӟOGHk^- zhZyID\G +ϣlztd$)36։TBVY'U9"H)-b2SĂ JzN"NK^J}SAZَl銧%Wp'$aG'_ڜ8ろl9c*/LuyR8..ۿg8]g5Jc3ƙx3,keF`B XεWz7ns|oWe :&Ξ^|y/3Ki^qtpuiO6gYXS?R6Ch6}l JNH;:If[wf r5$/H6h#? ,6-sB֍@~ԍo~='+(KL%G>iuWVjC,T" ɌѢ86-ς 9j>Lkzi`[I%h;' ])XJnQ$%)RGd6ZC` (}V;vV;wG~]Y#ۭ[T;v܅fӛBԂa G4!)rm۷㋄Xۇ+P~Pcc&̘xW N%_p5&,BEc,:cr` rphuᵼ |߿k'vSEA*!¿4Ǭ.lFw֓mtDZq|2X!Ǥ&F@N&JiM H: ֑ wv玤oC5oնCF6/)5LP["KhAKR!툪r~@7{: rGDceF6hT#f@CH=W#NcʁmRn`\$ ^f7lMo+0NnVΆo=Xm%eï! >0OV.\7Hw#N} ۘɰnf_28;O|א ơ޴c Yq IN\9Q R#Dj'.@IDAT:h`&tX*,OTl4v馛?oڴi-2'}]J|aA 2`%l||6Wq t^-kV/[ir,egJmY,-V;D-D)-.kX(K /6#(Usk%bR-nGJu%DۥnH-[;m_S7C:dTʫX?6bᬯ#R w `B߈g1Tq+US<8u3(vw)~&~CLi c'"wea*{Kצ&&'',a{C) 𞡊0Y5"Fҩɸ.QhMG䘩dZL1 &;$fiH,O K2Cd,}ZTЦV3Ԗk:M1(-3m?mcB|[35~e9J5ħ<[uX~N=9%K;tHM,l4j ?JI?: 놢=:tu~1ª2SL*xrǃo dzm p&7B̽=}7|y`a[x\36^ Ը2"cu/E`fdX*s/X!#AiA[zy;ݍg2YowBNcBt28bAQ96.4jb|\tU&]pf97[ nEerURT6wcyϽԍ暩`fzIͶv"V1Vu&wҽTl%p[FSլSаABA%eeF6&U\I 6_1onWg5djd9y7z`倄%iAc)gܮ+*bcY,$%MIY%]%-^mStDTW(2%f[AJY@:V TwZ_:X692\vG :sp).Óe@#l0MlBH U7>l3 4)EJQ3O+T3/_4@\:8!!M|;n>|mJoj;EU@ؗxpϯLYSj%k< p0wM%YK#VJ=vڹ0됱R"&qghAFL9OtdNEvȪl[vG:%#-(3C)((B)H3n8^k|ip_VS IlJ[1skA ,xaҙlۂK_ZRID+kD"k TN ZDF:fPP>8{|f4ւW@h?^R߼:U}|kvhȼ/hIǾ w9`fH>t;xɣm t5V8ii1aFH+W:-D@9sM97z;vv?}޹d`a{r:U,'phg~)K{@z-{]Xc5 lњV+JRFJ:HO}Ws6cNv%uL6qRtdfkVITJCqVU:٦4$AFIAИIAu'8Ƌ@̕ H瘰Յ A. qw XȦG= /, 4 ˜sR.ZrZ䔖ِh"vnSrEco8U߄1ZqTnG,zy[ >Jmx,܃Q?bO c @;o?p+!8ViӘ܉F 3Xlv7.aFD+a:`rDI}N H 3[RMGvkytMSkdL-_95dSm$n̐:WnbG2$`)g1q !զGBbvV.|zש go(Ek|]Ri@@]p(7ϼ1xw)<$ q=GӋfH%ꀍp9?J*$i-h"۷Fe jR֣=$jWq ẅ5[g 8״$#޿-DY+,% LK>} ـܥJ Q \xe* "e7IH_LJz8>c_!f-ᔋVh*t#.Xx騈>O7޽]=+UxF"lL]ff6\uy7N9UMl`6V}H^ˑ^ZF~NI3tƵ5Ң>4;l"!`ܚKS4FFgcAJD%ߨMخPӴ4MeFpv"Dv8(݆rTfB l(Dj!`ytj7oP-%!AIvL6)%тr Ig96u2F$KI'PȶE@mX,t ᕇoNzBYX9 U@cX=77{kՅ؉nXl L;Zڹk}G ;9nơqę=3h1DY_feSҕyѣS'W`-ϼ{_F aǑWN"\ COY~Z&MH?8mہ[QDꊖu7]FҨ \"N/2mzFSjDQ!fNz-.PR PZ1w/8IH#سvVnڞ$#m_Ҭ\i_+dDmI3&^g pC.5h-dލM_|b5U"c!&IZL LB[ A#I!2@ aI-E0u]QdO}֡jtH{b-J^=9p*by W7Y b÷9lfSzzƿEC0g3ϼ;_?ԇǘ!chg鰔3q#.BNdT0X`L mn{Q>V |D!K.qC Uwϴ+>w.\$yD %xٓdqStKr&dԡ쇍#4aVxgء~=?ďju=pV]r.rCUK75HtC:N>`CDwJw `bQ[ rdV.r L/~uնܠm=aKN/?v~o1>㧬a"7?9@;w<114^1xѻ;|1厵W8Pcq0%-dbKt(\1(B23͞t+wv:t.| 7];o\æoi`H~,w쐍$f@e{ =p;jǯI%+6;mֳ6,Siڈf\Cz`hs^z9o( f~eDNlk5Tj~uK! VD`SJ-i%P8u? +Յ~l ᅤyI [|HsRR$\xGc.d(5 2iJ@ HW>fTPi0B8 X1gϞMozӛ{%KJ셻<:=MPB;B.=H^ /OD(@#"ߒا>ym}j:K)ҦV>`%ƒDM)aqzu+-l$Gke5(6tۛ8)<7}'gx[1Z踠4 ǜ@l)4x+w߽'<yZy,'9y&˂u)g%Trj(Osq7W' !"G*Ekҏi{x7~j-%݊|B F6y//Is>Vpm_Nϳg_G07p c~|t ?"f؂CDR x3<C).gw\(JYA}P;"%7t6m\~P jwNV~E >"G.z_^x(6~ebHgįڭ-ujzlO0k\j/v\$Y/ޯ }=#@:"Q!sI)7ysmWW-cfrU z\9 iWcFs|w'Gߥ_{8G#}g ~-Nm֩/|T_k;-1:[3Wdɬ`Fm,o/f[ rڬ(/>CWSU$)Hnٜ3Oſ reZ6w' ٟ7|\s *D$}vܫD ϿwAOoSɾYJEB&Aؙ8pZRBgl]A<]i+ޥ"EF8Iz9~ gZ<\`/.?"D]}ƶuYrh-M=p6^vT&V }}i 6. G L|*^D0MJg.l3Vg%l ѾgGz?ͣG?\}غ7x Xw3F*%w?@9 8Xy^HzU'sӡ}{\i']<C%dO55c<)$!vq&z#2&'w~Cx#uF2^5M'01oiFLܤinN!kSrVjczgR2΃$m)r>"/C [Ч㜹πqHBjxHFHG'f(&܎vFP:hXSk\iwMVV{)6cZϯ~yz)P_>͓'6>y!:QR],T's=((+Ig`;kydf/u,j\A])O'O'ݸq` ^ѵ>/+<\K@>_@9`.,MS|u9zBq[><ș EP<+̚a~nӖT9^Sň*ɵI1:d/`ZJ'6𝲤i`nI+xkF;r. neL|Lq 4gxC+W'_mD 3%KX]>׿<a>gá Snᴓ ȒV T~ܸ~kOpoZ9s s;Ϋϻʛvβ:i|TO2su;˟Wg#ľ9~ V\ jǛq\|H_VȘ$s0; ʓ.\O䱑7!:9ʣ|`SǶOnuIˎyM1C5^S|h?6>HAؾM!CO`q<]LCHv`>]o %MR J6Ɠ ?2+YlXn9گ#rhJ46.<э!S6 ƒس[o< [=7Vx{xT+g?1Ui7h<=3_|0e|L L/ҥ5)UERoZ|t??2UܻG\`#UrV,kݺQRJ ;жܨe)t<0K!N. @Y1{+)[W4Χ}[T}rXb@i:SkF"`z{-VE]d$|9п^d<#!Fm-BdGx3]xsé=rQ2pł 70K;.ͬ,l+Nzl|vAF6_q?CZÞ?|5ٷeS+ùE/RQ'<޸q>"~e4=feǑle R󢥁 sm$=pUy/yRF=yNu^ӉM;+GXHz=0倓ŀ(<⼆~?'~>PT5iC!6ylrvV`^X5O8%; %i4v zv.B/G &7'7PLEP< 5'p $:U&RF@Aþ}qcNJڸrꝏvsu.d'gT|Mԝ*~ӵjL^]ut#|?u,Rc#ZM3K!שmR@WX<4;Ujĉ".='G}LcžhIVMiZav9&f_I>٣`xUT=,  jV㼘DJY$/螅pOzFO28R'>M$6+Dy}'miod* ^evf[PPT6_DLZNRk-|s뇻&iqB)`I 蘟ہk 5,yݏ8ߥ6g\IZB$$]T*] ZLB7m71 $m^4ʕ+'7J\>6vBFU+uF?`8)|_z޽8vy490i̽ܚqݝ"gMs\9gÌ:5C Vҟ韾{ʕ[B2$raiTq8X#9'{t$&A΅,ܑxLHV@+S|NSlj+[4jð<[.FJR`KbiWc]a֌>6 Y?M K4ju6'!>KQk kˊpbH\\Kt\U} #֝BGs/@Aw PF0CKqw\P<֣QE#{J&t$)KdcjAx{mx(^rܒA>23~ЁfR(qU2xXyyxn|e~ |g\|bi1 OS u(;eG1^z//ۿw<{ 8tߙwtreWKqSo;P+!T50Qx$^ҤFtea/c84ɶI ;« k2Ex2ʢ 9r$+F xT&uJ2^915?Ϙg"4 HGi4cV K@`(j1o/\aJ. )rW4;‚&s@m`=OfWm.?JxGkp '0x!V]NB$UG+ΏsY@5YOxsyő'}PI[o];z@ zg;# I@  $6AY_mW蕐oAE٨U[SErd耍\W%].x9! Y%{ؙqFpE[OhMsO/%e@wB6KyFt%3-e;@\*YDR.۬ahNh8˲ٿ,2F,vM@Vig":Z!R)J WG',$p_ WD.8E+R\DVչsS_|;;<>x2*1p ot뭷2@yu)6ϼn1QB =ofMXiM @IDAT@BΝ;y:09֙ &vБL`b!_˜ա٥͗x}5*Sv+_L4/hpYvU818Cf>i(bQ^ȇƥ(ࡢ!QNXO, i(b4 *(% P'V%lڭKk9\g]; Nҗ,z`.`K,p_qQ,W,}ڀu ̸66 FwTJLd_k0;lQw/4~hz e=gz}vaL_bIy!/~  ոQg0\l:W}޽s hN!xHu; 6V*Zmrq۷o_{7.O${?Q^Q8dP,|#(7=}xd,_] W> >95&U1+&[tifk3$F Y}xro[C:c( Yk8RYEl̡^KeAt @/znjG}Կ-ΖRU&0'o@GݩfS(Зʲ`•"*a 4YE^~f:Cl >-?f" 7R'|jG1G[qʜh4\~7?^YYi4s2;vZcg%ׯ_|B/GDۉ`MNƹfGϻXKz?ʴ]-~ti-bCk.Xf<+%@!qKbdOL6lӑ8zW|ET]/O~9F2k'HI }U0~n4.p`&@ms[dg]cCl-CQYrj#CIaUe%̚o}F-ڟ^Xt4R2f 7.4vy5콗.J/OW}Щ96'XkTAGQqJ1HRƥBiߣcqq ]tSf57 iC*@q@&t >իWo Ok}ƹ8Nߢm,pǓl\UR_n7Qc"PK1cH%~Nޒz+(O>m̈N4F1R{uϔB-k'ˋLo9ӟu ʔ(&QC)U֎ul*3i]P8dj) O)62VGs;؍T|7᤹ mU9d&IBZdKR fy}_ecc#hq|gEs..Of Ƣà96c!0+W/ߠ h! MHS-mU۰ODy-XS+MW;Hv'%A20}~B3H P dF$wj60zI6nImB?JKYʋr d>skGL_i\:N%'-c72GҞ;d;]1g׮}G&9։B'@eQZ]>y6D OjRya]ZӿO>ܿ)&7y^#i oV_܃,sADCx8Ϟr̐yG4*lݶ|'o3'_sJ27bs ̏~Gw~睷MK*2"c̟GS@+N)~7>dZF|BQ.bUfjitNbۂOq³[C]Z-b (,q$,J>a5`YӜ5bI8_jDXVYCD_X85kd0O^ROפּG2۫Kl_,:Kw)X:v8C=i!pKI^9 3N˼ P@6GBϗ } \ANJCN#[oOY\?"kxS-Y-:Fಬ+U\p\ Pu 졙Ʊ6g 4˦}ıJneR36V٬k+궦 .l%W}մ ^` f2R*JgXġA^:-h}]~l"[6z7 =^j †ZRD)dΉ-0 YzCa!kȉ«hp*KrVTugo%7*Ab,TY"[ym(,Plj&]BaUWJRIk:p/gr ~ HH 'Gn1Y'#U!mk{ mn loW,y\&RGe gLTzّ`ܰ趩>¢ ͛7޸w.z*pHC3f6!?tt6,|nX݇h,ƍ7ވ K2c)"@Qh+7U0OV,~3' 4D^!;̽5NwdSr0ng|0P}ٟp~S '9SNJ[OMՓe1*QdIa^q ɩLPc{ f0mOZ^ EݓZQ(agٳ٥l [ᅵ }H;)1NGl|q1?cF濮?/+X;{)7ۦKMf+ 8kuwL:m[E-87]r4h8ZE@ F+nr]A̞hjf)XSJjuT-NGӹx)яm>/b6j 'ϫourIb8% Z͹ u\pF5]s:s2ܢUT+x:(lEmT} 3y^|d {Oi`W=Hh7M9 H 4&>ͩL$#xiy%t( ͘tL, ZevÜsgo޼yQlh?Vjb H#$w"h[\Ф&<| ͻA9^ٙ1\eZ"p*ia H!xF8S@OO¬Ͷ_9DmH/ ;21!Pz<{|?رjw7GX8޾YQLzXª.|:&?DdanjIC?l/ho*WXچ,0 {m:k^%ygL8kU EȪI2kȑÛxAs)cA=A*Zn q=vY4}G.%њyV,~r1gJa,m?|'z|1|yvOhpʯ\@VϵFmՕf"󤧾/]ҺYsm:Ք;pp1>A,)$˧v{<][}b.mx%/,pa';1U9C,oDY껇s{ "P۱vYV'Mf(s|$p֏qiWI$ů5*8A'[09FDgo}|@M,nԑSOL+m6Xn9,sL[wa%qEDPv4V䅛l64}&s3X|l<"b%_Ķ4еW}ЉGH*xKw{K =e5 Ab1`$,6>ӓ|d\=Ş9]G ;.N%#.GBc.Bɸ6qR1).A2|h-MpEd&}hW]Dnun߹5lE8\@Oj g9$ 4'qa;q{`e18A$߃ xV20dfJi̥2ձ+J^޽wL~`bB5 }<7Q#x ھ"){ʞނ IS-6\d)X~kB&S'Ictr<⋾S֗qBE碿ot^'tj({/[PHiH.hY_˲1FC奤,7l\-ǒCZ!0s0F!hrs@9qt#G8lV| >goph&B>\rƽ{s5rb)Lĝ9m;E֩+;WrCtnǐu i.[,P/woֈlMQMO>>%X.̇4c0CܹN=[\hs)v bI\rO 'b$;hi1ouWF򗭵"?oi*DzER='釁X/, T/"XAS9 ˠJ&iRe=keܙL'l>?[`5sJDtM5?NCO)/hexÏ肙{"~#XHtU+iz\,qr"UVѯ0v`(jaxGB.lCɸ/YE^zdQf+(45us؈fCЀ JGƳ.z ;O"?GSc{H)оj0F@x_GPJ7dbNRNڅ~ޖMgUYhi 3匃@ mr y3+,H.N `)(4;1:j.P:6?`\J!9)\+*;MjRj;8=uq0vR4myԏ%t)XK!mH"ㆡtX~xѱ(񦑒}Dg֭S+s)a\M6D| $)2oTCҪPKJz!</RWO> b"r2kDAXvByS:!>^rUW_7Te, nmx! \8V̚_~ 6c` 2Hbl 4RC df ڪV)GD|/w qSnx7KD8M҉nD#St { Ŕ|;7ʉbl||AsIѣy ~—27x*9dG9DMOiMWeڳ?dESZ 4?] 1GY =h"t}/*؎_ 1Ꮥ(WqlRJWYTI$cj'ceyn΅.Dy5I#Ez3?%[IAKKIOa,l5qtx#r0 w;k(RJ 1Z`Ѷkw#FQke,m/z@|esa?ֻ2HkⒺZʺzot1u$h`F(F7;%CvG`$ KE%`Zb`B]LR|Oщ[oF>:~i|3 (=$W}g,v(s`8EͶ}ʡRa5f[K8 NNKDa=[QP6I"nO6;}l ~D֥.%UW<+KGBr$pb\;%d Goa{WA>.WT/ϯ."Z7>YbĮX5 <=Qe阁Jj{ n7hx_!HӾaZ]Ʌn(6/2d:z/R!vѱ] [3z,Đ_z^ZyYh[+hբQGf+aN<ֵҢ%&d >5S<8=2nIQ-&]k{$`P֘ OFζS9Me]O_ezM.SE` %횂5}z4辷Թ}6=^Tb)w`/TX.v.c5J5jf^5#*+ϯ 9 J,8Ҫz􇰵? 2: ?r{#)$]x,WjsX Vr :S ڬd4u3§F=MW>9!5nnz狼N .l<j;E1@X*!pr@sG9Y&E9ENe`Os+=;t*,t4wFKު@=%qrr1ѣ@b`˟>A z k0FqLFͧ"ޖ@qmٿ6 8D*1'[oYFo\/?pL>ML6 d.G 1D:Ȱrk)kGn&YYJl DUzbzpQ Yjv/z"&}68-;21޹i_ T??>a),|PTi< 6G,#7x~bV8@ZN^αyvB@r &0nv!h&WavLDtZ;@;^ۺH/&ꇡ3Mh 6#RkxVP%%[ΥԪcwUgtp n.b Ъ7.|{0+V[k&"7h_`(x`êΠkVX5_Mv&6lZ"DF{ln#P5Ue'^ySddb{iHTL;jo_e#'aVoX6-X8jXRQ/̂ d~Z}u`q{'uD;zp"jv_\^G$WsGMQ8퉜UN)f"KߵMbj^BYuŎIԄF:T5Vj؈-  WKtZ[.2٪zGq v\v1q%ד:,yւ•bg!@ݑB3z(ٕ { &$a4ѶZ(9<-ݱw+3D3&ǽ#{b䠟6D)=t4B:C틩l $qtv?/UŦRkw j&E,Z;(ւyt| aL:9vDHF0ZTAy/RRaVd!0q a)ak J8)3s'} ӟg_4/MXn&]_Vc2PKv-P$=n%'yk>ŃeG=bLPfhCʣujcH{[G?pH珀׹[ĺ'6qAtybJՈXj/UV׬ 2(^VL ߏv{noyp<=nG0'lmyDTm/h-%$3X1 qPRW^ lҢm@ɳ-3yT+giԇQ&@?V#Џˎftvv~,.^8'[qmFn)[.#czeEoQń% n6 6N⤒B{0@O2\ZE>Fs_>Um(Xs' 5!'bҮ  j\ u%ɜ=XR<3]am]i΅CyN0_č}JljQ IUBFfhH HY}sDoZӢ.3cvcY~ qEc"WϲNؔT/W#{KXo~c_{,*ie~5 nΫBێ~ǽ-g2:vÙE6WœXm1@ Q(e8G2-KNPx;oZ@7:$g PC^;hws/ҥ-}3mGнuiՆGK5Y3q rך\"?Sp{|Q [.+4G/uo}Ŷ={x.e"ru\X2[Gv^1Mlٟ27أy(ev ]C3 +rɋ S}[lzv͡#Oe^W_NC%TJ|<F뎁:mC,CӺ`9MUq ݢ=3F.zy<MmΆ#or؏zW/ -9$v`Y6j햽.gD[4Y61FɜKpRIaC@5u4]zkS{hgX2SeLߗ@JXlرL'@Z.6DsAp }f9#p:UhU]⾱t^(oa"'?{6˃8P뽼VmF(rZjnkle-Y}/#y?[yBiΒ%44oy= ӗ -dS:;}] -*-$ņgg O%D%ReKtxK5+ND65kyX }f7>rxQN& `H3Þ[ōI2֖ E* J-٭&C@V.\җ02,k1>Xx6xmЩ,! dȋ%$y>m{@@A5bp@@ =Ila;:*#>(uG=ch1_"oU1 %.\A'IK!N%e^|G2>Giّ^䧋ߴк 4#lY佇J}(yVFFnPK]ji: "{17jz 6w,QGP̝l95OGJ3Jfk, v\7&x aK钯*FlXkVOBTƥK86) B@w$hR2d`0kz.A[c^`g&k9nOzK]D[6DEwG.zu}/.%e+KAnE~ Lz'p'Sч'N7gy\Y<I!؊9yii4˜[|<蝘kqIzD8C&!wdJsӃżZŷ5HgOKl>$ ᐜ1Y1bxQ_}UX}?Ts2 hG;T&ZK5i> e O2VNiѹ3'k\Y;#Z8( k0.Ͱ*q'Lsvz.]z,F |s^ >BFGVȋ  V{8=D`WDqܯɺP]0^=fQ!VOx KC ]?3АaAE @FA&Nm݄ iuMTjiǣ垨ahA^"oQ՗G|@?tO[cZl㡫ªAN#~Y-ҳQGV5J%uUxH.^Pa@X;jeSN _~HX+Ө9Ք'5$ݱhx5>X߁9t_GM \"}'Mg* 뢙,""c!*)˗q CD+_ې^ȱM&DDǑO2 Z-HLx;Cn)XPr*Z:X5@j3€mj y@2ʥ\[|Kg˹ƽvG]ߡ):vj|4{V\C{ң fi;L֬v6`F@QQKLt`: fɮ<%>I{B9C>q ?#GsE: lKx+4Y0Q[[3;m`]&WEfT'h0s(T!4. {v$%`ho]l==|0Vj?I9 'x: ;֧;t%̵rEؿSsy79>N@IDATq`ha2#D Dƕ)bٓ'OEsJϸ3v&{bR52bH{3A n;.ʣ gLe訦OQ)VpKr`3 Lwn?rDJc PL>=^pyoڣǛ^(bx?.3C]nno.Sq~Dl.,_ J&\с O†>NLTZZ!DmYr59Ǧ ܥj%V08ٱpcM08~? +9[HFe9,Զ@ٜd<1q$3x"Ęxw *ZJpoG k`ç"d `tO&wgO6.(x}NeXt8wcO:*|S||5z1W_:-زh'E>7s3 'rTW敀tTG K=~ $IQeOY80V*#=le.%Օ`R1T"Ӕ&;mb}бk B! ;J״  j zw7t+5Z'z _ݼ߼! wxt,|&8HBh*52YX9:=݋X[ L7_D­?XuN:$L-djivVjkejrSv1[CPcō*gDƔe\/?aC9(/NLsK) !w^hE,tZrcΧ0a,L.]x@,G:Zvv_ ]L+ ZʟIpڵzgo▆u&iM.j\Sξ{6mwǏ=R9SZK 7'Q4y\;;Ƀ4u11&1 SOĈXv[8˗.]bQ)‰pV]3AE\aʴ4Ջ/^`9+2K#zhE:TUJmO~M>Y=q ٦Tӣ?MӡQcGʤ" z Rzr0[!7b'a9y0ݫQ|d>kf^xN(@69=tp`08X ѢBd-ukV+>/ a+)lV@.FH(^*YnLRpܝA<-* S("6[*~]t+cX |\\[$ɔgLη=pz _IO,kOp|Ma7(}FUBQ}'4+'?b9K{$Dw. J*zrr")Fc U] Lwțy9 :Da{_o*i)i#U 숀9]HZD |?vS|l6|\ .e;-A/Ѡ#K1Jp3.&wͭzY[Ѭ&Z`XҐݫ{ 5/J+PBbV~ᧅw]EU,颾=ehɳ'L$=ESR5пP_^GぱQǂ+L>L-`MC$ޚh$9sO[?gu>Sg5Vw'. JX纅ͫEk˧Ϝ7`C<[y|# [j PL鮴*r h۟G @r5_!op0>@[@j=]lu 8IL`|[ W֞vG ԆtX[S00Cd@ĺ̌,_d@0/ H 1 וj8 [rwEJeug.!Qza+/X}(UG(1WƝO>r?**q?mRCt@!KgKdhy?geO٥ۗϪ,jQN3KÿP_^Tn[A0q#P,G/Ky!]|Ywfd!밆ff3]݋]:Y@@aw1_.t;ŃsyLBE9*,>| CUzSC̢ScocZO+2Օ%6]~aōQMVIPpxo|= iX lՋ.-3WM {9]|ZR;kF&5@.|E"[!mEwm4s*^YclgkQp[|/gh{{W5a;V-JRIJ hhVhq7>GaYǓA. * ~>h'1][ s#*෢D%5nYLlf| `tr3^x @ X<_ì ?Sq ѽRHۉۻr7@QH}{b7 :vrZCd{ml>8=d3sM{4R-gF͖*:XCaiP~*D }NVo>ʜH1DBK.@+1gʸx?Z֕ot0ͨ+b')g<]+i pZ8lhucDb+"w.{VLV螡ZiA5LɊĬį>=RӶ6p'g) rU>H.\u"ׇ%5G]_-fj3.01߷DL`/gHbi?ӭ5%Fw>sF;>.%Ql!?sU| o K(4iMzKO[n6kB} &Ə ~1SکzK% . p &޲:PN8:m?Ȟ:X\K24- E+:ٶ*Wv uz=kOtAN؜=Y*bczxW6uH##(V' cxB$wI֙yExObU2߱!:ka:!0W8@Pa(ɄɼJV``X&7pܚ0rEYyhƉ$  CcUU2cE4@GW ݺ{a}4dIqHXBci{<=+ۋk6{n7铺Yj2 OQs=jsWg*4-dPI: ^7gÆ ?_ _+A`Bi(CR w%uk niՎ++pZ&'z2 Z-*M=uE۾}X` ZR'!vB͛?|< @)AѺ~#do]?׬?U#bZ=V,2aÌ6 gq <}:G- O|cB3O,E 6PW!p PV!YpĽgϜί(aF3MةVn F\!3i48q̥˗+KJŋ+er"zӫCV;|aIG7J06t,{~nATo] k-}SgFdG v4$GFG:<~Dh;m{s@>ZbR~s)=-V:ۚn+28Ɍ- s߷zo0E&9|>헮A}8N36`$4Y"HS|G;] 9B#Cbꪍ.r ʕoaV4zPѣGO_||4,9t)T8ܱ(r2-({ d`t-" #~U8iL&bz88\1Jpq)Kxe/_$bNd+e Etyl Xܷr~ ,!cnm kqhY$ ۞"=|g;m{7ctsATגjMLTaZwl~̰Ʋ~D^ˆ^ъ0}x_<)Dbe@}XC+qQEZ96]] I3pرcx¹"kd&ł`\:;Hl ]>Eߋ+=^[1n?(b? DH .uu_CK\<׿xsƶ=ps'HE&Zm ՞C*g:ދN`:2۱␝8\>RE^ JBzEoMel=]'|τ1K) &3IfdJ1(bca';w @KN8o@k\%0ÿ́׻h gܰsHx8VVY9w\S{ޢȀC}%@_$=;WMXg`g/,O1vlZ6wcdϥ xDR`2A B"[XL-k _ķKJg|8GuKPhjȭXal왶&9scH='0'g +X0el"ί}iEP'YZ۸զefTZtIzI-KhAb/y2>ѤI}Ww Ycq]?D?y;6b0R7p7kV1mň[1!ٻzI8>͗DpdusH#r\f-MvCg엀nDߋ &״0V[Z,wN@v[bb$@s\w{~l6e@^j69LI#Y l6e#LL&*Tg|1w6{09rӧN aѩ"pO<q\]BMҷ2D8  _? O meFhRZ6S:ɑ0}y|;]|@U: +k-@cݐͣhE*2<@[v.1L+p&(bE&@J +-:ŀSnadJ.|ڍ+>AϽ=Wi DV6-s JJg^na&<p_D:QE%ӊ*HR'qM|PйT*=Ǖ}xIZȡJoN1ڒy)a8CFf|@Nq1׽"` oG<[ϔ Azt: ð"B/g kvX j3QC{EӵuƂ78GL'8W&(wsRxcq߹нw_? JɡFª8%#UbwlZ9ؓP͠ɀTv knwr-66vo\KzO$O0` ?4'wR]- pqq 9Fc #MlLbYr,\tѭcy!q*sgUywJd :R2+p/qEPS;~^CORNm: >顩ZbkٗdJ Y_`bEY)17(b-- 5Rvr?4)Ve[%9x^/u rtlLF^\uA[_-J EVs=0cŸ=cU raZ@iQVl"_ZםAjMJ\oYԍ|{0x7]w%4VNyI'SRSL'zխ.|w>7J~K%f;?^-`?PGL8cJyA"a7VGy+Џl\)j1\#ŊTJ&o85QqYN!ɗx@W聧G|*S}+t} ܥVZ4#AB\!`<gVȅ 9:{w=\)~ɾZYU;L q"][ٱ+=>o K>kWu\4VtONd}ȘX0b G{’$%j'ؙxb?}[_&}HM2 [{hp4h%'[YdY)mb'Ak3_#e.YvQ%E87O$p"ψŝ "%gtT]^^;IvX<ם6Yi'@78]z=k;aJSRTF-T惀\QGrԍ = d<^ $cLY-;(}3FX9hZ~ZMu0 ,: .qž"`StoX`O׌m:Z|D6(35lXoMEۿOtOvU`L !xg8 z<^"焌1I;BkI`,Uގ>8wB+{~Jm|QƠ\$q9d^Xt=[^¤[C͊rv5?eJ"э%Ww_.xH͝>BɃGm%N5tzN N%I7׍m( cPPH{IQbJ EjEna3:r!)d8J*:tCd[D4!Igf(^V k @ υcm}7c~F_Gl" ~P]8rHrp{?\>ǿ!L%J, ˒D7.*)3.hjZܧ׷V?vg’+iD?Z$瓕,ᒋ~؁W/p. DZN܉%VNشWpyez6f&ޔ9z}XuWUvbW N9hHT<=Cr+ۋ[K\t5cDhQ&CuʘK\M  ,o=P0d s+,WT1Q[eGl鎉m&O=Wx=K/N<'.qXc |VB^_L،ϜbHzɊf|nKl.*w ɪQT'Ӡ"z Xn{`eP񞊇E' R֣U-+|lUfe3hQ?#~?#/tyKK\Y%1t! x;1 |Šs-㦁*@,K4<6_xqlzݰKxr+7Z9v[1+~ 2 AXCPgÖ;֍q}{sL\?ڭubgxGߥ)a':%z񪱨q_ V9yO}8 X Hc)ˌEkuʕ0O>J  #oc*HX Tt uୀ:>Qo7r80ԢN ŝIlވVĸ>m)tq_@1'"yՋuU"l;L"ֆ[)cg`1>#)kN8R5&ȖAț~15Tj7Rlȱ5*8-b>^z8?>Nn,;!8k|W՛p+">?zBrѵݙӧc_ Se "7&]Kȕ%!ZjH+Ǐ>Qt};p,"ʊWĥ>x_媯.PP.`WJp-!7ǝP 㵼1_DUBB򄣔 5ev<+\'.+s?l$R$UXJ㶲*R,9@LBz Xi7#hۊ-Dr3>a|e g߁4 (?#CG V6oMgy+_`brE)qdrСgϞ=}! (j:ˇ'W}Q vu,߇_VO]B$0kmY ɧkmnI"A\:w{i˲kҤgNN5%^G/wx>t;mA=&J^ULhpKds|d:"@O(&U;"[ [Enc{6dԨ(9V>w>;<\X:{ $ \AjbcOO"XT>'66gb4+ilvk@7hg ߍw䩓r߅4뀠k \-!*t|$G(.]c4` RVϪf5d =YP@j5mAm']Rm/ !0E杳F~ ܆w}q`PveսAfY9;>g$Ơ(%cd6^BB O[@mט㉈B `w(dK|s sR" 0kBܴ܍aK9IPZ b/ ! ΫuR|)G/H>8oGo]ih=BzyKR2NG!8׮;1ɦ t%uz9g]/սw6]> ;tǀd#!7ˊK,oZ^TA)빬U[4Qwj13௅eZ0ߋ30tw%| p!|l3:\Vb1rI+%E"%GVaǜ)Ӄ[w`b5%s% 3g1fg9rc>@s@DY$ | I"Չ_gu!@{ bpg)ֶRR6Rf+D` YKFh>\r[pʲJJ*`K-+wJ@f?6Q熶1w$*i1^<^k SEeX%@UjNTקrZ{ Op>r^0Fo16x ~4$|xc(HO ?eWp2!;og8(sMiHehҳrfᇚA>8/d {XYQb</9~剅987ї5TB<@VMrZ|' gLNz-.SV;hv~%?[oYyW\k5"(5ԺE`2RK[[ybJbpL NA`-Dm-FVGPgY< io"" pЭ,|?Lih+T;soC3>)n2ň*ƄPѸZ#fDj 6s6x 묿l'>),3\:*")&wEPx9zU~3O.IXBagon6KGxTn$ȠФqR%n\/ٱPENe ʂWe6\:=sEn>{qZƢgk2XRObKI=$HY‘ 1fbȫ5hsn.bխE}%y8ן<}!>ڿGpZ, =>c-yȮ={G2l;р I7N.9iL-t']"27 QY-奥3b-Ddb {^ܝ>uJI+²>A3 d)yM]Z! %T tdKb& p!1USz%R}e[Z_Hv/^"<~?AGj+R\#ӛױ鋉\z6eT%yGsPbkt,`ry @9Q6qމu~X=ߥvhlO><+vQb yAPYd'\%&p==qßuc9c]LA#*L7&˛܎c[ W/=sH4sNǥ@CHk()D"_&g{Ϝj k`C5b չ׺=^Bn4 ALcBm|ˑz-y PB{fC~Sl=?^e7;/y@5AXc9⡖1V.iI7T4!3cI2_rI>73w^_xw]^],pySxH_ !÷40r7t"IgUl~XKW< J!@kN7c0V\In4aƕ/򕉯~o]xP ωՎM*2aҘV27$9Xఞ nf?:tD&V' }w ^@IDATc9˰xMOgJ,|ЩH<7ƗzO V♃X%n9F<_w~ iNp0xr#t̓9UFzJ++6\\—B!E^0UQ|Hvߠ\Tӳ]KIeBG<*\MY% RhqU&Gr.UD ݊(Ӯv~<Z%L ]7|=:aze'$"\pqpϏ>K|?F xV/ο}a $jTV֥ӔS]wQL- 5j(]WF DdHz|_dƯ d#e'Wk*hu u? yTTP2o?jzѶZH]|#31W $ _^^xQp| qwNR 1J0}L zaCs|w8Y9^1As]oXGo-qota9f v};9J gaX dvoD 䇁KP3YUa'|.LmHh-N"hq@T!03XFpnnʀZUӪ1$#7ۋ9:v5/<;e %}T0ד킳z[M:cBJhSh4Zk:hwI+xǼïJ_|nTz[iSo~x?"M2zYL9'lu6ngokgՑ8x V d*E( 8:DYXWNQ >  ܱC%)t1*J ۖҭf6- 5!;@>UfP&8G=e8V^CV}[`HL4vj ÿ z`/%=npv裠jRtfF$-0*UlE]jK2n:M8'z)9ع?{;q<]5f`cNиB@^+3Ƿ S |oX/ Si9'|C,S6*'7<7֎ 9^9-8pc~ZeL bZ%(J8m~"e(th<4[N|ƷZ>Qpv:!\>Ԉi`LbtLCTSYvDҤJ^!=!$tSeEƎxtuOG#Ǯ|igtgy.%dT %RA*BNIF@)t(!7YL`Vhԑ^չO=ӄ E[CsgN/-=Cvv]|r:wuw>1C!=GuGU8 i9\~,۬GEo^ SʪLl4*h7*bdu㖱C|O  NvOS)t38} oBOsJblBscswO/=>T4S 0;]9*6w^B(Yg6"-6r@eʌgsYmMK1ښ>޹/-L r~8t gߌ# pyYP#Q~bsa~ 39l|qMPw@*҉{s3ky}<~Z_VZLS; [>AY󗜀*Q8/Bͼ d#m &olKM셵Rܘ-e<ěe.)V kCN.ZTqe4ef$ԢD86)Jm̟'E\1H /?5~̋bʹ|])þf- O#] &zS,x2X *ʺUMzI(#PJ:MzgBc8,FD.0+PtpPtק!5A$+G%`&?+OK2F|­LK,CkqX/159N'eH]h56iފK ?1ʢf!1hͧʓzMջ]??^}R<|gδEղDȜ&O[/FXKke=5C2FlOfcQd93=~|Ǘ"hsw N$֗i$0)dIX={A_?]pE1,RL&*f7c;lgFG&:.˃˟|/]8 .S`"&M3)_{zU? qر<.-#jh pgw**năd6ъLneZ#4W^)1%0[*Pॉh$$7Y7 (fOF+g3J&\553f5K :be: Ղǔ#Zmg\ ie61qY|ہ}8A'n/,.b^>3拉0.OFxJ[qK?='2< ֎iu6҈sc2XO7μvq&^|nngpӍGu9o~O{8};)* id..=zD^\ܩ'$0z檑+-ֵh[##f[rvC)Ze*;?{ [6QhFIW{Hh&RO /SIi &P zR>'܎ kWZayǶSE L32<(#)UBzM]\FLY~¯-޽: B4^?:tHC6C8{ HE֮8"v.\e(>dnnZ-ϒHU.B Bܼ` ]?"ФW t8{x歼&Տm?'wP)2۶CIO EYXp_3Jk * M2of¾, kOS?ʹŅ',&SZ'a~͡+u@a_+6D UA|?\[17Y4iLs vVaә獍SivT k{9~jȈ6$:5ӥ".>I9TYb07*W~ &Ai&1}mM,P4ݬGx;dڄqS=PjP\*@vDsݾ_Uep=pX Rg{CH)RʙRp2`g6Y^GO/A5qog-,ε?[xS\Z\;{|Cq|OKL:y8B !='͘( }뭷Z` M 4maF.6Rs:x޲U@Wι7~[2a[H3IpA%1躣ɘǗO:"K|vj@"^?ynṇERnA/o.!Q·"A.K-%ɭˆJ;- KX9>@Vnxo<[o'w{wɐ;-oCĤ>fcs>PRTTƬA%[Ě]}B>zd5r M(^vOv'N_=Ni=A$ Uxx6 >UbwGaV/W?6d"_tb-Oy0'-=Ņ)!HmSCB ŲMc8d{]@uzdV9rn3ۻc7a@pդ<=h3J R>>\v0@|;!p6^QSq s2TZ'Ɓ7zJopgδ/ K|>OMz36K=f,)ձ?y?`?WmY9}ܮ?sJ ƣCI BlzU#lc >f&mɕlLHr2' zSRCu0%\ nn^™?l/hg_(#*N^sB0\D+ )"2\C*W41.@SHvn⸞!XSw\a3++{ow/twqHwa,z@}΅B|'?$ Bm 9v4:& Yn`VHY.@%ֱ ‚}XPLAQB$H_t;oAC\ U i~$t+eQҙJpo!HGhtPnt ?dX8ziaacGVCQvft(KũGJ:|/sv]Ƶ&MgC܋99NJΓ2؉qHwrJ ,l`VʅcǏE-֗r7Ű1P%s7#*3D6ƺ \ 皙Μ`؎#q'?O`{G~W_G߱n;9D]VmR< "pAWvKR|@kI2ϗR݃ u/x_ X,eG30N[ B,r |9?Ur s!QdQ LnJR3qvq~+wG=w< yW"R*<԰`~*(H{ĸx?< U؎Ѷ؁nW&1[=jL5F!P薲v9FgA z֡:q֔"Cm󭣜DmR1߽򿼴% q>il-#BUH^Xtwabw J@cCSR;cN5czS.q%i8}7^!#u>M.LЊ1:hOC[m7ʹgvlӘasG D}W\>#:]}u!ӫ M(YDYmf๣GyӠ2~xGPsO85q\w,*Ǝvi++~> X{9utҠ "w(n@~I?tp.x=WGt$adkF4knOuv2nZrtRٳ@PRD$ӐhQ7[ n9U,v=U?ݖ5_Yb3RZI.dMW,MB@ǘlK#^LLJ]6zϷg=;u|i8~y2(&Vˮx5O'm<1e`]1d25dE!sB_|[pɱW3rL9< G@ת~k_{oǤ)v1rEIP2gN N?دK >᜷_Bgvbw\|̅ [:١c4AiLv69clL׼)9-k!PmI701Zf 72ʖmTv27`ׇ k6")EB٤IPy/ ?z/ $3* }נ\Ar+&w_g}xsQE6>_mYy(NCF`3,k7 (=?^QT 9x{'J@!Rc''*T_‹OWnX3]׋/LNi,YsNޤQ9rki^4UH%Ur@<[9Qna|E"}ܶj'}lic:ҧ7&MWrE/E"4{B(hGť64MW|clf$vܽ>ǠN:OcA/\! - DOB_ss/{0ǘ7)\ Fbgs}OXNgRGW8%>DJV#*opp'ľծsW'&*iSvsS䒵\iiiJ~5Q`16[ʣeOUBdmݎ}'Ϻ־(1MU:[o@ILcVz1ْIY& +TժܗΰS処e+AkS6a[?sp~8H.a$-~?OmU1bw0Qw "$Q\]??vrQ1cr^Z3 p6ܰeLcNM۪صkWم~Wec6@o* U֠ 2'w}x.7(r jLߢ/;~eǫxr={l<׶L8pd##` mX|>ffMdCe;hjdw@IMv.HR57`=TBXs{x_&d\*jS.3mr_'e9`ZcHX491@1>F+w k?_ۉg8ɢ30tU~1/vW\@_QYV3ngOT6ҁZKg{CNNO*tx@J4)?{3;bNna ;?,!(SpĉW>gDui&A;Ua|~r`nnA?-yA]!F5hXjJ mxEUകRG$?6c4`+.*ʍ΢hW LQ7ԱԳ5);!4i[ԢSh+w{- YIgkk\R]"N$A[ L4<`G9mH#vWdeX(8I=U_TCE؇s|zۏ϶j7~Ǜ>Lpkvn{o9俼|ɷ|Oca ").9i"\L b6'{1 xHgYAȕu e҆` r4<(T,ʂ 0Gڹo?~,?ڪ=`]uq[J*ЊJDT\IW_m˖eQ<3BRn3^ji4)mdZ˖UCڬ~+#4r+'7?s/wxOE4 h"[ Ot ;wݡ? s_C2ѓz0'(pwiyߟqƗ{bi.eE;> 7_yHv:sv??2_?:u#ētZr\.–W N g RbTYꗑ7@|hI<e=M[Zaԛ⸾M2(EA?[x ,lVrn%gٲjsi5ŖUE]Z욫0z E65 9?/A|O*X\4nL_;xP|~fc@Tvr"K˹ nqNm:3WP_ǥ6&`¹M ҍݸjFgxh4ٱv%ٲ\8r%~?).'.q"YeٲN5){ hYB<=kZfδ53{ HoɱTP,xN#• /_RKt1|رc}w]&J~sݡ\ڥ-ֶ%D,+[/m^y qe2eǥ2R|*%a;\Vk Md&tq7vUn2hZ#w(j{Oېl F*l!Rc7JG͏N}:_z%:]Э*)VT $ i|:!pPgqZI'ݎ뮚]zQjl5[!y.B ,zFfJ183[ l@XD]ZТ;. ~L0rĒx؞Fn;[;,[ we߉LF~/Z/DdϪcq~m``D'úuVg6j)F;UBy;~?v4)pgddEo)w=ܳ }"A'A9:;`cvb~@9qt8O\I|`tvv"oJ7l={aW3#GDa[`Z"Ki=,d%>nh"*oEt(, Rij1Nf*yE o4^,-"?\Vl, 79]!;I3O+:stLg'W#S.荤ǣTBX'rTO?'qTI_{-on|e*yϥ(Q+d&'X75ϑS[1IX,?D h6 HotWCuEu"vXǹ%1r?'u^ɛѰ`@[Ϝ9dɒeFoVڹݒ>V'A%ļ8:zZWA+f.} {wcZԦaCEUYx!R RQ6(+0ԣ%fpUE//9 .%_ ~z0 )&j͖[vTR]^9]Lz mc)*Waj`O $f9楀[.g* R[@ j['cs)QneݿxɧzcJi/ b,Qc"BV;foWO>ӧ@'c@3s@.#LEΉOYr/ƸiSfvi![nC:-4$*2y3FZn j=Pv6Gu(uzV#Mb1 't^szF+pd7{e76"EwGWzW"s14qWQf8qyCaNԔET  &%F pgs?<9ܮq4ts{\D_úTwF7gz>T[PvROp`?uj,hwغuh=_JcK09QVLv7X ri9{?t8](*}Wخv叛\G`apPҐe Tv*7EM~[#ޤ,_3T(%]/fr]dz/N)5)55 !˃#!N n^׭ Tbo )im)G<ϯ-ݥV.L*gi#+-vT/WY<+ȦMy[HGm$k%:x݄67ٳ'ʫm%DUr㨹Tlt-Z?ϺHoeeeH8ZSC Kfh 8knr8ǁw˳·6e΄hkbpo|p^|QΏ5JIw,G2l)lR|w:<$pN4#~N! [, ^'SV)b ݰ4**]5 ,2bdK-Tt n΁ȹs}9. 楟EfG:'`W9P|?7XEݏMs:68覔w "*eRN~ɳG.,XL^U*p NMy[ǹ(?K 6_lY҃Q>mz[ ]JQ8#%98wOdh_vӧ:! _S$)`'p L.s '” »\~[}.NǞ lha^:\4~%K׵uK[[[ˑ(Ȭ(ƂWȮ ^vQWUX0$L1/Lz'lJ}pzƟռ~UxC$oSjJP͜&% JpQԉTgӌ^Rjr?0%/j-7Qxs?r39^i (=r`ʾl/S¨ RV \4C.lqPJd:\!˄\6m{Q_Rtϧ5#W?fJ_݌u=a6'tT0&FRikM'=o%q}bbrΕ/w\N@ GXg*r;.Us$4xUN+~/ܚ16O]fUj<e!3UT ;E|ayԀS[S.]R'6 ? [׾.kن+3 1&D@ə ) D=`(ȳҐ 6XJ9!SQD$:4ԥ?>y$nn 6r,i0zѝc€KJWg̵G) |7(EtJؚSacʫƔAZSN&JpZ,P_S2ԡJWcG,+3Q[dɨICx0,r-=='O8)3v,>4nFf.Z#?o);D>ϕ2xl'ҭ|>YbE8 `*&2C ,ܴtT8&X H5kz>Uս,n/NA{vc珉>DZϢY`)\9䌵syN>yPb3bj*kvתCa? T 33W$%F\y4{%y~KfT\.J={7@IDAT7C{ǶP3?7X`=66ygM*b "ȋUɌK4-;ݴi9}>;hs}9u1jvs\CswnKJ"]'W\54d x.}Y{̝<*Ě`O3A+GÀv`bNK?O=a\S:hU*} 8^|SGd*݀FA6@cs$E;PKb |"0ld8;-x<"5tu.iTk?*lFYle1895Su 8WDQiw'Co%r;Gx 0!-JpyY9v'<<|Ӷ ھ,`+3nurW_}5 6W]5JG7OgV)>-Q+~l>6rh/$Rwd2qێ)~َ }Oa횵82^YDdRڇZ^6CV˘Q;354`GʩXa8܀aA 9*Z, v fy(U0g4$e!Q8K:T&t9]1Ei.{OG"nGݍ7+;}oB(PkގGep]'&XSFXL&=_Z|쵶r8Nvol=V3b,Hs|r4xVh #=]/NS.Pyars4 miv}un {Gw@*M'֞#KGn=fQ/Sʏ,K)c!cLAK@@A"`Y,g%ö O2>s-W.0/U \$e8bqdrVWf0Jv:GY-lV^9cv9S̕<\|])sWp'JP; K,Z)!ysêx -(3ɪ&6o^u]iW!j"O 6HVm$NM?Scɷ\{'e3~Wr]R.u9t=UӪlps=nU蠞N֟UofRaSFNW"+Ul,DcfJ=qDҩUY'T.\{FeҮem¶mS ]]=ƫ_{ԌП% <6D`c`㷋pO9Ky0m؀qor,ӮPԅG2^-8I Y?,'Ɩw/>kNK"|漹}>yކq_ MF[EW4@J]`u@菩`FqI1$)YɄ$^Iԟ2~z|wolFî]c;\ye\[curxy tӱ`۶3gL/]:<2Wdi{$\| YKޢQzBz} =:S&bau۝RNe̙cꍫfkba2ӶW#%+Cŭ_GcS-dpsԓ,z3*s&ٮ@ܮ<Y4dZIo{2sB2%iuLBjJΉ--+_ }Z~LR@T|gI>l8K_31M{?LW6U>୨:G ]vD{+NImw?6+~q_vrHeYᝎMJ\#w+gֶ7\ÚuJJ^eC@hf"heLVyk׬֕x8mwIU?v P(: :j4)e|u Dzd;&J1iȓI%=8E`xGL.ctw'xG >|詻~s #W(|Gd>;"9pKn_.²(K;#Ǹ Fw 8-:orttO[YY ɨ++m+ue+.LV; íK±~m~>j2:`,YЂyju;|rڵ:!z6ڕ, ΃TysIgɗ`耠Ǚa֭0`Xn=u\` UJ]C`.5XaL֑"yjR\9D`k:j&Uɸ-Sʒ&*Z]Y.ŭF$װ.o Sj>J5\qՠ(œ/(eע ~aY2m|oxsm>w[E;Ǣ.u~u%^czJ+3O)c]iXu4Zƿ.9?"D\$OD,wp9G.?ŧV>螀/xzhhhNdt.t1]Cʰ 8Қ-@ pN' 6/Nm ,N6FlQ`Oxsrg;?fۧqRuETJ,y<$/"8s|[y"0;Fk6 p\Hz|wۑ3`7НmUؖ*J,<67[Nʛ״ԃz0l `M&~H <`/Pp'G򮑜j=Ӟ_ hB~yԏeW G΢8c. YO e8W:*DҿWjOjhs*.CRNxֳZ/:KFk/AV)1``,o[nvj{R5!,(,*`5-x'g+HЬk&bb+ #;h~-*9~؃\+x*WPk@INt*,X_ <|nKכOI{hw]36?Y{4;MAM:6'7*jmlu=:U·~Őw](fnrSvK_Or=.s7z:D9൩/uK u8S=3ULdUd&A<1ڻEJLgÏ*/SIE}TŢg:e^~衰{ϮЧuBgD`H ɨ9O?(8s;o9ʹw?G>QK? qWz V^RB;!n#`5PE'4õt/nY-(s$ 4o0 ,LV,(ԭ~۫WpC:>-{,.W} &Ȱ|o:t#@x>xbGV,;S_31?O(%ys[*..9wòQMm I1G{7C{'6oTg`dn'DE5 8}g]pB:3b~lQ?7R2Qjf^rĕ 4+ *\9*;@ *3G׬^>)/b"M vik"A8z:lX)N;!=s@@^\9r;>bN5Yj;Xi1gaM^MCnW"8.n\h,P *E;,DTw1_Ozyc_k *[d=59Ԯq9o?l۶U_Z ?P [['2A?p4 #)o;s>ٹc'# rG$;GN3RSv;ec/ Emǹ;ZVY;[}[[7v.N-J*" :X5AGŀ@bU)fi6+ë^ ϟvM4x𳜞ȳbN ;PI`C,ŁʑOO a1NLHV ς,؊sk$؛c~vL'O虘еRpӬ_nGYF \2:*0NQ$=BWbMF3%Cե|+[:24` g868XCn`|б^vbJ;?#E0W9, 7 _(?yXSr5 b;RksW.9k;;m{u8 1ʾJJUPI.y41hS0|OuЏtgOIˮ< h׊{:t!SOt G%̈́&4Iť̔Nrţ>X8}?gxs%vHtJCn.~Fp-(6&k=!\ZQckTc-v(9Ur[9l VL:(lb[Ikw*@ ]aMj& h~^o^C&\_\Z1vpΓe>W~pEL]RسM?׹&>#ڎ ?|y+vf)u#iiU=w;_|%uZBwW-v.S >ʱ2䍱e~ ~#lB 董vSO#]9%mb. Idin3m<3nQ" XSWRamPy,-_0''kgc+V>e[fb.^9̎g3W 2aESq9ѭ:q\j !l`ٍÛWyŰ~en/DC[7?/"ĢPKEڢZL=C]]ݟEƕf1]dFpbXǦm1"͚NEߕ>QUJ(M'F J[dߋ@zQYWsQQyҧ40dhhEGgMiEmX[d/g&S TYmKhs i7i *U@0lfЩ:۔ݛo@b\YG,ls.\2sZ!)w;6k[޳e˳/W?; Q,/9 72w,U1.9>բ=uen>n+&* OSѲ10^ӫ4u^6\1ZX(h'kIA7;l`LMs)@Z0=+ ~?i; ;@gn_TXW'oi`61~쑴ߟvf3`ڦw?fqЏ9) ~]"ǚĆ;>D+)lx~oO?8DG`,4Vyt+@OLò*eLs^v\-z*xwht]{ k< iz9x, 2f1 hբΎkl5-W 3C`bbkqm6*ׅ+EpzyX4[߂Qy当qCwBʺy'yPXO3 j;XCJ8G3,跁h/p"ż&ʊ\. G5ɷzN{u@Ω]>vG>OBKaʥ./wJxR4rTdrR, +/%0@繳g'&ڵkZT %7R~/EADf3K3Ҡࡰ|rqXe_<1 xfVpmuan;6J옖Euc,TEa3G0<~{z|3榮?/?:'`eF)H 4EL:cXPq˳_4~#yo)oL"5zn5ʴТ_蜖S}}}0/CW}ďѾ63Q2_vH.㙈G 0Zq:9>>wU:ja~`f1&@/ Gl=$7xq0?9Ek Q1cCAUºalf,m 0/DiV T/JՒ)*ci}12f? O-k{ yCO6f Y{ǟ}4~oR6 92Nl!6^ji?}fxP 87?집#q€ ңS09c(Uَs.?H>'m'',Z){D?n-eȕD*lf8c μ׬F )i + U*dpd \?O]d:|RzR 5u.4)piZ)Bz]w |>|wzZ%5tVi_b_WWwx'g=ޠͲ2.AP{A9  C ^* HL_WK*_,Gn pnW呿X,4sSn nb e)_vgLt $̭WВk:;ז=OP5,PXO3 ?vܦζN*9`kRYM$Sոkc8}?_eOH&Rcio?Hw,}l E&)FyG?|~ףw,6㳡)dOԦЖ(k;;:æM~oZCfbǏ]G_Ne*qks㍻}bџ(v_ø)m0sSv+^L=ZiUr*XeU'\qN@޽-Y2׫ܥ3*#~!ϔyQt+!pѻ˧tuЖDjJJsFGL+V]vU;ܬ69fJ!7!Kz]LH^di]L^[73ޯAQ`{QtJ O=t7 o%b8IEȲw .=HX1 tSTzRÏ<ϩݝVx~я+|G(۔bܘ]玒b%<|sXuA Sө}[6:߯[:)9*Z刣ys]*} Ld > :#+~4#@z]߹!,gZxrD1S@ǤiM{&ٿ2bޛЯP|=uRF*1gʖOŹX"֋&VO<~_j<ѯ:M\"G\fXUݯ;Xx8111ע?W>wn8`#TauC:䚕"eUnvkʌmIe6߰\7h]#nz7}2O6$+izEAfŢӧNk n:tX:Sg< Œ6+`80KۗcSGáC7͎%%}ŜLysx_҈5Zz^4D)O] ؏6Ff6s8ℿ_uW8| Ad3cPN{x1k:'RgNg34n凖ԕ#O 2ѫ\ 2TeD<p oCjy]sUrL׸JNQ|䭀ő4} f^Mbk'Ϟ;6l`5]g )F`*-rZbڵkº ald82uXiU7|_h"9_|_x/pIk8c9C}֮V؜ӚxA[K]}~ r¯A?Lfm:nb{Cv'##+1rXbCDaFVcG7m࿳O֯ҕ~}ԟ+W)G`[ ^RQ a8U05SNLNLtj 3T(%+S#$&\=2V+W!5e xqClc0ڱ,|C`vSi.ټ\.Ժb9_JeVuN*~j̨5@p~t;k~s}AS-t#LM B@rK/{Z :v IQ[ح'NsR'Q[5vlt?p 0N;.W2;K,m*<TB |U+F"Gy۶nG. Zhq#rZ.`]XOM OV' i7SI P۵~~4| |>k;5 `0A]{ n|0nQ' &3 .f\)XQ層K櫙I xS^c)o/Nv;tBuU!ʻAD7CFjFm[~vOlhm^#L:>-B7cr G7b )|ZiMMNM}W~,E9xrЏ+}lWeȹwhYY(Bd]vtb=/E|@[谠m:oJ%XJEձm @ %X"Ot#:1a5[qpq`*ƪ4 NۺEu|h8u[b\f 4SҦIᯇ.˰kEm[y O?1;Ol wiV$jn3U-(y#]',ͥWCܹOt#hH8qDxHV ` Z 'B5Ek'( cыٙ9 |Nstw77_PnS:@c___!Mn7.Ջ!!0!BC"cQ;s+U/ -R;%~꼮%K52}81}<>"j_))L(~P! _jX۵Js/;N;mxh=8~񋟇~{[߮}Vّ N 'fD0Nӎ,~֢Ow뿞z?ә3iџwRG+|" `; vt]﫽@gJBs m Hp:Ok~ Z$sejPI =xadVAgjS^f -2^Pilh k7l8+/;Ej@3v ~"`NcBeXQLs_?O/&t+§N1D /2Qb.^5kC 0験 ]f&ݩSN̔? qJsOkO9,Ra_ &u!yO^$^mcѾVp^Cѧk􉠇WP:*2X Ɩ";0^%=޳kLVMml@\ !/,jΨM՝k{? MVA j@3z߆kUM4Ef7Ox ./LC* :8l?3l۶M;32cP >9f$CT}Vl:_L ͒ꞟN s_Ǖ}@W~* •yjy^=#e\BREwy O]-ּ`pС=׵vWۘODFjb̵CPsƙPQɁe `|FcbY?;pcMᤎ>0N7GLf \T PK(}4 Bo뷺}ʋ3VGcFBF zz{uXخ?>ܧF ĎY"[;&)|!|7???O:o{|j&i)++|_x'[5rXhOUIg Aϙ:W[ur-<8y0͂"AK櫙LO _ٶ(|kÍ7tyt_Czj8 ~bX ??'U47-y˂k|dcKI8;D db`gS'4 95yΧs m ^Fdz;͑e}5wXkY\v;in|Ӝ˘ů/3 ɹM08)娧N:^z:ۏ{yM_W=D ³I4T3΁FO1nH`jj~oc1~_wa0ap)sڢk9yB҈'c |<-15"!S:1p:g0PzUf%N&FbD`N( ` yݝ! 5xW*fXzuq|? c8eJЙׇuxPv9 89}B]-4@IDAT8q&j@3,:J?Fw_dkG~dL|1& fcJھΝ~uu/߷nc_ [%ȚJۇtYx &TsL݆K3SG1)_n~vNi䳬EF~OS+.4;1R/PƕU<Lt<-$VF?Mo&AeUH%(_g:KCi>YK @]qnU&GJ܀-ҢfI}=N:4h\)ЯE~_b>5ڣg7ڱY퓘Z}t<TV}Rgý7Av?0C)()FGA0yGgEltvKAaa27>?JIw4;1a̖Ҹ˻xS;N*aFسg[}gCZuy: hP=*LGG)]'thc'Y!Su|C"6#V&4:#;Wk *xY/.RX>#^ /&LgN䉓g 1_D45FƋhLpPQcؑ,ɗi  >_lz|i8qOJ?ߛ8hNq7@(X9 9KNa ^vܮ ߻onm< {Fֽ8\iC̱]8Py`4=Q,bZN|^ eЛN Ϡ/JhۗR&,-\J +XpZr!F Մ)R/Y{M){~}Q7DO=ǰFު㿹[?pn#c6YGLѬa ap .a@kst׷g6?'s9]=ZxH=ZJ+eGSA<\2UQDI2 #ڦ[88)k`:qԟ+D[ :N&###}`Es-c: !b4S`ՉLSw|g>8y zMQ&Nũ})M6O£>Zʟ7ȓPN80$ǃ՟>2eM-nڿK[ψWS9ߕt Y89͔ p*{7;3^6U8j.>ܯU̠gtwa ;o򫡨§5U)&POy*+p_9+܌:8>~ҬD8cLhT cBk8B!,=EÂSmm5|jـhe5vɩ:E4X)e|X|<|oŸ-ݺZ;f:X#C݊^YF6t)VvwWʫIuY.2AqA Nh2@& Nۼm_+\髫~.ɕ>ϕ>I;_6U2UnvڋxN9r~ݯPqeC"Zث B8VFY*EPZO!#x=ZHfZ͝oVG`ddF !ъha+=`'g||Zv>3sZ l>ycD4f \)vUݏI݊ԞkQ ֦@/V w~.9}5?Pȡ^b1@*8 0XSxXirɓ^y_n{W{P6W[R5wv +FY#F)#6mK vvv-SߠSPj jw 'uDְ 쑖GDiFejz2Kͷ ]] dA2Od|`[c" EѦu,Blദ>9Vcɵwje(}"l`7\-:Kxj' upsux8r}(Gx'|FxQ MȌ^F c<0̪2lVu߿k' #~]v&1lmV6U2UnvX ypnCnp/9qCx\ӵV Oiǧh4SrMR(lh _[K_ ]Cvəb?N.9 gU*#8]q Ǐn?(ed00H*9Z?%KoܸNq&6 _(|+~44u,ZR`2nh^_sz> |TΞޞqJ;4z%ޭ(yg{5 X<ȏ[&û*dD? HYܹ:`=zDg.9 mLDC"8<.$[M=7cL؂s5zچz%w;'?F:Y(~f{pTV}a޽?]q3vPLN)ou/U)ǤL p`ӟPSGhONNgާU_V?/|~ninp4 Rh0%t5d]̆ gsƸ P%oYsgD È*TMoao)x|+q#8¬7I,{MCsu8`CٳAضA"s56Vl;hH0 D[P w&  |('Cok8` St5SҦ͵ت\T~ҾЮѻ}@z'(* 49KcZB~8T=;Oq#fLM! <^h?w;ˆn7ewNkJf`ŀ:;CX-9=yvVktԾ{HxK*3EAtyPY#蕇N !b.Boݺ5h `¶AjvB.A#< @ WDGo>#@k <)]rc9 _8i7SK+|}YhSJw*LGnC"Ty(Z5?~VasYyԕ(ff㨑E=2YCix[c4w/Mnv{q,5 W/~>~ fO8 Sƕc@PK@ ^sWvy=Ś&xPgQwͯT$O?b"(L솉,QtgߜJ>d1JaPF"#/:7NnբN&hm'ghn5{GAg ܢ{t邮dq9f C)gQθ>3呯جPYJ-xUn+Šh[C1۟UHc*ñc#<'?OEZCS/ 3܄x¢'Bh'u/o'i)J?Gw~TJw4;^gx{n ?0n^ۀxM'tZuеH-WLդiQ` X 'rT)mMz}$0:uԀEMCgZt хng;=S7 { xCSNMv52㯴7_Q,qKZ-XGUbHu&U@;F:e\4+ޯ6ȣ i_?:ڬ1P^gJZ߂jbw[;2;7?/IP(ǻ;s;#"f' Qv;iW@P( @&eNwLraF zC#=eu*f bզ71J9֚C V8(7JԴOk׮v 0Kzժ?Я);v~B0ihH v̨#0nL'(,i̜|uuS+=meQgK _OGwъ ,_U;˷u,Ey&2έ4۾}~\7Aqf?y߃ NXcsXҿ~k.'Kp9{f]e˖ߟ={N(zѯw7x1"&C`oWgɮ_)Z#r*>A`p48-,8EZhj.왪QٷoV֚}"Jڇ6$J\΀mE#,v_>9AfO~+ 3LLSş *c_jGnʞT>oQ68#Dwuu?ںm[xlC=doOP°EG@jmGY܊8:8aC~!{vG[7iPqE8vGh4恎q[{MٝӚp\o@nI+p8`p61Cn>nW=C Gt]v7M t{Ez[L{5_NeLqp!lN8fpb ~ wp>`G +|y0I/n hĈC<N =9<9D7/_'D) \)Ҥ#s^m#޿F;w}fN>Mc4+|)l"1YkIgi1o;t hq%ob))Qk(0D1?8,fYqA-x[B!ȗ;YU?&][d.r7seM|)Pen92wQn{g;u1zgVZU58QEpVS6'<.LhdX?7cX?pkų:1[4r1PApL ݺ7r8#ë㯘{\Gɿ~ihl9ps)}f۪ ɣrE'i J.Q _m܄Vo wugxB٬zpG% bf!i<@${<f$Dnj1w P ܭ>}zLRP>w]vؕ?vBqcR*FGzW)@hUXv?0 qq+.o)4mooQ33L),MF[R32n/4`7Vݑ*\+,bhUiYv8w a ]K J-r;ׯ[> |> ¤Y7埪 ui<@a;z2^3s6N'׹'sE[Ç?a!8~v-.dCX +d)\7ZXxiŰSk+![8؜j^t螉e'u`kH;>I;i˫U*rg2T3rL1'O3 /~-^by,eܤ8( K䦸xj2%7_AЧ4`P޳kCo6'˱Ϯ"wepоc(Uacp;nemyR*=8)HV۪l;0vg0vLMO뢎ց%rFG~}6F8UP͈)X_ ` ]2gT)a0>{n3& ^7}Q0sq%uDҌݫ–-i{1mZ+}*&m_!F@rcF vƩ"" 4\˭[PV=zt㫯PeJϴ3U}.+}W$w)h<ΗEeiJ2.hq;6şhwh!^zްaÍ_{zޯo7DK5NFAMo{geq&1}#@ nĢQˑ9G9b'qdE)G$X~{s`̠jzuaB{]KyIIE^d+O~N cQgiْJiO0 U/t0JIF*OSH̍3ݧWNto]z{ww.Ń ȋAl\#z+1,Su{}MԥNiK ?JC]szy˫׺~/~v'?۪w7suVE5ڪRYF١Բ#qႂ{=/k?jWuߔ5 ^ ~۷;}dΟP;iPshX`Ȯ5ڼOoG6w`3  /zhf~QNoSQ%n'3E ` Kz ^Ҩs :^C(3ea ۋ~~ֽK"B )ǦTcw_f_NeƼrvGW>R'8ݝv&Eus<{WG ;ΣÛwG7===~7q^Ǧ`('4$YԖ"3uQt?^{we}4Um)~ xpW]N?a2w~-F+ЛߠME:}ϹBs!;3q`1h ~X1N3 ey×i,3USCy 7^-P~&9OV~) `WwRܣ{/~{'uuE QԹrT:Mr DBb!.ވe cyxgx{ݛ>р!|Ȗ|R[lT:~tOWb?`ms] F H_>x8% =yCF][fϔ~{khG<0;BW[h%VLxYXoI+Hْb!N!=8sGfmUҪ > q\m$3h}DGlN>[^:m}ׯG>,ds:ZY_D W>o>:o j`ٹso֣P~UTGX釋 UŚ'iJ}s*X4.ō> ߮ b =nn-}1rܶ} ꖫV vK[;_ޓFc؊>S~{z.\؝:yկ~~}嗝m~*z駞a;#/I=Nxܲ)H̒41KlgJ:aLêsSr'ݿ5(=YJt&JI$C'N4/wg k_jPp\N5Cp«ez$y;ҹΔM-F8"NHC>*3|kzLン5=·z`?fpp$CadFi}ɃkrNէyjp* P}"8YLXAzs9KMzYI@Q@>s-rg|ӟJN>?7? hgh\C 5u2hh*_j]8u@@"^ >꼐\w;=c?wMYG9 7i c/</viꤐ42@x(wtxz1?яW^y%<EEX|t%\*"ND8 YtNQ+H~pg89S!PA=7 >@'1`{7.G.kF^^r>֯(ZՂ--Ny˭ [u羹ۺa[ǖDplRd3&\'PGvþQ3_1d}B_3<9$LQRm@;׿֧y[>I9v{.y1` $rs@A]~ H.ۿzku.҇c#>.cgfgOǸCD;t @:mp-PZ*gڲj!<@G25'ώ߸ TkԩCO:xe}O%Mk>X= zA6叟lq n3v8XB{^rIKENu.\Z ccǴfym2o=8i}ҴmXɑQ:I'J =h!zx㚩HxmLt|'~sA %l'6},5iC^}6;<1zJQ{AytG z׉_ĉCE3kY8걝n-SLӳH- f.T{M-Cl[ D r !W-2qSN<<חXdz}>g[k%AyԐrǁyL`σ(n+'q.IHKOUC3>2{K;~75P޸~ @Lfg: i*ZoMcjV ~/Gʀ280G;|x q6(C>sw޿;f,}r<%ya@Pj$N,}G F6!FgqtNG}=13[ XU.B<1s }1@:Ң^w<&i#ys`%u'k_߼W"f ~xSQEl˱8@tI{5>V8g7nT0XdB;m'n痵rN ߤGyosu%c+˝ClER [0)zt.YچqZkz?sV}0هK:ץa)jr7"p /*Ωh # 7r#:ZY[Y״~;1_WfݾyTYr@6CJJylo!O!s4pH2 HϛqӞ6n3=ބ]gy4/l^Y};gwYʮE@=^ ~%BI_cPsN ^~e}x1^Yd+e 0@%Y7΅67?ϝ1;Fi.rt Px% .uo|-m@#Ϡᚶ[ rs EC3/}K@[f6)sZN$G'aERC33.*&"D(;srC!| rՔq blo8vs3'=:]aW4Ycy5mq`p=:&u*s<޻߭}kMsmq*= *_S ,}H )^Ag:ROvGj'Df 툙>&I-gM9Dgw8})}m|}']>UHr~+ƹJl{fi&:vOQԎ^O:VvEvʺ-|MXl賔ӏ7s4=4:84! IA ]1|ˌn^x0`g9Ѝ lT)?={^S5^PgT~Sы/\9 RuV}<g7fuݳZqd]:9uvxǎ?۰a.8[Q]MV%WyA8!pB'ש/ gVuO>T,8P.UrgyǷ9 EXq!ecd̊#$J\Q"9KSln7e"DJؓa".\PK׽߉-yَwÑ<]!1\ޅC<Ϋ?3ջe[jEY,:x1=k_~чWN+ Smv䇳]CX;}d43.2hx p|St3{lEZ,P_v8< )ÃF \w"[m۾ȑ?307(^+՛iR=3WE]rT;żW`gqda |>BdʘH<̱رg-~:%Uz= 슬}+er>mKR򰣣GiR*r2bg?|),c S̅EJ2/׷0uf=,ym/q}<-tUyG￈}x=Dg2GM݁8Acۢ*j Q'_i`sz|\ 3-2hx hskRhEY`|E8opv@2o^F9ˇyLS>;w|nm/o"z7\@=Exp]GQ;{;IMݘ 1!:zń{4x$Q#c6QҞ;-?,9VrG8)9+- ]> ,ؐđ4@?^QˍDTn)  \,RBOD8s>Nv;/i#~O@9/ )PG“>J'@+{vUba!`V@:Ίz~ێnhL;!ΟF6Q w? f1} !/`#LBݹsg.8}msRS-}2gȬ&Ǽ=1pb$Lm}Obo{_\bZ_u/} #fgi; su!,P}PV%-0t,:Rqv5Ӈo>;N>Õ˿FR\Ť\+W~-Mu. ـ/]9==84Fp@hYY*pXQj h[e#7v5m<|6 ]l{L2?W|${:A(0ߣ:EwEvSgOL0u:y8__:ԗý{=ӦDg$<oo_C;w;LMi ~=-0Y/?m}bb kCn)^=!>vex8>@@:M^=x^ iOk!6L+27ңR1#P8c2]G  )tlX;mڬ-{7p dxNB/:}Aζ|G h.zL->9bp䢯ʉ_ +큼{`]c/}a*2qroC:/N*1m=~UԻT.fEЀ"@}dM 27MS28uE'|pzuWk}f gjՙӮߐ ^.<<A4y36ʡW|O?ʉ GqHi ek3%@i:Bnqt3a0`Qt:fbX^s!qyl=_A4l i ggq<iH}~qjPI:3?KTT qJC]~ڨ?8qd1lM:0Ƴ5j1 7cǮ?)S$"7aCgh=+: bS!u*!{ ڇޛs L܏ݪ,0 Ků/cM4oZ}iȞ/8˝;C δ("94Wrt _hpˬet@GytpӆvC8z@PAMdp$|G{3 ēԭfiGMu:N_w׷W;c@sGy6nHNo G(t8x^-k:@ EƮ߼iܴq?@M&~_ߠ7v(nWܫ7xL4{ J::lQ84$)Ж&Aw k~M۱A`B-GX rn恏kFa L>V-0v%vLgtk=25| k+89=آGY8}j pDSxpFx 8|mfPP[D'Yϳ=M'>YG%}hSC&2":M0m5lɭwUvd-qbsF!ݺ6q=}ԁG: pRd74n8%uY@^Ca?x9Y!Yf@PgK9Nfl6?Ҫs f9y="ВOivOj@pLgz@gI]"PӅێ<:p'A>3RZ}eA;?D]ae:ZG 9zƃ: ɲ^סZ/ NiZ;e>!tʦ68zXfY9Y(Z>IMh0[vi O -wt>'tT &-ܞdUvX;u(kiyO}/ zyp玟ygN݃#7ߴӓy5nZ*0PN؁Nպ^^jW;[`M)2`Ƈd〳~-ϴqCt3S?kb@ a9 :EJn p h0K;Hn {B}vu-5q|"c<[`恏L3]ZV40~-:xƘ S,xrh 'ˍ?-p'G`Cr$"N0iPfQCJBdӢXs;k_ g័Px2|~g/ ʈ8 k4j`i`xY -+JBD{U.237>5iz5Dn "AoYY'󧥩Z`I;k3m@.t8A0r97we~T(٢$-;G7 ܗX3/+*,pXI_I8n?iNYgVT^qK9-!2SXڕ5_7 ܷX3o+*,X`%irJƇ2,gyrAwHedY2ɼ_I\fÛִC]'*, plV|f):K9Y8vu9+͇t.pox ,8v+rS4,ɼsͲiJMӲt.fuiՙK㵓z -pMލ~vӯwkXuFS4Y-Ol2XMǼy©,֦['6Bz{hu]{-Ҏ7[;fe[^֖[ve_Y`Y`Z;Vfd؜ZjJn/`;o9)ohhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhÄI`pIENDB`nzbget-16.4/osx/Resources/Images/statusicon@2x.png0000644000175000017500000000122012630544544022017 0ustar andreasandreasPNG  IHDR"":G bKGDC pHYs  tIME 0$-iTXtCommentCreated with GIMPd.eIDATX͗=@ߙٵe M Đ:$ cҤ əO4+ꤳ̼3@ky^)|yo|}XTPϨiV3 mwG[Q (tE5D zιW^eVFhQW\'Iw]; at6oB<8*1|)nM55 RvbLw"$ }b9=)z?1}Ǿ9;fv=# q%f3 /숨l31or`5F>2G" AD~[kp84E8ր()t:}#Q4%OfS""fY@oF&1d2RWוR?yFVG)M4%k+SNZ~߫[fCg5SGe+g=c>ȝZKJJo>`3+iڬ!Ԇġ!IENDB`nzbget-16.4/osx/Resources/Images/statusicon.png0000644000175000017500000000065412630544544021457 0ustar andreasandreasPNG  IHDRZbKGD pHYs  tIME 0&u8iTXtCommentCreated with GIMPd.eIDAT8˽AN0 E:i&#E=KN ׁQK&#eJڎ%/91-$db8Ȗ*EDq 1Z*T/Fk-3kCC $*qk !R|d/1pL(sWxJIDu]_OcSsm5" D'U34My_rZ?R*_,]e/Ѡ})UU&cݯ~C絵fzRLUPNIENDB`nzbget-16.4/osx/Resources/Welcome.rtf0000644000175000017500000000215212630544544017453 0ustar andreasandreas{\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf510 {\fonttbl\f0\fnil\fcharset0 LucidaGrande;\f1\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \margl1440\margr1440\vieww9000\viewh8400\viewkind0 \pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural \f0\fs32 \cf0 Welcome! \f1\fs24 \ \ \pard\pardeftab720\sa260 \f0\fs26 \cf0 NZBGet is a downloader from binary newsgroups. An account on a news server is needed to use this program. \fs18 \ \pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural \fs26 \cf0 NZBGet works as a background process and can be controlled from a web-browser. An icon in menubar gives a quick access to few important functions. \fs18 \ \pard\pardeftab720 \fs26 \cf0 \ This program is free software; you can redistribute it and/or modify it under the terms of the {\field{\*\fldinst{HYPERLINK "http://www.gnu.org/licenses/gpl-2.0.html"}}{\fldrslt GNU General Public License}}.\ \ For more information visit {\field{\*\fldinst{HYPERLINK "http://nzbget.net"}}{\fldrslt NZBGet home page}}.}nzbget-16.4/osx/Resources/Credits.rtf0000644000175000017500000000124312630544544017455 0ustar andreasandreas{\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 usenet downloader\ \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 \cf2 \ul \ulc2 http://{\field{\*\fldinst{HYPERLINK "http://nzbget.net"}}{\fldrslt nzbget.net}}}nzbget-16.4/osx/PreferencesDialog.xib0000644000175000017500000006364012630544544017467 0ustar andreasandreas 1070 11G63 3084 1138.51 569.00 com.apple.InterfaceBuilder.CocoaPlugin 3084 YES NSButton NSButtonCell NSCustomObject NSImageCell NSImageView NSTextField NSTextFieldCell NSUserDefaultsController NSView NSWindowTemplate YES com.apple.InterfaceBuilder.CocoaPlugin PluginDependencyRecalculationVersion YES PreferencesDialog FirstResponder NSApplication 3 2 {{196, 436}, {357, 155}} 544735232 NZBGet Preferences NSWindow 256 YES 268 {{67, 119}, {213, 18}} YES 67239424 0 Start at login LucidaGrande 13 1044 1211912703 2 NSImage NSSwitch NSSwitch 200 25 268 {{67, 99}, {213, 18}} YES 67239424 0 Show in menubar 1211912703 2 200 25 268 {{67, 79}, {213, 18}} YES 67239424 0 Show Web-Interface on start 1211912703 2 200 25 268 YES YES Apple PDF pasteboard type Apple PICT pasteboard type Apple PNG pasteboard type NSFilenamesPboardType NeXT Encapsulated PostScript v1.2 pasteboard type NeXT TIFF v4.0 pasteboard type {{20, 30}, {32, 32}} 2 YES 134348288 33554432 NSImage NSInfo 0 3 0 NO YES 268 {{66, 20}, {244, 42}} _NS:1535 YES 67108864 4325376 Only OSX-specific options are located here. For all other options see Settings page in Web-Interface. LucidaGrande 11 3100 _NS:1535 6 System controlColor 3 MC42NjY2NjY2NjY3AA 6 System controlTextColor 3 MAA {357, 155} {{0, 0}, {1440, 878}} {10000000000000, 10000000000000} YES YES YES window 11 autostartButtonClicked: 24 autostartButton 25 showStatusIconButton 28 autoShowWebUI 32 delegate 12 value: values.ShowInMenubar value: values.ShowInMenubar value values.ShowInMenubar NSValidatesImmediately 2 23 value: values.AutoStartWebUI value: values.AutoStartWebUI value values.AutoStartWebUI NSValidatesImmediately 2 34 YES 0 YES -2 File's Owner -1 First Responder -3 Application 1 YES 2 YES 3 YES 4 8 YES 9 14 29 YES 30 35 YES 36 37 YES 38 YES YES -1.IBPluginDependency -2.IBPluginDependency -3.IBPluginDependency 1.IBPluginDependency 1.IBWindowTemplateEditedContentRect 1.NSWindowTemplate.visibleAtLaunch 14.IBPluginDependency 2.IBPluginDependency 2.IBUserGuides 29.IBPluginDependency 3.IBPluginDependency 30.IBPluginDependency 35.IBAttributePlaceholdersKey 35.IBPluginDependency 36.IBPluginDependency 37.IBPluginDependency 38.IBPluginDependency 4.IBPluginDependency 8.IBPluginDependency 9.IBPluginDependency YES com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin {{322, 782}, {298, 74}} com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin YES com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin AccessibilityDescription AccessibilityDescription information com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin YES YES 38 YES PreferencesDialog NSWindowController autostartButtonClicked: id autostartButtonClicked: autostartButtonClicked: id YES YES appearanceText autoShowWebUI autostartButton generalText showStatusIconButton YES NSTextField NSButton NSButton NSTextField NSButton YES YES appearanceText autoShowWebUI autostartButton generalText showStatusIconButton YES appearanceText NSTextField autoShowWebUI NSButton autostartButton NSButton generalText NSTextField showStatusIconButton NSButton IBProjectSource ./Classes/PreferencesDialog.h 0 IBCocoaFramework com.apple.InterfaceBuilder.CocoaPlugin.macosx com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 YES 3 YES YES NSInfo NSSwitch YES {32, 32} {15, 15} nzbget-16.4/osx/WelcomeDialog.xib0000644000175000017500000006403312630544544016616 0ustar andreasandreas 1060 11G63 3084 1138.51 569.00 com.apple.InterfaceBuilder.CocoaPlugin 3084 NSButton NSButtonCell NSCustomObject NSImageCell NSImageView NSScrollView NSScroller NSTextView NSUserDefaultsController NSView NSWindowTemplate com.apple.InterfaceBuilder.CocoaPlugin PluginDependencyRecalculationVersion WelcomeDialog FirstResponder NSApplication YES 1 2 {{196, 240}, {497, 136}} 611844096 NZBGet NSWindow 256 268 Apple PDF pasteboard type Apple PICT pasteboard type Apple PNG pasteboard type NSFilenamesPboardType NeXT Encapsulated PostScript v1.2 pasteboard type NeXT TIFF v4.0 pasteboard type {{20, 52}, {64, 64}} 2 YES 134348288 33554432 NSImage NSInfo 0 3 0 NO YES -2147483380 Apple PDF pasteboard type Apple PICT pasteboard type Apple PNG pasteboard type NSFilenamesPboardType NeXT Encapsulated PostScript v1.2 pasteboard type NeXT TIFF v4.0 pasteboard type {{60, 60}, {16, 16}} 2 YES 134348288 33554432 0 3 0 NO YES 292 {{17, 18}, {153, 18}} 2 YES 67239424 131072 Don't show again LucidaGrande 11 16 1211912703 2 NSImage NSSwitch NSSwitch 200 25 289 {{387, 12}, {97, 32}} 2 YES 67239424 134217728 Continue LucidaGrande 13 1044 -2038284033 129 DQ 200 25 274 2304 2322 {375, 36} 134 375 1 67119717 0 3 MQA 6 System selectedTextBackgroundColor 3 MC42NjY2NjY2NjY3AA 6 System selectedTextColor 3 MAA 1 MCAwIDEAA {8, -8} 13 1 6 {463, 10000000} {223, 36} {375, 64} {4, 5} 79691776 file://localhost/Applications/Xcode.app/Contents/SharedFrameworks/DVTKit.framework/Resources/DVTIbeamCursor.tiff 3 MCAwAA 2 -2147483392 {{-100, -100}, {15, 62}} _doScroller: 1 0.85256409645080566 -2147483392 {{-100, -100}, {87, 18}} 1 _doScroller: 1 0.94565218687057495 {{102, 52}, {375, 64}} 133120 {497, 136} {{0, 0}, {1440, 878}} {10000000000000, 10000000000000} WelcomeDIalog YES okButtonPressed: 428 _badgeView 441 _imageView 442 _okButton 443 window 445 _dontShowAgainButton 449 _messageText 454 _messageTextScrollView 455 value: values.DoNotShowWelcomeDialog value: values.DoNotShowWelcomeDialog value values.DoNotShowWelcomeDialog 2 424 0 -2 File's Owner -1 First Responder -3 Application 356 383 384 387 388 397 398 417 418 426 427 450 451 452 453 com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin AccessibilityDescription AccessibilityDescription information com.apple.InterfaceBuilder.CocoaPlugin AccessibilityDescription AccessibilityDescription information com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin 455 0 IBCocoaFramework com.apple.InterfaceBuilder.CocoaPlugin.macosx com.apple.InterfaceBuilder.CocoaPlugin.macosx YES 3 {32, 32} {15, 15} nzbget-16.4/osx/App_Prefix.pch0000644000175000017500000000245212630544544016125 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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 #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) nzbget-16.4/osx/DaemonController.h0000644000175000017500000000402312630544544017010 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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 @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 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 nzbget-16.4/osx/WelcomeDialog.m0000644000175000017500000000510412630544544016262 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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 "WelcomeDialog.h" @implementation WelcomeDialog - (id)init { self = [super initWithWindowNibName:@"WelcomeDialog"]; return self; } - (void)setMainDelegate:(id)mainDelegate{ _mainDelegate = mainDelegate; } - (void)showDialog { BOOL doNotShowWelcomeDialog = [[NSUserDefaults standardUserDefaults] boolForKey:@"DoNotShowWelcomeDialog"]; if (doNotShowWelcomeDialog) { [_mainDelegate performSelector:@selector(welcomeContinue)]; return; } DLog(@"creating window"); NSWindow *window = [self window]; DLog(@"creating window - END"); // set file icon NSImage *image = [NSImage imageNamed:@"mainicon.icns"]; [_imageView setImage:image]; // load warning icon [_badgeView setImage:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kAlertNoteIcon)]]; [[_messageText textStorage] readFromURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"Welcome" ofType:@"rtf"]] options:nil documentAttributes:nil]; // adjust height of text control and window [[_messageText layoutManager] ensureLayoutForTextContainer:[_messageText textContainer]]; NSSize scrollSize = [_messageTextScrollView frame].size; float deltaHeight = [_messageText frame].size.height - scrollSize.height + 6; if (deltaHeight < 0) { deltaHeight = 0; } NSSize winSize = [[window contentView] frame ].size; winSize.height += deltaHeight; [window setContentSize:winSize]; // set active control [window makeFirstResponder:_okButton]; // show modal dialog [NSApp activateIgnoringOtherApps:TRUE]; [window center]; [self showWindow:nil]; } - (IBAction)okButtonPressed:(id)sender { [self close]; [_mainDelegate performSelector:@selector(welcomeContinue)]; } @end nzbget-16.4/osx/MainApp.xib0000644000175000017500000012022312630544544015422 0ustar andreasandreas 1070 11G63 3084 1138.51 569.00 com.apple.InterfaceBuilder.CocoaPlugin 3084 YES NSCustomObject NSMenu NSMenuItem NSUserDefaultsController YES com.apple.InterfaceBuilder.CocoaPlugin PluginDependencyRecalculationVersion YES NSApplication FirstResponder NSApplication NSFontManager MainApp YES YES Starting 2147483647 NSImage NSMenuCheckmark NSImage NSMenuMixedState YES YES Left 2147483647 YES YES 2147483647 Show Web-Interface 2147483647 Show in Finder 2147483647 submenuAction: Show in Finder YES Default Destination 2147483647 YES YES 2147483647 Intermediate Files 2147483647 Incoming NZBs 2147483647 Scripts 2147483647 YES YES 2147483647 Config-File 2147483647 Log-File 2147483647 Info-Links 2147483647 submenuAction: Info-Links YES Home Page 2147483647 Downloads 2147483647 Forum 2147483647 Troubleshooting 2147483647 submenuAction: Troubleshooting YES Restart 2147483647 Restart in Recovery Mode 2147483647 Open Config in TextEdit 2147483647 YES YES 2147483647 Reset to Factory Defaults 2147483647 YES YES 2147483647 About NZBGet 2147483647 Preferences... 2147483647 YES YES 2147483647 Quit NZBGet 2147483647 YES YES delegate 824 statusMenu 831 quitClicked: 832 preferencesClicked: 850 aboutClicked: 856 webuiClicked: 865 webuiItem 866 homePageItem 874 downloadsItem 875 forumItem 876 infoLinkClicked: 877 infoLinkClicked: 878 infoLinkClicked: 879 infoItem 893 restartRecoveryItem 896 destDirItem 911 interDirItem 912 nzbDirItem 913 scriptDirItem 915 showInFinderClicked: 916 showInFinderClicked: 917 showInFinderClicked: 918 showInFinderClicked: 919 showInFinderClicked: 922 info2Item 925 info1Item 926 destDirSeparator 927 showInFinderClicked: 929 configFileItem 930 logFileItem 931 restartClicked: 940 openConfigInTextEditClicked: 941 restartClicked: 943 restartClicked: 944 factoryResetItem 945 YES 0 YES -2 File's Owner -1 First Responder -3 Application 373 823 827 YES 828 840 849 851 860 861 867 YES 868 YES 869 870 872 880 YES 881 YES 882 885 887 889 890 897 898 YES 899 YES 900 902 906 908 910 920 921 924 928 934 935 YES YES -1.IBPluginDependency -2.IBPluginDependency -3.IBPluginDependency 373.IBPluginDependency 823.IBPluginDependency 827.IBPluginDependency 828.IBPluginDependency 840.IBPluginDependency 849.IBPluginDependency 851.IBPluginDependency 860.IBPluginDependency 861.IBPluginDependency 867.IBPluginDependency 868.IBPluginDependency 869.IBPluginDependency 870.IBPluginDependency 872.IBPluginDependency 880.IBPluginDependency 881.IBPluginDependency 882.IBPluginDependency 885.IBPluginDependency 887.IBPluginDependency 889.IBPluginDependency 890.IBPluginDependency 897.IBPluginDependency 898.IBPluginDependency 899.IBPluginDependency 900.IBPluginDependency 902.IBPluginDependency 906.IBPluginDependency 908.IBPluginDependency 910.IBPluginDependency 920.IBPluginDependency 921.IBPluginDependency 924.IBPluginDependency 928.IBPluginDependency 934.IBPluginDependency 935.IBPluginDependency YES com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin com.apple.InterfaceBuilder.CocoaPlugin YES YES 945 0 IBCocoaFramework com.apple.InterfaceBuilder.CocoaPlugin.macosx com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 YES 3 YES YES NSMenuCheckmark NSMenuMixedState YES {11, 11} {10, 3} nzbget-16.4/osx/WelcomeDialog.h0000644000175000017500000000245012630544544016256 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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 @interface WelcomeDialog : NSWindowController { IBOutlet NSButton *_okButton; IBOutlet NSButton *_dontShowAgainButton; IBOutlet NSImageView *_badgeView; IBOutlet NSImageView * _imageView; IBOutlet NSScrollView * _messageTextScrollView; IBOutlet NSTextView * _messageText; id _mainDelegate; } - (void)setMainDelegate:(id)mainDelegate; - (IBAction)okButtonPressed:(id)sender; - (void)showDialog; @end nzbget-16.4/osx/NZBGet-Info.plist0000644000175000017500000000207612630544544016435 0ustar andreasandreas CFBundleDevelopmentRegion English CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIconFile mainicon.icns CFBundleIdentifier net.sourceforge.nzbget CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 15.0-testing LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion 10.7 LSUIElement NSHumanReadableCopyright Copyright © 2007-2015 Andrey Prygunkov NSMainNibFile MainApp NSPrincipalClass NSApplication nzbget-16.4/osx/DaemonController.m0000644000175000017500000002446612630544544017032 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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 #import #import "DaemonController.h" #import "RPC.h" NSString* MAIN_DIR = @"${AppSupDir}"; NSString* LOCK_FILE = @"${AppSupDir}/nzbget.lock"; NSString* CONFIG_FILE = @"${AppSupDir}/nzbget.conf"; NSString* SCRIPTS_DIR = @"${AppSupDir}/scripts"; 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* scriptsDir = [self resolveAppSupDir:SCRIPTS_DIR]; if (![[NSFileManager defaultManager] fileExistsAtPath:scriptsDir]) { NSString* scriptsTemplate = [NSString stringWithFormat:@"%@/usr/local/share/nzbget/scripts", bundlePath]; [[NSFileManager defaultManager] copyItemAtPath:scriptsTemplate toPath:scriptsDir 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:SCRIPTS_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 nzbget-16.4/osx/MainApp.h0000644000175000017500000000466212630544544015077 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2014 Andrey Prygunkov * * 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 #import #import "DaemonController.h" @interface MainApp : NSObject { 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; BOOL preventingSleep; IOPMAssertionID sleepID; 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; - (void)updateSleepState:(BOOL)preventSleep; @end nzbget-16.4/osx/PreferencesDialog.m0000644000175000017500000001041112630544544017125 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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 nzbget-16.4/osx/WebClient.m0000644000175000017500000000532412630544544015427 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2013 Andrey Prygunkov * * 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 #import "WebClient.h" @implementation WebClient - (id)initWithURLString:(NSString*)urlStr receiver:(id)receiver success:(SEL)successCallback failure:(SEL)failureCallback { self = [super init]; _receiver = receiver; _successCallback = successCallback; _failureCallback = failureCallback; NSURL *url = [NSURL URLWithString:urlStr]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]; [connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; return self; } - (void) start { [connection start]; } - (void)connection:(NSURLConnection *)aConnection didReceiveResponse:(NSHTTPURLResponse *)aResponse { responseHeaderFields = [aResponse allHeaderFields]; if ([aResponse statusCode] != 200) { failureCode = [aResponse statusCode]; [connection cancel]; [self failure]; return; } NSInteger contentLength = [[responseHeaderFields objectForKey:@"Content-Length"] integerValue]; if (contentLength > 0) { data = [[NSMutableData alloc] initWithCapacity:contentLength]; } else { data = [[NSMutableData alloc] init]; } } - (void)connection:(NSURLConnection *)aConnection didReceiveData:(NSData *)newData { [data appendData:newData]; } - (void)connectionDidFinishLoading:(NSURLConnection *)aConnection { [connection cancel]; [self success]; } - (void)connection:(NSURLConnection *)aConnection didFailWithError:(NSError *)error { if ([[error domain] isEqual:NSURLErrorDomain]) { failureCode = [error code]; } [connection cancel]; [self failure]; } - (void)success { SuppressPerformSelectorLeakWarning([_receiver performSelector:_successCallback withObject:data];); } - (void)failure { SuppressPerformSelectorLeakWarning([_receiver performSelector:_failureCallback];); } @end nzbget-16.4/windows/0000755000175000017500000000000012630544544014252 5ustar andreasandreasnzbget-16.4/windows/setup/0000755000175000017500000000000012630544544015412 5ustar andreasandreasnzbget-16.4/windows/setup/uninstall.bmp0000644000175000017500000014523612630544544020136 0ustar andreasandreasBMv(:(  PP   #/3<@GMN S##Z$$[''b((g++l,,q--r//x00{11~334455556677778899::;;<<<<==@@AAAABBCCDDEEEEFFGGIIKKNNPPQQTTWWZZ]]^^aabbddggjjkkllooqqttuuvvxxyy{{||~~999999999999999999999999999999999999999999999999999999999999999999999999(99999999999999999999999999999999999999999999999999999999999999999999999(9999999999999999999999999999999999999999999999999999999999999999999999-9999999999999999999999999999999999999999999999999999999999999999999994999999999999999999999999999999999999999999999999999999999999999999994 99999999999999999999999999999999999999999999999999999999999999999998 9999999999999999999999999999999999999999999999999999999999999999999 999999999999999999999999999999999999999999999999999999999999999999  99999999999999999999999999999999999999999999999999999999999999999(  99999999999999999999999999999999999999999999999999999999999999994  9999999999999999999999999999999999999999999999999999999999999998  999999999999999999999999999999999999999999999999999999999999999!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!99999999999999999999999999999999999999999999999999999999999999(!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!99999999999999999999999999999999999999999999999999999999999994!"""""""""""""""""""""""""""""""""""""! """""""""""""""""""""""""""""""""""""""""9999999999999999999999999999999999999999999999999999999999999"""""""""""""""""""""""""""""""""""""" """"""""""""""""""""""""""""""""""""""""999999999999999999999999999999999999999999999999999999999999$"""""""""""""""""""""""""""""""""""""!!"""""""""""""""""""""""""""""""""""""""999999999999999999999999999999999999999999999999999999999994"""""""""""""""""""""""""""""""""""""" """"""""""""""""""""""""""""""""""""""99999999999999999999999999999999999999999999999999999999999!""""""""""""""""""""""""""""""""""""" """""""""""""""""""""""""""""""""""""9999999999999999999999999999999999999999999999999999999999$"""""""""""""""""""""""""""""""""""""!""""""""""""""""""""""""""""""""""""9999999999999999999999999999999999999999999999999999999998"""""""""""""""""""""""""""""""""""""!"""""""""""""""""""""""""""""""""""999999999999999999999999999999999999999999999999999999999 !####################################!##################################999999999999999999999999999999999999999999999999999999994####################################!#################################99999999999999999999999999999999999999999999999999999999####################################3-"################################9999999999999999999999999999999999999999999999999999999-"###################################333-"###############################9999999999999999999999999999999999999999999999999999999###################################36363-"##############################999999999999999999999999999999999999999999999999999999(###################################3333333-"############################"999999999999999999999999999999999999999999999999999999&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&636363636-%&&&&&&&&&&&&&&&&&&&&&&&#&&"99999999999999999999999999999999999999999999999999999(&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&63636363636-"&&&&&&&&&&&&&&&&&&&&&&&&"99999999999999999999999999999999999999999999999999999"&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&3636363636363-"&&&&&&&&&&&&&&&&&&&&&&%9999999999999999999999999999999999999999999999999999(&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&666666666666666-%&&&&&&&&&&&&&&&&&&&&%9999999999999999999999999999999999999999999999999999%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&66666666666666666-%&&&&&&&&&&&&&&&&&&%9999999999999999999999999999999999999999999999999994&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&6666666666666666666-%&&&&&&&&&&&&&&&&%999999999999999999999999999999999999999999999999999"&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&666666666666666666666-%&&&&&&&&&&&&&&%999999999999999999999999999999999999999999999999994&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&66666666666666666666666-%&&&&&&&&&&&&%::::::::::::::::::::::::::::::::::::::::::::::::99$"&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&6666666666666666666666668-%&&&&&&&&&&%:::::::::::::::::::::::::::::::::::::::::::::::999&&&&&&&&&&&&&&&&&&&&&&&&&&&&&'666666666666666666666666666-&&&&&&&&&&:::::::::::::::::::::::::::::::::::::::::::::::::-'&&&&&&&&&&&&&&&&&&&&&&&&&&&&86868686868686868686868686868-&&&&&&&&:::::::::::::::::::::::::::::::::::::::::::::::::&&&&&&&&&&&&&&&&&&&&&&&&&&&&'8888888888888888888888888888888-&&'&&&::::::::::::::::::::::::::::::::::::::::::::::::8&'&'&'&'&'&'&'&'&'&'&'&''&''888888888888888888888888888888888-&&'&::::::::::::::::::::::::::::::::::::::::::::::::("'&'&'&'&'&'&'&'&'&'&'&'&'''888888888888888888888888888888888883&&3::::::::::::::::::::::::::::::::::::::::::::::::'''''''''''''''''''''''''''8888888888888888888888888888888888888-38:::::::::::::::::::::::::::::::::::::::::::::::8''''''''''''''''''''''''''8888888888888888888888888888888888888883388:::::::::::::::::::::::::::::::::::::::::::::::(&''''''''''''''''''''''''''3888888888888888888888888888888888888888833888:::::::::::::::::::::::::::::::::::::::::::::::'''''''''''''''''''''''''''&38898989898989898989898989898989898989898338989:::::::::::::::::::::::::::::::::::::::::::::::(''''''''''''''''''''''''''''389999999999999999999999999999999999999993389999::::::::::::::::::::::::::::::::::::::::::::::-&''''''''''''''''''''''''''''''3899999999999999999999999999999999999999933999999:::::::::::::::::::::::::::::::::::::::::::::9$''''''''''''''''''''''''''''''''39999999999999999999999999999999999999999339999999:::::::::::::::::::::::::::::::::::::::::::::;(((((((((((((((((((((((((((((((('399999999999999999999999999999999999999993399999999::::::::::::::::::::::::::::::::::::::::::::98#((((((((((((((((((((((((((((((((('3999999999999999999999999999999999999999933999999999:::::::::::::::::::::::::::::::::::::::::::::-((((((((((((((((((((((((((((((((((('3<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<33<<<<<<<<<<::::::::::::::::::::::::::::::::::::::::::::9$(((((((((((((((((((((((((((((((((((('3<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<33<<<<<<<<<<<;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;((((((((((((((((((((((((((((((((((((('3<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<33<<<<<<<<<<<<;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;#(((((((((((((((((((((((((((((((((((((('3<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<33<<<<<<<<<<<<<;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4(((((((((((((((((((((((((((((((((((((((((3<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<33<<<<<<<<<<<<<<;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;$((((((((((((((((((((((((((((((((((((((((((3<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9$ (((((((((((((((((((((((((((((((((((((((((((3======================================================;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+(((((((((((((((((((((((((((((((((((((((((((3=====================================================;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;#(+(+(++(++(++(++(++(++(++(++(++(++(++(++(++((6====================================================;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8(((((((((((((((((((((((((((((((((((((((((((((((6===================================================;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4++(++(++(++(++(++(++(++(++(++(++(++(++(++(++(++(6==================================================;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;(++++++++++++++++++++++++++++++++++++++++++++++++(6=?=??????????????????????????????????????????????;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;$ +++++++++++++++++++++++++++++++++++++++++++++++++*6????????????????????????????????????????????????;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;$++++++++++++++++++++++++++++++++++++++++++++++++++*6???????????????????????????????????????????????;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;#+++++++++++++++++++++++++++++++++++++++++++++++++++*6??????????????????????????????????????????????;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;#.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+*6?????????????????????????????????????????????;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;'+++++++++++++++++++++++++++++++++++++++++++++++++++++*6@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<.......................................................*6@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<........................................................*6@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<.........................................................*6A@A@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@A<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<2..........................................................*6A@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<4...........................................................*8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<-/./././././././././././././././././././././././././././././.-8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<4//./././././././././././././././././././././././././././././.-6CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<-./////////////////////////////////////////////////////////////-6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<4///////////////////////////////////////////////////////////////)CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<;-&21///////////////////////////////////////////////////////////1-8CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<4&<2/1//1111111111111111111111111111111111111111111111111111/11-8CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<-&<<7//11////////////////////////////////////////////////////1-8CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:4&<<<<2//1111111111111111111111111111111111111111111111111111-8CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<2"<<<<<7111111111111111111111111111111111111111111111111111108CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<2111111111111111111111111111111111111111111111111108CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<72111111111111111111111111111111111111111111111108DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<72111111111111111111111111111111111111111111108DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<9<<<<<<<<<<<72112121212121212121212121212121212121211209DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<5<<<<<<<<<<<<<72121212121212121212121212121212121212209DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<5<<<<<<<<<<<<<<<72122222222222222222222222222222222209DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<$-<<<<<<<<<<<<<<<<<72222222222222222222222222222222209DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<$-<<<<<<<<<<<<<<<<<<<72222222222222222222222222222209DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<-&<<<<<<<<<<<<<<<<<<<<<<7222222222222222222222222209DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<2"<<<<<<<<<<<<<<<<<<<<<<<<7222222222222222222222209DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<9?& "?EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE@" "@EEEE===============================================- >& "@================================================:-????????????????????????????3 -FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF@" &>??>& "================================================= "?AAAAAAAAAAAAAAAAAAAAAAAAAAAA3 -FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF@" &>AAAA>& =================================================-3A?AAAAAAAAAAAAAAAAAAAAAAAAAAA3 -FFFFFFFFFFFFFFFFFFFFFFFFFFFFF@" &>AAAAAA>& =================================================="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3 -GFFFFFFFFFFFFFFFFFFFFFFFFFF@" &>AAAAAAAA>& ==================================================$ 8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3 -FFFFFFFFFFFFFFFFFFFFFFFFF@" &>AAAAAAAAAA>& ==================================================8&AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3 -FFFFFFFFFFFFFFFFFFFFFFF@" &>AAAAAAAAAAAA>& ===================================================  9AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3 -GFFFFFFFFFFFFFFFFFFFF@" &>AAAAAAAAAAAAAA>& ===================================================2-ABBABABABABABABABABABABABABABABA6 -GFFFFFFFFFFFFFFFFFF@" &?BBBBBBBBBBBBBBBB?& ====================================================>BABABABABABABABABABABABABABABABA6 -FFFFFFFFFFFFFFFFF@" &>AAAAAAAAAAAAAAAAAA?& ====================================================-(ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBA6 -GGGGGGGGGGGGGGG@" &?BBBBBBBBBBBBBBBBBBBB?& ===================================================== 9BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB3 -GGGGGGGGGGGGG@" &?BBBBBBBBBBBBBBBBBBBBBB?& =====================================================-&BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB6 -GGGGGGGGGGG@" &?BBBBBBBBBBBBBBBBBBBBBBBB?& ====================================================== 6BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB6 -GGGGGGGGG@" &?BBBBBBBBBBBBBBBBBBBBBBBBBB?&======================================================-"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB6 -GGGGGGG@" &?BBBBBBBBBBBBBBBBBBBBBBBBBBBB?=======================================================3BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB6 -GGGGG@" &?BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=======================================================2">???????????????????????????????????????????5"3DEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE???????????????????????????????????><:88888::<>>????????????????????????????????????????<$ 3ADDDDDDEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE??????????????????????????????????<:85222225588::<>>??????????????????????????????????????5-????????????????????????????????????<("39==@ACDDEEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF???????????????????????????????><852($ $$((-22588::;<>????????????????????????????????<:2 "689=@CDDDEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF??????????????????????????????>:85-$  $$((-22558::<<>??????????????????????????<::822 )68<@CDDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF?????????????????????????????>;82-$  $$(--22588::????????????????????>::82-($    .8=@DDEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF????????????????????????????<:52($  $((--22588::???????????????<:852($  "3?CDDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF??????????????????????????><:52(   $&(--25588::?????????><:52-$   "3@EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF?????????????????????????><85-(    $((--25588::????><:52($  "-6=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF????????????????????????>:82-$ =L1  $((-225588::<<<<85-$    &-3<=FFFFFFFFFFFFFFFFFFFFFFFFFF=:82( '@=@DOOOOOOOOOOOOD'  $$$($   ?????????????????????<:2(  5@@@@DOOOOOOOOOOOOOOOO=     ?????????????????????:5-$ 8@@@@@DOOOOOOOOOOOOOOOOOOOO=    ????????????????????>:2(@@@@@@@DOOOOOLJLNOOOOOOOOOOOOOOO8    ????????????????????>82@@@@@@@@DOOOOOLIIIGIJNNOOOOOOOOOOOOOO8    ????????????????????=8,@@@@@@@@@DOOOOOLIIIGGGGGGILNOOOOOOOOOOOOOO1   $$ ????????????????????=:@@@@@@@@@@DOOOOOLIIGGGGGGGFEEEGJNOOOOOOOOOOOOOL1   (28=??:55((((((((55:???????????????????????= @@@@@@@@@@@@DOOOOOLIIGGGGGGFEEEEEDDDGILOOOOOOOOOOOOOL'        $-8:?????????????????????????????????????????????????=C@C@C@C@C@C@CDOOOOOLIGGGGGGGFEEEEDDDDDDCCDGJNOOOOOOOOOOO         (5:>????????????????????????????????????????????????=@@@@@@@@@@@@@DOOOOOLIGGGGGGFEEEEEDDDDDCCBBAAACEINOOOOOO           $28=????????????????????????????????????????????????=@CC@CC@CC@CC@FOOOOOLGGGGGGGFEEEEDDDDDDCCBBAA????=?DGLOD             -5:????????????????????????????????????????????????=CC@CC@CC@CC@CDOOOOOLGGGGGGFEEEEEDDDDDCCBBAAA????===<;;   ,1  ,,    (2:>???????????????????????????????????????????????=C@CC@CC@CC@CCDOOOOOLGGGGGGFEEEEDDDDDDCCBBAA????===<;;8    1111 ,111     &28=???????????????????????????@@@@@@@@@@@@@@@@@@@@=CCCCCCCCCCCCCFOOOOOLGGGGGFEEEEEDDDDDCCBBAAA????===<;; 155555,55555  $-5=?@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=CCCCCCCCCCCCCFOOOOOLGGGGGFEEEEDDDDDCCCBBAA????===<;;: 55555555 15555555  -5:?@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=CCCCCCCCCCCCCFOOOOOLGGGGFEEEEEDDDDDCCBBAAA????===<;;(18888888881888888888   (5:?@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=CCCCCCCCCCCCCFOOOOOLGGGGFEEEEDDDDDCCCBBAA????===<;;;5:8:::::::5::::::88: (29?@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=CCCCCCCCCCCCCFOOOOOJGGGFEEEEEDDDDDCCBBAAA????==<<;;<5::::::::=5:::::::==" (2:?@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=CCCCCCCCCCCCCFOOOOOLGGGFEEEEDDDDDCCCBBAA????===<;;::"""8=========8========="""(28?@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=CCCCCCCCCCCCCFOOOOOJGGFEEEEEDDDDDCCBBAAA???===<<;;:2"""":@@@@@@@@@@@@@@@@@@""""  (28?@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=CDCDCDCDCDCDCFOOOOOLGGFEEEEDDDDDCCCBBAA????===<;;;:$""""":C@C@C@C@C@C@C@C@""""&28?@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=CCCCCCCCCCCCCFOOOOOJGFEEEEEDDDDDCCBBAAA???===<<;;::""""""""""""=DCDCDCDCDCDCDC""""""""""""" (28?@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=DDCDCDCDDD@@DFOOOOOJGFEEEEDDDDDCCCBBAA????===<;;::: """""""""""""""@DDDDDDDDDDDD""""""""""""""(29?@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=DCDCDCDCD85CFOOOOOJFEEEEEDDDDDCCBBAAA???===<<;;:::""""""""""""""""@FFFFFFFFFF"""""""""""""""(2:?@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=DDDDDDDD' 5DFOOOOONJGEEEDDDDDCCCBBAA????===<;;:::9""""""""""""""""FFFFFFFFFFF"""""""""""""""(5:?@@@@@@@@@@@@@@@@@@@@@@@@@@AAAAAAAAAAAAAAAAAAAA=DDDDDD@ 8DFOOOOOOOOONJGEDDCCCBBAAA???===<<;;:::9=7"""""""""""""IIIIIIIIIIIII"""""""""""""" -5:@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=DDDDD5 5DDFOOOOOOOOOOOOONJGDBBBAA????===<;;:::99===7""2"""""""IIIIIIIIIIIIIII"""2"2"""""2"$-8=@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=DDDD' @DDDFOOOOOOOOOOOOOOOOONJGDA???===<<;;:::99$ =====7""""2""IIIIIIIIIIIIIIIII'""""""2"7"""&28=@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=DD@ 'DC'5DFOOOOOOOOOOOOOOOOOOOONNIGD===<;;:::999-========72""IIIIIIIIIILLLLLLLLL1"77"2""""7"2 (5:?@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=D5 8D@ 5DFOOOOOOOOOOOOOOOOOONNNNMNMMIED=;:::99997@@@@@@@@@=LLLLLLLLLL5FLLLLLLLLL1 )@===72""""  -5=@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=D CD1 5DIOOOOOOOOOOOOOOOOONNNNNNMMMMMMLIEA<9999)@@@@@@@@= LLLLLLLLLL1 FLLLLLLLLL1 )@@@@@=272&28=@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=D ,DD CDFOOOOOOOOOOOOOOONNNNNMMMMMMMMLLLLLLIEA=@@@@@@@= MLMLMLMLMM1 ILMLMLMLML1 )@@@@@@7 (59?@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=D':D= ,DDDIOOOOOONJLNOOOONNNNMNMMMMLMLLLLLLILLIII8@@D@@@@ MMMMMMMMMM1 IMMMMMMMMM <@@@@@D$-8=?AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=DFD1 :DFFDIOOOOOONGGGGIJLMNMNMMMMMMMLLLLLILLIIIIIF)@DDDDD7 MMMMMMMM1 " IMMMMMMM1 )DDDDDD@(59?@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=FC CFFDDFFOOOOOONGGGGFEEEGIJLMMLMLLLLLILLLIIIIIII DDDDDDD7 NMNMMM1 )D@ INMNMM1 )DDDDDDD)&28=?@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=F 1FDFDFFFIOOOOOONGGGFEEEDDDDDDEIILLLLLLLIIIIIIFFFC7DDDDDDD7 NMNN5 )DDDD IMNM5 )DDDDDDDD -5:?@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=F =FFFFFDDDIOOOOOONGGFEEEDDDDDCBBAAAAEGILIIIIIFIIFFFDDDDDDDD7 NN1 .DDDDD@ LN5 .DDDDDDDD)(28=?@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=D'FFFFFD?:DFIOOOOOONGFEEEDDDDDCBBAA???===ACGIIIIFFFFF@.FFDFDFDF; 5 )FDFDFDF@ , .DFDFDFDFD &-8 * * 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$ * */ ; This is setup script for NZBGet for Windows. To compile the script you need ; NSIS (http://nsis.sourceforge.net). Moreover a special build of NSIS must be ; installed over standard NSIS installation. This special build provides ; extra logging used in this script: http://nsis.sourceforge.net/Special%5FBuilds. ; Also requires NSIS Simple Service Plugin: ; http://nsis.sourceforge.net/NSIS_Simple_Service_Plugin ;-------------------------------- ;Includes !include "MUI2.nsh" !include "FileFunc.nsh" !include "LogicLib.nsh" !include "WinVer.nsh" ;-------------------------------- ;General Name "NZBGet" OutFile "..\nzbget-setup.exe" ;Default installation folder InstallDir "$PROGRAMFILES\NZBGet" ;Get installation folder from registry if available InstallDirRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\NZBGet" "InstallLocation" !ifndef DEBUG_UI ;Request application privileges for Windows Vista RequestExecutionLevel admin !endif ;-------------------------------- ;Interface Settings ; !define MUI_ABORTWARNING !define MUI_ICON "..\resources\mainicon.ico" !define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\orange-uninstall.ico" !define MUI_WELCOMEFINISHPAGE_BITMAP "install.bmp" !define MUI_UNWELCOMEFINISHPAGE_BITMAP "uninstall.bmp" !define MUI_FINISHPAGE_RUN !define MUI_FINISHPAGE_RUN_FUNCTION RunAction !define MUI_FINISHPAGE_SHOWREADME "" !define MUI_FINISHPAGE_SHOWREADME_NOTCHECKED !define MUI_FINISHPAGE_SHOWREADME_TEXT "Create Desktop Shortcut" !define MUI_FINISHPAGE_SHOWREADME_FUNCTION FinishPageAction ;-------------------------------- ;Pages !insertmacro MUI_PAGE_WELCOME !insertmacro MUI_PAGE_LICENSE "..\NZBGet\COPYING" !insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_PAGE_FINISH !insertmacro MUI_UNPAGE_WELCOME !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES !insertmacro MUI_UNPAGE_FINISH ;-------------------------------- ;Languages !insertmacro MUI_LANGUAGE "English" ;-------------------------------- ;Installer Sections Section "Main" Delete "$INSTDIR\install.log" ; Command "LogSet" requires a special build of NSIS supporting extended logging: ; http://nsis.sourceforge.net/Special%5FBuilds LogSet on SetOutPath "$INSTDIR" ; Stop NZBGet (if running) ReadRegStr $R1 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\NZBGet" "InstallLocation" ${If} $R1 != "" ${AndIf} ${FileExists} "$R1\nzbget.exe" Delete "$R1\nzbget.exe" ExecWait '"$R1\nzbget.exe" -Q' $R2 DetailPrint "Stopping NZBGet..." try_delete: ; Wait up to 10 seconds until stopped StrCpy $R2 20 ${While} ${FileExists} "$R1\nzbget.exe" ${If} $R2 = 0 ${Break} ${EndIf} Sleep 500 IntOp $R2 $R2 - 1 Delete "$R1\nzbget.exe" ${EndWhile} ${If} ${FileExists} "$R1\nzbget.exe" MessageBox MB_RETRYCANCEL "NZBGet seems to be running right now. Please stop NZBGet and try again." \ IDRETRY try_delete IDCANCEL cancel cancel: abort ${EndIf} ${EndIf} !ifndef DEBUG_UI File /r "..\NZBGet\*" !endif ; Create shortcuts CreateDirectory "$SMPROGRAMS\NZBGet" CreateShortCut "$SMPROGRAMS\NZBGet\NZBGet.lnk" "$INSTDIR\nzbget.exe" CreateShortCut "$SMPROGRAMS\NZBGet\Uninstall.lnk" "$INSTDIR\Uninstall.exe" ; Add control panel entry for Uninstall WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\NZBGet" "DisplayName" "NZBGet" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\NZBGet" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\NZBGet" "Publisher" "Andrey Prygunkov" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\NZBGet" "InstallLocation" "$INSTDIR" ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 IntFmt $0 "0x%08X" $0 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\NZBGet" "EstimatedSize" "$0" ; Create uninstaller WriteUninstaller "$INSTDIR\Uninstall.exe" SectionEnd Function FinishPageAction ; Create desktop shortcut CreateShortcut "$DESKTOP\NZBGet.lnk" "$INSTDIR\nzbget.exe" ; Refresh desktop window System::Call 'Shell32::SHChangeNotify(i 0x8000000, i 0, i 0, i 0)' FunctionEnd Function RunAction ${If} ${AtLeastWinVista} ; Starting NZBGet with standard user privileges Exec "runas /trustlevel:0x20000 $\"$INSTDIR\nzbget.exe$\"" ${Else} ; Starting NZBGet with current privileges Exec "$INSTDIR\nzbget.exe" ${EndIf} FunctionEnd ;-------------------------------- ;Uninstaller Section Section "Uninstall" ; Stop service (if installed) SimpleSC::StopService "NZBGet" 1 30 Pop $0 ; returns an errorcode (<>0) otherwise success (0) ; Remove a service SimpleSC::RemoveService "NZBGet" Pop $0 ; returns an errorcode (<>0) otherwise success (0) try_delete: Delete "$INSTDIR\nzbget.exe" IfFileExists "$INSTDIR\nzbget.exe" 0 not_running MessageBox MB_RETRYCANCEL "File nzbget.exe could not be deleted. Please make sure the program isn't running." \ IDRETRY try_delete IDCANCEL cancel cancel: quit not_running: RMDir /r "$INSTDIR" RMDir /r "$SMPROGRAMS\NZBGet" Delete "$DESKTOP\NZBGet.lnk" DeleteRegKey HKCU "Software\NZBGet" DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\NZBGet" ; Refresh desktop window System::Call 'Shell32::SHChangeNotify(i 0x8000000, i 0, i 0, i 0)' SectionEnd nzbget-16.4/windows/setup/install.bmp0000644000175000017500000014525612630544544017575 0ustar andreasandreasBM(:(  TT   ( / 7 =DOTV[\^_aciknqvx ! # #!$"%#&#'$'%)&)'*'+(+(,(,),)-)-*.*.*.+/+/,0,0,0-1-1-2.2.2/3/3042637596;8<:>;?=A?C@DBFCGEIGKHLLOMQOSQT333333333333333333333333333333333333333333333333333333333333333333333333(333333333333333333333333333333333333333333333333333333333333##333333333333333333333333333333333333333333333333333333333333333333333333333333333$(333333333333333333333333333333333333333333333333333333333333$$3333333333333333333333333333333333333333333333333333333333333333333333333333333$.333333333333333333333333333333333333333333333333333333333333((33333333333333333333333333333333333333333333333333333333333333333333333333333$.33333333333333333333333333333333333333333333333333333333333311333333333333333333333333333333333333333333333333333333333333333333333333333(.333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333..33333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333331.3333333333333333333333333333333333333333333333333333333333333$$33333333333333333333333333333333333333333333333333333333333333333333333.3333333333333333333333333333333333333333333333333333333333331((333333333333333333333333333333333333333333333333333333333333333333333 (3333333333333333333333333333333333333333333333333333333333332.(3333333333333333333333333333333333333333333333333333333333333333333((333333333333333333333333333333333333333333333333333333333333311333333333333333333333333333333333333333333333333333333333333333331$3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333 33333333333333333333333333333333333333333333333333333333333333-333333333333333333333333333333333333333333333333333333333333333 33333333333333333333333333333333333333333333333333333333333333$-'-$3333333333333333333333333333333333333333333333333333333333333(14444444444444444444444444444444444444444444444444444444444444('---'3333333333333333333333333333333333333333333333333333333333333(4444444444444444444444444444444444444444444444444444444444444.$-----'333333333333333333333333333333333333333333333333333333333333 $4444444444444444444444444444444444444444444444444444444444444."-------"33333333333333333333333333333333333333333333333333333333333(44444444444444444444444444444444444444444444444444444444444444 --------- 3333333333333333333333333333333333333333333333333333333333314444444444444444444444444444444444444444444444444444444444444 -----------3333333333333333333333333333333333333333333333333333333333 (4444444444444444444444444444444444444444444444444444444444444$-------------333333333333333333333333333333333333333333333333333333333. 4444444444444444444444444444444444444444444444444444444444444(---------------3333333333333333333333333333333333333333333333333333333334444444444444444444444444444444444444444444444444444444444444.'---------------'33333333333333333333333333333333333333333333333333333333((4444444444444444444444444444444444444444444444444444444444444"-----------------"333333333333333333333333333333333333333333333333333333334444444444444444444444444444444444444444444444444444444444444 -/--/--/--/--/--/-- 3333333333333333333333333333333333333333333333333333333$.444444444444444444444444444444444444444444444444444444444444#/////////////////////3333333333333333333333333333333333333333333333333333333$444444444444444444444444444444444444444444444444444444444444$-/////////////////////-333333333333333333333333333333333333333333333333333333$444444444444444444444444444444444444444444444444444444444444('///////////////////////-333333333333333333333333333333333333333333333333333333(444444444444444444444444444444444444444444444444444444444441'/////////////////////////'33333333333333333333333333333333333333333333333333333$444444444444444444444444444444444444444444444444444444444443"///////////////////////////"33333333333333333333333333333333333333333333333333333.44444444444444444444444444444444444444444444444444444444443/////////////////////////////3333333333333333333333333333333333333333333333333333$44444444444444444444444444444444444444444444444444444444444$1111111111111111111111111111113333333333333333333333333333333333333333333333333333.4444444444444444444444444444444444444444444444444444444444(-111111111111111111111111111111333333333333333333333333333333333333333333333333333(4444444444444444444444444444444444444444444444444444444444.'1111111111111111111111111111111333333333333333333333333333333333333333333333333333.4444444444444444444444444444444444444444444444444444444442'1111111111111111111111111111111133333333333333333333333333333333333333333333333333.4444444444444444444444444444444444444444444444444444444444"11111111111111111111111111111111155555555555555555555555555555555555555555555555533(444444444444444444444444444444444444444444444444444444444333333333333333333333333333333333355555555555555555555555555555555555555555555555553555555555555555555555555555555555555555555555555555555555$333333333333333333333333333333333335555555555555555555555555555555555555555555555553$$55555555555555555555555555555555555555555555555555555555(/33333333333333333333333333333333333555555555555555555555555555555555555555555555555355555555555555555555555555555555555555555555555555555555.'3333333333333333333333333333333333335555555555555555555555555555555555555555555555552 55555555555555555555555555555555555555555555555555555552'4444444444444444444444444444444444444555555555555555555555555555555555555555555555555$(5555555555555555555555555555555555555555555555555555555"444444444444444444444444444444444444445555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555 444444444444444444444444444444444444444555555555555555555555555555555555555555555555552#555555555555555555555555555555555555555555555555555556$444444444444444444444444444444444444444455555555555555555555555555555555555555555555555$.55555555555555555555555555555555555555555555555555555(1444444444444444444444444444444444444444455555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555552-6666666666666666666666666666666666666666655555555555555555555555555555555555555555555555#65555555555555555555555555555555555555555555555555555$6666666666666666666666666666666666666666665555555555555555555555555555555555555555555555$.6666666666666666666666666666666666666666666666666666 666666666666666666666666666666666666666666655555555555555555555555555555555555555555555556666666666666666666666666666666666666666666666666666#666666666666666666666666666666666666666666665555555555555555555555555555555555555555555553 666666666666666666666666666666666666666666666666666&3666666666666666666666666666666666666666666665555555555555555555555555555555555555555555552(66666666666666666666666666666666666666666666666666(/868686868686868686868686868686868686868686868555555555555555555555555555555555555555555555$666666666666666666666666666666666666666666666666662-888888888888888888888888888888888888888888888855555555555555555555555555555555555555555555566666666666666666666666666666666666666666666666666$88888888888888888888888888888888888888888888888555555555555555555555555555555555555555555555 6666666666666666666666666666666666666666666666666 888888888888888888888888888888888888888888888888555555555555555555555555555555555555555555555(666666666666666666666666666666666666666666666666$888888888888888888888888888888888888888888888888855555555555555555555555555555555555555555555(666666666666666666666666666666666666666666666666(4888888888888888888888888888888888888888888888888855555555555555555555555555555555555555555555$66666666666666666666666666666666666666666666666(1888888888888888888888888888888888888888888888888885555555555555555555555555555555555555555555566666666666666666666666666666666666666666666664-99999999999999999999999999999999999999999999999999955555555555555555555555555555555555555555555$6666666666666666666666666666666666666666666666'899999999999999999999999999999999999999999999999999955555555555555555555555555555555555555555555(666666666666666666666666666666666666666666666 9999999999999999999999999999999999999999999999999999955555555555555555555555555555555555555555555466666666666666666666666666666666666666666666$9999999999999999999999999999999999999999999999999999995555555555555555555555555555555555555555555(66666666666666666666666666666666666666666676,69999999999999999999999999999999999999999999999999999995555555555555555555555555555555555555555555$6666666666666666666666666666666666666666666./;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5555555555555555555555555555555555555555555 7777777777777777777777777777777777777777774';;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5555555555555555555555555555555555555555555 #777777777777777777777777777777777777777777$;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5555555555555555555555555555555555555555555$77777777777777777777777777777777777777777#<<<<<<<<<<<<<<<<<<<<;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<5555555555555555555555555555555555555555555(7777777777777777777777777777777777777776$"""""""""""""""""""""4<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<5555555555555555555555555555555555555555555(777777777777777777777777777777777777777,-<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<66666666666666666666666666666666666666666668888888888888888888888888888888888888884-<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<6666666666666666666666666666666666666666666888888888888888888888888888888888888888-=====================================666666666666666666666666666666666666666666688888888888888888888888888888888888888-=====================================666666666666666666666666666666666666666666(8888888888888888888888888888888888888$-=====================================666666666666666666666666666666666666666666(888888888888888888888888888888888887(-?????????????????????????????????????666666666666666666666666666666666666666666(99999999999999999999999999999999998,-=====================================666666666666666666666666666666666666666666(89999999999999999999999999999999994-=====================================666666666666666666666666666666666666666666(8999999999999999999999999999999999#-?????????????????????????????????????666666666666666666666666666666666666666665(999999999999999999999999999999999999999999999999999999999999999999-?????????????????????????????????????666666666666666666666666666666666666666665(:99999999999999999999999999999999999999999999999999999999999999999-?????????????????????????????????????666666666666666666666666666666666666666666(?<9999999999999999999999999999999999999999999999999999999999999999/?????????????????????????????????????666666666666666666666666666666666666666666(??=999999999999999999999999999999999999999999999999999999999999999/?????????????????????????????????????666666666666666666666666666666666666666666(@???<9999999999999999999999999999999999999999999999999999999999999-?????????????????????????????????????666666666666666666666666666666666666666666.?@@@@=:99999999999999999999999999999999999999999999999999999999999/?????????????????????????????????????6666666666666666666666666666666666666666666@??????<9999999999999999999999999999999999999999999999999999999999/?????????????????????????????????????6666666666666666666666666666666666666666666@@@@@@@@=<99999999999999999999999999999999999999999999999999999999/?@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@6666666666666666666666666666666666666666666?@@@@@@@@@=:999999999999999999999999999999999999999999999999999999/@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@6666666666666666666666666666666666666666665-@@@@@@@@@@@=:::::::::::::::::::::::::::::::::::::::::::::::::::::/@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@66666666666666666666666666666666666666666660@@@@@@@@@@@@?=:::::::::::::::::::::::::::::::::::::::::::::::::::/@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@6666666666666666666666666666666666666666666&@@@@@@@@@@@@@@?=:::::::::::::::::::::::::::::::::::::::::::::::::/@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@6666666666666666666666666666666666666666666#$@@@@@@@@@@@@@@@@?=:::::::::::::::::::::::::::::::::::::::::::::::/@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@6666666666666666666666666666666666666666666  @@@@@@@@@@@@@@@@@@@=<::::::::::::::::::::::::::::::::::::::::::::/@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@6666666666666666666666666666666666666666666&@@@@@@@@@@@@@@@@@@@@@?<::::::::::::::::::::::::::::::::::::::::::/@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@6666666666666666666666666666666666666666666(@@@@@@@@@@@@@@@@@@@@@@@@=<:::::::::::::::::::::::::::::::::::::::/@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@66666666666666666666666666666666666666666666<@@@@@@@@@@@@@@@@@@@@@@@@@?=:::::::::::::::::::::::::::::::::::::/@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@66666666666666666666666666666666666666666666-@@@@@@@@@@@@@@@@@@@@@@@@@@@@?=::::::::::::::::::::::::::::::::::/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA77777777777777777777777777777777777777777776&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@?=<::::::::::::::::::::::::::::::/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBB77777777777777777777777777777777777777777777  AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@?=<::::::::::::::::::::::::::/AAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBB77777777777777777777777777777777777777777777$AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@?=<<:::::::::::::::::::::/AAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBB77777777777777777777777777777777777777777777.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@?==<<:::::::::::::::/AAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBB777777777777777777777777777777777777777777777/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA??===<<<::::::/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB777777777777777777777777777777777777777777777$AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@?6BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB777777777777777777777777777777777777777777777 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB777777777777777777777777777777777777777777776&AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC77777777777777777777777777777777777777777777740BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB6CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC7777777777777777777777777777777777777777777777 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB6CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC7777777777777777777777777777777777777777777777 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB6CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC7777777777777777777777777777777777777777777777&9BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB6CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC88888888888888888888888888888888888888888888888&BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB6CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC88888888888888888888888888888888888888888888888BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB8DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD88888888888888888888888888888888888888888888888$9BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB8DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD888888888888888888888888888888888888888888888884$BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB8DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD888888888888888888888888888888888888888888888888?BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB8DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD888888888888888888888888888888888888888888888888$8ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB8DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD8888888888888888888888888888888888888888888888884 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB8DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD8888888888888888888888888888888888888888888888888>BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB8DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD8888888888888888888888888888888888888888888888888('BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB8DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD88888888888888888888888888888888888888888888888888BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB8DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD88888888888888888888888888888888888888888888888888#/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB8DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD88888888888888888888888888888888888888888888888888.BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB8DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD8888888888888888888888888888888888888888888888888889BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB8DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD888888888888888888888888888888888888888888888888888( CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC8DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD88888888888888888888888888888888888888888888888888889CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC8EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE8888888888888888888888888888888888888888888888888888$ CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC8EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE888888888888888888888888888888888888888888888888888889CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC8EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE88888888888888888888888888888888888888888888888888888$CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC8EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE888888888888888888888888888888888888888888888888888888/CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC8EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE888888888888888888888888888888888888888888888888888889$BCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC8EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE8888888888888888888888888888888888888888888888888888888'ACCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC8EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE8888888888888888888888888888888888888888888888888888888(>CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC8FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEF88888888888888888888888888888888888888888888888888888888 CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC8FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEF88888888888888888888888888888888888888888888888888888888(/CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC8FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEF999999999999999999999999999999999999999999999999999999999>CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC9DFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFF9999999999999999999999999999999999999999999999999999999994"CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC9FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9999999999999999999999999999999999999999999999999999999999$/CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC'-------------------------------------99999999999999999999999999999999999999999999999999999999999>CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC99999999999999999999999999999999999999999999999999999999999,CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC999999999999999999999999999999999999999999999999999999999999#'CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC99999999999999999999999999999999999999999999999999999999999994CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC9999999999999999999999999999999999999999999999999999999999999,>CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC99999999999999999999999999999999999999999999999999999999999999$CDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD999999999999999999999999999999999999999999999999999999999999999"CDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD9999999999999999999999999999999999999999999999999999999999999994'DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD9999999999999999999999999999999999999999999999999999999999999999,-DCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$99999999999999999999999999999999999999999999999999999999999999999$6CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC9999999999999999999999999999999999999999999999999999999999999999994CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC9999999999999999999999999999999999999999999999999999999999999999996;CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC99999999999999999999999999999999999999999999999999999999999999999996?CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC99999999999999999999999999999999999999999999999999999999999999999999,?CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC999999999999999999999999999999999999999999999999999999999999999999999(;CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC9999999999999999999999999999999999999999999999999999999999999999999999&6CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC99999999999999999999999999999999999999999999999999999999999999999999999&6CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC999999999999999999999999999999999999999999999999999999999999999999999999&-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD9999999999999999999999999999999999999999999999999999999999999999999999999&'DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD99999999999999999999999999999999999999999999999999999999999999999999999999& DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::&?DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::&6CDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::&'DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::,?DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::,1DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::6"?DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::6 1DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::$ ?DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::,'DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD:::::::::::::::::::::::::::::::::::::9999999:::::::::::::::::::::::::::::::::::::::::61DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD:::::::::::::::::::::::::::::::::::9986666666999:::::::::::::::::::::::::::::::::::::::$6DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD::::::::::::::::::::::::::::::::::9764300.0036667999::::::::::::::::::::::::::::::::::::,6=???BBBBDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD::::::::::::::::::::::::::::::::::8630((&&&((,00366989::::::::::::::::::::::::::::::::::::#-689;==?BBDDDEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE::::::::::::::::::::::::::::::::9660,&$# #$&&(,,00366699:::::::::::::::::::::::::::::::::,$-/1689=?BBDDEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE:::::::::::::::::::::::::::::::9730($  ##&&(,,0336669:::::::::::::::::::::::::::::966#$'-148;=BBDDEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE::::::::::::::::::::::::::::::963,(#  ##&((,,0336699:::::::::::::::::::::::9663,,& ''-38;?BDDEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE:::::::::::::::::::::::::::::860,&# #$&((,,0336689::::::::::::::::::8630,&$  '-38>=>CGOSSSSSSSSSS (((((((((((((((%( %(((((((((((((((   &06::::::::::::::::::::::::::::::::::::::::::::::::9<<<<<<<<<<<<>===<<::@FOPSSSSC(((((((((((((((!((( (((((((((((((((% $,39:::::::::::::::::::::::::::::::::::::::::::::::9<<<<<<<<<<<<>==<<:::9888=CJN ((((((((((((((( !(,(,%((((((((((((((( #(37:::::::::::::::::::::::::::::::::::::::::::::::9><<<<<<<<<<<>===<<:::8887665$ ((((((((((((((%,,,,,,!(((((((((((((((  &06:::::::::::::::::::::::::::::::::::::::::::::::9>>>>>>>>>>>>>NSSSSSJGGFFDDCCBBB@@???>>==<<:::98886765 ))()()()()()))( ,,,,,,,,! ))()()()()()() &,3:::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;9>>>>>>>>>>>>>NSSSSSJGFFDDCCCBBB@@??>>===<<:::8887665. )))))))))))))) 1111111111%)))))))))))))) #,39;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9>>>>>>>>>>>>>NSSSSSJGFFDDCCBBB@@???>>==<<:::98886765 )))))))))))))) ,33333333333 )))))))))))))) #(38;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9>>>>>>>>>>>>>NSSSSSJFFDDCCCBBB@@??>>===<<:::88876656 +++++++++++++%3363363363363 +++++++++++++% #(07:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9>>>>>>>>>>>>>NSSSSSJFFDDCCBBB@@???>>==<<:::988867655%++++++++++++ !999999999999991 +++++++++++++  &06:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9>>>>>>>>>>>>>NSSSSSJFDDCCCBBB@@??>>===<<:::88876655 ++++++++++++)999999999999999<%.+++++++++++&06:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9>>>>>>>>>>>>>NSSSSSJDDDCCBBB@@???>>==<<:::988867655 ............ ><<<<<<<<<<<<<>><<% ........... &06:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9>A>A>A>A>A>A>NSSSSSJEDCCCBBB@@??>>===<<::9888766564........... >>>>>>>>>>>>>>>>>>A> +..........+&,6;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9>>>>>>>>>>>>>NSSSSSJEDCCBBB@@??>>>==<<:::9888675554 .11111111.9AAAAAAAAAAAAAAAAAAAAA 1.111111111 &,6;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9A>A>A>A>A>A>ANSSSSSJDCCCBBB@@??>>==<<<::98887665644 11111111. EEEEEEEEECEC 1111111111  &06;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9>A>A>A>A>A%AANSSSSSGDCCBBB@@??>>>==<<:::98886755444111111111$EEEEEEEEEEEE 111111111  &06;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9AAAAAAA>9AANSSSSSGCCCBBB@@??>>==<<<::988876656443322222222 HEHEHEHEHEHE 222222222  &08;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9AAAAAAA,AANSSSSSRNJFBB@@??>>>==<<:::988767554443D8333333333333333HHHHHHHHHHHH 33333333333333333 #(38;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<:AAAAAA!AANSSSSSSSSSSNJD??>>==<<<::9888766564433-E=54444444444444HHHHHHHHHHHH 44444444444444444 #,39<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:AAAA<%AAANSSSSSSSSSSSSSSNJD>=<<:::9887675644433 'DFE=755555555555HLHHLHHLHHLH 65555555555555555$,3:<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:AAA(1A6AANSSSSSSSSSSSSSSSSSSPJD>998887665644333 FFFFF@<666666666LLLLLLLLLLLL 9:666666666666666 &07:<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:AA!<>(AANSSSSSSSSSSSSSSSSSSSQQPPJD=77656444333FFFFFFFD@=966666LLLLLLLLLLLLFFFFFDB?<7666666# (09;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:C%AA!ACNSSSSSSSSSSSSSSSSSQQQQPPPPPNJB=5443333 GGGGGGGGGGGFDB@@ LLLNLNLNLNLNGGGGGGGGGGE?8889#,39;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:A6C1ACNSSSSSSSSSSSSSSSSQQQQPPPPNNNNNLLFB<433(GGGGGGGGGGGGGGGG"LNNNNNNNNNNNGGGGGGGGGGGGFA:8  &06;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:C!AC%6CCNSSSSSSSSSSSSSSQQQQPPPPPNNNNNLLLLLLLIB;"JGGGGGGGGGGGGGG"NNNNNNNNNNNNGGGGGGGGGGGGGGF=#(38;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:C(C>!CCCCPSSSSSSOJMNPSSQQQQPPPPNNNNNLLLLLLHLLHHHGGGJGGJGGJGGJGG"PPPPPPPPPPPPGJGGJGGJGGJGGJG"&,6:;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:CC3,CCCCCPSSSSSSOGFFDDGMNPPPPNNNNNNLLLLLLLLHHHHH,AJJJJJJJJJJJJJJ"PPPPPPPPPPPPJJJJJJJJJJJJJJJ #(08:<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:C%>CCCCCCPSSSSSSOGFDDCCBB@CGINNNNLLLLLLLLLHHHHHHEJJJJJJJJJJJJJJ"PPPPPPPPPPPPJJJJJJJJJJJJJJ8 &,69;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:C!CCCCCCCCPSSSSSSODDDCCBB@@??>>?CDILLLLLHHHHHHHHEE!BJJJJJJJJJJJJJ"PQQQQQQQQQQQJJJJJJJJJJJJJJ$,38:;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:C,CCCCC@6CCPSSSSSSODDCCBB@@??>>=<<:::?BFLHHHHEHEEEEEJKJJJJJJJJJJJ"QQQQQQQQQQQQJJJJJJJJJJJJJ1#&06:;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:C>EECCE<44ECPSSSSSQMCCCBB@@??>==<<::988766==<<::98876654HHEEEEEEECC KKKKKKKKKKKK"%%%%%%%%%%%%KKKKKKKKKKKK&,38:;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:EEEEE@66666EEPSSSQQQJCBB@@?>>==<<::988766544HEEEEEEECCC1KKKKKKKKKKK"KKKKKKKKKKK1#,08:<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:EEEE=555555EEPSQQQQPJBB@@?>>==<<::9887665443EEEECECCCCAA!"KKKKKKKKKK"KKKKKKKKKKD $,08:<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<====================:EEF86666666EEPQQQPPPJB@??>>==<<::98876554432EEEEECCCCCAAA1KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKD #,06:<<====================================================:EB766666666EENQQPPPNJ@??>>==<<:9988665543321EEECCCCCAAAAA> 2MKMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMD ,06:<<=====================================================:E6666666666EENPPPPPNJ??>>==<<:99886654433211ECCCCCCAAAAA>>6 "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMK9 ,38:<<======================================================:E7777777777EENPPNPNNG?>>==<::998866544332111ECCACAAAAA>>>><6 MMMMMMMMMMMMMMMMMMMMMMMMMMMMMM% 038:<<=======================================================:E8888888888EELPPNNNND>>==<::9888665443321111CCCCAAAAA>>>>>><< 2MOOOOOOOMOOMOOMOOMOOMOOOOOD $069:<=========================================================:E8888888888EENPNNNNND>==<::9887665443321111.CCAAAAA>>>>>><<<<>>>>><<<<<99,>>>>><<<<<999999! >>>>><<<<<999999666! ****" ,8::<<===============================================================:H::::::::::HHLLLLLLLHHEECCAA>><996633311,,,,A>>>>>>><<<<9999999666666(  &,6:<==================================================================:H::::::::::HHLLLLLLLHEEECAA>><<996633111,,((>>>>>><<<<<999999666666333333!  &08<===================================================================:H::::::::::HHLLLLHLHHEECCAA>><996633311,,,,(>>>>><<<<999999966666633333311111,1,,,, &08<===================================================================:H<<<<<<<<<><<996633111,,(,(>>><<<<<999999666666333333111111111,,,, &08<===================================================================:L<<<<<<<<<><996633311,,,,((>><<<<99999996666663333331111111,,,,,,, &08<===================================================================:H==========HHLLHHHHHEECAA>><<996633111,,((((<<<<<99999966666633333311111111,,,,,,,( &08<===================================================================:L==========HLHHHHHHEECCA>>><996633111,,,,(((<<<<99999966666633333311111,1,,,,,,,,,, &08<===================================================================:H==========HLHHHHHEEECAA>><<996633111,,(,(((<<91!%%(,36636333333111111111,,,,,,,((, &08<===================================================================:L>>>>>>>>>?LLHHHEEEEEAA>>><996633111,,,,(((%<993(%!!!!!%((,33331111111,,,,,,,,(,,,( &08<===================================================================:L>>>>>>>>DLHLHHEEEEECAA>><<99663311,,,((((((99999663,(%%!!!!!%((,1111,,,,,,(,(,,((( &08<===================================================================:L>>>>>?BILLLLHEEEEEECA>>><996633111,,,,(((%(9999666666333,((%!!!!!,,,,,,,,,,(,((,(( &08<===================================================================:L????@DLH<LLHEEEEECCA>><<99663311,,,(,((((%9966666633333311111((%,,,,,,,,,,,,,(((( &08<===================================================================<L???CILE%LLHEEEECCA>>><996633111,,,,((((%%96663636333331111111,,,,,,,,,(,(((((((( &08<===================================================================<L@@FLL6LLEEECCCCA>><<99663311,,,(((((%(%6661%(,,33311111111,,,,,,,,((,((,(((((( &08<===================================================================<LDLLE!6LLEECCACCA>><996633111,,,,((((%%%6661%!!!!%%(,111,,,,,,,,,,(,,(,(((((((( &08<===================================================================<LLL3%HLLLECCCCAAA><999663311,,(,((((((%%663331,(%%!!!!!%((,,,,,,,(,(,(((((((((% &08<===================================================================<LE!>LL,LLECCCAAAA><<96633111,,,,((((%%%%33333331111,((%!!!!!%%%((,(,(((((((((%( &08<===================================================================<L%LL>LLECAAAAA>>>>><96611,,,,(((((%%%%3333331111,11,,,,(%%!!!!!!%%%(((((((%(( &08<===================================================================<LCNL%LLEAAAAA>>>>>><<<<<9631,((((%%%%%333311111111,,,,,,,,,,((%%!!!!!!%%%((%% &08<===================================================================<L(LL6!LLEAAA>>>>>><<<<<9999996631,((%%%333,(,11,1,,,,,,,,(,(,,(((((%%%!!!!(%(% &08<===================================================================<LCNH%1NLNCAA>>>>>><<<<<999999666666333,(311,!!!%%((,,,,,((,(,((((((((((((%%%%%% &08<===============================================>>>>>>>>>>>>>>>>>>>><LL1%HNLNLC>>>>>><<<9<<9999966666633333331111((%%!!!!!%%((,,(((((((((%(%(%%(%%%% &08<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><L!9NNNNNNC>>>>><<<<!99996666363333331111111,,,,,(%!%!!!!!%%((((((((%(%(%%%%%% &08<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><N%NNNNNNNNC>>><<<<< !666333333111111,1,,,,,(,,(,(%%%!!!!!%%%%((%%%%%%%%% &08<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><N>NNNNNNNNNC>><<<<<%  !11111111,1,,,,,,,,(,,((((((%%!!!!!!!%%%%%%%%% &08<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><N(NNNNNNNNNNNC<<<<<91QQQ6(  !,,,,,,,,(,,(,(((((((((%((%%%%!!!!!!%%%% &,6<<=<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><NNNNNNNNNNNNNA<<<<9<LQQQPPNPNN(!  !,,,(,((,(((((((((%(%%(%%%%%!!%%%% "(06689<<<==>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><PPPPPPPPPPPPNA<<999,QQQPPPPNNNNLLLLL(!  !((((%(((%(%(%(%%%%%%%%%%!%!  $&,006689<=>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><NNNNNNNNNNNNPA<999%QQQPPPPNNNLNLLLLLLLHHHH(!  !(((%((%%%%%%%%%%%%%%! #$&(,068<=>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><PPPPPPPPPPPPP>9999 PQQQPPPPNNNNLLLLLHLHHHHHEEEEEE%!  %%%%%%%%%!%!%!%  "&(069<=>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><PPPPPPPPPPPPPA996LQQQPPNPNNNNLLLLLLLHHHEHEEEEECCCCAAAA%  %%%%%!!%!  $,38<=>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><PPPPPPPPPPPPP>96!(QQQPPPPNNLNLLLLLHLHHHHHHEEEEEECCCCAAAA>>>>6!  !!!! ",38<=>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><PPPPPPPPPPPPP>91QQQPPPPNNNNNLLLLLLLHHHHHEEEEEECCCCAAAA>>>>><<<<991!   #,09<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><PPPPPPPPPPPPP>6 SQQQPPPPNNNNLLLLLHLHHHHHEEEEEECCCCAAA>>>>>>><<<<99999666,!  $,69=>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><PPPPPPPPPPPPP<6QQQPPNPNNLNLLLLLLLHHHHHEEEEECCCCACAAAA>>>>><<9<999696663633331,!  &06<=>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><QQQQQQQQQQQQQ%%QQQPPPPNNNNLLLLLLHLHHHHHEEEEEECCCCAAAA>>>>><<<<9999966666333333111111(! $,6:<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><QQQQQQQQQQQQLSQPPPPNPNNNNLLLLLLLHHHHHEEEEEECCCCAAAA>>>>><<<9<999996666633333111111,,,,,"(09<=>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><QQQQQQQQQQPE%.4=DGPPNNNNLLLLLHLHHHHHEEEEEECCCCAAA>>>>>>><<<<999996666633333111111,,,,,( &06<=>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><QQQQQQQQQL<< (((((((+2=?DILLLLLHHHHEHEEEECECCACAAAA>>>>><<<<9996966636333331111,1,,,,,,$,6:=>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><QQQQQQQPC<<, (((((((())))).48;DFHHHHEEEEEECCCCAAAA>>>>><<9<99999666663333331111111,,,,!"(08<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>9QQQQQQH>>>< (((((((()()))+)++++..66;B@EEECCCAAAAA>>>>><<<<<999996666633333111111,,,,,, &06<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><8SSSSNCA>>>6((((((((()))))++++.....111146;=@AAAAA>>>>><<<<99999666663333311111,,,,,,,$,39<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><6SSQHCAAA>A(((((((()())))++++++...11111123334689;<<>><<9<9996966666333331111,11,,,,,! &08<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><90SNECCCAAA> (((((((((())))))++.+....111112333445566689;;<<99996666663333311111,1,,,,,#'39=>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>:6,HEECCCCAA(%(((((((((()))))+++++...1111112333445566688899::=;9966663333311111,1,,,,( (0:=>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><90'!EEEEECCCC (((((((((()))))+++++....1.11112333445566788899::<<<==>><996311111,1,,,,,%  &06<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>:8,EEEEEEECC9 )(((((((((()))))+++++...1111112334445566788899::<<<==>>>??@@B==961,,,,,, $,6:=>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><90(!HHEEEEEEC 344411.++)()))))++++++....111112334445566788899::<<===>>>??@@BBBCCCDBB66  &08<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><6,HHHHEEEEEC344656678886532..++++....1111112334445566788899::<<===>>???@@BBBCCCDFFGG9 $,3:<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><90'LHHHHHEEE(.344656678889:::<<=<9864311111122334445566788899::<<===>>???@@BBBCCEDFFGFG  &06:>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>:60ALHHHHHHEE3344456678889:::<<==>>>??@@>>9:65334445666788899::<<===>>???@@BBBCCDDFFGGGB ",39<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><83(LLLLHHHHH6(3344656678889:::<<==>>>??@@BBBCCDDDB?><:7678889:::<<===>>>??@@BBBCCDDFFFGGG $06<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><80CLLLLHHHHH 33344656678889:::<<==>>>??@@BBBCCDDFFFGGGGJFFBB>=:::<<<==>>>??@@BBCCCDDFFGGGJ #(0:<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><90'NLLLLLHLHA33344656678889:::<<==>>>??@@BBBCCDDFFFGGGGJJJJMMIOM-:<<===>>??@@BBBCCEDFFGGGG" $06<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><60ENNLLLLL9333344456678889:::<<==>>>??@@BBBCCDDFFFGGGGJJJJMMMIO3<<<==>>??@@BBBCCDDFFFGGGJ   &08<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>=:3, PNNNNL9333344656678889:::<<==>>>??@@BBBCCDDFFFGGGGGJJMJMMOO:<<==>>??@@BBBCCDDDFFGGGG1 $,6:>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><90HPPNN, 33333344656678889:::<<==>>>??@@BBBCCDDFFFGGGGJJJJMMMOF6<<==>>>??@@BBCCCEDFFFGGGJ #,3:=>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><80 PPP!$3333333344656678889:::<<==>>>??@@BBBCCDDFFFGGGGJJJJMMIO*<<<==>>??@@BBBCCDDFFGGGGJ9 "&38<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><8 >H(333333333344456678889:::<<==>>>??@@BBBCCDDFFFGGGGJJJJMMMO*,<<==>>??@@BBBCCDDFFGGGGGJ#,08<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><8 .33333333333344656678889:::<<==>>>??@@BBBCCDDFFFGGGGGJJMJMMO<<==>>??@@BBBCCDDDFFFGGGJ@  $,39<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><#33333333333333344656678889:::<<==>>>??@@BBBCCDDFFFGGGGJJJJMMMO$<==>>>??@@BBBCCEDFFGGGGJJ"$&,06<=>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>= 33333333333333344656678889:::<<==>>>??@@BBBCCDDFFFGGGGJJJJMMIO <<==>>??@@BBBCCDDFFGGGGGJJ,0069<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>&&933333333333333344456678889:::<<==>>>??@@BBBCCDDFFFGGGGJJJJMMOI $<==>>??@@BBBCCDFFFF9"'68:<=>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>0>=< 33333333333333344656678889:::<<==>>>??@@BBBCCDDFFFGGGGGJJMJMMO  <==>>???@?@@CD'  &<<=>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>6 (3333333445556788899::<<==>>>??@@BBBCCDDFFGGGGGJJJJMMMO <==>???1" $<<=>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>$  #0788899<<<===>>>??@@BBBCCDDFFFGGGGJJJJMMIO <8$ #,<=>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>6$$$  $$$>>>?@@@BBBCCDDFFFFGGGJJJJMMMO $, $<<=>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>0$$  "''4DFFDGGGGJGJJJMO2"&'0 $,<=>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>($$  ***DMM* ,,069&>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>6$$$  3689<=>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>0$$ ::<<=>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>$$0>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888885555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555533333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333nzbget-16.4/windows/install-update.bat0000644000175000017500000002050412630544544017671 0ustar andreasandreas@echo off rem rem Batch file to update nzbget from web-interface rem rem Copyright (C) 2015 Andrey Prygunkov rem rem This program is free software; you can redistribute it and/or modify rem it under the terms of the GNU General Public License as published by rem the Free Software Foundation; either version 2 of the License, or rem (at your option) any later version. rem rem This program is distributed in the hope that it will be useful, rem but WITHOUT ANY WARRANTY; without even the implied warranty of rem MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the rem GNU General Public License for more details. rem rem You should have received a copy of the GNU General Public License rem along with this program; if not, write to the Free Software rem Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. rem title Updating NZBGet rem make sure the commands "tasklist", "find" and "ping" use default system programs set PATH=%SystemRoot%\system32;%PATH% if "%1"=="/step2" goto STEP2 if "%1"=="/step3" goto STEP3 if x%NZBUP_BRANCH%==x ( echo This script is executed by NZBGet during update and is not supposed to be started manually by user. echo. echo.To update NZBGet go to Web-interface - Settings - System - Check for updates. echo. pause exit ) @setlocal enabledelayedexpansion rem extracting link to update-info-URL from "webui\package-info.json" set UPDATE_INFO_LINK= for /f "delims=" %%a in ('type "%NZBOP_WEBDIR%\package-info.json"') do ( set line=%%a set line=!line:update-info-link=! if not %%a==!line! ( set UPDATE_INFO_LINK=!line! rem deleting tabs, spaces, quotation marks and commas set UPDATE_INFO_LINK=!UPDATE_INFO_LINK: =! set UPDATE_INFO_LINK=!UPDATE_INFO_LINK: =! set UPDATE_INFO_LINK=!UPDATE_INFO_LINK:"=! set UPDATE_INFO_LINK=!UPDATE_INFO_LINK:,=! rem deleting the leading colon set UPDATE_INFO_LINK=!UPDATE_INFO_LINK:~1%! ) ) rem "%~dp0" means the location of the current batch file set NZBGET_DIR=%~dp0 cd /D %NZBGET_DIR% rem Determine if NZBGet is running as a service set NZBGET_SERVICE=no for /F "tokens=3 delims=: " %%H in ('sc query "NZBGet" ^| findstr " STATE"') do ( if /I "%%H" EQU "RUNNING" ( set NZBGET_SERVICE=yes ) ) echo Downloading version information... rem using special command "-B webget" NZBGet works like a simple wget rem and fetches files from web-servers nzbget.exe -B webget "%TEMP%\NZBGET_UPDATE.txt" "%UPDATE_INFO_LINK%" if errorlevel 1 goto DOWNLOAD_FAILURE rem extracting version number from info file if %NZBUP_BRANCH%==TESTING set VER_FIELD=testing-version if %NZBUP_BRANCH%==STABLE set VER_FIELD=stable-version set VER=0 for /f "delims=" %%a in ('type "%TEMP%\NZBGET_UPDATE.txt"') do ( set line=%%a set line=!line:%VER_FIELD%=! if not %%a==!line! ( set VER=!line! rem deleting tabs, spaces, quotation marks, colons and commas set VER=!VER: =! set VER=!VER: =! set VER=!VER:"=! set VER=!VER::=! set VER=!VER:,=! ) ) rem extracting setup URL from info file if %NZBUP_BRANCH%==TESTING set VER_FIELD=testing-download if %NZBUP_BRANCH%==STABLE set VER_FIELD=stable-download set DNL_URL= for /f "delims=" %%a in ('type "%TEMP%\NZBGET_UPDATE.txt"') do ( set line=%%a set line=!line:%VER_FIELD%=! if not %%a==!line! ( set DNL_URL=!line! rem deleting tabs, spaces, quotation marks and commas set DNL_URL=!DNL_URL: =! set DNL_URL=!DNL_URL: =! set DNL_URL=!DNL_URL:"=! set DNL_URL=!DNL_URL:,=! rem delete first character (colon) set DNL_URL=!DNL_URL:~1,1000! ) ) rem extracting signature URL from info file if %NZBUP_BRANCH%==TESTING set VER_FIELD=testing-signature if %NZBUP_BRANCH%==STABLE set VER_FIELD=stable-signature set SIG_URL= for /f "delims=" %%a in ('type "%TEMP%\NZBGET_UPDATE.txt"') do ( set line=%%a set line=!line:%VER_FIELD%=! if not %%a==!line! ( set SIG_URL=!line! rem deleting tabs, spaces, quotation marks and commas set SIG_URL=!SIG_URL: =! set SIG_URL=!SIG_URL: =! set SIG_URL=!SIG_URL:"=! set SIG_URL=!SIG_URL:,=! rem delete first character (colon) set SIG_URL=!SIG_URL:~1,1000! ) ) SET SIG_FILE=nzbget-%VER%.sig.txt echo Downloading verification signature... nzbget.exe -B webget "%TEMP%\%SIG_FILE%" "%SIG_URL%" if errorlevel 1 goto DOWNLOAD_FAILURE SET SETUP_EXE=nzbget-%VER%-bin-win32-setup.exe echo Downloading %SETUP_EXE%... nzbget.exe -B webget "%TEMP%\%SETUP_EXE%" "%DNL_URL%" if errorlevel 1 goto DOWNLOAD_FAILURE echo Verifying package authenticity... nzbget.exe -B verify "%NZBOP_APPDIR%\pubkey.pem" "%TEMP%\%SIG_FILE%" "%TEMP%\%SETUP_EXE%" if not "%ERRORLEVEL%"=="93" ( del "%TEMP%\%SIG_FILE%" del "%TEMP%\%SETUP_EXE%" goto VERIFY_FAILURE ) del "%TEMP%\%SIG_FILE%" rem using ping as wait-command, the third parameter (2) causes ping to wait 1 (one) second ping 127.0.0.1 -n 2 -w 1000 > nul echo Stopping NZBGet and installing update... ping 127.0.0.1 -n 2 -w 1000 > nul rem After NZBGet is stopped the script cannot pring any messages to web-interface rem In order for user to see any error messages we start another instance of the rem script with its own a console window. rem We need to do that because of another reeson too. When the update is installed rem it is possible that the script "install-update.bat" will be updated too. rem In that case the command interpreter will go grazy because it doesn't like the rem batch files being replaced during execution. copy install-update.bat "%TEMP%\nzbget-update.bat" > nul if errorlevel 1 goto COPYSCRIPT_FAILURE start "Updating NZBGet" /I /MIN CALL "%TEMP%\nzbget-update.bat" /step2 "%NZBGET_DIR%" %SETUP_EXE% %NZBGET_SERVICE% echo [NZB] QUIT exit :STEP3 echo Third stage goto UPDATE :STEP2 echo Second stage :UPDATE rem init from command line params set NZBGET_DIR=%2 set NZBGET_DIR=%NZBGET_DIR:"=% cd "%NZBGET_DIR%" set SETUP_EXE=%3 set NZBGET_SERVICE=%4 rem in service mode redirecting the output into install-update.log if "%1"=="/step2" ( if "%NZBGET_SERVICE%"=="yes" ( "%TEMP%\nzbget-update.bat" /step3 "%NZBGET_DIR%" %SETUP_EXE% %NZBGET_SERVICE% > "%NZBGET_DIR%\install-update.log" 2>&1 ) ) rem check if nzbget.exe is running echo Stopping NZBGet... echo. tasklist 2> nul > nul if errorlevel 1 goto WINXPHOME set WAIT_SECONDS=30 :CHECK_RUNNING if "%WAIT_SECONDS%"=="0" goto QUIT_FAILURE tasklist 2> nul | find /I /N "nzbget.exe" > nul if "%ERRORLEVEL%"=="0" ( ping 127.0.0.1 -n 2 -w 1000 > nul set /a "WAIT_SECONDS=%WAIT_SECONDS%-1" goto CHECK_RUNNING ) goto INSTALL :WINXPHOME rem Alternative solution when command "tasklist" isn't available: rem just wait 30 seconds ping 127.0.0.1 -n 31 -w 1000 > nul :INSTALL echo Installing new version... echo. "%TEMP%\%SETUP_EXE%" /S del "%TEMP%\%SETUP_EXE%" echo Starting NZBGet... if "%NZBGET_SERVICE%"=="yes" ( net start NZBGet ) else ( start /MIN nzbget.exe -app -auto -s ) if errorlevel 1 goto START_FAILURE ping 127.0.0.1 -n 2 -w 1000 > nul exit :DOWNLOAD_FAILURE rem This is in the first instance, the error is printed to web-interface echo. echo [ERROR] *********************************************** echo [ERROR] Download failed, please try again later echo [ERROR] *********************************************** echo. exit :VERIFY_FAILURE rem This is in the first instance, the error is printed to web-interface echo. echo [ERROR] *********************************************** echo [ERROR] Package authenticity verification failed echo [ERROR] *********************************************** echo. exit :COPYSCRIPT_FAILURE rem This is in the first instance, the error is printed to web-interface echo. echo [ERROR] *********************************************** echo [ERROR] Failed to copy the update script echo [ERROR] *********************************************** echo. exit :QUIT_FAILURE rem This is in the second instance, the error is printed to console window start "Error during update" CMD /c "echo ERROR: Failed to stop NZBGet && pause" ping 127.0.0.1 -n 11 -w 1000 > nul exit :START_FAILURE rem This is in the second instance, the error is printed to console window start "Error during update" CMD /c "echo ERROR: Failed to start NZBGet && pause" ping 127.0.0.1 -n 11 -w 1000 > nul exit nzbget-16.4/windows/resources/0000755000175000017500000000000012630544544016264 5ustar andreasandreasnzbget-16.4/windows/resources/mainicon.ico0000644000175000017500000132262612630544544020571 0ustar andreasandreas ( f ( @@ (B(00 %j  h.(         !"#$&&&'()********)('&&&$#"!  !"$&(**-./0223456666666666543220/.-**(&$"!  !#&'*,.02468:;=>@ABBDDEFFFFFFEDDBBA@>=;:86420.,*'&#!  !#&*,.136:<>@BEGIJMNOPRRSUUVVVVVVUUSRRPONMJIGEB@><:631.,*&#!   "&),/269<?BEHJNPRUWY[]^`bbcdffffffffffdcbb`^][YWURPNJHEB?<962/,)&"   !#'*.269=@DGKNRUX[^ag s}"""&&&)))+++...///000111222222333222222111000///---+++)))&&&!!!~ ujca^[XURNKGD@=962.*'#!   $'+/37;?DGLPSW[^bf }(((///111111222222333333333333333333333333333333333333333333333333333333333333333333333333333222222111000///((( ifb^[WSPLGD?;73/+'$   "&*/38=AFJNSX\`m&&&---333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333---&&&pd`\XSNJFA=83/*&"  !%*.27<AFKPUZ^co!!!...222333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222...!!!rhc^ZUPKFA<72.*%!  "',06:@EJPVZ` t)))///333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333///))) xf`ZVPJE@:60,'"  $(-28>CINTZbn...333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333...rh`ZTNIC>82-($   %).4:?FKRX^g ---111333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333111--- md^XRKF?:4.)%   %*/5;BHNTZg(((333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333'''naZTNHB;5/*%  $*/6<BIOV]e$%%000222333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222000###kd]VOIB<6/*$  #)/5;BJQW_(((555444333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333111000///...---,,,,,,+++++++++,,,,,,---...///000111333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333%%%f_WQJB;5/)#  "'.4;BJQW_|334766677555544433333333333333333333333333333333333333333333333333333333333333333333333333333333333222111000...---888YYYuuuuuuYYY888---...000111222333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333343232000f_WQJB;4.'"  !&,2:AHPW_333:::999787666656455333333333333333333333333333333333333333333333333333333333333333222...***+++JJJhhhhhhJJJ+++***...2223333333333333333333333333333333333333333333333333333333333333333333333335445657770/0g_WPHA:2,&!  $*18?GNV`y9:9===;<;::;999998777566555344333333333333333333333333333333333333333333333333333333555888OOOOOO888555333333333333333333333333333333333333333333333333333333333333333334544566777878999665g_VNG?81*$  "(.6=EMU^767???=>>==<<;<::;999888877665555444333333333333333333333333333333333333333---'''GGGGGG'''---3333333333333333333333333333333333333333333333334445556667769889:::::;<;344e^UME=6.("  %+2:BJRZ n555BBB@?@?@?>>>===<<<;;;9:9999888676656444434333333333333333333333333222000>>>NNNNON>>>00022233333333333333333333333333333333333344455576787888999::;:<<<<=<>>>111 ucZRJB:2+%  "(/6>FOXa333BABCCCBAB@@A@@@>>>>==<<<<<;::;998888676665555444333333333333333333--.&&&yyyߙz|bd[^TVMPGJBE>@:=8;8;:=>@BEGJMPTV[^bdz|ߚzzz%%%---333333333333333333333333333333454555767888898;::;;;<<<===>>>??@>?>000jaXOF>6/("  $+2:CKT^BBBEFEEDDCCCABBA@A@@@>?>>>><<<;<;:::9:9898777766455444333333333...:::ZZZqsIL8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;ILqsZZZ99:...333333333333333333444555666777888998:::;;;<====>>>?@@?@AABBB>??f^TKC:2+$  !'.6>FPYbAA@GGGFFFFFFEDDCDDBBBAAA@@@???>>>===;<;;;;:9:998777676565555433000,,,؃NQCF=@8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;=@DFNQل,,,0003333333333344446656667879899:::::<;<===>>=???A?@AAABBBCCCCCC<<<lbYPF>6.'!  "*1:BKT^---GGGIIIHHHGGGFFFDEEDDDBBCAAAAA@@@@?>>====<<;;<:;:999888877233222fffݕ_a8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;_bޖfff111000444554556767877999:::;;;;;<==>>>>???A@@AABCCCCDCEDDEFFDDD)))g^TKB:1*"  %,4=FNY kDDCKKLKKKIIIIHIGGGGFFEEEDEDCCCBBBAAA?@????==><=<;;;::::::898777ppqSU8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;SVooo333555666776888999:::;;<<<<=>>?>>?@@A@ABBBCCCDDDEEEFFFGGGIIH@A@ ubYNF=4,%   &.6?IR]+,,KKKMMMLLLKKKKJJHIIHHHGGGFFFEEECDCBBBAAB@@@???>>?===<<<776988hk8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;ik565334999:::;;;;;<===>>>?>?@A@AA@BBBCCCDDEEEEFFFHGHIIHIJJGGG(('f]RI?6.&   ")09BLVa???OPPOOOMMMMLMKKKJJJIJIHHHGHGFFFEEECDDBCCBBA@AA@@@>?><<>????@@ABBACBBDDCDDEFFFFFGHHHHHHJJJKKKLLL:::k`VLB90)"  #*2;ENYwKKKQQQPPPOOONNMMLMLLLKKJJJJHIHGHHFFGFEEDDECCCBCBAAA???222Z\8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;[]./.<<<=>>????@@AAABABCCCDDDEEEFFFGGGIIHIIIKKJLKLLLLNNNGGFcYNE;2*#  $,4>GQ[...TTTSRRRRQQPPOPONNNMNMLLLKKKJJJIHIHHHGFGFFFEEEDDCABCABAgggߣ?B8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;?Bfef>>>???@@AAABBCCCCCEEDFEFFFFHGHIIHJJJKJJLLLMLMNNNOOOPPP***f[QG>4,$  %-6?IT^DDDUVUTTTSSSRRRPQQPPONNONNNMMLKLKJKJIIIHIHHGGGFFEFEBBBEEEwyCF8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;CFxzBBB???BAACCCDDDEEEEFFHGGHHHIIIIKJKKKLMLMMMNOOOOPPQPRRQ@@@i^TI?6-%  &.7AKVmLLLWWWUUUUUTSSSSRRQQQPPQOOONNNMMMLLLKKKJJJIIIHHHDDEJJJڐ8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;ےFGHAAADEDEEFFGFGHHIIHJJJKKJLLKLMMNNNOOOOPPQQQRRRSSSHHHwaVKA7.&  &/8BMWqVVVXXYWWWUVVUUUTTTRSSQQQPQQPPPNNNMMNLLLLLLJJJIIIGGGddc}=?8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;=@~׀aaaDDDFFFGGGHHHHHHJJKKKKMLLMMMNNNPOOPPPQQQRRRTSTUUUSSR|bWMB8/&   '09CNY())XWWZZZXXYWWXVVVUUUTTTSSSRRRQQQPPPOOONNNLMMKLKIIILMLݟ?B8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;?BޠIIIEFFIHHIIIJJKKLKMMMNMNONNPPPQQQQRRSSSTTTUUUVVVTTT%%%eYNC90'    '0:DOZ...[[[[[[ZZZYYYXWWWVWVVUTTTSTTSRSRRQPQPOPOONNMMMLLKkjkAD8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;ADjjiIHHJJJKKKLLLMMMNONOOOPQPQQQRRRSTTTTTUVUVWVXXXWWX***fZOD:0'    '0:EP[CCC]]^\\\\\[ZZZYYZXYXWWWVVVUUUTUTSRRRRSPQPPPONNOLLLgj8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;hkHHHKKKLLLMMMOONOOOQPQRRQSSSTSSUUUVVVWWWXXXYYXZZZ>>>g[PE:1'    '0:EP\CCC__`]^^]]\[[\[[[ZZYYYXWXXWWVVUUTTUSSSRRRRQQMNMaaaڔ8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;ۖ^^_JJJNNNOOOPPPQQQRQRSSSUUTUUUVWVXWWXYYYYY[[[[[[?>?g\PE:1'   '0:EP\LKK```___^__]]]]\][[[ZZZYYYXXXWWWVVUUUUTSTSSTMNMPR8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;PSJJJPPPPPPQRRSSRTTTTUTUVVWWWWWXYYYZZZ[[[\\\]]]GGFh\PE:0'  &0:EP\LLLbbbaa`______]]^]]\[[[[[ZYYYYXXXWWVWVUUVTTUUVU׌8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;ُSRRQQPRRRSSSTTTUUUVVVWWWXXXYYZZZZ[[\]\]]^]_^_HHHh\PE:0&  &.:DP[NMMcdcbbbbbb```__`^^^]]]\\\[\\ZZZXYYXXXWWVUUUWWWEH8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;EHSSTRQRTTSUTUVVUVWWXXXYYXZZZ[[[\\\]]]^^^__^```HHHh[PD9/&  %.8COZOOOeeeddccccbaba`a`__^__^^^]]\[[[Z[[ZZYXXXRRR{{{ް:=8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;:=zyyOONUUUVVVWXWXXXYYYZZZ[\\\\\]]]_^__`_a`aabaKKJg[OC8.%  $-7BNZHHHffgefeddeccdbbb`aa```__^^^^]]]]\][[[YZZYYYjl8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;kmVVVVVVXWXYYXZZY[[[[[\]]]]^^___``_a`abbbcccCCCgZNB7-$  #,6AMZKJJhhhgggeffeeddccbccbba```___^^^]]]]]\[[\TTTWZ8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;X[QPQYXXYYZ[ZZ[[\\\\^]]^^_`__```ababbbccdeddFEEfZMA6,#  "*4?JW555iiiiihgggfgfeeeddecccbbbaab```_`_^^^]\\^^^8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;[\\YYY[[Z[\\]]\^^^`_^`_`aaababcccdddeefeff000dWK?4*"   )2>IV101jjjjjjiiigghgfgfffeeedddcbbbbb`a```__^_``aט8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;ښ]]^\[[\\\]]]_^___```abaabbccddddeffffffggf,,,bVJ>2)   &0;GTpijikkljjjiiiihhgggfffeefdddcccbbcaaa___edeԅ8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;ֈbbb\\\]]^_^_`_`aaaabbccbdddedefffggghhhfff|aTG;0&  %.9EQkjkklmllllkkkijjihihhhffgefeededddcbcabaggfeg8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;fhddc^^^`_``a`bbabbbccdeeeeefggghghhhiijjfgfx^QE9.%  #,6BN[aaaonnmnmlmllkkjjjiiiihhhgggffefedddccceedZ]8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;[^baa_``aaabbbcccdeceeefffggghhhiiijkkkkk]]]i[NB6,#  !*4?KYZZYpppooonnmmmllllkkkjiihhiggggggfefeedfff=@8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:6:6:6:7:6:7:6:7:6:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;=@cbbbabccbddcdddeeffffghhhhhjjjjjjllkmmmUUUfYK?4*!  '1<IV???qrrppqpoonnnmnmlllkklkkkjiihhhhgggfg`__JL8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:6:6:6:696:6:696:6:6:6969696:6:6:696:6:6:6:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;JM\\\cdcdddeeefffgghihhiiijjjlkkmllmmmnon:::cVI<1'  $.9FR! !tsssrrqqqqppponnnnnmlllllkjkjjjhiihhhggf=@8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:6:6:6:6:6:6969696969696969696969696969696969696969696969696:6:6:6:6:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;=@ccdedeeeegggggghiiiiijkklllmlmnmnnnoppp_RF:.$  "+6BN^jkjtssrssqqqpppooonnnmnnmlmkklkkjiiidddZ\8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:7:7:7:6:6:6:6:69696969696969696969696969696969696969696969696969696969696969696969696:6:6:6:7:7:7:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;[]`a`gffghhhhhjjjjjjlkklllmmmmnopooqpqgffk]NB6+"  (2>KY\\\ututttssrrrrqqqpooooonnmmmmlllkkkjjjcf8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:7:7:6:7:6:6:69696969696969696969696969696969696969696969696969696969696969696969696969696969696:6:7:7:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;egegfhhhhihjjjjjkllkmmmnmnooopooqqqrrrWVWfYK>2(  %/:FTCCCvwwuvvuttsstrrrqqrppppoonnnnmnmmmkkklllу8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:7:6:6:6:69696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696:6:6:7:7:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;Ӆihhhhhjjijkkkkklllmnmnnnppoppprrqrrrsst===bTF:/%  "+6CPgtttwwwvvvuuuttssssrrqqqqpppooomnnnlmnnmԔ8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:7:7:6:6:696969696969696969696969696969696969595959595959595969596959595959596969696969696969696969696969696969696:6:7:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;חkjkjjjkkkllkmmmnnnnonpppppqrrrssrsttpppu^PC6+"  (2>KYmllxxxwwxvvvvvvuutttssrsrrqqpqoopoonjkjٸ8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:6:696969696969696969696969696969595959595959595959595959595959585959585858595959695969695959696969696969696969696969696:7:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;ݽgggkllmmlnnmnnnoooqppqqqrsrsssuttuuuggggYK>2(  $.:FTKKKzz{yyyyxxwwwvvvuuutttrssrrrqrqppqnmn8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:6:696969696969696969696969695959595959595959595858585959585858585858585959595959595959595959595969696969696969696969696969696:6:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;kjjmmmmnoooopopqqqrrrrrsttttuuvvvvwvEEEbTF:.$   *6BO^xxx{z{zyzxyyxwxvwvvuvtuussssssqrrqqp|||UX8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:6:6:6969696969696969696969595959595959595859585858585858585858585858585858585858585858585858585858585959695969596969696969696969696969696:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;WYzzymmmonnopopqqrrrssrsstttuuvuwvvwwxttul^OB6*   &1=JWpoo|||{{{z{{yyyxxxwwxwwvuvutttsstsrspoofh8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:6:6:6:69696969696969696969695968595858585858585858585858585858585858585858585858585858585858585858585858585958585959596959696969696969696969696:6:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;hjlllooppppqqqrrrsstuttuuuvvvwwwxxxyyykkkfWJ=1&  ",8ER888|}}}||{{|{{zzyzxxxxxxvwvvvvtuuttusssէ8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:6:6:6:6969696969696969696959595859595858585858585858585858585858585858484848484848584858585858585858585858585858585859595958595959696969696969696969696:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;تoppqqqrrrssssttttuuvuvwwwwwyyyzyzzzy333aRE8-"  '2>MZyzy~}}}}}||{{zzzyyzyyxwwwvwwvvvsss9<8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:6:6:6969696969696969695959585959585858585858585858585848484848485848484848584848485848584858585848484858585858585858585858585859585959595969696969696969696:6:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;9@8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:6:696969696969596959585858585848484848474747474747373747474747363637373737373636-0 ) $&26263636363737373637363737373737474747474748484848584858585858585959596969696969696:6:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;>A}}}fVH:-"  '2?N332yqs8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:6:6:6969696969695959585858585858485847484848474747473747373737373737363737363626/1 6 #26262636363737363737373737374737374747474848484748485858585858585959696969696969696:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;tv}~~---]N?2'  !,8FTlllнK[ʁ8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:696969696969595959585858585858484848484747474737374737373737363736262626263636.1 * !26262626262626363637373737373647473747474747484848484858585858585959696969696969696:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;΄kZK>0%  )6CRiii8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:696969696969596959585858585848484847484847473737374737373637373636263626263626/2> "25252636262636263636363736363747473737474748474847584858585858585859696969696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;aabaRC6)  ".:I_km8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:6:6969696969595959585858585858484848484747474737374737373637362626262626262625/3G~26262526263636363636363637373747473747474747474748584858585858585959696969696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;mo nXI:."  &2@N888|AD8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:696969696969595959585858584848584748474747373747373637373636262636262626262604 5 }262626252626263626363637373736363737474747484847485848585858585958595969696969696:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;AD212^N@2&   *7ETvvvŒʎ8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:6:6:6969696969695958585858585848484748474737474737373637363726262636362626262514 9!262526262626362626263636373736373737373747474848484848585858585959696969696969696:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;ΒpppeTE7*   $/<J]=@8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:6:6969696969696959585858584848584747474747473737373737363636362626262625252504Q{252626262526363636362636373737363747374747474848484848585858585969596969696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;>AmZJ</$  '3APcccʐ8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:696969696969695958595858585858484847474737473737373737373626362626262626252625Ku262526262525252636362636373737373747473747474847475858585858585959596969696969697:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;͓[[[`PA3'  !+8FWݔSU8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:6:69696969696959585858585848484747474747473747363637373626363626252625262626 :x1515262625262626262626363636373737373737474747484848585858585959695969696969697:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;TWhVF8+!  #/=Kb8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:6969696969696958585858585848474848484747373737373637372626263626252626252604M{15152526262526262636262636363737474737374748474858484858585859595969696969696:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;rZK=/#  '3APVVVln8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:69696969696959595858585848584847474747373737363737372636262626252626261515au04151526262626252626362636363737363737374747474748485858585859695969696969696:6:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;oqNNN`PA3'   *7EU◘8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:69696969696959595858585848484747484747373747473636362626362626252626261515Mr251515162526252626362626363737374737374747474747585858585858595969696969696:6:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;fUE7*   ".;J###jǏ8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:696969696959595858585858484847484747374737363636362626362625262525161515Iw252515152625262626362626363637373737474747484848584858585859596969696969696:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;˓xZJ;.#  &2?N``_]_8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:696969696959595958585858484847484747373737373737362636263626252626261504fo151525252526262626263626373637373747474747484758585858585858695969696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;_aWWX^N?2&  )6DSɚ8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:696969696969595959585858584847484847473737363737373626262636252526162515gk0415251526262626262636363737373737374747484748484858585858595969696969696:6:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;{zzcSD6)  !,9GXꛛƍ8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:6969696969595959585858584848484747473747473736373636362626262626262515Pk1514252625252626262626363637363747374747484748585858585858595969696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;ʒhXG9,!  #/=LbLN8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:69696969695959595858585848484848474737373736373626362626252626261525cl15252516262625263636363636363737374747474848584858585858585969696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;MPr[L=/#  &2@Plllķ8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:69696969695959585858585848484748474737473737373726262626252626261525wi14152515252525263626263736374737373747474848485858585859595969696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;˾eee`P@2&  *6DS垞Ý8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:6:6969696959595958585848584848474737374737373736263626252626251515lg151515162626262626262637373737473747474748484858585858595969696969696:6:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;ɢdSD6*  !,9G`z|8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:696969696959595958585848484748474747373736373736263626262626261615aa2504251526262626363636363737373737474748484848585858595969696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;~pWG9,!  #.<KEEE~TV8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:6969696969595958585858485848474847373737363637262626262626262615y`251526262526362626363637373737474747484848585858585958596969696969697:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;VX===[K<.#  %1?Nbbb8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:6:6:69696969695958585858584848484747374737373737362626262526251525`04251626262526262636373737373737474748484848585858596969696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;YYY^N?1%  '3BR8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:6:6:696969695958595858585848484847474747373636362636262526252526o_15042626262626262636363737374747474748485858585858585969696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;ǞxyybRB4'  *6EU࣢kn8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:6:696969695959595858585848474747474737473736362626262625252625z[25152625262626362637373737373747484748485858585958596969696969697:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;npfUE7*  !,:HXGJ8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:6969696969595858585858584748474737473736363736262626262525"\042526262626362636373637373747474848584858585859595969696969697:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;HKiXH:,!  ".<J111q@C8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6969696969596959585858485848484747373737373636263626251526"Z0526252626262636363737373747474748484858585859586969696969697:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;@Cƻ+++[J<."  $0>Mjji;>8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:69696969695959585858584848484847373737373637263626252526 S25142626362626363637473737474748484848585858585969696969697:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;;>įbaa^N>0$  &2@PŦ8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:69696969696959595858585848474847473737373736362626262525!#N262526262636363736374747474747484858585858595969696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;âaP@2&  '4BRꧨ|}8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:6969696959585958585848484847474747373737372626262626#&T0425262636363736363737474748474848585858585969696969696:6:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;ācRB4(  *6E Z򨨨^`8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:6:6969696969595958585858484847474747373737373726262626!$ S2614363626373737363737474748474858585859585969696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;ac jUE6*  *8G%%%eCF8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:6:69696969695959585858584848484747473737373736362636#&K2526263636373736373747474748584858585858595969696969697:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;DGuWG8*  !,:I555p8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:696969696969595858584848484747473737363637362626%(M152626363636373737474748484848585858595959696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;...~YI:-!  ".;JCCCy8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:6:6969696969695958585858484747473747373637362636&)P0426263637363747474748474848585858585859696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;;;;[J;."  #/=MPQP8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:69696969595958585858484848484737373637363626'* F26153637364647374747484748485858585969696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;‘HHH]M=/#  $0>Nhhhvw8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:696969696959585858584848484847473737373736'* @26263636373737474748474848585858585969696969696:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;{|```^N>0$  %2@Oyyycd8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:6:696969696959595858585848474747473747373636),G043636363737474748484848585858596969696969696:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;egqrq`O@2&  &2APSU8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:6969696969595958585848484848474747373736*.H3737373737474748485848585858595969696969697:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;TWbPA2&  &3BRɯNP8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:69696969596958585858484848474747373737,/  , 36364737374748484848585858595969696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;PRbRB3&  '4BRְIL8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:696969696959585858584848484847473737*.     ' 9 7 7 ON^eiwy "!#%'$'&)'*,/373747474748484748585858595959696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;JMcRB4'  (5DSᰱEH8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:69696969695969585858584848474747373736#&kps~    #!#!# ##%$'#&#&%'&)&(&)%*'+'+'+'+(-)-)-&* 8 +/.2.3.3.3.2/303/4/4/4/4040304150415151515251626262626263637373637374748484858585858595969696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;FIdSD5(  )6DU걱AD8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:696969696969595858584848484847473747373736262626262616151515141405141404040304/4/3/3/3/3.2/2,1I*..2.2/2.3/3/20303/3/4/303040404151515151525162625262626363736373737474748474848585858595969696969697:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;BEfUD6)  )6EU򲲱>A8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:6969696959595958585848484847474737373637362636262525161515251515050504040404/4/4/403/3.3.2.3-0H+..2/3.3.3.2.3/3/3/4040403040415151514152515152626262626363637363737474748485848585858595969696969696:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;?AfUE6*  *6EV;>8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:69696969595958585858484848474747373736363626362626262515251414150504140403/4/404/303/2.3.2.3,0 D*./2.2.3.3.3.2/2/3/4/3/404030415051515151515152626253636263636374737474748474848585858585969696969696:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;;>fVE6*  *6FV9<8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:69696969696958585848484848474737473737372626262625262515151515150505040403/4/403/3/2/2.3/3.3+0 A*..2/3.2/3.3.2/3/3/404/3040404041514151425151526252636263637373737474747484748585858585969696969696:6:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;9<fVF6*  *6FV8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6969696969695958585848484848474737373637373636262626251615151514151505140303030403/3/3/3.2.2.3,0 = +/.2.2.3/2/3/3/3/3/3/4/4030404040415141425151526252636362637373637474747484848485858595959696969696:6:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;fVF6*  *6FV8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:6969696969595958584858484747473737373736262626252626161525151515051404140303/4/403/2/3.3/3.3*/ 9 +0.2.2.3.2.3.3/2/3/4/3/303030404051515151525152625262626263637373737474748474848585859585969696969697:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;fVF6*  *6FV9<8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:69696969596959585848584748474737373737372636262625261615151514151404040403/4/404/3/3/3.2.3/3*/ 6 +/.2.2/3.3.3.3/2/3/304/404030405151515151525152626252626363737374737474747484848585858595969696969696:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;9<fVF6*  *6EV;>8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:69696969595958585858484748474737373737373626263626262625151515151505041404/4/3/403/3/3/3.2.2*. 2 ,1.2.2.3.3.3/3/3/3/303/304041404051415151515152626252626363637374737474748484848585858596969696969697:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;;>fVE6*  )6EU>@8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:69696969596959585848484848474737473736372626263625261515151514150515140404/4/304/3/3/3.3.3/3). . ,1/2.3.2.3.3.3/303/4/4/3030304150515152515151525262636362636373647474747474848485858585859696969696:6:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;>AfUE6*  )6DU򹹸AC8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:69696969595959585858484747484747374737372636262626262525151514141504140404/4/404/3/3/2.2.3.2).) -2.2.3/2.3/3/3/3/3/4/304030414051515151525152626262636263636364737374747484758585858585959696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;ADeUD6)  (5DS깹DG8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:696969696959695958585848484847474737373737362626262625261515151515050404140304/3/4/403/3.3.3/3(-%.2.2/2/3.3.3.203/4/4/303040314051515151515252526252526263637373637374748474848585858595859696969696:6:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;EHdSD5(  '4BRẺGJ8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:6969696969595858585848484748473737373736363626252525261515151514041404040404/404/4/3/3.3/3/3(, .2/3/3.3/3.3/20303/4/4/404041405051515151525162626263626363737373747474847484858585859586969696969697:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;IKcRB4'  &3BRֺLN8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:696969696959595958585858484847474737373737363626362525262525151514051514040403/3/40303/3/2.2.3(,.1.2/2.2.3.2/3/3/4/4/3/4040404150515152515152626262626363637373737374747484748585858595859696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;MPbRB3&  &2APɼOR8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:6:6969696959595858585848484748474747473736373626362526261525251515150414040404040304/303/3.3.3(+.2/2.3.3.3/3/20304/4/404031404050514151515152626262636263637373737474748484848585858596969696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;QTbPA2&  %2@O\^8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:6:6969696969695858585848484748474737373737373626262526261515151515151414040303/4/4/3/3/3/3.3.3'*.2/3.3/3.2/3/30304/40304140405141515141515152526252636263737373737474748474848585858585969696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;`b`O@2&  $0>Nln8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:69696969696959585858585847484747473737373736362626262516151525151505150414040404/404/3/2/2.3&*  .3/3/3/2/2/3/303/3/40404041404051414141525152625263626263737373737474747475848585858585969696969697:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;qs}}}^N>0$  #/=Msss}~8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:696969696959595858585848484747473737373736362626362626261515251415051504140403/4/3/4/3/3/3/3&).3/3.3.3/2/3/3/3/4/404031404040514151515151625262636263636373747374747484858585858585859696969696:6:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;iij]M=/#  ".;J[[[8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:6969696969585958585848484748473747473737362636262526261515251515140505140404/304/40303/3/3%)!.2/3.2/3/3/303/4/40404041404051515151515252526262626263637373737474747484848485858586969696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;QPQ[J;."  !,:IMMLx8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:696969696959595858584848484847473737363736263626262526262515251415051504040304/30304/303/3$)+ .3.3.3/3/303/3/4/40403040415151514152515152626262626263737373737474747474848585858596959696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;DCDYI:-!  *8G===nAC8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:6:69696969695958585858584848474747373737363736263626262626251515141415051404040304/4/4/4/3/3%' 4 .3/2.2/3/3/4/4/3/40414041405041415151515152626252626363636373737474748474748585858595959696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;BD555|WG8*  *6E***dVW8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:6:6:69696969595858585848484848484737373737373626362626262615152515141504050404040404/4/3/4/3*-#     M.3.3/3/3030304/4030304040505151514251515262526262626363736373737474847484858585858586959696969697:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;X[$%$rUE6*  '4BWlm8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:6:6:69696969695959585858484847484747374737363736262626262516251515151415041504140404/4/40403/3$'        !#" " " "!#"%!%"$!$!%$'%'$($($('*.3/2/20304/3/4/40403041405151514151525162526262626263736363737474748484848585858595959696969696:6:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;pr gRB4(  &2@P8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:6:6:69696969695958585858484847484747473736363626262636262626251515251415140404140403/3/4/4/403/3/3.2/2/3/3.3.3.2.2.2-2.1-1.1.2-1-2.1-2-1.2-2.2-2.2.1.2-2-2.1-2-2.1.2.3.2.3.2.2.3/3/3/3040403/3040314040405151515151515252626262636263737373747374748484858585858585959696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;aP@2&  $0>M:=8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:6969696969595958585858484848474737473737373626262626252615151515151504151404140303/404/4/403/3/3/3.3.3.3.3.3.2.2-1-2.2-2.1-1.2-1-2-2-2.2-1-2-2.2-2-2-2-2.1.1/3.2/3.3.3.3/2/2/2/3/4/3/403/4040404040505141515152526262625263626363737363737474747485848585858595969696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;:=^M>0$  ".<J>@8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:6:6969696969595958585858484848484737373737363726362626262626151515151514050514040404/4/40403/303/2/2.3.3.3/2.3.3/3.2.2.2-1.2-2.1.2-2-1-2.1-2-2-1-1.2-2.2.2.2/2/3.3.3.3.2.2/303/3/4/4/303/303041404150515151515151525262625262636373737373747474747484848585858595969696969697:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;>Auuu[J<."  !,:H=<=nCE8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6969696969596958585858484847484747373737373736262626252525161515251515151505040404030403/3/3/3/3/3/2.3/3.2.3/2.3.3/3.3.2.2.2-2-1-2.2-1-2-1-2.2.2.2.2.2/3.3.3.3.3/3.3.3/202/3/3/404/3/404041404140415151515151515262625262636363636374737474748484858585858595969696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;DG555}XH:,!  *6EU\^8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:6:696969695969585858584848484747473737373637372626262626252625251515151405050414040304/4/4/4/3/303/3/3/2/3.2.3.2.3.3.2.3.3.2/2.2.2.2.2.1.2.2.2.2.3/3/2.3/3.3.3.3.3.2/3/3/3/4/3/4/3/30303140405151415151515151525252526262626373737374737474848484848585858595959696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;`afUE7*  '3BR{{8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:6:69696969695959595858585848474847473747373636362636262626251615152515151414041414040304/304/4/4/4/303/3/2/3.3/3.2.3.2.3.3.2.3.3.2.3.2/2.3.2.3.3.2.2.3.3.2/2/3/3/3/2/303/4/404/403040404040415051414151525151625262626262636373747374737474748584858585858586969696969697:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;bRB4'  %1?N8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:6:6:69696969695858585858484848474747473747363737262626262626261525151514151504150404040404&) !!  ~~znmmmmmmnYZZZZZZZZXXXXWWVVVUO"&/3/404040414040505041515152515152626252626262637373637474747474847585858585859595969696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;^N?1%  #.<KzzzKM8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:69696969695958585858584847484847473737373637362626362626262625151525151515050404040404_z/40404040404141514151415251525152625252636263637373737374747474748485858585859696969696969697:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;MOqqq[K<.#  !,9GXXX{ce8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:69696969696958595858584858484747474737373636362636263626262516151515151515151515041404Zw04040404040505051415151515151526252536262626363737373747474748484858585858595959696969696:6:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;hjOOOWG9,!  *6D\xy8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:696969696959595858585848484748474737474736363626263636262626261525151415151505051404Xt03041404050505151514151515252626262626263636363747474747474747484858585858585859696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;mSD6*  &2@P8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:796:6969696959595858585848484747484747373737373736362626252626251515152514141415050504Xo14140414051515151515251515262526262636263637373747374747474848584858585858595969696969697:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;`P@2&  #/=LDG8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6969696969695958585858584848484847474737373737372626362626262526162515151514141505/4Vl041514050415141415251515252625252626262636363737473747474748484858585858596959696969696:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;FH[L=/#  !,9G^kl8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:6:69696969596959585858484848474747373747373737372636262625262526251525151515151404Uh050505151515152515151626262625262626263736363637474747474847485858585859595969696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;qrnWG9,!  )6DS8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:6969696969695959595858584848474847473737373737373726362626252526251615152515151404Sd0505141414152515152525252625262636363636363747373747474748484858585858595959696969696:6:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;cSD6)  &2?NMO8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:69696969695959585858585858484848474747373737373636262626362626252616251525151504Q`1514141425151515162526252625362626373737363737374747484848485858585859596969696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;PR^N?2&  ".;Jhj8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:69696969696959595858585858484748484747473737373737263626262626262626161515152504O[1514152515152515262626262626262626363737374737374747484758485858585859596969696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;oqxxyZJ;.#  *7E111e8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:6:6969696959595958585858485848474847473737473736363626263626262526262525151504NW1515251515251626252525263636262637373747373737474747484858485858585959696969696969697:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;***tUE7*   '3APTV8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:6:6969696969595958585858584848484747473747373737373726362636362625252616151503LS151525252526252626252626262626373737373747474747484847584858585858596969696969696:6:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;XZ`PA3'  #/=Kyyy8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:69696969696959595858585858484748474747474747473637363626262626262625252526/3IN151525151626262525362636263637373636373737474747474848585858585859595969696969697:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;oopZK=/#  !+8F^FH8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:69696969696959595858585848584747474747373737373737373636263626262526262604HH1525252625252626362626262637373637373747474748484848485858585858595969696969696:6:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;HJnUF8+!  '3ARdf8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:69696969696969595858585858484848474747474737373737363737262636262626252604[Y16252626262625362626263637373736373737474748474748585858585858595969696969696:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;klbPA3'  $/<J:=8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:6:696969696959595959585858485848474847474747373737363737362626263626262614.2,0,0,0,0,/,/,/,/,/,/,/,/,/,/,/,/,/,/,/,/,/,/,.,.,/,.,.,.,/,/,/,/,/,/,/,/,/,/+0.126262526262626362626363737363747374747474748474848585858585859595969696969696:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;;>ZJ</$   *7E Wbc8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:6969696969696959595858585858485848484747474747373736363736262636263626262625262526161515251515151525151525151514141515151525152515251515151525151525262625252625262626262636363736373637374747474748484848485858585859595969696969696:6:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;hi gTE7*   &2@N||K~XZ8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:6:69696969696959595958585858485848484748474747374747473736363736363636263626263625262526262525252525252526252525262526252626252625262626262636363626262636373737373747373737374747474848484848585858585959595969696969696:6:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;^_ZK>0%  !,8FU~~~~st9<8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:696969696969695959595858585848584847484747474747373737373736373736363626263636263636262626252526262626252526262526252626262526263626262626262636263637373736373737374737474747474847584848585858585958595969696969696:6:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;:=}}eTF8,!  '2?N}~~}}}|||QS8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:6:696969696969695958585858585848484847484847474737374737373736(* " " " " "ommmmeQPPPPPPR + . . . . . . . . - - - - - - - - - , $ &(3636373737473737374747474848474848485858585858595969696969696969696:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;UW]N?2'  "-:HRRRs~}~~~}}}||||{|{{z:=8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:6:696969696969695959585858585858485847474847474747373747373737\"$373736473737473747474748484847584858585858585959696969696969696:697:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;:=IIIVH:-"  (4BO~~~|}}|||zz{zyy[]8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:6:69696969696969595958585858585848484848484847474747373747\!#3747373737474747474847474758485858585858585959596969696969696:6:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;ac_OB4(  $.;IFFFozyz||{{{{zzzyyyt{u;>8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:6:69696969696959595959585858585848584847484747474747473737[!#4737473737474747474748484848485858585858595959596969696969696:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;;>>>>|WI;.$  )5BP|{{z{zyyyxxxwwwef8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:6:6:6969696969695959595858585858584848584748474847474747Z "373747474748474748484848484858585858585959596969696969696:697:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;no_PB5)  $.<Iyyy||}yzzyyxwwxvvvuuuHJ8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:696969696969696969695858585858585848485848484848484747Z!474747474848484848484848585858585858595969696969696969696:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;JMonoWI</%   *5BPxxxxxxvwvvvvuuugi8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:6:69696969696969696959595858585858584858484847474847Y| 47484848484847484848585858585858595969696969696969696:6:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;qr_PB6*   %/;IAAAlvvwwwwvvvuuutttsssUV8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:6:6:6969696969696959585958585858585848485848484847Xx 484748474758484858585858585859596959696969696969696:6:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;[\~~~999yWI;/%  *5BQwvwuvuuuusssrrroto?B8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:6:69696969696969595959585858585858585848584848Vu4848484858485858585858585858585959696969696969696:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;ACzy~~~`PB5*  $/;Hdde}wwwttttttsrsrqrqqqab8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:6:6:6969696969696959595958585858585858585848Vp48485858585858585858585958695969696969696969696:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;ii||}}}}~\\\VH;/$  )4ANtttsssrsrqqqpppoooPR8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:6:696969696969696969695959595958585858585858Ul584858585858585858585959595969696969696969696:6:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;VWzz{|{{}|}~}}~~~^NA4)  #.:G)))arsrrqrqqpoooonnkok;>8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:69696969696969696959596958595958585858Th58585858585858595958696969696969696969696:6:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;Lyyypqqpoponnnnnllldxd8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:6:6:6969696969696969695969585959585858Rc58585858585958585959696969696969696969696:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;mlxxxxyyz{z{{{ZM?2'  ",8DRopooopnnnmmmlllkkkYZ8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:6:6:696969696969696969595959595958N\58585959595959596969696969696969696:6:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;`avwvwxxyxxzzzzzzaRE8,"  &1=JkkkmmmmmmmmlkkkjjkiiiEG8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:6:6:6969696969696969695959595948v <====>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>==9 |"585959585959696969696969696969696:6:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;HKuuuuvuwwwxxxxxxaaaWJ=1&  !*6BO|||lllkllkkkjiihiihgg@B8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:6:7:6:696969696969696969696969484848474848484747474747474747474747474747474747474747474747474747474747474848374859696959696969696969696969696:6:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;BDssstttuuvvvvwwv^OB6*   $.:FTqqqklkjijiiihhhfgf_q`8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:6:6:696969696969696969696969596959595958585958585858585858585858585858585858585858585859585859595859595959696969696969696969696969696:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;j{jrsrtttuttvvv{{|bTF:.$  (2>KcccijjhhhgggfffeefY{Z8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:6:6:6:69696969696969696969696959695959695859595859595859585859585858585858585958595959585969596969596969696969696969696969696:6:6:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;bcqqqrrqsssuttoooYK>2(  "+6BPihighhgggeffeedcdcNP8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:6:6:6:696969696969696969696969696969696969595959596959595959595959595969595969596969696969696969696969696969696969696:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;TVpoppppqqqrsrsts^PC6+"  %/:F$$$^}}}fefeffeeedccbbbbbbIK8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:6:6:6:6969696969696969696969696969696969695959595969595959695959596959695969696969696969696969696969696969696:6:7:7:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;NPmmnnnoppoqpqpppkTF:/%  (2>Knnneefeddccdbbbbba_`_BD8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:7:7:7:6:6:696969696969696969696969696969696969696969696969696969696969696969696969696969696969696:69696:7:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;EGklkmmmnnnooopppxxw}~}YK>2(  "+6BN```dddcccaab`a`___^^^?A8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:7:6:6:6:6:696969696969696969696969696969696969696969696969696969696969696969696969696969696:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;ADjjjkklmllmnmnookkk]NB6+"  $.9FScccbbbaaa```__^^^^YdZ9<8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:6:6:6:6:6:6969696969696969696969696969696969696969696969696969696969696:69696:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;98;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:6:6:6:6:69696969696969696969696969696969696:696:69696:6:6:6:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;@8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;@Baj`dddfffggghhhihi[NB6,#  %.9EQ{{{\[\[[\ZZZYYYXXXWVWVVV?A8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;BDbbbbbcdedeeegffgfg^QE9.%  &0;G000alllZZZYZZXXXXXXVVWVUUSTSCE8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;HJ_a_aaabbccdceedeeevvu***mTG;0&   )2>I888gyyxYYYXXXWWWUVVTUTSTSRRREF8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;LM^^_`_`aaabbbdccddd333qVI>2)   "*4?JpppuutVVVVWWUUUTTUSSSRRRQQQIkK8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;StT]]]^^^__````babaaa}}}hhiWK?4*"  #+6AMzzzSSSTUTTTSSRRRRRQPPOOOK\K;>8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;=@VgV[\[\\]^]]___```^^^sstZMA6,#  $-7BNTTTSSSRRRQQQPPPOONNNMLLL=?8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;@BXXYZZY[[Z\\\^]]^^____ZNB7-$  %.8COMMLRQRQPQPOOMONMMMLLLKKKDrD8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;LzMWWWXXXYZYZZZ\\\\\]XXXZOC8.%  &.:DPZZZNNNNOONMMLLLKKKJJJIIIE[F9;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;:?8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;CEQVRTTSTUUUVVXWWXXYZZYsss\PE:0&  '0:EQCCCLKKJJJIIIGHHGGGEEFDED@e@9;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;:=IoIQPQRRRSSSTTTVVUVVWOON\QE:0'   '0:EPRRRHHHIHIHHGGGFEEECDECCC@FA<=8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;BDMRMPPOPPQRRRSSRTUTTTT]]]\PE:0'    '0:EP^^^FFGGFGFFFEEDDCCBBCAAAA@@=W>9;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;<>GbGLLLNMNOONPPPQQRRRRRRRgggZPE:0'    '0:DOFEEEEEDDDCCCBBBAAA@@@>?>>==:_;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;DhEJJIKKKLLLMNMONNPPPPPPQQQZOD:0'    '09CNQRQCDCCCCBAB@@@?@?>>>===<<;:A;8x98:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;?AELFIHHIIIKKKKKLMLMNNNOOO[[[wwwYNC90'   &/8BMtttBABAA@@@@??>=>><<<;;<9::999888797:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;<>DDDEEEGFFHHHIIIJJJKKKMMLMMNlllWMB8/&  &.7AK:::gCCB@?@??>===<=<:;::9:9887776675H5678;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;;=?S@BBBDDDEEEFFFHHHHHIJJJKKLNNN555pVKA7.&  %-6?I221a;;<;;;<;;;:::99888777566545444353687:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;:;>A=?@@AAABBBCCDEEEFFEGGGGGGFGG,--kTI?6-%  $,4>GQ```>??9999998886665554444333333333>35r67:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8:9v:;F;===>>>@?@AA@BBACCDDDEDEEIJJihi[QG>4,$  #*2;ENVVU1107876664554443333333333333333335p6798;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:8s9999:::<<;<==>>>??@@@@ABACCC===___YNE;2*#  ")09BLJJJlNNN1225453333333333333333333333333533;3688;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;685=5686888999:::;<;<<<==>>?>@A@>>>YXXDDDuVLB90)"   &.6?IS½/..1113333333333333333333333333333333<35n6688;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;685n64<3433555566777999:9:;;;<<<>>>===9::^RI?6.&   %,4<FNONN---2223333333333333333333333333333333335n6797:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:795n63333333333333344456567778889:9:::666WWW󉊊YNF<4,%  #*1:BK'&'^GGG3333333333333333333333333333333333333333@34[557698;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;69574[53@3333333333333333333333544555776798999MNN###gTKB:1*#  !'.6>FPvuv...---3333333333333333333333333333333333333333334]5677:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:674]5333333333333333333333333333333333333444/00233yxxYPF>6.'!  $+2:BKT<<<5552223333333333333333333333333333333333333333333=33J44V45y6687:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:685y64V43J43=3333333333333333333333333333333333333333222555===^TKB:2+$  "(/6>FOvvv///+++2223333333333333333333333333333333333333333333333333333333K44e55|7686979797:7:7:7:7:8;8;7:7:7:7:7:797969685|74e53K4333333333333333333333333333333333333333333333333333222+++///oonWOF>6/("  %+2:BJR```EEE3330002223333333333333333333333333333333333333333333333333333333333533733933<33=33?33@33A33A33@33?33=33<3393373353333333333333333333333333333333333333333333333333333333222000333EEE___ZRJB:2+%  "(.6=DM...aHHH**)///333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333///***HHH***iUMD=6.("  $*18?GNqqq}tttXXX===,,,...111333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333111...,,,===XXXtttlllWNG?81*$  !&,2:AHQJIIl```111000111222333333333333333333333333333333333333333333333333333333333333333333333333333333333333333222111000111```EEEsXPHA:2,&!  "'.4;BIPsss\\\FFF222'''***+++---...///000111222222222111000///...---+++)**'''222FFF\\\sss׃WPIB;4.'"  #)/5;BIPQQQouuueeeWWWMMMFFFMMMWWWeeeuuuLLLuWPIB;5/)#  $*/6<BIO_`_sĹZZZyVOIB<6/*$  %*.5;AGNU[TNGA;5.*%   %).4:?FKYeeewaaa|_RKF?:4.)%   $(-28>CINWݧ ]TNIC>82-($  "',06:@EJ R"""^⏏!!!bWPJE@:60,'"  !%*.27<AFKP<<@BEGIJMNOPRRSTUVVVVVVUTSRRPONMJIGEB@><:631.,*&#!  !#&'*,.02468:;=>@ABBDDEEFFFFEEDDBBA@>=;:86420.,*'&#!  !"$&'**,./0223456666666666543220/.,**'&$"!  !"#$&&&'())******))('&&&$#"!        ???????????????????(     !$$$$$$!   !%+/25789:<<<<;99753/*&!  "(058<=>FKRZ_cfffeb]XPKD@@=95/("  $,49=?K[ l"""%%%'''***---///000111111111111///...+++(((&&&$$$~eWHA@:5,$  ",4:>I[{###)))...222333444444444444444444444444333333444444444444444444444444333000***&&&jXFB<5*"  %19=Ib)))///333444444444333333333333333333333333333333333333333333333333333333333333333444444444444111,,,###zZFA;1%  (4;@X(((000444555444333333333333333333555555666777777444333222111111333444666777777555555444333333333333333333444444222--- rPC>3'  '4;D f!""///333455333333333333333333555666555111---***&&&"""%%%,,,111555787676333./.((("""$$$(((+++///333666666444333333333333333444444111(((XE@4'  $2<Cl)))566777555333333333333444666555000)))$$$)))777NONhhh~~stt\\\AAA///%%%&&&-,-333666656333333333333333454443...YE?1$   .:@ e,,,;<;<<=::9676444333434666555...%%%)))>>>mmmTTT000%%%(((222666555333333333444787999232UE<-   '6>T***?>>AAA>>>;;;9:9777666666111&&&)))LLLޜݕܑܑݕޜmnn333%&&,,,555656343454767999====>>344}LC6&  .;C?>>FFFCCD@@@>>><<<;;<;;:010'('777|||ܒy{RT?B7:/2,/+/*-)-)-*-+/,//27:?BQTy|ܒUUU***(((444998888:9:;;<>>>BBBAAA///aG>-  $5>X888IIIIIIEEECCCAAA@@@@?@555--,LMLه_b>A/2*--036697:9<;>;>;>;>;>;>;>;>9<7:6936-0*-/2>@_bهopo222***888==><==??>AAACDDGGGAAAND4$  (9@wGGGONOKKKHHHGFGDEDEEE<<<232SSSׂMP14*-1469:=;>;>9<9<8;8;8;8;8;8;8;8;8;8;8;8;9<9<;>;>:=6914*-14MP؂{{{777111???AAABAADDDEEFJIJJJJ333[G:'  ,;H333PPPQQQMMMLKKIIIIIIEEF777LLL`b7:+/47:=;>:=9;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;9;:=;>:=47+.7:acooo667:::FFFEEEGGGIIHLKKOOOCCCrI?+  0=TCCCVWVSSSPPPOOOMMMMMMAAABCCߧQT/2038;;>9<8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;9<;>8;/3/2QTߧZZZ888FFFJJJJJJLLKNNNRSSNNN$$$LB.  2=_MMMZZ[UUUSSSQQQQQQNNN???edeQS-025:=:=8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;:=:=25-0QSCDDBBBNNNMMMOOOPPPTTTUUT100PC0  3>mTTT]]]XXXVVVTTTUUUKLKIII`b-036;>:=8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;:=;>36-0acccc@@@PPPQQQRRRTTSVWVYYY;;;TD1   3="!!tZZY_``[[[ZZYWWWXXXKKK^]^ւ3703;=:=8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;:=;>0347ׄFEFPPPUUUTUUVWWYYZ]]]BBBVE1  1=$##t^^^cbb^]^\\\[[[ZZZKKKwwxMO,/9<:=8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;:=9<,/MPPOOPPOXXXWWXYZZ\\\aaaEEEVD/  /=!!"o``afefa`a___^^^[\\OPPؒ3625;>8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;9<8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;9<<>,/RU}}}TTTbaba`accceefhhh;;;L?(  $8L[\\opojjjhhhgggede^^^޽BE14;>8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;;>14CEXXXdeedddfffiiiijj-.-H:#  4ARRRrrrmmlklkjijihh^]]ܳ6947:=8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:6:6:6:6:6:6:6:6:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;;>4769ݴwww]]\ggggggiihnmmgggrF4  .;:::tssqqqnnnllmmmm`a`ܻ5869:=8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:6:6:6:6969696969696969696969696:6:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;:=6968޼ooocccjjjjjjlklqqr`__[D- (8 dooovuuqqqooopppcccBE47:=8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:6:69696969696969696969696969696969696969696:6:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;:=57BEeeeiiilllmmmoootttOOPN=&  5IfeeyyztttrrrrrrjjjQS15:=8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:69696969696969695959595959595959596969696969696969696:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;;=14QTdddnonooopppsssttt334G6   /;JJK|||wwwvvututqqqqqqhj-0;>8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:6:6969696969695959595958585958585858585859595959696969696969696:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;;>-0ikeefqrrrqqsssxxxkkkbC- %7 bwww}||xxywwwvwwllmҋ-0;>8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:6:696969696959595858585858585858485848585858585858585858595969696969696:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;;>,0Ӎlmlttttuuvvv|||VVVL<$ 1Aggg|{{zzzzzyppp֭378<8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:6:696969696959595858585858484848484858375947484848585858585858595959696969696:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;36ذppptutvvvxxxzzzyyy---}E1 (8;;;z}}}||{xxx|}}JM25:=8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:6:69696969595958585858484848484847478; " ) 04694747484848484858585858585959696969697:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;:=25KNnnnyyyyyy{{{hhhU?' 3Gsssх~~~~~sstxz-0;>8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:6969696969595858585848484848474747367:#% , 03593747474747484848485858585859696969696:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;;>-0{|uuu{|{|||~~???E4  )7IIIyyzұ489<8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:6969696959595858584848484747474747366:"% & /259263747474747474748485858585969696969697:7:7:7:8;8;8;8;8;8;8;8;8;8;8;9<47ִuuu}||~~~qqqZ@' 2Iyyyԉ|||[]14:=8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:6969696959585858484747474737373737266:$'  ( /2592647373747474747474858585858596969696:7:7:7:7:8;8;8;8;8;8;8;8;8;8;:=14[^wwwEDDE3  '7JJJ}}}ϛ.1:=8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:6969696958585858484848474747373737266:$(  ' /2582537373737474747484848585858596969696:7:7:7:7:8;8;8;8;8;8;8;8;8;8;:=.1ў}}}tttX>%  0CxxyʎLO379<8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:6:696969595858584848474747373737362669&( & -0582637373737374747484848585858596969696:7:7:7:7:8;8;8;8;8;8;8;8;8;9<36MP|||<<<C0 #4888r̓/2:=8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:69696969595858484848473737373736361559')  % -1481536363637374747474848585858596969696:7:7:7:8;8;8;8;8;8;8;8;8;8;:=/2ϗoooP;"  ,:mmmMO369<8;8;8;8;8;8;8;8;8;8;8;7:7:7:6:696959595858484847474737373626361559'*#-0481526263637373747474848585858596969696:7:7:7:8;8;8;8;8;8;8;8;8;9<36MOrA+ 1 U꒒˙14:=8;8;8;8;8;8;8;8;8;8;8;7:7:7:6:696969595858584847473737373626261559)+# -0482526363637373747474848585858696969697:7:7:7:8;8;8;8;8;8;8;8;8;:=04ΜZZZG 4 &4WWWXZ14:=8;8;8;8;8;8;8;8;8;8;7:7:7:7:696969595858584847474737373636261549(, ,/481526263637373737374848585859596969697:7:7:8;8;8;8;8;8;8;8;8;:=14Y\Z<$  ->xxxɬ7:8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:696969695858584847473737372726261548),!+/3815262626363737374747485858596969697:7:7:7:8;8;8;8;8;8;8;8;8;8;7:α,,,zB, 0Z񘗘pr.1:=8;8;8;8;8;8;8;8;8;8;7:7:7:6:6969695958584847473737362626261549),,/38142626263737374747485858585969696:7:7:7:8;8;8;8;8;8;8;8;8;:=.1stfgfF 4 $2STSHJ589<8;8;8;8;8;8;8;8;8;8;7:7:7:6969695958584848473737372626361548*-*.47142626263737374747485858586969696:7:7:7:8;8;8;8;8;8;8;8;9<58IKX:# *7qpqŘ259<8;8;8;8;8;8;8;8;8;8;7:7:7:6:69695959585848474737373726261548*-+.48152626363637374748485858596969697:7:7:8;8;8;8;8;8;8;8;8;9<25ʜj@(  -Kݟqs.1:=8;8;8;8;8;8;8;8;8;8;7:7:7:6:696959585848484737373726261448+. *-381426262637374747484858585969696:7:7:7:8;8;8;8;8;8;8;8;;=-1tuWWWB / 0---cTW369<8;8;8;8;8;8;8;8;8;8;7:7:6:696959595848484737373726262548+/*-4815262636373747484858585969696:7:7:7:8;8;8;8;8;8;8;8;9<36VXwwwI 6 "1TTTǽvw.2:=8;8;8;8;8;8;8;8;8;8;7:7:6:69695958584848373737362547-0 ' *,48153636374747485858596969697:7:7:8;8;8;8;8;8;8;8;:=.1y{777~@*  + M槨df04:=8;8;8;8;8;8;8;8;8;7:7:7:6:696959585848474737372648/2 , ),592536373747484858595969697:7:7:8;8;8;8;8;8;8;8;:=03gifff@/  -""!ZIL489<8;8;8;8;8;8;8;8;8;7:7:7:696969595858484737373647/3 , ),5925373747474858585969696:7:7:7:8;8;8;8;8;8;8;9<47KMvvuD 2  /333f=@6:8;8;8;8;8;8;8;8;8;8;7:7:7:696969595848474737364704 4 ),59253747474858585969696:7:7:7:8;8;8;8;8;8;8;8;69=@~~~K 5  1GGGs7:8;8;8;8;8;8;8;8;8;8;8;7:7:6:6969595858484847264815 4 )-6:364747474858586969697:7:7:8;8;8;8;8;8;8;8;8;7:P7!1___269<8;8;8;8;8;8;8;8;8;8;7:7:6:69695958584847474825;%(483747484858595969697:7:7:8;8;8;8;8;8;8;8;9<25X9 #1ooo}~14:=8;8;8;8;8;8;8;8;8;7:7:7:6:6969595858484747159      % . 7 >ENJc153747484858595969697:7:7:8;8;8;8;8;8;8;8;:=03ǿ^9!%2zzzvx03:=8;8;8;8;8;8;8;8;8;7:7:7:6:6969595858484748.2#%&*')'*'*(+(,(,),)-).)-*.(,%h/4,0-0.2-2.2/304152637485948363747484858595969696:7:7:8;8;8;8;8;8;8;8;:=03z|Żb;$&2qs/3:=8;8;8;8;8;8;8;8;8;7:7:7:6:6969595858484737485:4848383736272615151504/4-2 + l26.3/30405040415151515252525363737474858585969696:7:7:7;8;8;8;8;8;8;8;:=/2uwùe;$&1mo/2:=8;8;8;8;8;8;8;8;8;7:7:7:6:696959584848473737262525251514050403/3/3/2/3,1&m16.1.2/3/4030404141515253636373747474858585969696:7:7:7;8;8;8;8;8;8;8;:=.2rsf<$&1mn/3:=8;8;8;8;8;8;8;8;8;7:7:7:6:696959585848473737372626261515150413/4/4/3/3+0#o16-2.3/3/3040305151515252526373747484858595969696:7:7:8;8;8;8;8;8;8;8;:=/2qrf<$&1np03:=8;8;8;8;8;8;8;8;8;7:7:7:6:696959585848473737262626252515151504/4/3/3/4+/q26-2/3/3/3040405151515262636363747484858585969696:7:7:8;8;8;8;8;8;8;8;:=/2stf;$%1rs14:=8;8;8;8;8;8;8;8;8;7:7:7:6:69695958584847373736262625151404040404/4/3/4*/r26.2.3/3/4030415151525262636363747474858585969696:7:7:7;8;8;8;8;8;8;8;:=03vxc;$#0|||uw159<8;8;8;8;8;8;8;8;8;7:7:7:69696959585848474737363625252515150404/4/4/3/4*.r26-2/203/4/41405151526262636373747484858585969697:7:7:8;8;8;8;8;8;8;8;:=14z| _9!"0qpp{|369<8;8;8;8;8;8;8;8;8;7;7:7:6:6969595858484747373736262625151514040404/304)-q27.2/30404041405141526262637373747484858585969697:7:7:8;8;8;8;8;8;8;8;9<36Z9  /]]]{7;8;8;8;8;8;8;8;8;8;8;8;7:7:7:69695958584847473737262626161515051404/4/304)- y27.2/3/4/4140405141525262626373747484858586969697:7:7:8;8;8;8;8;8;8;8;8;7:S7 /DDDk/  )Cfg14:=8;8;8;8;8;8;8;8;8;8;7:7:6:6969595858484847373736262625151515151403/4040404/5/50403/3.3/3/3.3.3.3/3.3/3/3/4/40303/4/40304040515152626263636374747485858596969697:7:7:8;8;8;8;8;8;8;8;:=03jkccc= * (4rs369<8;8;8;8;8;8;8;8;8;8;7:7:7:69695958584848473737362626262515150505040404041404040405050504/40404040515150506152504/30304051515151526262637373747484858585969696:7:7:7:8;8;8;8;8;8;8;8;9<36xz%%%k<&#/;>7:8;8;8;8;8;8;8;8;8;8;7:7:7:6:696959585848474737373726262625151515040404(+$(%(#&#&"&!$!$!$!$"""""""" " "+0040404051515152525262637374747485858596969697:7:7:7:8;8;8;8;8;8;8;8;7:;>[8! .RQRoKM589<8;8;8;8;8;8;8;8;8;7:7:7:6:696959585848484737373626262626151515050514 6 #&26/3051515151526252636373747474858585969696:7:7:7:8;8;8;8;8;8;8;8;9<47MOL 5 +"""T\^15:<8;8;8;8;8;8;8;8;8;8;7:7:7:696969595858484847373737363626251515151504 3 $&36/4051515152626263637374747484858585969696:7:7:7:8;8;8;8;8;8;8;8;:=14ab?0   );rt479<8;8;8;8;8;8;8;8;8;8;7:7:7:6:6969595958484847373737362626252515141504 3 "&3704151515262626263637374748485858596969697:7:7:8;8;8;8;8;8;8;8;8;9<47zzFFFz= ( $/AC698;8;8;8;8;8;8;8;8;8;8;7:7:7:6969695958585848474737373626262516151504 0 "%37041515262526263737374747485858595969696:7:7:7:8;8;8;8;8;8;8;8;9<69BE[9# -OOPjXZ259<8;8;8;8;8;8;8;8;8;8;7:7:7:6:69695959585848484737373726262626252504 / !$47141516262626273737374748485858596969697:7:7:8;8;8;8;8;8;8;8;8;9<25[]I 3 +Ewx8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:69696959585858484747473737362626252604 +  #481426252526363737374748485858596969696:7:7:7:8;8;8;8;8;8;8;8;8;8;7:pop>- &1IK479<8;8;8;8;8;8;8;8;8;8;7:7:7:7:696969595858484847473737373726252615<!%5815262626373737374748485858585969696:7:7:7:8;8;8;8;8;8;8;8;8;9<47KMb;$ -PPPjhi489<8;8;8;8;8;8;8;8;8;8;8;7:7:7:696969595958584848474737373726262626.2-1-1-0-0-0-0-0-0-0-0-0-0-0-0-0-0-1-1-1042626262626373747474848485858596969696:7:7:7:8;8;8;8;8;8;8;8;8;9<47opI 4 +?BD699<8;8;8;8;8;8;8;8;8;8;8;7:7:7:696969595958584848474747373737262626262626262525252525251525251516262626262626263636373737474848585858595969696:7:7:7:8;8;8;8;8;8;8;8;8;9<69CFbbb= + $0}}}bc479<8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:69696959595848484847473737373736474848484848585858585959595959595959696937263737373747474748585858596969696:7:7:7:8;8;8;8;8;8;8;8;8;8;9<36ijY9" -O~zzAC699;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:696969695858584847474747373737+-')'*%(%'$'!#!#!$!$        ~ /2483737374747474858585859696969697:7:7:7:8;8;8;8;8;8;8;8;8;9<69BE@1  '4vywxab379<8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:6969695959585858484847474737 5(+59364747474848585858596969696:7:7:7:7:8;8;8;8;8;8;8;8;8;8;9<36ii&%%f;&  .000ZyyyxwxFH589<8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:6:69696959595858584848474747 3 (+59374747484858585859696969697:7:7:7:8;8;8;8;8;8;8;8;8;8;9<58HJC4 )5rsq{t{eg699;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:6:696969695958585858484848 3 (*6937484858585859696969696:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;9<69op}888k;( /000XqsqwowOQ489<8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:6969696959585858584848 2 '*6;475858585859696969696:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;9<37TU|z|{B4 )5opotrtlul>A698;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:6:69696969695958585858 3 &)7;5758585959696969696:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;9<69@Bxxzxz+++e<' .Nilitks]^698;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:6:6969696959595858 . %(8;47595969696969696:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;69ff}u}|~|@2 &1{{{ikipfpPR589<8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:6:69696969595958K ( & & & & & & & & & & & & & & & ' ! (*7;4869696969696:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;9<47UW|s|qtqU:$  .>hiijjjieiEG589<8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:6:69696969695848484847474747474747474747474747474838586969696969697:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;9<47HJwswpopeef= . !/OOO_`aaigiajb>A698;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:6:696969696969696969695959595959595959596969696969696969697:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;9<69?Bnunqnp}~|D 6  )2\^\gagZpZ:=7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:6:6:69696969696969595959595959695969696969696969697:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;9<7:;>ezfpkpnpnY;'  - .  0!!!OkkjZ\Y`X`PyQ8:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:6:6:696:6:6:6:6:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:8:XZmdmaca@5  &1aaakaaaWYW]T]OqP:<7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;9<7::=Y{Yjai^_^I 9$  *2^^]RUSYQZOdO;>6:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7;7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;59=@YoZf]fZ\Z T;( .5bbbMPMWPVOVO>@7:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:7:7:7:7:7:7:7;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;69AD[c[a[aX[X**+`= , 0:kjkGHFRPRMJNAB8:7;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:GH[X[ZWZVWVKKKm= 0  0=CDCKLLLBLBkC9<7;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7::=KtLZP[QQQ^`^TTSs=1   0=FFFCECJAICLC;=8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:7:ABPYPUMUJLIppoSSSu=3  !0=ZYY:<:EBEC;C<_=8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:;>FhFQIQKIKJMKJJJo=3   0:;;:9;9A:@<;<8m98:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;9<@tAIIIKDK@B@___233d=1  /5WWX/1/888;1;6<65n67:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;G>>>EHGX; /  .2___jBBB*,*3246,54844h6798;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8:9l:>B>A9A;9;:<:opoK: -  *1!!!Ný665')'0/05-55254R5688:8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;7:676S6979:2:434363_`_xxwA8)  & 0<>>>&)'+,,3.36/64945c557698;8<8<8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8<8<8;7:685c66:65.51+1***353cdd566d;5$   .2EEE^RSR/1/%'%.-.2*26.65565E55m657677:8;8;8;8;8<8<8<8<8<8<8;8;8;8;7:67575m75F56664,41(0)()()(;=;zzz󐑑H8/  )/=JJJ454$&$*)*/+/2)26,66165855B55W55j75s65y75~76767575y75s75j75X65B66967264+40'0-*-%%%*+*>?>```444a: 4 (  !.2NdedFGF342%'%%&%)()-),/)/1(13)24*45,56,56,55+53*32(21'0.(.+(+'&'#$#+-+<>]]]jǼU;4 0'  $ *-1E```kݶ***Z>41,#  '+-/;(((TiiioðGFGd K720-&  %)+./4D"""RBBB_[[Zk||{|iiisMMMf332ZN>320-*$    #'(*-./001111111110-+*(#      "#&&&&&&#"     ?????????(@ @           %2>JT Y XSI=1%  "7 [$$$,,,///111444444444333111...***"""~T5#   . [ ---444666555010+,+')'$&$"$"#$#%'%(*(,-,222666666333***S-  0m+++333777222'('"$"*+*ABAZX[pjo{kfkVTV;;;'('"$")*)444777111$$$ `/  '`434===;<;---"$"9;:plpd`c010"$"///:::999+++T(  <111EEEFFF777,.,VWVceHJ69.1,/,//269HKegEEE&(&888DDD???$$$8 !ZEEEPPPGGG565[]\úvx<>(,)-/2478:9<:=:=9<8;47.1),),=@z|HIH242HHHLLK999M% %)**vRRRWWWEEEILI[]+.,/69;>;>:=9<8;8;8;8;8;8;9<:=;>;>69+.+._a9<8;8;8;8;8;8;8;8;8;8;8;8;8;8;9<;>:=/2),kmXYXGHF[[[SSSm*$;;:bbb```NOM46.1;>:=8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;:=;>-047xvxJLJa`aZZZ m(!777zgggfefWXVvw(,8;;>8;8;8;8;8;8;8;8;8:8:7:7:7:7:8;8;8:8;8;8;8;8;8;8;;>7:'*{}QRQgfg^^^a%  )))`jjjmmm[][\^*-;>9<8;8;8;8;8;8;8:7:7:7:7:7:6:6:7:7:7:7:7:7:8;8;8;8;8;8;9<;>),`cWYWmmn^^^M   @hhhuuubba[^+.8;8;8;8;8;7:7:7:696969595858484869595948585859596969697:7:7:8;8;8;8;;>),ޠjljzyzqqqT   3rrrЃrsr588;9<8;8;8;8;7:7:7:69695958584847378<rA594847484858585969697:7:7:8;8;8;9<7:58npo[[[/%&% NNN||||_a/3:=8;8;8;8;7:7:6:69695858484747367;!F484736474848585969697:7:7:8;8;8;:=.1df~~~!!!`  2{{zыӦ-1:=8;8;8;8;7:7:7:69695848484737266; "E473736374748585869697:7:7;8;8;8;:=,0ۭxzxbbb-+++DDDkTW25:=8;8;8;8;7:7:69595848473737256:!#D373736374747585869697:7:8;8;8;:=14WZS()( !uuuļϦ/2:=8;8;8;8;7:7:69695848474737256:"$C363626374748485869697:7:8;8;8;:=.2֭RRR#%%%  A蓓df/3:=8;8;8;8;7:6:695848473736156:"%B2636263737475859696:7:8;8;8;:=.2ikxxx6999JJJm˻;>7:8;8;8;8;7:7:695958483737156:"%@2537253747485869697:8:8;8;8;7:>> utt¸Ǖ/2:=8;8;8;8;7:6:69584847371569#&>25372537474859697:7:8;8;8;:=.1͜III~,,,  %%% (ǢǾno/2:=8;8;8;8:7:6959584847255:$'<26372637485859697:8:8;8;:=.1sużkkk&...&&&<棣¿SU259<8;8;8;7:7:695958472669%( ;254736485858697:7:8;8;9<25VX1!!!999---OBE699<8;8;8;7:69695848367;'* ;3658374858697:7:8;8;9<58CFú> DDDCCC_8:8;8;8;8;8;7:6:59584759$' / /248485869697:8;8;8;8;8;INNNXXXm479<8;8;8;8:7:6969584825`Rabglk X~u{ "!+.484858596:7:8;8;8;9<36SWWWbbbu269<8;8;8;8:7:6969484837595948383737/4$'5926374848594837485869697:8;8;8;9<25X&&&ZZZeeev369<8;8;8;8:7:695948473726150404/3/3+/!$05.2/30414153647485869697:8;8;8;9<25!!!X(((UUU`a`p479<8;8;8;8:7:6959584737362515140404+/"%16/30415152636374858596:7:8;8;8;9<47T###PPPPPPb8;8;8;8;8;8;7:6969584737362615150404*. #&15/30415152637474858696:7:8;8;8;8;8;KEEF999Q@C699;8;8;8;7:6969584837362616150415*. %(15/30415162636474859697:7:8;8;9<69AD? 889 ?LO479<8;8;8;7:6:69595847372626150504/4+.)-)-)-)-)-*-*.+/,0.204040515262637484859697:7:8;8;9<36NQ2###((( )]^259<8;8;8;8;7:6968584737362626151404+/)-)-(,(+'*'+'+'+(+.204051515263737485859697:8:8;8;:=15`a%;;:OOO pr369<8;8;8;8;7:69695848473726261515/3$(,260525262647475859696:7:8;8;8;9<25wxlll@@@ UUUvvvw;>7:8;8;8;8;7:7:69595847373726251503),371526263747485859697:8:8;8;8;7:<>...Z***;;;/0/GQS369<8;8;8;7:7:6:695858473737262603(+4725263747485858696:7:8;8;8;9<36SU8  666 #lm589<8;8;8;8;7:7:696958484737362615xhlllllllj-03726373747585859697:7:8;8;8;9<48ss"<<< PPPx~|~GI589<8;8;8;8;7:7:69595848473737368<9=8<8<8<7<8<8<8<8<3736373748485859697:7:8;8;8;9<58HJ777[+++'''7ce589<8;8;8;8;7:7:6:69595848474736gMNGF???=B.1584748585869697:7:7;8;8;8;9<48ij.(((GGG totIK489<8;8;8;8;7:7:69696958484836-/6947585869697:7:7:8;8;8;9<48JMXXXl/// !!!;c{c9<8;8;8;8;8;8;7:7:6:6969585837,/7:585869697:7:7:8;8;8;8;7:9CEVOV%,,, (('(G=G7O8<>9=8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;:=>@?X@bYb&,,, ,,,%gaf/*/2`3:<:=8<8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;9<;>;>7e8B:=9<8;8;8;8;8;8;8;8;8;8;9<;>;>68-W/424h`hQQQ_!---777GGGMXPX.&-&6&,_-358;;=;>;>;>;>;>;>:=8:24*],&6&5-4ldl?+,+::: )ZQZ<3<*(*'/'%>&'I()P*)P)'I'&>&'0'.+.B9Bf\fhhhk&,,, 999---:{t{lemogoyԃ}3... KKK 7yijmmmk1@@@ )))WWW #666GxƬȺxxxnA! NNN $#$hhh>== )"""?LLKQlllbouunbcb^BBBO<(HHH TTT GGGmmmeee /0/ ;<; ddd ___000GGGaaakkkuuuzzzyyyqqqdddZZZ777 ????????(0` $%%%111///......""")))000((((((---###... +; HONG9) ''',,, $ P"""+,+...-.-+,+(*()*)+,+-.--.-***~K# %&&+++ 3|..../.')'$&$+++968GBGVOVTMTE@E747**)$&$(*(///,,,r/ %%% '$$$|:::121$&$;9;riri`i535$&$010555o%### M@@@CCC030SPSgiKN;>4758;?LOhkFBF.0.ABA777D %%% ,,,tPPQCEDLLLMP*-(+.1367:8;8;7:36.0(++/ORAAADFDJJJc ''' ;;;[[[HJG}w|@C'*36;>;>:=9<8;8;8;8;9<:=;>:=35&*BEjdjGIGVVV)))v!!! ,,, BBBcbcQTR^`&)69>>wjjj[][8;8;8;8;7:7:6:696969696969697:7:7:8;8;8;;>3636bdbfef D )kkloqo;>58:=8;8;8;7:7:6:696959586:7:48596969696:7:8;8;8;:=37>A~pqpZ[[&''' NOOUUU|}|~~~XZ/2;>8;8;8;7:6:69695858477:.1),8;3758585969697:7:8;8;;>-1\_tstyzy888o===fff8}}}y|z֑)-;>8;8;8;7:6:69585848365914 # )+7;3647485869697:7:8;8;<>),ڕxzxppp/ qrq `aa=@699<8;8;7:6:69584847364804 *  '*6:2647485859697:7:8;9<69?B@@@qZZZ(ֈσ,/;>8;8;7:7:69594847264814 .  ')6:2547474869697:8;8;;>+.Ӈrrr#DCDFFF\DG589<8;8;7:69584847264714 1  &)5925374758696:7:8;9<47GJžJ ___ sssʚ/2:=8;8;7:7:595847363715 4  %(5925474858697:8;8;:=.2͝RRR}cbcțpr.1:=8;8;7:695848364725 6 %(6:264858696:7:8;:=-1tuvvvwww 3袣RT269<8;8;7:6958374826;&(7;364859697:8;9<25UW*CCC&&%444HAC699<8;8;6:69485826 9"$594758697:8;9<59BE:LLLX7:8;8;8;7:69594848V  $ & ' - - ;.15948697:8;8;8;8;G\[[c589<8;8;7:69585826.104/3.205D-104142737264758696:8;8;9<58$$$O__^d589<8;8;7:695948374726150315 A,1/4031525364758696:8;8;9<58&&&OUUU[7:8;8;8;7:69594837361514/305 9 -104041526374758697:8;8;8;7:H???J>A698;8;7:6:6948473626150415 9 '.204041526374858697:8;9<69?B:5IK479<8;8;7:6958473726151404-1,0+0+/+/,0-1-2/304152536374859697:8;9<47KM+EEE;;;oooY[259<8;8;7:6958483726261526!`eb__Wt0415152637475869697:8;:=15^_ no379<8;8;7:6:69584737261538E& 14152536474859697:8;8;9<25wxvvvqqqd>A698;8;8;7:69585847372548F% 252626374758596:7:8;8;7:>A555O ,VW379<8;8;7:6:695848473736/2-1-1-1-1-1-1.2263637485859697:8;8;9<36Z\$VVV ss;>7:8;8;8;7:6:6958484748%(vxtooi!3737475859697:7:8;8;7::=www{???@QS589<8;8;8;7:696958477;D ) 58485859697:7:8;8;9<47WX3 TTTrrrAC699<8;8;8;7:6:69588;D & 585869697:7:8;8;9<69@C||}|~~~uuu0XmY;>7:8;8;8;8;7:7:697:.0)+*,*,),*,*,,.69696:7:7:8;8;8;8;:=g|hý';;; $$$ yyyaJpJ:=7;8;8;8;8;8;7:7:8;9<8<8<8<8<9<9<7:7:7:8;8;8;8;8;8;T{U===N\\\xyx{BhB;>8;8;8;8;8;8;8;7:7:69696969697:8;8;8;8;8;8;8;:=LrMxyxteeeaa`>S>9<:>8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;8;:=9;MbN\\\ )))UUU FFF0v1:=;>9<8;8;8;8;8;8;8;8;8;8;8;8;9<;>9<5{6]]]XXX 999___ ndn-<-*x,7:;?;>9=9<8;8;8;8;9<:=;>;?68-{.>M>~􁁁x\\\ ===nno wxxah^h5=5'X(*,14698;:=:=8;5802*,(X)>> ),,,,,+-.-cccccc-.-+++)+)) CCC555&'&h@@@ADAӺy{DG8;8;8;8;DGz|ּ?B?<<<))(j???))(666ONOwxwҾNQ8;;>9<8;8;8;8;9<;>8;ORӾxxxGGG;;;CCC;;;]]\ӈ8;:=8;8;8;8;8;8;8;8;8;8;:=8;ԉVVVBBBOOO 000gjjjce9<8;8;8;8:7:6:7958697:7:8;8;8;9A8;8;7:695836261404 5 8 '&* 04152748586:8;8;>A¾XZ9<8;7:6:583736150515154648697:8;9<\^iii|8;8;8;7:6948473616/2.2/2.30315374859698;8;8;ttt ,񯯯@C8;8;7:7:59583737 373758697:8;8;BD*vw8;8;8;7:6:6958474848697:8;8;8;,NP8;8;8;8:7:6948695959596858697:8;8;8;TV)nnn ^^^f@B8;8;8;8;8;7:697:79696:7:8;8;8;8;DF```i }}}BC8;8;8;8;8;8;7:7:8;8;8;8;8;8;HIRgR7:8;7:7:8;8;8;8;7:7:8;9;Zp[wvwdddfJeK577:8;8;8;8;7:57KeKeeegQQQ ,ioiioi򙙙,  ,|涶} ,*00*??(    qqq6!#!SSSyyyxxxSSS " 8XXX575www˂@C8;7:@C̃www141XXXCGC8;;>8;7:697:<>8;@D@AAA58;:=7:5914-069:=8;BBB8dfdxz;>7:5803,069;>{|bdb>A8;5803-06:>A 8;8;5925/316577;8;  8;8;5926/3-2-2/315577;8; <>8;59360536598; * |-2-2-2,1-2-2-2-2-2-2-2-2-2-2-2,1-2-2-2#' "!!!!!" #'-2-2-2-2 AM-2-2-2-2-2 DP-2-2-2-2 > K-2-2-2-2!%"&-2-2,1,1-2-2-2-2-2-2-2-2-2-2-2,1,1-2-2-2} : MJJJJJL 9 -2-2-2-2-2-2 < H-2-2-2-2 BM-2-2-2-2Q     ^-2-2-2-2-2(-%)&*&*&*&*&*&*&*%)(--2-2??nzbget-16.4/windows/resources/trayicon_idle.ico0000644000175000017500000001246612630544544021616 0ustar andreasandreas h&  (   $   O [  a a555 `  %   ??????( @ ttt 111b = ]1 [6 R0777a :000 h <+++a <... a ?"""(((l Fp Ij B e=== :+8//0/'$!*/0/0: v 8ZQRRRRRQ[4Bl`bbbbbal=ul)$%%%%%$)??nzbget-16.4/windows/resources/resource.h0000644000175000017500000000536612630544544020276 0ustar andreasandreas//{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by nzbget.rc // #define IDI_MAINICON 101 #define IDR_TRAYMENU 102 #define IDI_TRAYICON_IDLE 102 #define IDD_ABOUTBOX 103 #define IDI_TRAYICON_PAUSED 103 #define IDI_TRAYICON_WORKING 104 #define IDD_PREFDIALOG 105 #define IDD_RUNNINGDIALOG 106 #define IDD_FACTORYRESETDIALOG 107 #define IDC_ABOUT_ICON 1001 #define IDC_ABOUT_VERSION 1002 #define IDC_ABOUT_HOMEPAGE 1003 #define IDC_ABOUT_NAME 1004 #define IDC_PREF_AUTOSTART 1005 #define IDC_ABOUT_GPL 1005 #define IDC_PREF_CONSOLE 1006 #define IDC_PREF_TRAY 1007 #define IDC_PREF_WEBUI 1008 #define IDC_RUNNING_ICON 1009 #define IDC_RUNNING_TITLE 1010 #define IDC_RUNNING_TEXT 1011 #define IDC_QUIT 1013 #define IDC_RUNNING_QUIT 1013 #define IDC_RUNNING_PREFS 1014 #define IDC_RUNNING_WEBUI 1015 #define IDC_RUNNING_OK 1016 #define IDC_FACTORYRESET_CANCEL 1016 #define IDC_FACTORYRESET_RESET 1017 #define IDC_FACTORYRESET_TEXT 1018 #define IDC_FACTORYRESET_TITLE 1019 #define IDC_FACTORYRESET_ICON 1020 #define IDC_PREF_TRAYPAUSE 1021 #define IDC_PREF_TRAYWEBUI 1022 #define ID_SHOWWEBUI 40004 #define ID_ABOUT 40005 #define ID_EXIT 40006 #define ID_MENU_INFO 40007 #define ID_INFO_HOMEPAGE 40008 #define ID_INFO_DOWNLOADS 40009 #define ID_INFO_FORUM 40010 #define ID_Menu 40011 #define ID_PREFERENCES 40012 #define ID_SHOW_CONFIGFILE 40020 #define ID_SHOW_LOGFILE 40021 #define ID_SHOW_DESTDIR 40022 #define ID_SHOW_INTERDIR 40023 #define ID_SHOW_NZBDIR 40024 #define ID_SHOW_SCRIPTDIR 40025 #define ID_MENU_TROUBLESHOOTING 40026 #define ID_TROUBLESHOOTING_RESTART 40027 #define ID_TROUBLESHOOTING_OPENCONFIG 40032 #define ID_TROUBLESHOOTING_FACTORYRESET 40036 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 106 #define _APS_NEXT_COMMAND_VALUE 40037 #define _APS_NEXT_CONTROL_VALUE 1023 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif nzbget-16.4/windows/nzbget-command-shell.bat0000644000175000017500000000264412630544544020762 0ustar andreasandreas@echo off rem rem Batch file to start nzbget shell rem rem Copyright (C) 2009 orbisvicis rem Copyright (C) 2009 Andrey Prygunkov rem rem This program is free software; you can redistribute it and/or modify rem it under the terms of the GNU General Public License as published by rem the Free Software Foundation; either version 2 of the License, or rem (at your option) any later version. rem rem This program is distributed in the hope that it will be useful, rem but WITHOUT ANY WARRANTY; without even the implied warranty of rem MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the rem GNU General Public License for more details. rem rem You should have received a copy of the GNU General Public License rem along with this program; if not, write to the Free Software rem Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. rem rem ####################### Usage instructions ####################### rem rem After starting the batch file you can use all nzbget commands rem (like nzbget -s, nzbget -L, etc) without typing the full rem path to nzbget executable. rem rem ####################### End of Usage instructions ####################### rem expression "%~dp0" means the location of an executing batch file set PATH=%PATH%;%~dp0 cmd /U /K "cd %USERPROFILE% & nzbget" nzbget-16.4/windows/package-info.json0000644000175000017500000000017112630544544017470 0ustar andreasandreas{ "update-info-link": "http://nzbget.net/info/nzbget-version-win32.json", "install-script": "install-update.bat" } nzbget-16.4/windows/README-WINDOWS.txt0000644000175000017500000000222712630544544017103 0ustar andreasandreas===================================== NZBGet ReadMe for Windows ===================================== This is a short documentation. For more info visit http://nzbget.net/Installation_on_Windows ===================================== NZBGet can be used in application mode (tray icon and optional DOS window) or as a service. When you use the program for the first time you should start it at least once in application mode to create the necessary configuration file from template. ===================== Provided batch-file: ===================== nzbget-command-shell.bat Starts console window (DOS box) where you can execute remote commands to communicate with running NZBGet. ===================== Service mode ===================== First you need to install the service. From NZBGet shell (batch file nzbget-shell.bat) use command: nzbget -install To remove the service use command: nzbget -remove To start service: net start NZBGet To stop service: net stop NZBGet =================================================================== For description of the program and more information see file README. nzbget-16.4/configure.ac0000644000175000017500000004201212630544544015045 0ustar andreasandreas# # This file is part of nzbget # # Copyright (C) 2008-2015 Andrey Prygunkov # # 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. # # # -*- Autoconf -*- # Process this file with autoconf to produce a configure script. AC_PREREQ(2.59) AC_INIT(nzbget, 16.4, hugbug@users.sourceforge.net) AC_CONFIG_AUX_DIR(posix) AM_INIT_AUTOMAKE([foreign]) AC_CONFIG_SRCDIR([daemon/main/nzbget.cpp]) AC_CONFIG_HEADERS([config.h]) dnl dnl Set default library path, if not specified in environment variable "LIBPREF". dnl if test "$LIBPREF" = ""; then LIBPREF="/usr" fi dnl dnl Check for programs. dnl AC_PROG_CXX AC_PATH_PROG(TAR, tar, $FALSE) AC_PATH_PROG(MAKE, make, $FALSE) AC_PROG_INSTALL dnl dnl Do all tests with c++ compiler. dnl AC_LANG(C++) dnl dnl Checks for header files. dnl AC_CHECK_HEADERS(sys/prctl.h) AC_CHECK_HEADERS(regex.h) dnl dnl Check for libs dnl AC_SEARCH_LIBS([pthread_create], [pthread]) AC_SEARCH_LIBS([socket], [socket]) AC_SEARCH_LIBS([inet_addr], [nsl]) AC_SEARCH_LIBS([hstrerror], [resolv]) dnl dnl Getopt dnl AC_CHECK_FUNC(getopt_long, [AC_DEFINE([HAVE_GETOPT_LONG], 1, [Define to 1 if getopt_long is supported])],) dnl dnl fsync dnl AC_CHECK_FUNC(fdatasync, [AC_DEFINE([HAVE_FDATASYNC], 1, [Define to 1 if fdatasync is supported])],) AC_CHECK_DECL(F_FULLFSYNC, [AC_DEFINE([HAVE_FULLFSYNC], 1, [Define to 1 if F_FULLFSYNC is supported])],,[#include ]) dnl dnl use 64-Bits for file sizes dnl AC_SYS_LARGEFILE dnl dnl check ctime_r dnl AC_MSG_CHECKING(for ctime_r) AC_TRY_COMPILE( [#include ], [ time_t clock; char buf[26]; ctime_r(&clock, buf, 26); ], AC_MSG_RESULT([[yes, and it takes 3 arguments]]) FOUND="yes" AC_DEFINE([HAVE_CTIME_R_3], 1, [Define to 1 if ctime_r takes 3 arguments]), FOUND="no") if test "$FOUND" = "no"; then AC_TRY_COMPILE( [#include ], [ time_t clock; char buf[26]; ctime_r(&clock, buf); ], AC_MSG_RESULT([[yes, and it takes 2 arguments]]) FOUND="yes" AC_DEFINE([HAVE_CTIME_R_2], 1, [Define to 1 if ctime_r takes 2 arguments]), FOUND="no") fi if test "$FOUND" = "no"; then AC_MSG_RESULT([no]) AC_MSG_ERROR("function ctime_r not found") fi dnl dnl check getaddrinfo dnl AC_CHECK_FUNC(getaddrinfo, FOUND="yes" [AC_DEFINE([HAVE_GETADDRINFO], 1, [Define to 1 if getaddrinfo is supported])] AC_SEARCH_LIBS([getaddrinfo], [nsl]), FOUND="no") dnl dnl check gethostbyname_r, if getaddrinfo is not available dnl if test "$FOUND" = "no"; then AC_MSG_CHECKING(for gethostbyname_r) AC_TRY_COMPILE( [#include ], [ char* szHost; struct hostent hinfobuf; char* strbuf; int h_errnop; struct hostent* hinfo = gethostbyname_r(szHost, &hinfobuf, strbuf, 1024, &h_errnop); ], AC_MSG_RESULT([[yes, and it takes 5 arguments]]) FOUND="yes" AC_DEFINE([HAVE_GETHOSTBYNAME_R_5], 1, [Define to 1 if gethostbyname_r takes 5 arguments]), FOUND="no") if test "$FOUND" = "no"; then AC_TRY_COMPILE( [#include ], [ char* szHost; struct hostent* hinfo; struct hostent hinfobuf; char* strbuf; int h_errnop; int err = gethostbyname_r(szHost, &hinfobuf, strbuf, 1024, &hinfo, &h_errnop); ], AC_MSG_RESULT([[yes, and it takes 6 arguments]]) FOUND="yes" AC_DEFINE([HAVE_GETHOSTBYNAME_R_6], 1, [Define to 1 if gethostbyname_r takes 6 arguments]), FOUND="no") fi if test "$FOUND" = "no"; then AC_TRY_COMPILE( [#include ], [ char* szHost; struct hostent hinfo; struct hostent_data hinfobuf; int err = gethostbyname_r(szHost, &hinfo, &hinfobuf); ], AC_MSG_RESULT([[yes, and it takes 3 arguments]]) FOUND="yes" AC_DEFINE([HAVE_GETHOSTBYNAME_R_3], 1, [Define to 1 if gethostbyname_r takes 3 arguments]), AC_MSG_RESULT([[no]]) FOUND="no") fi if test "$FOUND" = "yes"; then AC_DEFINE([HAVE_GETHOSTBYNAME_R], 1, [Define to 1 if gethostbyname_r is supported]) AC_SEARCH_LIBS([gethostbyname_r], [nsl]) fi fi dnl dnl Determine what socket length (socklen_t) data type is dnl AC_MSG_CHECKING([for type of socket length (socklen_t)]) AC_TRY_COMPILE([ #include #include #include ],[ (void)getsockopt (1, 1, 1, NULL, (socklen_t*)NULL)],[ AC_MSG_RESULT(socklen_t) SOCKLEN_T=socklen_t],[ AC_TRY_COMPILE([ #include #include #include ],[ (void)getsockopt (1, 1, 1, NULL, (size_t*)NULL)],[ AC_MSG_RESULT(size_t) SOCKLEN_T=size_t],[ AC_TRY_COMPILE([ #include #include #include ],[ (void)getsockopt (1, 1, 1, NULL, (int*)NULL)],[ AC_MSG_RESULT(int) SOCKLEN_T=int],[ AC_MSG_WARN(could not determine) SOCKLEN_T=int])])]) AC_DEFINE_UNQUOTED(SOCKLEN_T, $SOCKLEN_T, [Determine what socket length (socklen_t) data type is]) dnl dnl Dir-browser's snapshot dnl AC_MSG_CHECKING(whether dir-browser snapshot workaround is needed) if test "$target_vendor" == "apple"; then AC_MSG_RESULT([[yes]]) AC_DEFINE([DIRBROWSER_SNAPSHOT], 1, [Define to 1 if deleting of files during reading of directory is not properly supported by OS]) else AC_MSG_RESULT([[no]]) fi dnl dnl check cpu cores via sysconf dnl AC_MSG_CHECKING(for cpu cores via sysconf) AC_TRY_COMPILE( [#include ], [ int a = _SC_NPROCESSORS_ONLN; ], FOUND="yes" AC_MSG_RESULT([[yes]]) AC_DEFINE([HAVE_SC_NPROCESSORS_ONLN], 1, [Define to 1 if _SC_NPROCESSORS_ONLN is present in unistd.h]), FOUND="no") dnl dnl checks for libxml2 includes and libraries. dnl AC_ARG_WITH(libxml2_includes, [AS_HELP_STRING([--with-libxml2-includes=DIR], [libxml2 include directory])], [CPPFLAGS="${CPPFLAGS} -I${withval}"] [INCVAL="yes"], [INCVAL="no"]) AC_ARG_WITH(libxml2_libraries, [AS_HELP_STRING([--with-libxml2-libraries=DIR], [libxml2 library directory])], [LDFLAGS="${LDFLAGS} -L${withval}"] [LIBVAL="yes"], [LIBVAL="no"]) if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then PKG_CHECK_MODULES(libxml2, libxml-2.0, [LIBS="${LIBS} $libxml2_LIBS"] [CPPFLAGS="${CPPFLAGS} $libxml2_CFLAGS"], AC_MSG_ERROR("libxml2 library not found")) fi AC_CHECK_HEADER(libxml/tree.h,, AC_MSG_ERROR("libxml2 header files not found")) AC_SEARCH_LIBS([xmlNewNode], [xml2], , AC_MSG_ERROR("libxml2 library not found")) dnl dnl Use curses. Deafult: yes dnl AC_MSG_CHECKING(whether to use curses) AC_ARG_ENABLE(curses, [AS_HELP_STRING([--disable-curses], [do not use curses (removes dependency from curses-library)])], [USECURSES=$enableval], [USECURSES=yes] ) AC_MSG_RESULT($USECURSES) if test "$USECURSES" = "yes"; then INCVAL="${LIBPREF}/include" LIBVAL="${LIBPREF}/lib" AC_ARG_WITH(libcurses_includes, [AS_HELP_STRING([--with-libcurses-includes=DIR], [libcurses include directory])], [INCVAL="$withval"]) CPPFLAGS="${CPPFLAGS} -I${INCVAL}" AC_ARG_WITH(libcurses_libraries, [AS_HELP_STRING([--with-libcurses-libraries=DIR], [libcurses library directory])], [LIBVAL="$withval"]) LDFLAGS="${LDFLAGS} -L${LIBVAL}" AC_CHECK_HEADER(ncurses.h, FOUND=yes AC_DEFINE([HAVE_NCURSES_H],1,[Define to 1 if you have the header file.]), FOUND=no) if test "$FOUND" = "no"; then AC_CHECK_HEADER(ncurses/ncurses.h, FOUND=yes AC_DEFINE([HAVE_NCURSES_NCURSES_H],1,[Define to 1 if you have the header file.]), FOUND=no) fi if test "$FOUND" = "no"; then AC_CHECK_HEADER(curses.h, FOUND=yes AC_DEFINE([HAVE_CURSES_H],1,[Define to 1 if you have the header file.]), FOUND=no) fi if test "$FOUND" = "no"; then AC_MSG_ERROR([Couldn't find curses headers (ncurses.h or curses.h)]) fi AC_SEARCH_LIBS([refresh], [ncurses curses],, AC_ERROR([Couldn't find curses library])) else AC_DEFINE([DISABLE_CURSES],1,[Define to 1 to not use curses]) fi dnl dnl Use par-checking. Deafult: yes. dnl AC_MSG_CHECKING(whether to include code for par-checking) AC_ARG_ENABLE(parcheck, [AS_HELP_STRING([--disable-parcheck], [do not include par-check/-repair-support])], [ ENABLEPARCHECK=$enableval ], [ ENABLEPARCHECK=yes] ) AC_MSG_RESULT($ENABLEPARCHECK) if test "$ENABLEPARCHECK" = "yes"; then dnl PAR2 checks. dnl dnl Checks for header files. AC_HEADER_DIRENT AC_HEADER_STDBOOL AC_HEADER_STDC AC_CHECK_HEADERS([stdio.h] [endian.h] [getopt.h]) dnl Checks for typedefs, structures, and compiler characteristics. AC_TYPE_SIZE_T AC_C_BIGENDIAN AC_C_CONST AC_C_INLINE AC_FUNC_FSEEKO dnl Checks for library functions. AC_FUNC_MEMCMP AC_CHECK_FUNCS([stricmp] [strcasecmp]) AC_CHECK_FUNCS([strchr] [memcpy]) AC_CHECK_FUNCS([getopt]) AM_CONDITIONAL(WITH_PAR2, true) else AC_DEFINE([DISABLE_PARCHECK],1,[Define to 1 to disable par-verification and repair]) AM_CONDITIONAL(WITH_PAR2, false) fi dnl dnl Use TLS/SSL. Deafult: yes dnl AC_MSG_CHECKING(whether to use TLS/SSL) AC_ARG_ENABLE(tls, [AS_HELP_STRING([--disable-tls], [do not use TLS/SSL (removes dependency from TLS/SSL-libraries)])], [ USETLS=$enableval ], [ USETLS=yes] ) AC_MSG_RESULT($USETLS) if test "$USETLS" = "yes"; then AC_ARG_WITH(tlslib, [AS_HELP_STRING([--with-tlslib=(OpenSSL, GnuTLS)], [TLS/SSL library to use])], [TLSLIB="$withval"]) if test "$TLSLIB" != "GnuTLS" -a "$TLSLIB" != "OpenSSL" -a "$TLSLIB" != ""; then AC_MSG_ERROR([Invalid argument for option --with-tlslib]) fi if test "$TLSLIB" = "OpenSSL" -o "$TLSLIB" = ""; then AC_ARG_WITH(openssl_includes, [AS_HELP_STRING([--with-openssl-includes=DIR], [OpenSSL include directory])], [CPPFLAGS="${CPPFLAGS} -I${withval}"] [INCVAL="yes"], [INCVAL="no"]) AC_ARG_WITH(openssl_libraries, [AS_HELP_STRING([--with-openssl-libraries=DIR], [OpenSSL library directory])], [LDFLAGS="${LDFLAGS} -L${withval}"] [LIBVAL="yes"], [LIBVAL="no"]) if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then PKG_CHECK_MODULES([openssl], [openssl], [LIBS="${LIBS} $openssl_LIBS"] [CPPFLAGS="${CPPFLAGS} $openssl_CFLAGS"], FOUND=no) fi AC_CHECK_HEADER(openssl/ssl.h, FOUND=yes TLSHEADERS=yes, FOUND=no) if test "$FOUND" = "no" -a "$TLSLIB" = "OpenSSL"; then AC_MSG_ERROR([Couldn't find OpenSSL headers (ssl.h)]) fi if test "$FOUND" = "yes"; then AC_SEARCH_LIBS([CRYPTO_set_locking_callback], [crypto], AC_SEARCH_LIBS([SSL_library_init], [ssl], FOUND=yes, FOUND=no), FOUND=no) if test "$FOUND" = "no" -a "$TLSLIB" = "OpenSSL"; then AC_MSG_ERROR([Couldn't find OpenSSL library]) fi if test "$FOUND" = "yes"; then TLSLIB="OpenSSL" AC_DEFINE([HAVE_OPENSSL],1,[Define to 1 to use OpenSSL library for TLS/SSL-support.]) fi fi fi if test "$TLSLIB" = "GnuTLS" -o "$TLSLIB" = ""; then INCVAL="${LIBPREF}/include" LIBVAL="${LIBPREF}/lib" AC_ARG_WITH(libgnutls_includes, [AS_HELP_STRING([--with-libgnutls-includes=DIR], [GnuTLS include directory])], [INCVAL="$withval"]) CPPFLAGS="${CPPFLAGS} -I${INCVAL}" AC_ARG_WITH(libgnutls_libraries, [AS_HELP_STRING([--with-libgnutls-libraries=DIR], [GnuTLS library directory])], [LIBVAL="$withval"]) LDFLAGS="${LDFLAGS} -L${LIBVAL}" AC_CHECK_HEADER(gnutls/gnutls.h, FOUND=yes TLSHEADERS=yes, FOUND=no) if test "$FOUND" = "no" -a "$TLSLIB" = "GnuTLS"; then AC_MSG_ERROR([Couldn't find GnuTLS headers (gnutls.h)]) fi if test "$FOUND" = "yes"; then AC_SEARCH_LIBS([gnutls_global_init], [gnutls], FOUND=yes, FOUND=no) if test "$FOUND" = "yes"; then dnl gcrypt is optional AC_MSG_CHECKING([whether gcrypt is needed]) AC_TRY_COMPILE( [#include ] [#if GNUTLS_VERSION_NUMBER <= 0x020b00] [compile error] [#endif], [int a;], AC_MSG_RESULT([no]) GCRYPT=no, AC_MSG_RESULT([yes]) GCRYPT=yes) if test "$GCRYPT" = "yes"; then AC_CHECK_HEADER([gcrypt.h], AC_SEARCH_LIBS([gcry_control], [gnutls gcrypt], FOUND=yes, FOUND=no), FOUND=yes) fi fi if test "$FOUND" = "no" -a "$TLSLIB" = "GnuTLS"; then AC_MSG_ERROR([Couldn't find GnuTLS library]) fi if test "$FOUND" = "yes"; then TLSLIB="GnuTLS" AC_DEFINE([HAVE_LIBGNUTLS],1,[Define to 1 to use GnuTLS library for TLS/SSL-support.]) fi fi fi if test "$TLSLIB" = ""; then if test "$TLSHEADERS" = ""; then AC_MSG_ERROR([Couldn't find neither OpenSSL nor GnuTLS headers (ssl.h or gnutls.h)]) else AC_MSG_ERROR([Couldn't find neither OpenSSL nor GnuTLS library]) fi fi else AC_DEFINE([DISABLE_TLS],1,[Define to 1 to not use TLS/SSL]) fi dnl dnl checks for zlib includes and libraries. dnl AC_MSG_CHECKING(whether to use gzip) AC_ARG_ENABLE(gzip, [AS_HELP_STRING([--disable-gzip], [disable gzip-compression/decompression (removes dependency from zlib-library)])], [USEZLIB=$enableval], [USEZLIB=yes] ) AC_MSG_RESULT($USEZLIB) if test "$USEZLIB" = "yes"; then INCVAL="${LIBPREF}/include" LIBVAL="${LIBPREF}/lib" AC_ARG_WITH(zlib_includes, [AS_HELP_STRING([--with-zlib-includes=DIR], [zlib include directory])], [INCVAL="$withval"]) CPPFLAGS="${CPPFLAGS} -I${INCVAL}" AC_ARG_WITH(zlib_libraries, [AS_HELP_STRING([--with-zlib-libraries=DIR], [zlib library directory])], [LIBVAL="$withval"]) LDFLAGS="${LDFLAGS} -L${LIBVAL}" AC_CHECK_HEADER(zlib.h,, AC_MSG_ERROR("zlib header files not found")) AC_SEARCH_LIBS([deflateBound], [z], , AC_MSG_ERROR("zlib library not found")) else AC_DEFINE([DISABLE_GZIP],1,[Define to 1 to disable gzip-support]) 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 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 may be neccessary on 32-Bit BSD)])], [SIGCHLDHANDLER=$enableval], [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]) fi dnl dnl Debugging. Default: no dnl AC_MSG_CHECKING(whether to include all debugging code) AC_ARG_ENABLE(debug, [AS_HELP_STRING([--enable-debug], [enable debugging])], [ ENABLEDEBUG=$enableval ], [ ENABLEDEBUG=no] ) AC_MSG_RESULT($ENABLEDEBUG) if test "$ENABLEDEBUG" = "yes"; then dnl dnl Begin of debugging code dnl AC_DEFINE([DEBUG],1,Define to 1 to include debug-code) dnl dnl check for __FUNCTION__ or __func__ macro dnl AC_MSG_CHECKING(for macro returning current function name) AC_TRY_COMPILE( [#include ], [printf("%s\n", __FUNCTION__);], AC_MSG_RESULT(__FUNCTION__) FUNCTION_MACRO_NAME=__FUNCTION__, AC_TRY_COMPILE([#include ], [printf("%s\n", __func__);], AC_MSG_RESULT(__func__) FUNCTION_MACRO_NAME=__func__, AC_MSG_RESULT(none))) if test "$FUNCTION_MACRO_NAME" != ""; then AC_DEFINE_UNQUOTED(FUNCTION_MACRO_NAME, $FUNCTION_MACRO_NAME, [Define to the name of macro which returns the name of function being compiled]) fi dnl dnl variadic macros dnl AC_MSG_CHECKING(for variadic macros) AC_COMPILE_IFELSE([ #define macro(...) macrofunc(__VA_ARGS__) int macrofunc(int a, int b) { return a + b; } int test() { return macro(1, 2); } ], AC_MSG_RESULT([yes]) AC_DEFINE([HAVE_VARIADIC_MACROS], 1, Define to 1 if variadic macros are supported), AC_MSG_RESULT([no])) dnl dnl Backtracing on segmentation faults dnl AC_MSG_CHECKING(for backtrace) AC_TRY_COMPILE( [#include ] [#include ] [#include ], [ void *array[100]; size_t size; char **strings; ] [ size = backtrace(array, 100); ] [ strings = backtrace_symbols(array, size); ], FOUND=yes AC_MSG_RESULT([[yes]]) AC_DEFINE([HAVE_BACKTRACE], 1, [Define to 1 to create stacktrace on segmentation faults]), FOUND=no AC_MSG_RESULT([[no]])) dnl dnl "rdynamic" linker flag dnl AC_MSG_CHECKING(for rdynamic linker flag) old_LDFLAGS="$LDFLAGS" LDFLAGS="$LDFLAGS -rdynamic" AC_TRY_LINK([], [], AC_MSG_RESULT([[yes]]), AC_MSG_RESULT([[no]]) [LDFLAGS="$old_LDFLAGS"]) dnl dnl End of debugging code dnl else AC_DEFINE([NDEBUG],1,Define to 1 to exclude debug-code) fi dnl dnl Enable test suite. Deafult: no. dnl AC_MSG_CHECKING(whether to enable unit and integration tests) AC_ARG_ENABLE(tests, [AS_HELP_STRING([--enable-tests], [enable unit and integration tests])], [ ENABLETESTS=$enableval ], [ ENABLETESTS=no] ) AC_MSG_RESULT($ENABLETESTS) if test "$ENABLETESTS" = "yes"; then AC_DEFINE([ENABLE_TESTS],1,[Define to 1 to enable unit and integration tests]) AM_CONDITIONAL(WITH_TESTS, true) else AM_CONDITIONAL(WITH_TESTS, false) fi AC_CONFIG_FILES([Makefile]) AC_OUTPUT nzbget-16.4/tests/0000755000175000017500000000000012630544544013722 5ustar andreasandreasnzbget-16.4/tests/testdata/0000755000175000017500000000000012630544544015533 5ustar andreasandreasnzbget-16.4/tests/testdata/dupematcher2/0000755000175000017500000000000012630544544020116 5ustar andreasandreasnzbget-16.4/tests/testdata/dupematcher2/testfile.part04.rar0000644000175000017500000000600012630544544023550 0ustar andreasandreasRar!zs t#, { F0 testfile.datnloadedSizeHi", "DownloadedSizeMB", "DownloadTimeSec", "PostTotalTimeSec", "ParTimeSec", "RepairTimeSec", "UnpackTimeSec"; - pp-script "EMail.py" now supports mail server relays (thanks to l2g for the patch); - when compiled in debug mode new field "process id" is printed to the file log for each row (it is easier to identify processes than threads); - if an nzb has only few failed articles it may have completion shown as 100%; now it is shown as 99.9% to indicate that not everything was successfully downloaded; - updated configure-script to not require gcrypt for newer GnuTLS versions (when gcrypt is not needed); - for downloads delayed due to propagation delay (option "PropagationDelay") a new badge "propagation" is now shown near download name; - added new option "UrlTimeout" to set timeout for URL fetching and RSS feed fetching; renamed option "ConnectionTimeout" to "ArticleTimeout"; - improved pp-script EMail.py: now it can send time statistics (thanks to JVM for the patch); - improvement in duplicate check: - if a new download with empty dupekey and empty dupescore is marked as "dupe" and the another download with the same name have non empty dupekey or dupescore these properties are copied from that download; - this is useful because the new download is most likely another upload of the same file and it should have the same duplicate properties for best duplicate handling results; - when connecting in remote mode using command line parameter "--connect/-C" the option "ControlIP" is now interpreted as "127.0.0.1" if it is set to "0.0.0.0" (instead of failing with an error message); - when option "ContinuePartial" is active the current state is saved not more often than once per second instead of after every downloaded article; this significantly reduce the amount of disk writings on high download speeds; - added commands "PausePostProcess" and "UnpausePostProcess" to scheduler; - unpack is now automatically immediately aborted if unrar reports CRC errors; - unpack is now immediately aborted if unrar reports wrong password (works for rar5 as well as for older formats); the unpack error status "PASSWORD" is now set for older formats too (not only rar5); - improved cleanup: - disk cleanup is now not performed if unrar failed even if par-check was successful; - queue cleanup (for remaining par2-files) is now made more smarter: the files are kept (parked) if they can be used by command "post-process again" and are removed otherwise; - improved scan-scripts: if the category of nzb-file is changed by the scan-script the assigned post-processing scripts are now automatically reset according to the new category; - added missing new line character at the end of the help screen printed by "nzbget -h"; - better error rep{@Z^nzbget-16.4/tests/testdata/dupematcher2/testfile.part43.rar0000644000175000017500000000022712630544544023560 0ustar andreasandreasRar!zs t!,C{AnF0 testfile.datzbget-0.1.0a - compiling-fixes nzbget-0.1.0 - initial release %{@,9*nzbget-16.4/tests/testdata/dupematcher1/0000755000175000017500000000000012630544544020115 5ustar andreasandreasnzbget-16.4/tests/testdata/dupematcher1/testfile.part01.rar0000644000175000017500000001000012630544544023537 0ustar andreasandreasRar!Zns =t",{ĊF0 testfile.datnzbget-15.0: - please see subversion log at http://sourceforge.net/p/nzbget/code/HEAD/log/?path=/trunk nzbget-14.0: - added article cache: - new option "ArticleCache" defines memory limit to use for cache; - when cache is active the articles are written into cache first and then all flushed to disk into the destination file; - article cache reduces disk IO and may reduce file fragmentation improving post-processing speed (unpack); - it works with both writing modes (direct write on and off); - when option "DirectWrite" is disabled the cache should be big enough (for best performance) to accommodate all articles of one file (sometimes up to 500 MB) in order to avoid writing articles into temporary files, otherwise temporary files are used for articles which do not fit into cache; - when used in combination with DirectWrite there is no such limitation and even a small cache (100 MB or even less) can be used effectively; when the cache becomes full it is flushed automatically (directly into destination file) providing room for new articles; - new row dddddddddtatistics and status dialog" in web-interface indicates the amount of memory used for cache; - new fields "ArticleCacheLo", "ArticleCacheHi" and "ArticleCacheMB" returned by RPC-method "status"; - renamed option "WriteBufferSize" into "WriteBuffer": - changed the dimension - now option is set in kilobytes instead of bytes; - old name and value are automatically converted; - if the size of article is below the value defined by the option, the buffer is allocated with the articles size (to not waste memory); - therefore the special value "-1" is not required anymore; during conversion "-1" is replaced with "1024" (1 megabyte) but it can be of course manually changed to any other value later; - integrated par2-module (libpar2) into NZBGet’s source code tree: - the par2-module is now built automatically during building of NZBGet; - this eliminates dependency from external libpar2 and libsigc++; - making it much easier for users to compile NZBGet without patching libpar2; - added quick file verification during par-check/repair: - if par-repair is required for download the files downloaded without errors are verified quickly by comparing their checksums against the checksums stored in the par2-file; - this makes the verification of undamaged files almost instant; - damaged (partially downloaded) files are also verified quickly by comparing block's checksums against the checksums stored in the par2-file; when necessary the small amounts of data is read from files to calculate block's checksums; - this makes the verification of damaged files very fast; - new option "ParQuick" (active by default); - when quick par verification is active the repaired files are not verified to save time; the only reason for incorrect files after repair can be hardware errors (memory, disk) but this is not something NZBGet should care about; - if unpack fails (excluding invalid password errors) and quick par-check does not find any errors or quick par-check was already performed the full par-check is performed; this helps in certain rare situations caused by abnormal program termination; - added multithreading par-repair: - doesn't depend on other libraries and works everywhere, on all platforms and all CPUs (with multiple cores), no special compiling steps are required; - new option "ParThreads" to set the number of threads for repairing; - the number of repair threads is automatically reduced to the amount of bad {@qnzbget-16.4/tests/testdata/dupematcher1/testfile.part24.rar0000644000175000017500000001000012630544544023544 0ustar andreasandreasRar!zs t#,{ɺF0 testfile.datnow printed to screen/log-file; - messages about obsolete options are now printed to screen/log-file; - imporved example postprocessing script: added support for external configuration file, postprocessing parameters and configuration via web-interface; - option now can contain parameters which must be passed to the script; - added pausing/resuming for post-processor queue; added new modifier to remote commands <--pause/-P> and <--unpause/-U>; added new commands and to XML-/JSON-RPC; extended output of remote command <--list/-L> to indicate paused state of post-processor queue; extended command of XML-/JSON-RPC with field ; - changed the command line syntax for requesting of post-processor queue from <-O> to <-L O> for consistency with other post-queue related commands (<-P O>, <-U O> and <-E O>); - improved example post-processing script: added support for delayed par-check (try unrar first, par-repair if unrar failed); - added modifier to command <-E/--edit> for editing of post-processor-queue; following subcommands are supported: <+/-offset>, , , ; subcommand supports deletion of queued post-jobs and active job as well; deletion of active job means the cancelling of par-check/repair or terminating of post-processing-script (including child processes of the script); updated remote-server to support new edit-subcommands in XML/JSON-RPC; - extended the syntax of option in two ways: 1) it now accepts multiple comma-separated values; 2) an asterix as hours-part means ; - added svn revision number to version string (commands <-v> and <-V>, startup log entry); svn revision is automatically read from svn-repository on each build; - added estimated remaining time and better distinguishing of server state in command <--list/-L>; - added new return code (93) for post-processing script to indicate successful processing; that results in cleaning up of download queue if option is active; - added readonly options , and for usage in processing scripts (options are available as environment variables , and ); - renamed ParStatus constant to for a consistence with ScriptStatus constant , that also affects the results of RPC-command ; - added a new return code <95/POSTPROCESS_NONE> for post-processing scripts for cases when pp-script skips all post-processing work (typically upon a user's request via a pp-parameter); modified the example post-processing script to return the new code instead of a error code when a pp-parameter was set to ; - added field to result of RPC-Command and fields and for command ; - in and output-modes the download speed is now printed with one decimal digit when the speed is lower than 10 KB/s; - improvement in example post-processing script: added check for existence of and command ; - added shell batch file for windows (nzbget-shell.bat); thanks to orbisvicis (orbisvicis@users.sourceforge.net) for the script; - added debian style init script (nzbgetd); thanks to orbisvicis (orbisvicis@users.sourceforge.net) for the script; - added the returning of a proper HTTP error code if the authorization was failed on RPC-calls; thanks to jdembski (jdembski@users.sourceforge.net) for the patch; - changed the sleep-time during the throttling of bandwidth from 200ms to 10ms in order to achieve better uniformity; - modified example postprocessing script to not use the command , which is not always available; thanks to Ger Teunis for the patch; - improved example pos{@nzbget-16.4/tests/testdata/nzbfile/0000755000175000017500000000000012630544544017164 5ustar andreasandreasnzbget-16.4/tests/testdata/nzbfile/dotless.nzb0000644000175000017500000000150312630544544021353 0ustar andreasandreas alt.binaries.test 1@ngroups.net alt.binaries.test 1@ngroups.net nzbget-16.4/tests/testdata/nzbfile/plain.txt0000644000175000017500000000022012630544544021022 0ustar andreasandreas# number of files 3 # file names (one line per file) ubuntu-12.04-desktop-i386.nfo ubuntu-12.04-desktop-i386.par2 ubuntu-12.04-desktop-i386.r00 nzbget-16.4/tests/testdata/nzbfile/dotless.txt0000644000175000017500000000017112630544544021401 0ustar andreasandreas# number of files 2 # file names (one line per file) 20155f68b0df03cc9bca2c58d426163ff 2261fa83d164f651d19a7ff923f633e00 nzbget-16.4/tests/testdata/nzbfile/plain.nzb0000644000175000017500000000214712630544544021006 0ustar andreasandreas alt.binaries.boneless 4f9a3e8a$0$14349$c3e8da3$33a0879d@news.astraweb.com alt.binaries.boneless 4f9a1f3a$0$26527$c3e8da3$e3f2c276@news.astraweb.com alt.binaries.boneless 4f9a1f3a$0$23145$c3e8da3$40cb80c2@news.astraweb.com nzbget-16.4/tests/testdata/parchecker/0000755000175000017500000000000012630544544017642 5ustar andreasandreasnzbget-16.4/tests/testdata/parchecker/testfile.nfo0000644000175000017500000000127012630544544022165 0ustar andreasandreasNZBGet: Andrey Prygunkov (versions 0.3.0 and later) Bo Cordes Petersen (versions ? - 0.2.3) Sven Henkel (versions 0.1.0 - ?) PAR2: Peter Brian Clements PAR2 library API: Francois Lesueur jQuery: John Resig The Dojo Foundation Bootstrap: Twitter, Inc Raphaël: Dmitry Baranovskiy Sencha Labs Elycharts: Void Labs s.n.c. iconSweets: Yummygum nzbget-16.4/tests/testdata/parchecker/testfile.dat0000644000175000017500000036517312630544544022172 0ustar andreasandreasnzbget-15.0: - please see subversion log at http://sourceforge.net/p/nzbget/code/HEAD/log/?path=/trunk nzbget-14.0: - added article cache: - new option "ArticleCache" defines memory limit to use for cache; - when cache is active the articles are written into cache first and then all flushed to disk into the destination file; - article cache reduces disk IO and may reduce file fragmentation improving post-processing speed (unpack); - it works with both writing modes (direct write on and off); - when option "DirectWrite" is disabled the cache should be big enough (for best performance) to accommodate all articles of one file (sometimes up to 500 MB) in order to avoid writing articles into temporary files, otherwise temporary files are used for articles which do not fit into cache; - when used in combination with DirectWrite there is no such limitation and even a small cache (100 MB or even less) can be used effectively; when the cache becomes full it is flushed automatically (directly into destination file) providing room for new articles; - new row dddddddddtatistics and status dialog" in web-interface indicates the amount of memory used for cache; - new fields "ArticleCacheLo", "ArticleCacheHi" and "ArticleCacheMB" returned by RPC-method "status"; - renamed option "WriteBufferSize" into "WriteBuffer": - changed the dimension - now option is set in kilobytes instead of bytes; - old name and value are automatically converted; - if the size of article is below the value defined by the option, the buffer is allocated with the articles size (to not waste memory); - therefore the special value "-1" is not required anymore; during conversion "-1" is replaced with "1024" (1 megabyte) but it can be of course manually changed to any other value later; - integrated par2-module (libpar2) into NZBGet’s source code tree: - the par2-module is now built automatically during building of NZBGet; - this eliminates dependency from external libpar2 and libsigc++; - making it much easier for users to compile NZBGet without patching libpar2; - added quick file verification during par-check/repair: - if par-repair is required for download the files downloaded without errors are verified quickly by comparing their checksums against the checksums stored in the par2-file; - this makes the verification of undamaged files almost instant; - damaged (partially downloaded) files are also verified quickly by comparing block's checksums against the checksums stored in the par2-file; when necessary the small amounts of data is read from files to calculate block's checksums; - this makes the verification of damaged files very fast; - new option "ParQuick" (active by default); - when quick par verification is active the repaired files are not verified to save time; the only reason for incorrect files after repair can be hardware errors (memory, disk) but this is not something NZBGet should care about; - if unpack fails (excluding invalid password errors) and quick par-check does not find any errors or quick par-check was already performed the full par-check is performed; this helps in certain rare situations caused by abnormal program termination; - added multithreading par-repair: - doesn't depend on other libraries and works everywhere, on all platforms and all CPUs (with multiple cores), no special compiling steps are required; - new option "ParThreads" to set the number of threads for repairing; - the number of repair threads is automatically reduced to the amount of bad blocks if there are too few of them; if there is only one bad block the multithreading par-repair is switched off to avoid overhead of thread synchronisation (which does not make sense for one working thread); - new option "ParBuffer" to define the memory limit to use during par-repair; - added support for detection of bad downloads (fakes, etc.): - queue-scripts are now called after every downloaded file included in nzb; - new events "FILE_DOWNLOADED" and "NZB_DOWNLOADED" of parameter "NZBNA_EVENT"; new env. var "NZBNA_DIRECTORY" passed to queue scripts; - queue-scripts have a chance to detect bad downloads when the download is in progress and cancel bad downloads by printing a special command; downloads marked as bad become status "FAILURE/BAD" and are processed by the program as failures (triggering duplicate handling); scripts executed thereafter see the new status and can react accordingly (inform an indexer or a third-party automation tool); - when a script marks nzb as bad the nzb is deleted from queue, no further internal post-processing (par, unrar, etc.) is made for the nzb but all post-processing scripts are executed; if option "DeleteCleanupDisk" is active the already downloaded files are deleted; - new status "BAD" for field "DeleteStatus" of nzb-item in RPC-method "history"; - queue-scripts can set post-processing parameters by printing special command, just like post-processing-scripts can do that; this simplifies transferring (of small amount) of information between queue-scripts and post-processing-scripts; - scripts supporting two modes (post-processing-mode and queue-mode) are now executed if selected in post-processing parameters: either in options "PostScript" and "CategoryX.PostScript" or manually on page "Postprocess" of download details dialog in web-interface; it is not necessary to select dual-mode scripts in option "QueueScript"; that provides more flexibility: the scripts can be selected per-category or activated/deactivated for each nzb individually; - added option "EventInterval" allowing to reduce the number of calls of queue-scripts, which can be useful on slow systems; - queue scripts can define what events they are interested in; this avoids unnecessary calling of the scripts which do not process certain events; - the list of scripts (pp-scripts, queue-scripts, etc.) is now read once on program start instead of reading everytime a script is executed: - that eliminates the unnecessary disk access; - the settings page of web-interface loads available scripts every time the page is shown; - this allows to configure newly added scripts without restarting the program first (just like it was before); a restart is still required to apply the settings (just like it was before); - RPC-method "configtemplates" has new parameter "loadFromDisk"; - options "ParIgnoreExt" and "ExtCleanupDisk" are now respected by par-check (in addition to being respected by par-rename): if all damaged or missing files are covered by these options then no par-repair is performed and the download assumed successful; - added new search field "dupestatus" for use in rss filters: - the search is performed through download queue and history testing items with the same dupekey or title as current rss item; - the field contains comma-separated list of following possible statuses (if duplicates were found): QUEUED, DOWNLOADING, SUCCESS, WARNING, FAILURE or an empty string if there were no matching items found; - added log file rotation: - options "CreateLog" and "ResetLog" replaced with new option "WriteLog (none, append, reset, rotate)"; - new option "RotateLog" defines rotation period; - improved joining of splitted files: - instead of performing par-repair the files are now joined by unpacker, which is much faster; - the files splitted before creating of par-sets are now joined as well (they were not joined in v13 because par-repair has nothing to repair in this case); - the unpacker can detect missing fragments and requests par-check if necessary; - added per-nzb time and size statistics: - total time, download, verify, repair and unpack times, downloaded size and average speed, shown in history details dialog via click on the row with total size in statistics block; - RPC-methods "listgroups" and "history" return new fields: "DownloadedSizeLo", "DownloadedSizeHi", "DownloadedSizeMB", "DownloadTimeSec", "PostTotalTimeSec", "ParTimeSec", "RepairTimeSec", "UnpackTimeSec"; - pp-script "EMail.py" now supports mail server relays (thanks to l2g for the patch); - when compiled in debug mode new field "process id" is printed to the file log for each row (it is easier to identify processes than threads); - if an nzb has only few failed articles it may have completion shown as 100%; now it is shown as 99.9% to indicate that not everything was successfully downloaded; - updated configure-script to not require gcrypt for newer GnuTLS versions (when gcrypt is not needed); - for downloads delayed due to propagation delay (option "PropagationDelay") a new badge "propagation" is now shown near download name; - added new option "UrlTimeout" to set timeout for URL fetching and RSS feed fetching; renamed option "ConnectionTimeout" to "ArticleTimeout"; - improved pp-script EMail.py: now it can send time statistics (thanks to JVM for the patch); - improvement in duplicate check: - if a new download with empty dupekey and empty dupescore is marked as "dupe" and the another download with the same name have non empty dupekey or dupescore these properties are copied from that download; - this is useful because the new download is most likely another upload of the same file and it should have the same duplicate properties for best duplicate handling results; - when connecting in remote mode using command line parameter "--connect/-C" the option "ControlIP" is now interpreted as "127.0.0.1" if it is set to "0.0.0.0" (instead of failing with an error message); - when option "ContinuePartial" is active the current state is saved not more often than once per second instead of after every downloaded article; this significantly reduce the amount of disk writings on high download speeds; - added commands "PausePostProcess" and "UnpausePostProcess" to scheduler; - unpack is now automatically immediately aborted if unrar reports CRC errors; - unpack is now immediately aborted if unrar reports wrong password (works for rar5 as well as for older formats); the unpack error status "PASSWORD" is now set for older formats too (not only rar5); - improved cleanup: - disk cleanup is now not performed if unrar failed even if par-check was successful; - queue cleanup (for remaining par2-files) is now made more smarter: the files are kept (parked) if they can be used by command "post-process again" and are removed otherwise; - improved scan-scripts: if the category of nzb-file is changed by the scan-script the assigned post-processing scripts are now automatically reset according to the new category; - added missing new line character at the end of the help screen printed by "nzbget -h"; - better error reporting if a temp file could not be found; - added news server name to message "Cancelling hanging download ..." to help identifying problematic servers; - added column "age" to history tab in web-interface; - debug builds for Windows now print call stack on crash to the log-file, which is very useful for debugging; - additional parameters (env. vars) are now passed to scan scripts: NZBNP_DUPEKEY, NZBNP_DUPESCORE, NZBNP_DUPEMODE; scan-scripts can now set dupekey, dupemode and dupescore by printing new special commands; - fixed potential crash which could happen in debug mode during program restart; - fixed: program could crash during restart if an extension script was running; now all active scripts are terminated during restart; - fixed: RPC-method "editqueue" with action "HistoryReturn" caused a crash if the history item did not have any remaining (parked) files; - fixed: RPC-method "saveconfig" did not work via XML-RPC (but worked via JSON-RPC); - fixed: a superfluous comma at the end of option "TaskX.Time" was interpreted as an error or may cause a crash; - fixed: relative destination paths (options "DestDir" and "CategoryX.DestDir") caused failures during unrar; - fixed: splitted .cbr-files were not properly joined; - fixed: inner files (files listed in nzb) bigger than 2GB could not be downloaded; - fixed: cleanup may leave some files undeleted (Mac OSX only); - fixed: compiler error if configured using parameter "--disable-gzip"; - fixed: one log-message was printed only to global log but not to nzb-item pp-log; - fixed: par-check could fail on valid files (bug introduced in libpar2 0.3); - fixed: scheduler tasks were not checked after wake up if the sleep time was longer than 90 minutes; - fixed: the "pause extra pars"-state was missing in the pause/resume-loop of curses interface, key "P"; - fixed: web interface showed an error box when trying to submit files with extensions other than .nzb, although these files could be processed by a scan-script; now the error is not shown if any scan-script is set in options; nzbget-13.0: - reworked download queue: - new dialog to build filters in web-interface with instant preview; - queue now holds nzb-jobs instead of individual files (contained within nzbs); - this drastically improves performance when managing queue containing big nzb-files on operations such as pause/unpause/move items; - tested with queue of 30 nzb-files each 40-100GB size (total queue size 1.5TB) - queue managing is fast even on slow device; - limitation: individual files (contained within nzbs) now cannot be moved beyond nzb borders (in older version it was possible to move individual files freely and mix files from different nzbs, although this feature was not supported in web-interface and therefore was not much known); - this change opens doors for further speed optimizations and integration of download queue with post-processing queue and possibly url-queue; - current download data such as remained size or size of paused files is now internally automatically updated on related events (download of article is completed, queue edited, etc.); - this eliminates the need of calculating this data upon each RPC-request (from web-interface) and greatly decrease CPU load of processing RPC-requests when having large download queue (and/or large nzb-files in queue); - field "Priority" was removed from individual files; - instead nzb-files (collections) now have field "Priority"; - nzb-files now also have new fields "MinTime" and "MaxTime", which are set when nzb-file is parsed and then kept; - this eliminates the need of recalculation file statistics (min and max priority, min and max time); - removed action "FileSetPriority" from RPC-command "editqueue"; - removed action "I" from remote command "--edit/-E" for individual files (now it is allowed for groups only); - removed few (not more necessary) checks from duplicate manager; - merged post-processing queue into main download queue; - changing the order of (pp-queued) items in the download queue now also means changing the order of post-processing jobs; - priorities of downloads are now respected when picking the next queued post-processing job; - the moving of download items in web-interface is now allowed for downloads queued for post-processing; - removed actions of remote command "--edit/-E" and of RPC-method "editqueue" used to move post-processing jobs in the post-processing queue (the moving of download items should be used instead); - remote command "-E/--edit" and RPC-method "editqueue" now use NZBIDs of groups to edit groups (instead of using ID of any file in the group as in older versions); - remote command "-L/--list" for groups (G) and group-view in curses-frontend now print NZBIDs instead of "FirstID-LastID"; - RPC-method "listgroups" returns NZBIDs in fields "FirstID" and "LastID", which are usually used as arguments to "editqueue" (for compatibility with existing third-party software); - items queued for post-processing and not having any remaining files now can be edited (to cancel post-processing), which was not possibly before due to lack of "LastID" in empty groups; - edit commands for download queue and post-processing queue are now both use the same IDs (NZBIDs); - merged url queue into main download queue; - urls added to queue are now immediately shown in web-interface; - urls can be reordered and deleted; - when urls are fetched the downloaded nzb-files are put into queue at the positions of their urls; - this solves the problem with fetched nzb-files ordered differently than the urls if the fetching of upper (position wise) urls were completed after the lower urls; - removed options "ReloadUrlQueue" and "ReloadPostQueue" since there are no separate url- and post-queues anymore; - nzb-files added via urls have new field "URL" which can be accessed via RPC-methods "listgroups" and "history"; - new env. var. "NZBNP_URL", "NZBNA_URL" and "NZBPP_URL" passed to scan-, queue- and pp-scripts; - removed remote command "--list U", urls are now shown as groups by command "--list G"; - RPC-method "urlqueue" is still supported for compatibility but should not be used since the urls are now returned by method "listgroups", the entries have new field "Kind" which can be "NZB" or "URL"; - added collecting of download volume statistics data per news server: - in web-interface the data is shown as chart in "Statistics and Status" dialog; - new RPC-method "servervolumes" returns the collected data; - new RPC-method "resetservervolume" to reset the custom counter; - fast par-renamer now automatically detects and renames misnamed (obfuscated) par2-files; - for downloads not having any (obviously named) par2-files the critical health is assumed 85% instead of 100% as the absense of par2-files suggests: - this avoids the possibly false triggering of health-check action (delete or pause) for downloads having misnamed (obfuscated) par2-files; - combined with improved fast par-renamer this provides proper processing of downloads with misnamed (obfuscated) par2-files; - fast par-renamer now detects missing files (files listed in par2-files but not present on disk): - when checking for missing files the files whose extensions match with option "ExtCleanupDisk" are ignored now (to avoid time consuming restoring of files which will be deleted later anyway); - added option "ParIgnoreExt" which lists files which do not trigger par-repair if they are missing (similar to option "ExtCleanupDisk" but those files are not deleted during cleanup); - added new choice "Always" for option "ParCheck": - it forces the par-check for every (even undamaged) download but in contrast to choice "Force" only one par2-file is downloaded first; - additional files are downloaded if needed; - improved par-check for damaged collections with multiple par-sets and having missing files: - only orphaned files (not belonging to any par-set) are scanned when looking for missing files; - this greatly decrease the par-check time for big collections; - eliminated the distinction between manual pause and soft-pause: - there is only one pause register now; - options "ParPauseQueue", "UnpackPauseQueue" and "ScriptPauseQueue" do not change the state of the pause but instead are respected directly; - RPC-methods "pausedownload2" and "resumedownload2" are aliases to "pausedownload" and "resumedownload" (kept for compatibility); - field "Download2Paused" of RPC-method "status" is an alias to "DownloadPaused" (kept for compatibility); - action "D2" of remote commands "--pause/-P" and "--unpause/-U" is not supported anymore; - implemented general scripts concept: - the concept is a logical extension of the post-processing scripts concept initially introduced in v11; - the general scripts concept applies to all scripts used in the program: scan-script, queue-script and scheduler-script (in addition to post-processing scripts); - option "NzbProcess" renamed to "ScanScript"; - option "NzbAddedProcess" renamed to "QueueScript"; - option "DefScript" and "CategoryX.DefScript" renamed to "PostScript" and "CategoryX.PostScript" (options with old names are recognized and automatically converted on first settings saving); - new option "TaskX.Script"; - old option "TaskX.Process" kept for scheduling of external programs not related to nzbget (to avoid writing of intermediate proxy scripts); - scan-script, queue-script and scheduler-script now work similar to post-processing scripts: - scripts must be put into scripts-directory; - scripts can be configured via web-interface and can have options; - multiple scripts can be chosen for each scripts-option, all chosen scripts are executed; - program and script options are passed to the script as env. variables; - renamed default directory with scripts from "ppscripts" to "scripts"; - script signature indicates the type of script (post-processing, scan, queue or scheduler); - one script can have mixed signature allowing it to be used for multiple purposes (for example a notification script can send a notification on both events: after adding to queue and after post-processing); - result of RPC-method "configtemplates" has new fields "PostScript", "ScanScript", "QueueScript", "SchedulerScript" to indicate the purpose of the script; - queue-script (formerly NzbAddedProcess) has new parameter "NZBNA_EVENT" indicating the reason of calling the script; currently the script is called only after adding of files to download queue and therefore the parameter is always set to "NZB_ADDED" but the queue-script can be called on other events in the future too; - post-processing scripts now have two new parameters: - env. var "NZBPP_STATUS" indicates the status of download including the total status (SUCCESS, FAILURE, etc.) and the detail field (for example in case of failures: PAR, UNPACK, etc.); - env. var "NZBPP_TOTALSTATUS" is equal to the total status of parameter "NZBPP_STATUS" and is provided for convenience (to avoid parsing of "NZBPP_STATUS"); - the new parameters provide a simple way for pp-scripts to determine download status without a guess work needed in previous versions; - parameters "NZBPP_PARSTATUS" and "NZBPP_UNPACKSTATUS" are now considered deprecated (still passed for compatibility); - updated script "EMail.py" to use new parameters "NZBPP_TOTALSTATUS" and "NZBPP_STATUS" instead of "NZBPP_PARSTATUS" and "NZBPP_UNPACKSTATUS"; - when changing category in web-interface the post-processing parameters are now automatically updated according to new category settings: - only parameters which are different in old and new category are changed; - parameters which present in both or in neither categories are not changed; - that ensures that only the relevant parameters are updated and parameters which were manually changed by user remain they settings when it make sense; - in the "download details dialog" the new parameters are updated on the postprocess-tab directly after changing of category and can be controlled before saving; - in the "edit multiple downloads dialog" the parameters are updated individually for each download on saving; - new action "CP" of remote command "--edit/-E" for groups to set category and apply parameters; - new action "GroupApplyCategory of RPC-method "editqueue" for the same purpose; - changed the way option "ContinuePartial" works: - now the information about completed articles is stored in a special file in QueueDir; - when option "DirectWrite" is active no separate flag-files per article are created in TempDir; - the file contains additional information, which were not stored/available before; - improved per-server/per-nzb article completion statistics: - the statistics are now available for active downloads in details dialog (not only for history); - the info on that page is constantly updated as long as the page is active (unless refresh is disabled); - download age info removed from details dialog to save place (it is shown in the download list anyway); - if backup news-servers start to be used for nzb-file a badge appears in the download list showing the percentage of articles downloaded from backup servers; - click on the badge opens download details dialog directly on the completion page; - per-server/per-nzb article completion statistics are now available via RPC-method "listgroups" for active downloads (not only for "history"); - improved RPC-API: - RPC-method "listgroups" now returns info about post-processing similar to info returned by method "postqueue"; - RPC-method "postqueue" is obsolete now; - web-interface requires less requests to NZBGet on each page update and it is now easier for third-party developers to obtain the info about download and post-processing status (no need to merge download queue and post queue); - RPC-method "listgroups" now returns new field "Status" making it easier for third-party apps to determine the status of download entry; - new field "Status" in RPC-method "history" to allow third-party apps easier determine the status of an item without inspecting status-fields of every processing step; - changed web-interface to use new field "Status"; - method "append" now returns id of added nzb-file or "0" on an error; - this makes it easier for third-party apps to track added nzb-files; - for backward compatibility with older software expecting a boolean result the old version of method "append" is still supported; - the new version of method "append" has a different signature (order of parameters); - parameter "content" can now be either nzb-file content (encoded in base 64) or an URL; - this makes the method "appendurl" obsolete (still supported for compatibility); - if an URL was added to queue the queue entry created for fetched nzb-file has the same "NZBID" for easier tracking; - added force-priorities: - downloads with priorities equal to or greater than 900 are downloaded and post-processed even if the program is in paused state (force mode); - in web-interface the combo for choosing priority has new entry "force" (priority value 900); - new fields "ForcedSizeLo", "ForcedSizeHi" and "ForcedSizeMB" returned by RPC-method "status"; - history items now preserve "NZBID" from queue items; that makes the tracking of items across queue and history easier for third-party apps; - field "NZBID" returned by RPC-method "history" is now available for history items of all kinds (NZB, URL, DUP); field "ID" is deprecated and should not be used; - post-processing scripts which move the whole download into a new location can inform the program about new location using command "[NZB] DIRECTORY=/new/path", allowing other scripts to process files further; - added support for power management on windows to avoid pc going into sleep mode during download or post-processing; - apostrophe is not considered an invalid file name character anymore; - adjusted modules initialization to avoid possible bugs due to delayed thread starts; - reorganized source code directory structure: created directory "daemon" with several subdirectories and put all source code files there; - added new option "PropagationDelay", which sets the minimum post age to download; newer posts are kept on hold in download queue until they get older than the defined delay, after that they are downloaded; - download speeds above 1024 KB/s are now indicated in MB/s; - data sizes above 1000 GB are now shown as TB in web-interface (instead of GB); - splitted files are now joined automatically (again); - adjusted modules initialization to avoid possible bugs due to delayed thread starts; - extended info printed by remote command "nzbget -B dump" (for debug purposes); - eliminated loop waiting time in queue coordinator on certain conditions - may improve performance on very high speed connections; - increased few wait intervals which were unnecessary too small; - improved error reporting: added error check when closing article file for writing and when deleting files or directories; - when building nzbget if both OpenSSL and GnuTLS are available now using OpenSSL by default (the preferred library can still be selected with configure-parameter --with-tlslib=OpenSSL/GnuTLS); - windows version is now configured to use OpenSSL instead of GnuTLS; windows binaries provided on download page now use OpenSSL; - column "age" in web-interface now shows minutes for recent posts (instead of "0 h"); - remote command "-B dump" now can be used also in release (non-debug) versions and prints useful debug data as "INFO" instead of "DEBUG"; - to detect daylight saving activation/deactivation the time zone information is now checked every minute if a download is active or once in 3 hours if the program is in stand-by; these delays should work well with hibernation mode on synology); - pp-script "EMail.py" now takes the status of previous pp-scripts into account and report a failure if any of the scripts has failed; - updated all links to go to new domain (nzbget.net); - impoved error reporting if unpacker or par-renamer fail to move files; - removed libpar2-patches from NZBGet source tree; the documentation now suggests to use the libpar2 version maintained by Debian/Ubuntu team, which already includes all necessary patches; - removed patches to create libpar2 and libsigc++ project files for Visual Studio on Windows, no one needed them anyway; - fixed: the program could crash during cleanup if files with invalid timestamps were found in the directory (windows only); - fixed: RSS feed preview dialog displayed slightly incorrect post ages because of the wrong time zone conversion; - fixed: sometimes URLs were removed too early from the feed history causing them to be detected as "new" and fetched again; if duplicate check was not active the same nzb-files could be downloaded again; - fixed: strange (damaged?) par2-files could cause a crash during par-renaming; - fixed: damaged nzb-files containing multiple par-sets and not having enough par-blocks could cause a crash during par-check; - fixed: if during par-repair the downloaded extra par-files were damaged and the repair was terminated with failure status the post-processing scripts were executed twice sometimes; - fixed: post-processing scripts were not executed in standalone mode ("nzbget /path/to/file.nzb"); - fixed: renaming or deleting of temporary files could fail, especially when options "UnpackPauseQueue" and "ScriptPauseQueue" were not active (windows only); - fixed: per-server/per-nzb article completion statistics could be inaccurate for nzb-files whose download were interrupted by reload/restart; - fixed: after deleting servers from config file the program could crash on start when loading server volume statistics data from disk; - fixed: download speeds above approx. 70 MB/s were not indicated correctly in web-interface and by RPC-method "status"; - fixed: cancelling of active par-job sometimes didn't work; - fixed: par-check could hang on renamed and splitted files; - fixed: the program could crash during parsing of malformed nzb-files; - fixed: errors during loading of queue from disk state may render the already loaded parts useless too; now at least these parts of queue are used; - fixed: queue was not locked during loading on program start and that could cause problems; - fixed: data sizes exactly equal to 10, 100, 1000 MB or GB were formatted using 4 digits instead of 3 (one digit after decimal point too much); - fixed: if post-processing step "move" failed, the command "post-process again" did not try to move again; - fixed: nzb-files were sometimes not deleted from NzbDir (option "NzbCleanupDisk"); - fixed: scheduler command "FetchFeed" did not work properly with parameter "0" (fetch all feeds). - fixed: port number was not sent in headers when downloading from URLs which could cause issues with RSS for web-sites using non-standard http ports; - fixed: queued nzb-files was not deleted from disk when deleting download without history tracking; 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.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.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 is obsolete; - new option sets directory where all pp-scripts must be stored; - new option sets the default list of pp-scripts to be assigned to nzb-file when it's added to queue; - new option to set the default list of pp-scripts on a category basis; - the execution order of pp-scripts can be set using new option ; - 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 and ; - new RPC-method 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 in the edit download dialog; - option renamed to (the old name is still recognized); - new option to define the location of template configuration file (in previous versions it must be always stored in ); - 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 in RPC-method ; - new action in remote command <--edit/-E> for history items (subcommand ); - added new feature which creates new download from selected files of source download; - new command in web-interface in edit download dialog on page ; - new action in remote command <--edit/-E>; - new action in JSON-/XML-RPC method ; - added support for manual par-check: - if option is set to 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 of option renamed to and respectively; - when set to all par2-files are always downloaded; - removed option since its functionality is now covered by option ; - result of par-check can now have new value ; - field in RPC-method can have new value ; - parameter for pp-script can have new value <4 = manual repair necessary>; - when download is resumed in web-interface the option 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 removed; a post-processing script is called only once per nzb-file, this behavior cannot be altered anymore; - with a new feature 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 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 now returns ID of NZB-file in the field ; - in versions up to 0.8.0 the field was used to identify history items in the edit-commands , , ; since version 9 field is used for this purpose; in versions 9-10 field still existed and had the same value as field for compatibility with version 0.8.0; the compatibility is not provided anymore; this change was needed to provide a consistent using of field 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 ; - pp-scripts terminated with unknown status are now considered failed (status=FAILURE instead of status=UNKNOWN); - new parameter (env. var) 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 (, , etc.); the message includes error text reported by OS such as 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 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 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 from message ; - 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 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 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 must be defined in project settings; defining it in 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 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 and 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 7-Zip executables); - new options , , , , ; - web-interface now shows progress and estimated time during unpack (rar only; for 7-Zip progress is not available due to limitations of 7-Zip); - when built-in unpack is enabled, the post-processing script is called after unpack and possibly par-check/repair (if needed); - for nzb-files containing multiple collections (par-sets) the post-processing script is called only once, after the last par-set; - new parameter passed to post-processing script; - if the option is enabled the post-processing- script is called after each par-set (as in previous versions); - example post-processing script updated: removed unrar-code, added check for unpack status; - new field in result of RPC-method ; - history-dialog in web-interface shows three status: par-status, unpack-status, script-status; - with two built-in special post-processing parameters <*Unpack:> and <*Unpack:Password> the unpack can be disabled for individual nzb-file or the password can be set; - built-in special post-processing parameters can be set via web- interface on page (when built-in unpack is enabled); - added support for HTTPS to the built-in web-server (web-interface and XML/JSON-RPC): - new options , , and ; - module completely rewritten with support for server- side sockets, newer versions of GnuTLS, proper thread lockings in OpenSSL; - improved the automatic par-scan (option ) to significantly reduce the verify-time in some common cases with renamed rar-files: - the extra files are scanned in an optimized order; - the scan stops when all missings files are found; - added fast renaming of intentionally misnamed (rar-) files: - the new renaming algorithm doesn't require full par-scan and restores original filenames in just a few seconds, even on very slow computers (NAS, media players, etc.); - the fast renaming is performed automatically when requested by the built-in unpacker (option must be active); - added new option to put intermediate files during download into a separate directory (instead of storing them directly in destination directory (option ): - when nzb-file is completely (successfully) downloaded, repaired (if neccessary) and unpacked the files are moved to destination directory (option or ); - intermediate directory can significantly improve unpack performance if it is located on a separate physical hard drive; - added new option to manually select cipher for encrypted communication with news server: - manually choosing a faster cipher (such as ) can significantly improve performance (if CPU is a limiting factor); - major improvements in news-server/connection management (main and fill servers): - if download of article fails, the program tries all servers of the same level before trying higher level servers; - this ensures that fill servers are used only if all main servers fail; - this makes the configuring of multiple servers much easier than before: in most cases the simple configuration of level 0 for all main servers and level 1 for all fill servers suffices; - in previous versions the level was increased immediately after the first tried server of the level failed; to make sure all main servers were tried before downloading from fill servers it was required to create complex server configurations with duplicates; these configurations were still not as effective as now; - do not reconnect on
    errors since this doesn't help but unnecessary increases CPU load and network traffic; - removed option ; it's not required anymore; - new option allows more flexible configuration of news servers when using multiple accounts on the same server; with this option it's also possible to imitate the old server management behavior regarding levels; - news servers configuration is now less error-prone: - the option is not required to start from <0> and when several news servers are configured the Levels can be any integers - the program sorts the servers and corrects the Levels to 0,1,2,etc. automatically if needed; - when option is set to <0> the server is ignored (in previous version such a server could cause hanging when the program was trying to go to the next level); - if no news servers are defined (or all definitions are invalid) a warning is printed to inform that the download is not possible; - categories can now have their own destination directories; new option ; - new feature in web-interface; new XML-/JSON-RPC method ; - improved the handling of hanging connections: if a connection hangs longer than defined by option the program tries to gracefully close connection first (this is new); if it still hangs after the download thread is terminated as a last resort (as in previous versions); - added automatic speed meter recalibration to recover after possible synchronization errors which can occur when the option is not active; this makes the default (less accurate but fast) speed meter almost as good as the accurate one; important when speed throttling is active; - when the par-checked requests more par-files, they get an extra priority and are downloaded before other files regardless of their priorities; this is needed to avoid hanging of par-checker-job if a file with a higher priority gets added to queue during par-check; - when post-processing-parameters are passed to the post-processing script a second version of each parameter with a normalized parameter- name is passed in addition to the original parameter name; in the normalized name the special characters <*> and <:> are replaced with <_> and all characters are passed in upper case; this is important for internal post-processing-parameters (*Unpack:=yes/no) which include special characters; - warning now is not printed when the connection was aborted before the request signature was read; - changed formatting of remaining time for post-processing to short format (as used for remaining download time); - added link to article to settings tab on web- interface; - removed hint from history dialog since it caused more questions than helped; - changed default value for option to ; most news servers nowadays do not require joining the group and many servers do not keep headers for many groups making the join-command fail even if the articles still can be successfully downloaded; - small change in example post-processing script: message are now printed only if ts-files really existed; - improved configure-script: - libs which are added via pkgconfig are now put into LIBS instead of LDFLAGS - improves compatibility with newer Linux linkers; - OpenSSL libs/includes are now added using pkgconfig to better handle dependencies; - additional check for libcrypto (part of OpenSSL) ensures the library is added to linker command even if pkgconfig is not used; - adding of local files via web-interface now works in IE10; - if an obsolete option is found in the config file a warning is printed instead of an error and the program is not paused anymore; - fixed: the reported line numbers for configuration errors were sometimes inaccurate; - fixed warning ; - fixed: some XML-/JSON-RPC methods may return negative values for file sizes between 2-4GB; this had also influence on web-interface. - fixed: if an external program (unrar, pp-script, etc.) could not be started, the execute-function has returned code 255 although the code -1 were expected in this case; this could break designed post- processing flow; - fixed: some characters with codes below 32 were not properly encoded in JSON-RPC; sometimes output from unrar contained such characters and could break web-interface; - fixed: special characters (quotation marks, etc.) in unpack password 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 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 version of the program); - added built-in web-interface: - completely new designed and written from scratch; - doesn't require a separate web-server; - doesn't require PHP; - 100% Javascript application; the built-in web-server hosts only static files; the javascript app communicates with NZBGet via JSON-RPC; - very efficient usage of server resources (CPU and memory); - easy installation. Since neither a separate web-server nor PHP are needed the installation of new web-interface is very easy. Actually it is performed automatically when you "make install" or "ipkg install nzbget"; - modern look: better layout, popup dialogs, nice animations, hi-def icons; - built-in phone-theme (activates automatically); - combined view for "currently downloading", "queued", "currently processing" and "queued for processing"; - renaming of nzb-files; - multiselect with multiedit or merge of downloads; - fast paging in the lists (downloads, history, messages); - search box for filtering in the lists (downloads, history, messages) and in settings; - adding nzb-files to download queue was improved in several ways: - add multiple files at once. The "select files dialog" allows to select multiple files; - add files using drag and drop. Just drop the files from your file manager directly into the web-browser; - add files via URLs. Put the URL and NZBGet downloads the nzb-file and adds it to download queue automatically; - the priority of nzb-file can now be set when adding local-files or URLs; - the history can be cleared completely or selected items can be removed; - file mode is now nzb-file related; - added the ability to queue URLs: - the program automatically downloads nzb-files from given URLs and put them to download queue. - when multiple URLs are added in a short time, they are put into a special URL-queue. - the number of simultaneous URL-downloads are controlled via new option UrlConnections. - with the new option ReloadUrlQueue can be controlled if the URL-queue should be reloaded after the program is restarted (if the URL-queue was not empty). - new switch <-U> for remote-command <--append/-A> to queue an URL. - new subcommand <-U> in the remote command <--list/-L> prints the current URL-queue. - if URL-download fails, the URL is moved into history. - with subcommand <-R> of command <--edit> the failed URL can be returned to URL-queue for redownload. - the remote command <--list/-L> for history can now print the infos for URL history items. - new XML/JSON-RPC command to add an URL or multiple URLs for download. - new XML/JSON-RPC command returns the items from the URL-queue. - the XML/JSON-RPC command was extended to provide infos about URL history items. - the URL-queue obeys the pause-state of download queue. - the URL-downloads support HTTP and HTTPS protocols; - added new field to nzb-info-object. - it is initially set to the cleaned up name of the nzb-file. - the renaming of the group changes this field. - all RPC-methods related to nzb-object return the new field, the old field is now deprecated. - the option now checks the -field instead of (the latter is not changed when the nzb is renamed). - new env-var-parameter for post-processing script; - added options and for remote command <--edit/-E>. With these options the name of group or file can be used in edit-command instead of file ID; - added support for regular expressions (POSIX ERE Syntax) in remote commands <--list/-L> and <--edit/-E> using new subcommands and ; - improved performance of RPC-command ; - added new command to RPC-method to set the order of individual files in the group; - added gzip-support to built-in web-server (including RPC); - added processing of http-request in RPC-server for better support of cross domain requests; - renamed example configuration file and postprocessing script to make the installation easier; - improved the automatic installation () to install all necessary files (not only the binary as it was before); - improved handling of configuration errors: the program now does not terminate on errors but rather logs all of them and uses default option values; - added new XML/JSON-RPC methods , and ; - with active option the NZB considered completed even if there are paused non-par-files (the paused non-par-files are treated the same way as paused par-files): as a result the reprocessable script is called; - added subcommand to remote command <-S/--scan> to scan synchronously (wait until scan completed); - added parameter to XML/JSON-RPC method ; - the command in web-interface now waits for completing of scan before reporting the status; - added remote command <--reload/-O> and JSON/XML-RPC method to reload configuration from disk and reintialize the program; the reload can be performed from web-interface; - JSON/XML-RPC method extended with parameter ; - categories available in web-interface are now configured in program configuration file (nzbget.conf) and can be managed via web-interface on settings page; - updated descriptions in example configuration file; - changes in configuration file: - renamed options , and to , and to avoid confusion with news-server options , and ; - the old option names are still recognized and are automatically renamed when the configuration is saved from web-interface; - also renamed option <$MAINDIR> to ; - extended remote command <--append/-A> with optional parameters: - - adds the file/URL to the top of queue; -

    - pauses added files; - - sets category for added nzb-file/URL; - - sets nzb filename for added URL; - the old switches <--category/-K> and <--top/-T> are deprecated but still supported for compatibility; - renamed subcommand of command <--edit/-E> to (the old subcommand is still supported for compatibility); - added new option to setup a script called after a nzb-file is added to queue; - added debug messages for speed meter; - improved the startup script so it can be directly used in without modifications; - fixed: after renaming of a group, the new name was not displayed by remote commands <-L G> and <-C in curses mode>; - fixed incompatibility with OpenSLL 1.0 (thanks to OpenWRT team for the patch); - fixed: RPC-method could return wrong results if the log was filtered with options ; - fixed: free disk space calculated incorrectly on some OSes; - fixed: unrar failure was not always properly detected causing the post-processing to delete not yet unpacked rar-files; - fixed compilation error on recent linux versions; - fixed compilation error on older systems; nzbget-0.8.0: - added priorities; new action for remote command <--edit/-E> to set priorities for groups or individual files; new actions and of RPC-command ; remote command <--list/-L> prints priorities and indicates files or groups being downloaded; ncurses-frontend prints priorities and indicates files or groups being download; new command to set priority of nzb-file from nzbprocess-script; RPC-commands and return priorities and indicate files or groups being downloaded; - added renaming of groups; new subcommand for command <--edit/-E>; new action for RPC-method ; - added new option , which enables syncronisation in speed meter; that makes the indicated speed more accurate by eliminating measurement errors possible due thread conflicts; thanks to anonymous nzbget user for the patch; - improved the parsing of filename from article subject; - option now efficiently works on Windows with NTFS partitions; - added URL-based-authentication as alternative to HTTP-header authentication for XML- and JSON-RPC; - fixed: nzb-files containing umlauts and other special characters could not be parsed - replaced XML-Reader with SAX-Parser - only on POSIX (not on Windows); - fixed incorrect displaying of group sizes bigger than 4GB on many 64-bit OSes; - fixed a bug causing error on decoding of input data in JSON-RPC; - fixed a compilation error on some windows versions; - fixed: par-repair could fail when the filenames were not correctly parsed from article subjects; - fixed a compatibility issue with OpenBSD (and possibly other BSD based systems); added the automatic configuring of required signal handling logic to better support BSD without breaking the compatibility with certain Linux systems; - corrected the address of Free Software Foundation in copyright notice. nzbget-0.7.0: - added history: new option , new remote subcommand for commands (list history entries) and (delete history entries, return history item, postprocess history item), new RPC-command and subcommands , , for command ; - added support for JSON-P (extension of JSON-RPC); - changed the result code returning status for postprocessing script from <1> to <94> (needed to show the proper script status in history); - improved the detection of new files in incoming nzb directory: now the scanner does not rely on system datum, but tracks the changing of file sizes during a last few () seconds instead; - improvements in example postprocessing script: 1) if download contains only par2-files the script do not delete them during cleanup; 2) if download contains only nzb-files the script moves them to incoming nzb-directory for further download; - improved formatting of groups and added time info in curses output mode; - added second pause register, which is independent of main pause-state and therfore is intended for usage from external scripts; that allows to pause download without interfering with options and and scheduler tasks and - they all work with first (default) pause register; new subcommand for commands <--pause/-P> and <--unpause/-U>; new RPC-command and ; existing RPC-commands und renamed to and ; new field in result struct for RPC-command ; existing fields and renamed to and ; old RPC-commands and fields still exist for compatibility; the status output of command <--list/-L> indicates the state of second pause register; key

    in curses-frontend can unpause second pause-register; - nzbprocess-script (option ) can now set category and post-processing parameters for nzb-file; - redesigned server pool and par-checker to avoid using of semaphores (which are very platform specific); - added subcommand to remote commands <--pause/-P> and <--unpause/-U> to pause/unpause the scanning of incoming nzb-directory; - added commands and for scheduler option ; - added remote commands and for XML-/JSON-RPC; - command now not only pauses the post-processing queue but also pauses the current post-processing job (par-job or script-job); however the script-job can be paused only after the next line printed to screen; - improved error reporting while parsing nzb-files; - added field to NZBInfo; the field is now returned by XML-/JSON-RPC methods , and ; - improvements in configure script; - added support for platforms without IPv6 (they do not have ); - debug-messages generated on early stages during initializing are now printed to screen/log-file; - messages about obsolete options are now printed to screen/log-file; - imporved example postprocessing script: added support for external configuration file, postprocessing parameters and configuration via web-interface; - option now can contain parameters which must be passed to the script; - added pausing/resuming for post-processor queue; added new modifier to remote commands <--pause/-P> and <--unpause/-U>; added new commands and to XML-/JSON-RPC; extended output of remote command <--list/-L> to indicate paused state of post-processor queue; extended command of XML-/JSON-RPC with field ; - changed the command line syntax for requesting of post-processor queue from <-O> to <-L O> for consistency with other post-queue related commands (<-P O>, <-U O> and <-E O>); - improved example post-processing script: added support for delayed par-check (try unrar first, par-repair if unrar failed); - added modifier to command <-E/--edit> for editing of post-processor-queue; following subcommands are supported: <+/-offset>, , , ; subcommand supports deletion of queued post-jobs and active job as well; deletion of active job means the cancelling of par-check/repair or terminating of post-processing-script (including child processes of the script); updated remote-server to support new edit-subcommands in XML/JSON-RPC; - extended the syntax of option in two ways: 1) it now accepts multiple comma-separated values; 2) an asterix as hours-part means ; - added svn revision number to version string (commands <-v> and <-V>, startup log entry); svn revision is automatically read from svn-repository on each build; - added estimated remaining time and better distinguishing of server state in command <--list/-L>; - added new return code (93) for post-processing script to indicate successful processing; that results in cleaning up of download queue if option is active; - added readonly options , and for usage in processing scripts (options are available as environment variables , and ); - renamed ParStatus constant to for a consistence with ScriptStatus constant , that also affects the results of RPC-command ; - added a new return code <95/POSTPROCESS_NONE> for post-processing scripts for cases when pp-script skips all post-processing work (typically upon a user's request via a pp-parameter); modified the example post-processing script to return the new code instead of a error code when a pp-parameter was set to ; - added field to result of RPC-Command and fields and for command ; - in and output-modes the download speed is now printed with one decimal digit when the speed is lower than 10 KB/s; - improvement in example post-processing script: added check for existence of and command ; - added shell batch file for windows (nzbget-shell.bat); thanks to orbisvicis (orbisvicis@users.sourceforge.net) for the script; - added debian style init script (nzbgetd); thanks to orbisvicis (orbisvicis@users.sourceforge.net) for the script; - added the returning of a proper HTTP error code if the authorization was failed on RPC-calls; thanks to jdembski (jdembski@users.sourceforge.net) for the patch; - changed the sleep-time during the throttling of bandwidth from 200ms to 10ms in order to achieve better uniformity; - modified example postprocessing script to not use the command , which is not always available; thanks to Ger Teunis for the patch; - improved example post-processing script: added the check for existence of destination directory to return a proper ERROR-code (important for reprocessing of history items); - by saving the queue to disk now using relative paths for the list of compeled files to reduce the file's size; - eliminated few compiler warnings on GCC; - fixed: when option was specified and nzbget was started as root, the lockfile was not removed; - fixed: nothing was downloaded when the option was set to <0>; - fixed: base64 decoding function used by RPC-method sometimes failed, in particular when called from Ruby-language; - fixed: JSON-RPC-commands failed, if parameters were placed before method name in the request; - fixed: RPC-method did not work properly on Posix systems (it worked only on Windows); - fixed compilation error when using native curses library on OpenSolaris; - fixed linking error on OpenSolaris when using GnuTLS; - fixed: option did not work; - fixed: seg. fault in service mode on program start (Windows only); - fixed: environment block was not passed correctly to child process, what could result in seg faults (windows only); - fixed: returning the postprocessing exit code <92 - par-check all collections> when there were no par-files results in endless calling of postprocessing script; - fixed compatibility issues with OS/2. nzbget-0.6.0: - added scheduler; new options , , , and ; - added support for postprocess-parameters; new subcommand of remote command to add/modify pp-parameter for group (nzb-file); new XML-/JSON-RPC-subcommand of method for the same purpose; updated example configuration file and example postprocess-script to indicate new method of passing arguments via environment variables; - added subcommands , and to command line switch <-L/--list>, which prints list of files, groups or only status info respectively; extended binary communication protocol to transfer nzb-infos in addition to file-infos; - added new subcommand to edit-command for merging of two (or more) groups (useful after adding pars from a separate nzb-file); - added option to automatically merge nzb-files with the same filename (useful by adding pars from a different source); - added script-processing of files in incoming directory to allow automatic unpacking and queueing of compressed nzb-files; new option ; - added the printing of post-process-parameters for groups in command <--list G>; - added the printing of nzbget version into the log-file on start; - added option to automatically delete already downloaded files from disk if nzb-file was deleted from queue (the download was cancelled); - added option to define the max time allowed for par-repair; - added command <--scan/-S> to execute the scan of nzb-directory on remote server; - changed the method to pass arguments to postprocess/nzbprocess: now using environment variables (old method is still supported for compatibility with existing scripts); - added the passing of nzbget-options to postprocess/nzbprocess scripts as environment variables; - extended the communication between nzbget and post-process-script: collections are now detected even if parcheck is disabled; - added support for delayed par-check/repair: post-process-script can request par-check/repair using special exit codes to repair current collection or all collections; - implemented the normalizing of option names and values in option list; the command <-p> also prints normalized names and values now; that makes the parsing of output of command <-p> for external scripts easier; - replaced option with new option which is now used by all scripts (PostProcess, NzbProcess, TaskX.Process); - improved entering to paused state on connection errors (do not retry failed downloads if pause was activated); - improved error reporting on decoding failures; - improved compatibility of yenc-decoder; - improved the speed of deleting of groups from download queue (by avoiding the saving of queue after the deleting of each individual file); - updated configure-script for better compatibility with FreeBSD; - cleaning up of download queue (option ) and deletion of source nzb-file (option ) after par-repair now works also if par-repair was cancelled (option ); since required par-files were already downloaded the repair in an external tool is possible; - added workaround to avoid hangs in child processes (by starting of postprocess or nzbprocess), observed on uClibC based systems; - fixed: TLS/SSL didn't work in standalone mode; - fixed compatibility issues with Mac OS X; - fixed: not all necessary par2-files were unpaused on first request for par-blocks (although harmless, because additional files were unpaused later anyway); - fixed small memory leak appeared if process-script could not be started; - fixed: configure-script could not detect the right syntax for function on OpenSolaris. - fixed: files downloaded with disabled decoder (option decode=no) sometimes were malformed and could not be decoded; - fixed: empty string parameters did not always work in XML-RPC. nzbget-0.5.1: - improved the check of server responses to prevent unnecessary retrying if the article does not exist on server; - fixed: seg.fault in standalone mode if used without specifying the category (e.g. without switch <-K>); - fixed: download speed indicator could report not-null values in standby-mode (when paused); - fixed: parameter in JSON/XML-RPC was not properly decoded by server, making the setting of a nested category (containing slash or backslash character) via nzbgetweb not possible; nzbget-0.5.0: - added TLS/SSL-support for encrypted communication with news-servers; - added IPv6-support for communication with news-servers as well as for communication between nzbget-server and nzbget-client; - added support for categories to organize downloaded files; - new option to create the subdirectory for each category; - new switch <-K> for usage with switch <-A> to define a category during the adding a file to download queue; - new command in switch <-E> to change the category of nzb-file in download queue; the already downloaded files are automatically moved to new directory if the option is active; - new parameter in XML-/JSON-RPC-command to allow the changing of category via those protocols; - new parameter in a call to post-process-script with category name; - scanning of subdirectories inside incoming nzb-directory to automatically assign category names; nested categories are supported; - added option to connect to servers, that do not accept -command; - added example post-process script for unraring of downloaded files (POSIX only); - added options and useful on slow CPUs; - added option to delete source nzb-file after successful download and parcheck; - switch <-P> can now be used together with switches <-s> and <-D> to start server/daemon in paused state; - changed the type of messages logged in a case of connection errors from to to provide better error reporting; - now using OS-specific line-endings in log-file and brokenlog-file: LF on Posix and CRLF on Windows; - added detection of adjusting of system clock to correct uptime/download time (for NAS-devices, that do not have internal clock and set time from internet after booting, while nzbget may be already running); - added the printing of stack on segmentation faults (if configured with <--enable-debug>, POSIX only); - added option for better debugging on Linux in a case of abnormal program termination; - fixed: configure-script could not automatically find libsigc++ on 64-bit systems; - few other small fixes; nzbget-0.4.1: - to avoid accidental deletion of file in curses-frontend the key now must be pressed in uppercase; - options and in news-server's configuration are now optional; - added the server's name to the detail-log-message, displayed on the start of article's download; - added the option to help to post-process-scripts, which make par-check/-repair on it's own; - improved download-speed-meter: it uses now a little bit less cpu and calculates the speed for the last 30 seconds (instead of 5 seconds), providing better accuracy; Thanks to ydrol for the patch; - reduced CPU-usage in curses-outputmode; Thanks to ydrol for the patch ; - fixed: line-endings in windows-style (CR-LF) in config-file were not read properly; - fixed: trailing spaces in nzb-filenames (before the file's extension) caused errors on windows. Now they will be trimmed; - fixed: XML-RPC and JSON-RPC did not work on Big-Endian-CPUs (ARM, PPC, etc), preventing the using of web-interface; - fixed: umask-option did not allow to enable write-permissions for and ; - fixed: in curses-outputmode the remote-client showed on first screen-update only one item of queue; - fixed: edit-commands with negative offset did not work via XML-RPC (but worked via JSON-RPC); - fixed incompatibility issues with gcc 4.3; Thanks to Paul Bredbury for the patch; - fixed: segmentation fault if a file listed in nzb-file does not have any segments (articles); nzbget-0.4.0: - added the support for XML-RPC and JSON-RPC to easier control the server from other applications; - added web-interface - it is available for download from nzbget-project's home page as a separate package "web-interface"; - added the automatic cleaning up of the download queue (deletion of unneeded paused par-files) after successful par-check/repair - new option ; - added option to allow to filter the (not so important) log-messages from articles' downloads (they have now the type instead of ); - added the gathering of progress-information during par-check; it is available via XML-RPC or JSON-RPC; it is also showed in web-interface; - improvements in internal decoder: added support for yEnc-files without ypart-statement (sometimes used for small files); added support for UU-format; - removed support for uulib-decoder (it did not work well anyway); - replaced the option with the option ; - added detection of errors and (special case for NNTPCache-server) to consider them as connect-errors (and therefore not count as retries); - added check for incomplete articles (also mostly for NNTPCache-server) to differ such errors from CrcErrors (better error reporting); - improved error-reporting on moving of completed files from tmp- to dst-directory and added code to move files across drives if renaming fails; - improved handling of nzb-files with multiple collections in par-checker; - improved the parchecker: added the detection and processing of files splitted after parring; - added the queueing of post-process-scripts and waiting for script's completion before starting of a next job in postprocessor (par-job or script) to provide more balanced cpu utilization; - added the redirecting of post-process-script's output to log; new option to specify the default message-kind for unformatted log-messages; - added the returning of script-output by command via XML-RPC and JSON-RPC; the script-output is also showed in web-interface; - added the saving and restoring of the post-processor-queue (if server was stopped before all items were processed); new option ; - added new parameter to postprocess-script to indicate if any of par-jobs for the same nzb-file failed; - added remote command (switch O/--post) to request the post-processor-queue from server; - added remote command (switch -W/--write) to write messages to server's log; - added option to automatically pause the download on low disk space; - fixed few incompatibility-issues with unslung-platform on nslu2 (ARM); - fixed: articles with trailing text after binary data caused the decode failures and the reporting of CRC-errors; - fixed: dupecheck could cause seg.faults when all articles for a file failed; - fixed: by dupe-checking of files contained in nzb-file the files with the same size were ignored (not deleted from queue); - updated libpar2-patch for msvc to fix a segfault in libpar2 (windows only); - fixed: by registering the service on windows the fullpath to nzbget.exe was not always added to service's exename, making the registered service unusable; - fixed: the pausing of a group could cause the start of post-processing for that group; - fixed: too many info-messages could be printed during par-check (appeared on posix only); nzbget-0.3.1: - Greatly reduced the memory consumption by keeping articles' info on disk until the file download starts; - Implemented decode-on-the-fly-technique to reduce disk-io; downloaded and decoded data can also be saved directly to the destination file (without any intermediate files at all); this eliminates the necessity of joining of articles later (option "DirectWrite"); - Improved communication with news-servers: connections are now keeped open until all files are downloaded (or server paused); this eliminates the need for establishing of connections and authorizations for each article and improves overal download speed; - Significantly better download speed is now possible on fast connection; it was limited by delays on starting of new articles' downloads; the synchronisation mechanism was reworked to fix this issue; - Download speed meter is much more accurate, especially on fast connections; this also means better speed throttling; - Speed optimisations in internal decoder (up to 25% faster); - CRC-calculation can be bypassed to increase performance on slow CPUs (option "CrcCheck"); - Improved parsing of artcile's subject for better extracting of filename part from it and implemented a fallback-option if the parsing was incorrect; - Improved dupe check for files from the same nzb-request to detect reposted files and download only the best from them (if option "DupeCheck" is on); - Articles with incorrect CRC can be treated as "possibly recoverable errors" and relaunched for download (option "RetryOnCrcError"), it is useful if multiple servers are available; - Improved error-check for downloaded articles (crc-check and check for received message-id) decreases the number of broken files; - Extensions in curses-outputmode: added group-view-mode (key "G") to show items in download queue as groups, where one group represents all files from the same nzb-file; the editing of queue works also in group-mode (for all files in this group): pause/unpause/delete/move of groups; - Other extensions in curses-outputmode: key "T" toggles timestamps in log; added output of statistical data: uptime, download-time, average session download speed; - Edit-command accepts more than one ID or range of IDs. E.g: "nzbget -E P 2,6-10,33-39"; The switch "-I" is not used anymore; - Move-actions in Edit-command affect files in a smart order to guarantee that the relative order of files in queue is not changed after the moving; - Extended syntax of edit-command to edit groups (pause/unpause/delete/move of groups). E.g: "nzbget -E G P 2"; - Added option "DaemonUserName" to set the user that the daemon (POSIX only) normally runs at. This allows nzbget daemon to be launched in rc.local (at boot), and download items as a specific user id; Thanks to Thierry MERLE for the patch; - Added option "UMask" to specify permissions for newly created files and dirs (POSIX only); - Communication protocol used between server and client was revised to define the byte order for transferred data. This allows hosts with different endianness to communicate with each other; - Added options "CursesNzbName", "CursesGroup" and "CursesTime" to define initial state of curses-outputmode; - Added option "UpdateInterval" to adjust update interval for Frontend-output (useful in remote-mode to reduce network usage); - Added option "WriteBufferSize" to reduce disk-io (but it could slightly increase memory usage and therefore disabled by default); - List-command prints additional statistical info: uptime, download-time, total amount of downloaded data and average session download speed; - The creation of necessary directories on program's start was extended with automatic creation of all parent directories or error reporting if it was not possible; - Printed messages are now translated to oem-codepage to correctly print filenames with non-english characters (windows only); - Added remote-command "-V (--serverversion)" to print the server's version; - Added option "ThreadLimit" to prevent program from crash if it wants to create too many threads (sometimes could occur in special cases); - Added options "NzbDirInterval" and "NzbDirFileAge" to adjust interval and delay by monitoring of incoming-directory for new nzb-files; - Fixed error on parsing of nzb-files containing percent and other special characters in their names (bug appeared on windows only); - Reformated sample configuration file and changed default optionnames from lowercase to MixedCase for better readability; - Few bugs (seg faults) were fixed. nzbget-0.3.0: - The download queue now contains newsgroup-files to be downloaded instead of nzb-jobs. By adding a new job, the nzb-file is immediately parsed and each newsgroup-file is added to download queue. Each file can therefore be managed separately (paused, deleted or moved); - Current queue state is saved after every change (file is completed or the queue is changed - entries paused, deleted or moved). The state is saved on disk using internal format, which allows fast loading on next program start (no need to parse xml-files again); - The remaining download-size is updated after every article is completed to indicate the correct remaining size and time for total files in queue; - Downloaded articles, which are saved in temp-directory, can be reused on next program start, if the file was not completed (option "continuepartial" in config-file); - Along with uulib the program has internal decoder for yEnc-format. This decoder was necessary, because uulib is so slow, that it prevents using of the program on not so powerful systems like linux-routers (MIPSEL CPU 200 MHz). The new decoder is very fast. It is controlled over option "decoder" in config-file; - The decoder can be completely disabled. In this case all downloaded articles are saved in unaltered form and can be joined with an external program; UUDeview is one of them; - If download of article fails, the program attempts to download it again so many times, what the option "retries" in config-file says. This works even if no servers with level higher than "0" defined. After each retry the next server-level is used, if there are no more levels, the program switches to level "0" again. The pause between retries can be set with config-option "retryinterval"; - If despite of a stated connection-timeout (it can be changed via config-option "connectiontimeout") connection hangs, the program tries to cancel the connection (after "terminatetimeout" seconds). If it doesn't work the download thread is killed and the article will be redownloaded in a new thread. This ensures, that there are no long-time hanging connections and all articles are downloaded, when a time to rejoin file comes; - Automatic par-checking and repairing. Only reuired par-files are downloaded. The program uses libpar2 and does not require any external tools. The big advantage of library is, that it allows to continue par-check after new par-blocks were downloaded. This were not possible with external par2cmdline-tool; - There is a daemon-mode now (command-line switch "-D" (--daemon)). In this mode a lock-file (default location "/tmp/nzbget.lock", can be changed via option "lockfile") contains PID of daemon; - The format of configuration-file was changed from xml to more common text-format. It allows also using of variables like "tempdir=${MAINDIR}/tmp"; - Any option of config-file can be overwritten via command-line switch "-o" (--option). This includes also the definition of servers. This means that the program can now be started without a configuration-file at all (all required options must be passed via command-line); - The command-line switches were revised. The dedicated switches to change options in config-file were eliminated, since any option can now be changed via switch "-o" (--option); - If the name of configuration-file was not passed via command-line the program search it in following locations: "~/.nzbget", "/etc/nzbget.conf", "/usr/etc/nzbget.conf", "/usr/local/etc/nzbget.conf", "/opt/etc/nzbget.conf"; - The new command-line switch "-n" (--noconfigfile) prevents the loading of a config-file. All required config-options must be passed via command-line (switch "-o" (--option)); - To start the program in server mode either "-s" (--server) or "-D" (--daemon) switch must be used. If the program started without any parameters it prints help-screen. There is no a dedicated switch to start in a standalone mode. If switches "-s" and "-D" are omitted and none of client-request-switches used the standalone mode is default. This usage of switches is more common to programs like "wget". To add a file to server's download queue use switch "-A" (--append) and a name of nzb-file as last command-line parameter; - There is a new switch "-Q" (--quit) to gracefully stop server. BTW the SIGKIL-signal is now handled appropriately, so "killall nzbget" is also OK, where "killall -9 nzbget" terminates server immediately (helpful if it hangs, but it shouldn't); - With new switch "-T" (--top) the file will be added to the top of download queue. Use it with switch "-A" (--append); - The download queue can be edited via switch "-E" (--edit). It is possible to pause, unpause, delete and move files in queue. The IDs of file(s) to be affected are passed via switch "-I" (fileid), either one ID or a range in a form "IDForm-IDTo". This also means, that every file in queue have ID now; - The switch "-L" (--list) prints IDs of files consequently. It prints also name, size, percentage of completing and state (paused or not) of each file. Plus summary info: number of files, total remaining size and size of paused files, server state (paused or running), number of threads on server, current speed limit; - With new switch "-G" (--log) the last N lines printed to server's screen-log, can be printed on client. The max number of lines which can be returned from servers depends on option "logbuffersize"; - The redesigned Frontends (known as outputmodes "loggable", "colored" and "curses") can connect to (remote) server and behave as if you were running server-instance of program itself (command-line switch "-C" (--connect)). The log-output updates constantly and even all control-functions in ncurses-mode works: pause/unpause server, set download rate limit, edit of queue (pause/unpause, delete, move entries). The number of connected clients is not limited. The "outputmode" on a client can be set independently from server. The client-mode is especially useful if the server runs as a daemon; - The writing to log-file can be disabled via option "createlog". The location of log-file controls the option "log-file"; - Switch "-p" (--printconfig) prints the name of configuration file being used and all option/value-pairs, taking into account all used "-o" (--option) - switches; - The communication protocol between server and client was optimized to minimize the size of transferred data. Instead of fixing the size for Filenames in data-structures to 512 bytes only in fact used data are transferred; - Extensions in ncurses-outputmode: scrolling in queue-list works better, navigation in queue with keys Up, Down, PgUp, PgDn, Home, End. Keys to move entries are "U" (move up), "N" (move down), "T" (move to top), "B" (move to bottom). "P" to pause/unpause file. The size, percentage 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 - 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. There are new configure-options now: "--disable-uulib" to compile the program without uulib; "--disable-ncurses" to disable ncurses-support (eliminates necessity of ncurses-libs), useful on embedded systems with little resources; "--disable-parcheck" to compile without par-check; - The algorithm for parsing of nzb-files now uses XMLReader instead of DOM-Parser to minimize memory usage (no mor needs to build complete DOM-tree in memory). Thanks to Thierry MERLE for the patch; - The log-file contains now thread-ID for all entry-types and additionally for debug-entries: filename, line number and function's name of source code, where the message was printed. Debug-messages can be disabled in config-file (option "debugtarget") like other messages; - The program is now compatible with windows. Project file for MS Visual C++ 2005 is included. Use "nzbget -install" and "nzbget -remove" to install/remove nzbget-Service. Servers and clients can run on diferrent operating systems; - Improved compatibility with POSIX systems; Tested on: - Linux Debian 3.1 on x86; - Linux BusyBox with uClibc on MIPSEL; - PC-BSD 1.4 (based on FreeBSD 6.2) on x86; - Solaris 10 on x86; - Many memory-leaks and thread issues were fixed; - The program was thoroughly worked over. Almost every line of code was revised. nzbget-0.2.3 - Fixed problem with losing connection to newsserver after too long idle time - Added functionality for dumping lots of debug info nzbget-0.2.2 - Added Florian Penzkofers fix for FreeBSD, exchanging base functionality in SingleServerPool.cpp with a more elegant solution - Added functionality for forcing answer to reloading queue upon startup of server + use -y option to force from command-line + use "reloadqueue" option in nzbget.cfg to control behavior - Added nzbget.cfg options to control where info, warnings and errors get directed to (either screen, log or both) - Added option "createbrokenfilelog" in nzbget.cfg nzbget-0.2.1 - Changed and extendddddddddddddddddd/server interface - Added timeout on sockets which prevents certain types of nzbget hanging - Added Kristian Hermansen's patch for renaming broken files nzbget-0.2.0 - Moved 0.1.2-alt4 to a official release as 0.2.0 - Small fixes nzbget-0.1.2-alt4 - implemented tcp/ip communication between client & server (removing the rather defunct System V IPC) - added queue editing functionality in server-mode nzbget-0.1.2-alt1 - added new ncurses frontend - added server/client-mode (using System V IPC) - added functionality for queueing download requests nzbget-0.1.2 - performance-improvements - commandline-options - fixes nzbget-0.1.1 - new output - fixes nzbget-0.1.0a - compiling-fixes nzbget-0.1.0 - initial release nzbget-16.4/tests/testdata/parchecker/testfile.vol00+1.PAR20000644000175000017500000001230012630544544023176 0ustar andreasandreasPAR2PKTn0,6YVPAR 2.0RecvSlic~r/`_XC({GY?;P(5c+)"@K44>>z ?LX Q_\ +i48E<-[GuYW xp}4jeda, Oec76KvpL=h'e-hn"GITYxW`*,f,'WlQ|Q3T ChP` L5bwMtGqByPHQ9TX=4simspS*wiFunado?">k:A}#'D_t?V-4|3#.?b7]Vt:a/\QCrqGbT2A'Jh2D %ur5:'@&] , m q-6'%=P* }CuFbSFb:h+hyc&hU3db0y%cQ%El"nzYR .)0pR#f'1y[ 8fPY}yXO,kD6@ i|?@E#r4!yD+ 3WS"Tqe P4L"0[ lxcZ`f4LdKR9dHz'k5\MF>s<eE!^X8JO ѥ}ObC/]P~#YYL,bA_EoH>,'8ic6Y9 #k+chNXA /y-@Q<=hbCz,K D$AlF_21cx _#eV+1XWHW{5A9Y}[(DPAR2PKT{2{>(.b z\PnBpƁ DR l%Svse2 Azbys`?'`{'Da$*IFIXQ<z`_ʉ1[m:BTu]dcY0ϤNӦެm}xXք: /j)&^!PʄvBAKg)*[<@vKO*Z k (Z9 oYZH.&?~p"SV`șGwhd#- {=̉Aknfwx8螴 ~CepJe]j:m$iHpM)π0㟭QanS >|\yzsbe'P-,Lu|]e7mm\:`?Sf?>&vD0r=PBW(уJȐЕ=ŪpԱ}G+/QderI9ءgeE[b~G!~ o&s!bCɽ!A 8| 26*nPԃ,̝"6eN/B$Q:`:UXM Yp3#63N~{o+Ua_ VV$&D2|$Ѡ&+; @7I }z[k{g"\ytTL R -šPd2h0,":ZerC>\L:bY_rS8 ՀVzz1 d-[_W;wE 3]3wqI@Ž(p2?{>H'Ip٬35]q]cJGvahރ|-Bc5+ 71Wx|FMª>9O88 +-ޓ%ABbM.LX ;y7omqP@kWj.A=hȼK.@AT$#)ƶcl#rS6h#@%?)3&M}h[oŧP &T30M>^EnRR,fӤ3_s{> GWrfJbd JcLG ?S$im֦H1|zχpV"\KofCLɇ ÖhYqE(̈&y.7HF?;ZE[Ѳ৕{qx=UJnʪYr"}cLJ|1)=b9:^$ګ!l]&iJ Mj{ڕv:(j ,c3zhNwԬBFL=*n\Q#Ejdz{Z&]fc+ǝSj עJr@ZVC"Ʌz%~!>7ِ=2'*2_O/16̸=nx'ft|Y Y?͹[)_w8#+PQ> 㘸,2Ԏb [:0*ۿK>z7` `x#!bw26oamk!*+nYw@t4wqv)MCInT) qO0wFڋX_ t _m> 湞(: a7xCBT>oloVYjf"yO5z$ШaFrŀE)v"< 䮆 Bs]vL_|LA v2u=\@,*[=QrJj#WaVen*ȔLk8+-rP-ɤ\^+q͞gR.LFTNIWP-#|w=4Hhqv2_-d4'x EaT}WNem-\0n=ǻ,ck yR!FI_c"{AH?uڥK#~|5n̸Ib PAR2PKTx3q=_* ,6YVPAR 2.0FileDesc8vw!p-4@9iow!p-4@9iotestfile.nfoPAR2PKTxqNphώB,6YVPAR 2.0IFSC8v Vs&oz70U`,;ܿNGtmPAR2PKTl.à^X*Qks8 )߯p$h_@K`?=g hRɔ/r T=Y!'힔*Y|,UZ9!acؒVXFL.d07Uiu%p>&2Xbɶ-|JV?9, -Ѵy1TȒmaڸy=_=U]NMB[XKO)(`^Gڒj J_ UT_s2C;%ט(#tnfNR:ց2&϶sI^昨`^]M )+s|vs%ٚ5MF[Ne"#SBR0_-3Xz!,pTO<RMf!n5'>kxoD|yOB= >RӃƴοPAR2PKT{2{>(.b z\PnBpƁ DR l%Svse2 Azbys`?'`{'Da$*IFIXQ<z`_ʉ1[m:BTu]dcY0ϤNӦެm}xXք: /j)&^!PʄvBAKg)*[<@vKO*Z k (Z9 oYZH.&?~p"SV`șGwhd#- {=̉Aknfwx8螴 ~CepJe]j:m$iHpM)π0㟭QanS >|\yzsbe'P-,Lu|]e7mm\:`?Sf?>&vD0r=PBW(уJȐЕ=ŪpԱ}G+/QderI9ءgeE[b~G!~ o&s!bCɽ!A 8| 26*nPԃ,̝"6eN/B$Q:`:UXM Yp3#63N~{o+Ua_ VV$&D2|$Ѡ&+; @7I }z[k{g"\ytTL R -šPd2h0,":ZerC>\L:bY_rS8 ՀVzz1 d-[_W;wE 3]3wqI@Ž(p2?{>H'Ip٬35]q]cJGvahރ|-Bc5+ 71Wx|FMª>9O88 +-ޓ%ABbM.LX ;y7omqP@kWj.A=hȼK.@AT$#)ƶcl#rS6h#@%?)3&M}h[oŧP &T30M>^EnRR,fӤ3_s{> GWrfJbd JcLG ?S$im֦H1|zχpV"\KofCLɇ ÖhYqE(̈&y.7HF?;ZE[Ѳ৕{qx=UJnʪYr"}cLJ|1)=b9:^$ګ!l]&iJ Mj{ڕv:(j ,c3zhNwԬBFL=*n\Q#Ejdz{Z&]fc+ǝSj עJr@ZVC"Ʌz%~!>7ِ=2'*2_O/16̸=nx'ft|Y Y?͹[)_w8#+PQ> 㘸,2Ԏb [:0*ۿK>z7` `x#!bw26oamk!*+nYw@t4wqv)MCInT) qO0wFڋX_ t _m> 湞(: a7xCBT>oloVYjf"yO5z$ШaFrŀE)v"< 䮆 Bs]vL_|LA v2u=\@,*[=QrJj#WaVen*ȔLk8+-rP-ɤ\^+q͞gR.LFTNIWP-#|w=4Hhqv2_-d4'x EaT}WNem-\0n=ǻ,ck yR!FI_c"{AH?uڥK#~|5n̸Ib PAR2PKTx3q=_* ,6YVPAR 2.0FileDesc8vw!p-4@9iow!p-4@9iotestfile.nfoPAR2PKTxqNphώB,6YVPAR 2.0IFSC8v Vs&oz70U`,;ܿNGtmPAR2PKTl.à^l9w.qvټͦҺM~w0xZ<~/bCP8יO^ Q/ -˩גu[8eqZo8ι|n%0ԟԖ8Zc^a햚CGW0'{+`^,7U/9$3hLBeTMō[Q?yBJOʷ_騙CQD;Z3Q8_U8E)fh΅y 8id`!TjNΏ^|(.b z\PnBpƁ DR l%Svse2 Azbys`?'`{'Da$*IFIXQ<z`_ʉ1[m:BTu]dcY0ϤNӦެm}xXք: /j)&^!PʄvBAKg)*[<@vKO*Z k (Z9 oYZH.&?~p"SV`șGwhd#- {=̉Aknfwx8螴 ~CepJe]j:m$iHpM)π0㟭QanS >|\yzsbe'P-,Lu|]e7mm\:`?Sf?>&vD0r=PBW(уJȐЕ=ŪpԱ}G+/QderI9ءgeE[b~G!~ o&s!bCɽ!A 8| 26*nPԃ,̝"6eN/B$Q:`:UXM Yp3#63N~{o+Ua_ VV$&D2|$Ѡ&+; @7I }z[k{g"\ytTL R -šPd2h0,":ZerC>\L:bY_rS8 ՀVzz1 d-[_W;wE 3]3wqI@Ž(p2?{>H'Ip٬35]q]cJGvahރ|-Bc5+ 71Wx|FMª>9O88 +-ޓ%ABbM.LX ;y7omqP@kWj.A=hȼK.@AT$#)ƶcl#rS6h#@%?)3&M}h[oŧP &T30M>^EnRR,fӤ3_s{> GWrfJbd JcLG ?S$im֦H1|zχpV"\KofCLɇ ÖhYqE(̈&y.7HF?;ZE[Ѳ৕{qx=UJnʪYr"}cLJ|1)=b9:^$ګ!l]&iJ Mj{ڕv:(j ,c3zhNwԬBFL=*n\Q#Ejdz{Z&]fc+ǝSj עJr@ZVC"Ʌz%~!>7ِ=2'*2_O/16̸=nx'ft|Y Y?͹[)_w8#+PQ> 㘸,2Ԏb [:0*ۿK>z7` `x#!bw26oamk!*+nYw@t4wqv)MCInT) qO0wFڋX_ t _m> 湞(: a7xCBT>oloVYjf"yO5z$ШaFrŀE)v"< 䮆 Bs]vL_|LA v2u=\@,*[=QrJj#WaVen*ȔLk8+-rP-ɤ\^+q͞gR.LFTNIWP-#|w=4Hhqv2_-d4'x EaT}WNem-\0n=ǻ,ck yR!FI_c"{AH?uڥK#~|5n̸Ib PAR2PKTx3q=_* ,6YVPAR 2.0FileDesc8vw!p-4@9iow!p-4@9iotestfile.nfoPAR2PKTxqNphώB,6YVPAR 2.0IFSC8v Vs&oz70U`,;ܿNGtmPAR2PKTl.à^b5Ф5\00GyXYG&t+&-\&Smx)~oG]IJHZ9 weρ:>ڣWXrT3wЅ1bCܮX:l H܍wj)nb \Gҡ޹ ݌YV21H˄դ9)~z 6~Og'"{pHɗ}U`Ftς72'I )v p2"9 %2l8k*/+b+3x^w|^H`.Ndp J@F3OxRg#=Šb!ߞLn@r ;djr3Yî^dž?_a-޾ޒ7oBvi-59TB5J"[gv ]T[ve3g|PlPAR2PKT{2{>(.b z\PnBpƁ DR l%Svse2 Azbys`?'`{'Da$*IFIXQ<z`_ʉ1[m:BTu]dcY0ϤNӦެm}xXք: /j)&^!PʄvBAKg)*[<@vKO*Z k (Z9 oYZH.&?~p"SV`șGwhd#- {=̉Aknfwx8螴 ~CepJe]j:m$iHpM)π0㟭QanS >|\yzsbe'P-,Lu|]e7mm\:`?Sf?>&vD0r=PBW(уJȐЕ=ŪpԱ}G+/QderI9ءgeE[b~G!~ o&s!bCɽ!A 8| 26*nPԃ,̝"6eN/B$Q:`:UXM Yp3#63N~{o+Ua_ VV$&D2|$Ѡ&+; @7I }z[k{g"\ytTL R -šPd2h0,":ZerC>\L:bY_rS8 ՀVzz1 d-[_W;wE 3]3wqI@Ž(p2?{>H'Ip٬35]q]cJGvahރ|-Bc5+ 71Wx|FMª>9O88 +-ޓ%ABbM.LX ;y7omqP@kWj.A=hȼK.@AT$#)ƶcl#rS6h#@%?)3&M}h[oŧP &T30M>^EnRR,fӤ3_s{> GWrfJbd JcLG ?S$im֦H1|zχpV"\KofCLɇ ÖhYqE(̈&y.7HF?;ZE[Ѳ৕{qx=UJnʪYr"}cLJ|1)=b9:^$ګ!l]&iJ Mj{ڕv:(j ,c3zhNwԬBFL=*n\Q#Ejdz{Z&]fc+ǝSj עJr@ZVC"Ʌz%~!>7ِ=2'*2_O/16̸=nx'ft|Y Y?͹[)_w8#+PQ> 㘸,2Ԏb [:0*ۿK>z7` `x#!bw26oamk!*+nYw@t4wqv)MCInT) qO0wFڋX_ t _m> 湞(: a7xCBT>oloVYjf"yO5z$ШaFrŀE)v"< 䮆 Bs]vL_|LA v2u=\@,*[=QrJj#WaVen*ȔLk8+-rP-ɤ\^+q͞gR.LFTNIWP-#|w=4Hhqv2_-d4'x EaT}WNem-\0n=ǻ,ck yR!FI_c"{AH?uڥK#~|5n̸Ib PAR2PKTx3q=_* ,6YVPAR 2.0FileDesc8vw!p-4@9iow!p-4@9iotestfile.nfoPAR2PKTSCoݑ Ywŗ,6YVPAR 2.0RecvSlicl3C)lOķ^0hxe(e_Y]4w`P**#I#߬b*~a~{|ЪdW 1|8UROl7NuDqHtR;N@ٞ¹sG E0,{c6p֋ dD oR++d f}0۹5K xqGAp-oñKT[V}nS2gm3kt4e3LNAD伨/ h18K^l_KPZ{v'&J`m֏.RMHHC- L~2}|z{d$>[\o-9!tr^gNjE;Ol6r Cl&|ޛɥZ&~L㠀 }qķTwн#SR[{Swgխ"e&| P~7 "@Zl}Xy$v;g_T8ig`o߽PAR2PKTxqNphώB,6YVPAR 2.0IFSC8v Vs&oz70U`,;ܿNGtmPAR2PKTl.à^7j*1>͎(}{Go[e se2&"> M9L4@Z]6"2;wn"Y߯ohA yjy9VI={oR.sV5=Ao#=lĞO- dxƬ8$VoYPAR2PKT窳E2qQ׾z,6YVPAR 2.0IFSC^(.b z\PnBpƁ DR l%Svse2 Azbys`?'`{'Da$*IFIXQ<z`_ʉ1[m:BTu]dcY0ϤNӦެm}xXք: /j)&^!PʄvBAKg)*[<@vKO*Z k (Z9 oYZH.&?~p"SV`șGwhd#- {=̉Aknfwx8螴 ~CepJe]j:m$iHpM)π0㟭QanS >|\yzsbe'P-,Lu|]e7mm\:`?Sf?>&vD0r=PBW(уJȐЕ=ŪpԱ}G+/QderI9ءgeE[b~G!~ o&s!bCɽ!A 8| 26*nPԃ,̝"6eN/B$Q:`:UXM Yp3#63N~{o+Ua_ VV$&D2|$Ѡ&+; @7I }z[k{g"\ytTL R -šPd2h0,":ZerC>\L:bY_rS8 ՀVzz1 d-[_W;wE 3]3wqI@Ž(p2?{>H'Ip٬35]q]cJGvahރ|-Bc5+ 71Wx|FMª>9O88 +-ޓ%ABbM.LX ;y7omqP@kWj.A=hȼK.@AT$#)ƶcl#rS6h#@%?)3&M}h[oŧP &T30M>^EnRR,fӤ3_s{> GWrfJbd JcLG ?S$im֦H1|zχpV"\KofCLɇ ÖhYqE(̈&y.7HF?;ZE[Ѳ৕{qx=UJnʪYr"}cLJ|1)=b9:^$ګ!l]&iJ Mj{ڕv:(j ,c3zhNwԬBFL=*n\Q#Ejdz{Z&]fc+ǝSj עJr@ZVC"Ʌz%~!>7ِ=2'*2_O/16̸=nx'ft|Y Y?͹[)_w8#+PQ> 㘸,2Ԏb [:0*ۿK>z7` `x#!bw26oamk!*+nYw@t4wqv)MCInT) qO0wFڋX_ t _m> 湞(: a7xCBT>oloVYjf"yO5z$ШaFrŀE)v"< 䮆 Bs]vL_|LA v2u=\@,*[=QrJj#WaVen*ȔLk8+-rP-ɤ\^+q͞gR.LFTNIWP-#|w=4Hhqv2_-d4'x EaT}WNem-\0n=ǻ,ck yR!FI_c"{AH?uڥK#~|5n̸Ib PAR2PKTx3q=_* ,6YVPAR 2.0FileDesc8vw!p-4@9iow!p-4@9iotestfile.nfoPAR2PKTxqNphώB,6YVPAR 2.0IFSC8v Vs&oz70U`,;ܿNGtmPAR2PKTl.à^(.b z\PnBpƁ DR l%Svse2 Azbys`?'`{'Da$*IFIXQ<z`_ʉ1[m:BTu]dcY0ϤNӦެm}xXք: /j)&^!PʄvBAKg)*[<@vKO*Z k (Z9 oYZH.&?~p"SV`șGwhd#- {=̉Aknfwx8螴 ~CepJe]j:m$iHpM)π0㟭QanS >|\yzsbe'P-,Lu|]e7mm\:`?Sf?>&vD0r=PBW(уJȐЕ=ŪpԱ}G+/QderI9ءgeE[b~G!~ o&s!bCɽ!A 8| 26*nPԃ,̝"6eN/B$Q:`:UXM Yp3#63N~{o+Ua_ VV$&D2|$Ѡ&+; @7I }z[k{g"\ytTL R -šPd2h0,":ZerC>\L:bY_rS8 ՀVzz1 d-[_W;wE 3]3wqI@Ž(p2?{>H'Ip٬35]q]cJGvahރ|-Bc5+ 71Wx|FMª>9O88 +-ޓ%ABbM.LX ;y7omqP@kWj.A=hȼK.@AT$#)ƶcl#rS6h#@%?)3&M}h[oŧP &T30M>^EnRR,fӤ3_s{> GWrfJbd JcLG ?S$im֦H1|zχpV"\KofCLɇ ÖhYqE(̈&y.7HF?;ZE[Ѳ৕{qx=UJnʪYr"}cLJ|1)=b9:^$ګ!l]&iJ Mj{ڕv:(j ,c3zhNwԬBFL=*n\Q#Ejdz{Z&]fc+ǝSj עJr@ZVC"Ʌz%~!>7ِ=2'*2_O/16̸=nx'ft|Y Y?͹[)_w8#+PQ> 㘸,2Ԏb [:0*ۿK>z7` `x#!bw26oamk!*+nYw@t4wqv)MCInT) qO0wFڋX_ t _m> 湞(: a7xCBT>oloVYjf"yO5z$ШaFrŀE)v"< 䮆 Bs]vL_|LA v2u=\@,*[=QrJj#WaVen*ȔLk8+-rP-ɤ\^+q͞gR.LFTNIWP-#|w=4Hhqv2_-d4'x EaT}WNem-\0n=ǻ,ck yR!FI_c"{AH?uڥK#~|5n̸Ib PAR2PKTx3q=_* ,6YVPAR 2.0FileDesc8vw!p-4@9iow!p-4@9iotestfile.nfoPAR2PKTxqNphώB,6YVPAR 2.0IFSC8v Vs&oz70U`,;ܿNGtmPAR2PKTl.à^ * * 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 "catch.h" #include "nzbget.h" #include "FeedFilter.h" void TestFilter(FeedItemInfo* pFeedItemInfo, const char* szFilter, FeedItemInfo::EMatchStatus eExpectedMatch) { pFeedItemInfo->SetMatchStatus(FeedItemInfo::msIgnored); pFeedItemInfo->SetMatchRule(0); FeedFilter filter(szFilter); filter.Match(pFeedItemInfo); INFO(szFilter); REQUIRE(pFeedItemInfo->GetMatchStatus() == eExpectedMatch); } TEST_CASE("Feed filter: one liners", "[FeedFilter][Quick]") { FeedItemInfo item; item.SetTitle("Game.of.Clowns.S02E06.REAL.1080p.HDTV.X264-Group.WEB-DL"); item.SetFilename("Game.of.Clowns.S02E06.REAL.1080p.HDTV.X264-Group.WEB-DL"); item.SetSize(1600*1024*1024); item.SetTime(time(NULL) - 60*60*15); // age: 15 hours item.SetCategory("TV > HD"); item.SetRageId(123456); item.SetSeason("02"); item.SetEpisode("06"); TestFilter(&item, "game", FeedItemInfo::msAccepted); TestFilter(&item, "games", FeedItemInfo::msIgnored); TestFilter(&item, "gam", FeedItemInfo::msIgnored); TestFilter(&item, "gam*", FeedItemInfo::msAccepted); TestFilter(&item, "am*", FeedItemInfo::msIgnored); TestFilter(&item, "*am*", FeedItemInfo::msAccepted); TestFilter(&item, "game of clowns", FeedItemInfo::msAccepted); TestFilter(&item, "game of clown", FeedItemInfo::msIgnored); TestFilter(&item, "game of clown*", FeedItemInfo::msAccepted); TestFilter(&item, "game.of?clowns", FeedItemInfo::msAccepted); TestFilter(&item, "game*of*clowns", FeedItemInfo::msIgnored); TestFilter(&item, "game*of*clowns*", FeedItemInfo::msIgnored); TestFilter(&item, "*game*of*clowns*", FeedItemInfo::msAccepted); TestFilter(&item, "game of clowns WEB-DL", FeedItemInfo::msAccepted); TestFilter(&item, "game of clowns -WEB-DL", FeedItemInfo::msIgnored); TestFilter(&item, "+title:@game", FeedItemInfo::msAccepted); TestFilter(&item, "-game", FeedItemInfo::msIgnored); TestFilter(&item, "-kings", FeedItemInfo::msAccepted); TestFilter(&item, "title:game.of.clowns size:<4GB", FeedItemInfo::msAccepted); TestFilter(&item, "title:game.of?clowns", FeedItemInfo::msAccepted); TestFilter(&item, "@game", FeedItemInfo::msAccepted); TestFilter(&item, "size:<1.4GB", FeedItemInfo::msIgnored); TestFilter(&item, "HDTV category:*hd* -badgroup s02e* size:>600MB size:<2000MB", FeedItemInfo::msAccepted); TestFilter(&item, "size:<2000MB", FeedItemInfo::msAccepted); TestFilter(&item, "age:<10", FeedItemInfo::msAccepted); TestFilter(&item, "age:<=10", FeedItemInfo::msAccepted); TestFilter(&item, "age:>1h", FeedItemInfo::msAccepted); TestFilter(&item, "age:>=1h", FeedItemInfo::msAccepted); TestFilter(&item, "$game.*\\.s02e[0-9]*\\..*", FeedItemInfo::msAccepted); TestFilter(&item, "R: game of", FeedItemInfo::msRejected); TestFilter(&item, "A(category:my series, pause:yes, priority:100):game", FeedItemInfo::msAccepted); TestFilter(&item, "A(c:my series, p:n, r:100, s:1000, k:1080p):game", FeedItemInfo::msAccepted); TestFilter(&item, "A(my series, paused, 100):game", FeedItemInfo::msAccepted); TestFilter(&item, "A(k:series=GOT-${1}-${2}): Game of clowns S##E##", FeedItemInfo::msAccepted); TestFilter(&item, "A(k:series=GOT-${1}-${2}): $.+S([0-9]{1,2})E([0-9]{1,2})", FeedItemInfo::msAccepted); } nzbget-16.4/tests/queue/0000755000175000017500000000000012630544544015046 5ustar andreasandreasnzbget-16.4/tests/queue/NZBFileTest.cpp0000644000175000017500000000445212630544544017650 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2015 Andrey Prygunkov * * 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 #include "catch.h" #include "nzbget.h" #include "NZBFile.h" #include "Options.h" #include "TestUtil.h" void TestNzb(std::string TestFilename) { INFO(std::string("Filename: ") + TestFilename); std::string NzbFilename(TestUtil::TestDataDir() + "/nzbfile/"+ TestFilename + ".nzb"); std::string InfoFilename(TestUtil::TestDataDir() + "/nzbfile/"+ TestFilename + ".txt"); NZBFile* pNZBFile = new NZBFile(NzbFilename.c_str(), ""); bool bParsedOK = pNZBFile->Parse(); REQUIRE(bParsedOK == true); FILE* infofile = fopen(InfoFilename.c_str(), FOPEN_RB); REQUIRE(infofile != NULL); char buffer[1024]; while (fgets(buffer, sizeof(buffer), infofile) && *buffer == '#') ; REQUIRE(*buffer); int iFileCount = atoi(buffer); REQUIRE(pNZBFile->GetNZBInfo()->GetFileCount() == iFileCount); for (int i = 0; i < iFileCount; i++) { while (fgets(buffer, sizeof(buffer), infofile) && *buffer == '#') ; REQUIRE(*buffer); FileInfo* pFileInfo = pNZBFile->GetNZBInfo()->GetFileList()->at(i); REQUIRE(pFileInfo != NULL); Util::TrimRight(buffer); REQUIRE(std::string(pFileInfo->GetFilename()) == std::string(buffer)); } fclose(infofile); delete pNZBFile; } TEST_CASE("Nzb parser", "[NZBFile][TestData]") { Options::CmdOptList cmdOpts; cmdOpts.push_back("SaveQueue=no"); Options options(&cmdOpts, NULL); TestNzb("dotless"); TestNzb("plain"); } nzbget-16.4/tests/suite/0000755000175000017500000000000012630544544015053 5ustar andreasandreasnzbget-16.4/tests/suite/TestUtil.cpp0000644000175000017500000000701512630544544017337 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2015 Andrey Prygunkov * * 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 #include #include #include #include #ifndef WIN32 #include #endif #include "catch.h" #include "nzbget.h" #include "Util.h" #include "TestUtil.h" bool TestUtil::m_bUsedWorkingDir = false; std::string DataDir; class NullStreamBuf : public std::streambuf { public: int sputc ( char c ) { return (int) c; } } NullStreamBufSuiteInstance; std::streambuf* oldcoutbuf; void TestUtil::Init(const char* argv0) { m_bUsedWorkingDir = false; char szFilename[MAX_PATH + 1]; Util::GetExeFileName(argv0, szFilename, sizeof(szFilename)); Util::NormalizePathSeparators(szFilename); char* end = strrchr(szFilename, PATH_SEPARATOR); if (end) *end = '\0'; DataDir = szFilename; DataDir += "/testdata"; if (!Util::DirectoryExists(DataDir.c_str())) { DataDir = szFilename; DataDir += "/tests/testdata"; } if (!Util::DirectoryExists(DataDir.c_str())) { DataDir = ""; } } void TestUtil::Final() { if (m_bUsedWorkingDir) { CleanupWorkingDir(); } } const std::string TestUtil::TestDataDir() { if (DataDir == "") { printf("ERROR: Directory \"testdata\" not found.\n"); exit(1); } return DataDir; } const std::string TestUtil::WorkingDir() { return TestDataDir() + "/temp"; } void TestUtil::PrepareWorkingDir(const std::string templateDir) { m_bUsedWorkingDir = true; std::string workDir = WorkingDir(); std::string srcDir(TestDataDir() + "/" + templateDir); char szErrStr[256]; int retries = 20; Util::DeleteDirectoryWithContent(workDir.c_str(), szErrStr, sizeof(szErrStr)); while (Util::DirectoryExists(workDir.c_str()) && retries > 0) { usleep(1000 * 100); retries--; Util::DeleteDirectoryWithContent(workDir.c_str(), szErrStr, sizeof(szErrStr)); } REQUIRE_FALSE(Util::DirectoryExists(workDir.c_str())); Util::CreateDirectory(workDir.c_str()); REQUIRE(Util::DirEmpty(workDir.c_str())); CopyAllFiles(workDir, srcDir); } void TestUtil::CopyAllFiles(const std::string destDir, const std::string srcDir) { DirBrowser dir(srcDir.c_str()); while (const char* filename = dir.Next()) { if (strcmp(filename, ".") && strcmp(filename, "..")) { std::string srcFile(srcDir + "/" + filename); std::string dstFile(destDir + "/" + filename); REQUIRE(Util::CopyFile(srcFile.c_str(), dstFile.c_str())); } } } void TestUtil::CleanupWorkingDir() { char szErrStr[256]; Util::DeleteDirectoryWithContent(WorkingDir().c_str(), szErrStr, sizeof(szErrStr)); } void TestUtil::DisableCout() { oldcoutbuf = std::cout.rdbuf(&NullStreamBufSuiteInstance); } void TestUtil::EnableCout() { std::cout.rdbuf(oldcoutbuf); } nzbget-16.4/tests/suite/TestMain.h0000644000175000017500000000173012630544544016751 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2015 Andrey Prygunkov * * 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 TESTMAIN_H #define TESTMAIN_H int TestMain(int argc, char * argv[]); void TestCleanup(); #endif nzbget-16.4/tests/suite/TestMain.cpp0000644000175000017500000000437012630544544017307 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2015 Andrey Prygunkov * * 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 #define CATCH_CONFIG_RUNNER #include "catch.h" #include "nzbget.h" #include "Thread.h" #include "Log.h" #include "Util.h" #include "TestUtil.h" int TestMain(int argc, char * argv[]) { TestUtil::Init(argv[0]); Log::Init(); Thread::Init(); if (argc == 1) { printf("Unit and integration tests for nzbget-%s.\nUse '%s -tests [quick]' to run only quick tests or '%s -h' for more options.\n", Util::VersionRevision(), Util::BaseFileName(argv[0]), Util::BaseFileName(argv[0])); } // shift arguments for catch to not see the parameter "-tests" char** testsargv = (char**)malloc(sizeof(char*) * (argc + 1)); char szFirstArg[1024]; snprintf(szFirstArg, 1024, "%s %s", argv[0], argv[1]); szFirstArg[1024-1] = '\0'; testsargv[0] = szFirstArg; for (int i = 2; i < argc; i++) { testsargv[i-1] = argv[i]; } argc--; testsargv[argc] = NULL; int ret = Catch::Session().run(argc, testsargv); free(testsargv); Thread::Final(); Log::Final(); TestUtil::Final(); return ret; } void TestCleanup() { // If tests were run (via "TestMain") the Catch-framework does clean up automatically. // Hoeweve if no tests were run the global objects remain alive and causing memory leak // detection reports. Therefore we clean up the Catch-framework when we don't run any tests. Catch::cleanUp(); } nzbget-16.4/tests/suite/TestUtil.h0000644000175000017500000000255112630544544017004 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2015 Andrey Prygunkov * * 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 TESTUTIL_H #define TESTUTIL_H class TestUtil { private: static bool m_bUsedWorkingDir; public: static void Init(const char* argv0); static void Final(); static const std::string TestDataDir(); static const std::string WorkingDir(); static void PrepareWorkingDir(const std::string templateDir); static void CleanupWorkingDir(); static void DisableCout(); static void EnableCout(); static void CopyAllFiles(const std::string destDir, const std::string srcDir); }; #endif nzbget-16.4/tests/postprocess/0000755000175000017500000000000012630544544016306 5ustar andreasandreasnzbget-16.4/tests/postprocess/ParCheckerTest.cpp0000644000175000017500000001656712630544544021700 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2015 Andrey Prygunkov * * 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 #include #include #include #ifndef WIN32 #include #endif #include "catch.h" #include "nzbget.h" #include "Options.h" #include "ParChecker.h" #include "TestUtil.h" class ParCheckerMock: public ParChecker { private: unsigned long CalcFileCrc(const char* szFilename); protected: virtual bool RequestMorePars(int iBlockNeeded, int* pBlockFound) { return false; } virtual EFileStatus FindFileCrc(const char* szFilename, unsigned long* lCrc, SegmentList* pSegments); public: ParCheckerMock(); void Execute(); void CorruptFile(const char* szFilename, int iOffset); }; ParCheckerMock::ParCheckerMock() { TestUtil::PrepareWorkingDir("parchecker"); SetDestDir(TestUtil::WorkingDir().c_str()); } void ParCheckerMock::Execute() { TestUtil::DisableCout(); Start(); while (IsRunning()) { usleep(10*1000); } TestUtil::EnableCout(); } void ParCheckerMock::CorruptFile(const char* szFilename, int iOffset) { std::string fullfilename(TestUtil::WorkingDir() + "/" + szFilename); FILE* pFile = fopen(fullfilename.c_str(), FOPEN_RBP); REQUIRE(pFile != NULL); fseek(pFile, iOffset, SEEK_SET); char b = 0; int written = fwrite(&b, 1, 1, pFile); REQUIRE(written == 1); fclose(pFile); } ParCheckerMock::EFileStatus ParCheckerMock::FindFileCrc(const char* szFilename, unsigned long* lCrc, SegmentList* pSegments) { std::ifstream sm((TestUtil::WorkingDir() + "/crc.txt").c_str()); std::string filename, crc; while (!sm.eof()) { sm >> filename >> crc; if (filename == szFilename) { *lCrc = strtoul(crc.c_str(), NULL, 16); unsigned long lRealCrc = CalcFileCrc((TestUtil::WorkingDir() + "/" + filename).c_str()); return *lCrc == lRealCrc ? ParChecker::fsSuccess : ParChecker::fsUnknown; } } return ParChecker::fsUnknown; } unsigned long ParCheckerMock::CalcFileCrc(const char* szFilename) { FILE* infile = fopen(szFilename, FOPEN_RB); REQUIRE(infile); static const int BUFFER_SIZE = 1024 * 64; unsigned char* buffer = (unsigned char*)malloc(BUFFER_SIZE); unsigned long lDownloadCrc = 0xFFFFFFFF; int cnt = BUFFER_SIZE; while (cnt == BUFFER_SIZE) { cnt = (int)fread(buffer, 1, BUFFER_SIZE, infile); lDownloadCrc = Util::Crc32m(lDownloadCrc, buffer, cnt); } free(buffer); fclose(infile); lDownloadCrc ^= 0xFFFFFFFF; return lDownloadCrc; } TEST_CASE("Par-checker: repair not needed", "[Par][ParChecker][Slow][TestData]") { Options::CmdOptList cmdOpts; cmdOpts.push_back("ParRepair=no"); Options options(&cmdOpts, NULL); ParCheckerMock parChecker; parChecker.Execute(); REQUIRE(parChecker.GetStatus() == ParChecker::psRepairNotNeeded); REQUIRE(parChecker.GetParFull() == true); } TEST_CASE("Par-checker: repair possible", "[Par][ParChecker][Slow][TestData]") { Options::CmdOptList cmdOpts; cmdOpts.push_back("ParRepair=no"); cmdOpts.push_back("BrokenLog=no"); Options options(&cmdOpts, NULL); ParCheckerMock parChecker; parChecker.CorruptFile("testfile.dat", 20000); parChecker.Execute(); REQUIRE(parChecker.GetStatus() == ParChecker::psRepairPossible); REQUIRE(parChecker.GetParFull() == true); } TEST_CASE("Par-checker: repair successful", "[Par][ParChecker][Slow][TestData]") { Options::CmdOptList cmdOpts; cmdOpts.push_back("ParRepair=yes"); cmdOpts.push_back("BrokenLog=no"); Options options(&cmdOpts, NULL); ParCheckerMock parChecker; parChecker.CorruptFile("testfile.dat", 20000); parChecker.Execute(); REQUIRE(parChecker.GetStatus() == ParChecker::psRepaired); REQUIRE(parChecker.GetParFull() == true); } TEST_CASE("Par-checker: repair failed", "[Par][ParChecker][Slow][TestData]") { Options::CmdOptList cmdOpts; cmdOpts.push_back("ParRepair=no"); cmdOpts.push_back("BrokenLog=no"); Options options(&cmdOpts, NULL); ParCheckerMock parChecker; parChecker.CorruptFile("testfile.dat", 20000); parChecker.CorruptFile("testfile.dat", 30000); parChecker.CorruptFile("testfile.dat", 40000); parChecker.CorruptFile("testfile.dat", 50000); parChecker.CorruptFile("testfile.dat", 60000); parChecker.CorruptFile("testfile.dat", 70000); parChecker.CorruptFile("testfile.dat", 80000); parChecker.Execute(); REQUIRE(parChecker.GetStatus() == ParChecker::psFailed); REQUIRE(parChecker.GetParFull() == true); } TEST_CASE("Par-checker: quick verification repair not needed", "[Par][ParChecker][Slow][TestData]") { Options::CmdOptList cmdOpts; cmdOpts.push_back("ParRepair=no"); Options options(&cmdOpts, NULL); ParCheckerMock parChecker; parChecker.SetParQuick(true); parChecker.Execute(); REQUIRE(parChecker.GetStatus() == ParChecker::psRepairNotNeeded); REQUIRE(parChecker.GetParFull() == false); } TEST_CASE("Par-checker: quick verification repair successful", "[Par][ParChecker][Slow][TestData]") { Options::CmdOptList cmdOpts; cmdOpts.push_back("ParRepair=yes"); cmdOpts.push_back("BrokenLog=no"); Options options(&cmdOpts, NULL); ParCheckerMock parChecker; parChecker.SetParQuick(true); parChecker.CorruptFile("testfile.dat", 20000); parChecker.Execute(); REQUIRE(parChecker.GetStatus() == ParChecker::psRepaired); REQUIRE(parChecker.GetParFull() == false); } TEST_CASE("Par-checker: quick full verification repair successful", "[Par][ParChecker][Slow][TestData]") { Options::CmdOptList cmdOpts; cmdOpts.push_back("ParRepair=yes"); cmdOpts.push_back("BrokenLog=no"); Options options(&cmdOpts, NULL); ParCheckerMock parChecker; parChecker.SetParQuick(true); parChecker.CorruptFile("testfile.dat", 20000); parChecker.CorruptFile("testfile.nfo", 100); parChecker.Execute(); // All files were damaged, the full verification was performed REQUIRE(parChecker.GetStatus() == ParChecker::psRepaired); REQUIRE(parChecker.GetParFull() == true); } TEST_CASE("Par-checker: ignoring extensions", "[Par][ParChecker][Slow][TestData]") { Options::CmdOptList cmdOpts; cmdOpts.push_back("ParRepair=yes"); cmdOpts.push_back("BrokenLog=no"); SECTION("ParIgnoreExt") { cmdOpts.push_back("ParIgnoreExt=.dat"); } SECTION("ExtCleanupDisk") { cmdOpts.push_back("ExtCleanupDisk=.dat"); } Options options(&cmdOpts, NULL); ParCheckerMock parChecker; parChecker.CorruptFile("testfile.dat", 20000); parChecker.CorruptFile("testfile.dat", 30000); parChecker.CorruptFile("testfile.dat", 40000); parChecker.CorruptFile("testfile.dat", 50000); parChecker.CorruptFile("testfile.dat", 60000); parChecker.CorruptFile("testfile.dat", 70000); parChecker.CorruptFile("testfile.dat", 80000); parChecker.Execute(); REQUIRE(parChecker.GetStatus() == ParChecker::psRepairNotNeeded); } nzbget-16.4/tests/postprocess/DupeMatcherTest.cpp0000644000175000017500000000767312630544544022070 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2015 Andrey Prygunkov * * 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 #include #include #include #include #ifndef WIN32 #include #endif #include "catch.h" #include "nzbget.h" #include "Options.h" #include "DupeMatcher.h" #include "TestUtil.h" TEST_CASE("Disk matcher", "[Par][DupeMatcher][Slow][TestData]") { Options options(NULL, NULL); TestUtil::PrepareWorkingDir("DupeMatcher"); char szErrBuf[256]; // prepare directories std::string dupe1(TestUtil::WorkingDir() + "/dupe1"); REQUIRE(Util::ForceDirectories(dupe1.c_str(), szErrBuf, sizeof(szErrBuf))); TestUtil::CopyAllFiles(dupe1, TestUtil::TestDataDir() + "/parchecker"); std::string dupe2(TestUtil::WorkingDir() + "/dupe2"); REQUIRE(Util::ForceDirectories(dupe2.c_str(), szErrBuf, sizeof(szErrBuf))); TestUtil::CopyAllFiles(dupe2, TestUtil::TestDataDir() + "/parchecker"); remove((dupe2 + "/testfile.nfo").c_str()); std::string rardupe1(TestUtil::TestDataDir() + "/dupematcher1"); std::string rardupe2(TestUtil::TestDataDir() + "/dupematcher2"); std::string nondupe(TestUtil::WorkingDir() + "/nondupe"); REQUIRE(Util::ForceDirectories(nondupe.c_str(), szErrBuf, sizeof(szErrBuf))); TestUtil::CopyAllFiles(nondupe, TestUtil::TestDataDir() + "/parchecker"); remove((nondupe + "/testfile.dat").c_str()); // now test long long lExpectedSize = Util::FileSize((dupe1 + "/testfile.dat").c_str()); DupeMatcher dupe1Matcher(dupe1.c_str(), lExpectedSize); CHECK(dupe1Matcher.Prepare()); CHECK(dupe1Matcher.MatchDupeContent(dupe2.c_str())); CHECK(dupe1Matcher.MatchDupeContent(rardupe1.c_str())); CHECK(dupe1Matcher.MatchDupeContent(rardupe2.c_str())); CHECK_FALSE(dupe1Matcher.MatchDupeContent(nondupe.c_str())); DupeMatcher dupe2Matcher(dupe2.c_str(), lExpectedSize); CHECK(dupe2Matcher.Prepare()); CHECK(dupe2Matcher.MatchDupeContent(dupe1.c_str())); CHECK(dupe2Matcher.MatchDupeContent(rardupe1.c_str())); CHECK(dupe2Matcher.MatchDupeContent(rardupe2.c_str())); CHECK_FALSE(dupe2Matcher.MatchDupeContent(nondupe.c_str())); DupeMatcher nonDupeMatcher(nondupe.c_str(), lExpectedSize); CHECK_FALSE(nonDupeMatcher.Prepare()); CHECK_FALSE(nonDupeMatcher.MatchDupeContent(dupe1.c_str())); CHECK_FALSE(nonDupeMatcher.MatchDupeContent(dupe2.c_str())); CHECK_FALSE(nonDupeMatcher.MatchDupeContent(rardupe1.c_str())); CHECK_FALSE(nonDupeMatcher.MatchDupeContent(rardupe2.c_str())); DupeMatcher rardupe1matcher(rardupe1.c_str(), lExpectedSize); CHECK(rardupe1matcher.Prepare()); CHECK(rardupe1matcher.MatchDupeContent(dupe1.c_str())); CHECK(rardupe1matcher.MatchDupeContent(dupe2.c_str())); CHECK(rardupe1matcher.MatchDupeContent(rardupe2.c_str())); CHECK_FALSE(rardupe1matcher.MatchDupeContent(nondupe.c_str())); DupeMatcher rardupe2matcher(rardupe2.c_str(), lExpectedSize); CHECK(rardupe2matcher.Prepare()); CHECK(rardupe2matcher.MatchDupeContent(rardupe1.c_str())); CHECK(rardupe2matcher.MatchDupeContent(dupe1.c_str())); CHECK(rardupe2matcher.MatchDupeContent(dupe2.c_str())); CHECK_FALSE(rardupe2matcher.MatchDupeContent(nondupe.c_str())); } nzbget-16.4/tests/postprocess/ParRenamerTest.cpp0000644000175000017500000000626012630544544021712 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2015 Andrey Prygunkov * * 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 #include #include #include #include #ifndef WIN32 #include #endif #include "catch.h" #include "nzbget.h" #include "Options.h" #include "ParRenamer.h" #include "TestUtil.h" class ParRenamerMock: public ParRenamer { private: int m_iRenamed; protected: virtual void RegisterRenamedFile(const char* szOldFilename, const char* szNewFileName) { m_iRenamed++; } public: ParRenamerMock(); void Execute(); int GetRenamedCount() { return m_iRenamed; } }; ParRenamerMock::ParRenamerMock() { TestUtil::PrepareWorkingDir("parchecker"); SetDestDir(TestUtil::WorkingDir().c_str()); } void ParRenamerMock::Execute() { TestUtil::DisableCout(); m_iRenamed = 0; Start(); while (IsRunning()) { usleep(10*1000); } TestUtil::EnableCout(); } TEST_CASE("Par-renamer: rename not needed", "[Par][ParRenamer][Slow][TestData]") { Options::CmdOptList cmdOpts; cmdOpts.push_back("ParRename=yes"); Options options(&cmdOpts, NULL); ParRenamerMock parRenamer; parRenamer.Execute(); REQUIRE(parRenamer.GetStatus() == ParRenamer::psFailed); REQUIRE(parRenamer.GetRenamedCount() == 0); } TEST_CASE("Par-renamer: rename successful", "[Par][ParRenamer][Slow][TestData]") { Options::CmdOptList cmdOpts; cmdOpts.push_back("ParRename=yes"); Options options(&cmdOpts, NULL); ParRenamerMock parRenamer; Util::MoveFile((TestUtil::WorkingDir() + "/testfile.dat").c_str(), (TestUtil::WorkingDir() + "/123456").c_str()); parRenamer.Execute(); REQUIRE(parRenamer.GetStatus() == ParRenamer::psSuccess); REQUIRE(parRenamer.GetRenamedCount() == 1); REQUIRE_FALSE(parRenamer.HasMissedFiles()); } TEST_CASE("Par-renamer: detecting missing", "[Par][ParRenamer][Slow][TestData]") { Options::CmdOptList cmdOpts; cmdOpts.push_back("ParRename=yes"); Options options(&cmdOpts, NULL); ParRenamerMock parRenamer; Util::MoveFile((TestUtil::WorkingDir() + "/testfile.dat").c_str(), (TestUtil::WorkingDir() + "/123456").c_str()); parRenamer.SetDetectMissing(true); REQUIRE(remove((TestUtil::WorkingDir() + "/testfile.nfo").c_str()) == 0); parRenamer.Execute(); REQUIRE(parRenamer.GetStatus() == ParRenamer::psSuccess); REQUIRE(parRenamer.GetRenamedCount() == 1); REQUIRE(parRenamer.HasMissedFiles()); } nzbget-16.4/tests/main/0000755000175000017500000000000012630544544014646 5ustar andreasandreasnzbget-16.4/tests/main/OptionsTest.cpp0000644000175000017500000000662512630544544017656 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2015 Andrey Prygunkov * * 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 #include #include #ifndef WIN32 #include #endif #include "catch.h" #include "nzbget.h" #include "Options.h" class OptionsExtenderMock : public Options::Extender { public: int m_iNewsServers; int m_iFeeds; int m_iTasks; protected: virtual void AddNewsServer(int iID, bool bActive, const char* szName, const char* szHost, int iPort, const char* szUser, const char* szPass, bool bJoinGroup, bool bTLS, const char* szCipher, int iMaxConnections, int iRetention, int iLevel, int iGroup) { m_iNewsServers++; } virtual void AddFeed(int iID, const char* szName, const char* szUrl, int iInterval, const char* szFilter, bool bBacklog, bool bPauseNzb, const char* szCategory, int iPriority, const char* szFeedScript) { m_iFeeds++; } virtual void AddTask(int iID, int iHours, int iMinutes, int iWeekDaysBits, Options::ESchedulerCommand eCommand, const char* szParam) { m_iTasks++; } public: OptionsExtenderMock() : m_iNewsServers(0), m_iFeeds(0), m_iTasks(0) {} }; TEST_CASE("Options: initializing without configuration file", "[Options][Quick]") { Options options(NULL, NULL); REQUIRE(options.GetConfigFilename() == NULL); #ifdef WIN32 REQUIRE(strcmp(options.GetTempDir(), "nzbget/tmp") == 0); #else REQUIRE(strcmp(options.GetTempDir(), "~/downloads/tmp") == 0); #endif } TEST_CASE("Options: passing command line options", "[Options][Quick]") { Options::CmdOptList cmdOpts; cmdOpts.push_back("ControlUsername=my-user-name-1"); cmdOpts.push_back("ControlUsername=my-user-name-2"); Options options(&cmdOpts, NULL); REQUIRE(options.GetConfigFilename() == NULL); REQUIRE(strcmp(options.GetControlUsername(), "my-user-name-2") == 0); } TEST_CASE("Options: calling extender", "[Options][Quick]") { Options::CmdOptList cmdOpts; cmdOpts.push_back("Server1.Host=news.mynewsserver.com"); cmdOpts.push_back("Server1.Port=119"); cmdOpts.push_back("Server1.Connections=4"); cmdOpts.push_back("Server2.Host=news1.mynewsserver.com"); cmdOpts.push_back("Server2.Port=563"); cmdOpts.push_back("Server2.Connections=2"); cmdOpts.push_back("Feed1.Url=http://my.feed.com"); cmdOpts.push_back("Task1.Time=*:00"); cmdOpts.push_back("Task1.WeekDays=1-7"); cmdOpts.push_back("Task1.Command=pausedownload"); OptionsExtenderMock extender; Options options(&cmdOpts, &extender); REQUIRE(extender.m_iNewsServers == 2); REQUIRE(extender.m_iFeeds == 1); REQUIRE(extender.m_iTasks == 24); } nzbget-16.4/tests/main/CommandLineParserTest.cpp0000644000175000017500000000720212630544544021556 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2015 Andrey Prygunkov * * 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 #include #include #ifndef WIN32 #include #endif #include "catch.h" #include "nzbget.h" #include "CommandLineParser.h" TEST_CASE("Command line parser: initializing without configuration file", "[CommandLineParser][Quick]") { const char* argv[] = {"nzbget", "-n", "-p", NULL}; CommandLineParser commandLineParser(3, argv); REQUIRE(commandLineParser.GetConfigFilename() == NULL); REQUIRE(commandLineParser.GetClientOperation() == CommandLineParser::opClientNoOperation); } TEST_CASE("Command line parser: initializing with configuration file", "[CommandLineParser][Quick]") { const char* argv[] = {"nzbget", "-c", "/home/user/nzbget.conf", "-p", NULL}; CommandLineParser commandLineParser(4, argv); REQUIRE(commandLineParser.GetConfigFilename() != NULL); REQUIRE(strcmp(commandLineParser.GetConfigFilename(), "/home/user/nzbget.conf") == 0); REQUIRE(commandLineParser.GetClientOperation() == CommandLineParser::opClientNoOperation); } TEST_CASE("Command line parser: server mode", "[CommandLineParser][Quick]") { const char* argv[] = {"nzbget", "-n", "-s", NULL}; CommandLineParser commandLineParser(3, argv); REQUIRE(commandLineParser.GetServerMode() == true); REQUIRE(commandLineParser.GetPauseDownload() == false); } TEST_CASE("Command line parser: passing pause", "[CommandLineParser][Quick]") { const char* argv[] = {"nzbget", "-n", "-s", "-P", NULL}; CommandLineParser commandLineParser(4, argv); REQUIRE(commandLineParser.GetPauseDownload() == true); } TEST_CASE("Command line parser: extra option (1)", "[CommandLineParser][Quick]") { const char* argv[] = {"nzbget", "-n", "-o", "myoption1=yes", "-o", "myoption2=no", "-p", NULL}; CommandLineParser commandLineParser(7, argv); REQUIRE(commandLineParser.GetOptionList()->size() == 2); REQUIRE(strcmp(commandLineParser.GetOptionList()->at(0), "myoption1=yes") == 0); REQUIRE(strcmp(commandLineParser.GetOptionList()->at(0), "myoption2=no") != 0); REQUIRE(strcmp(commandLineParser.GetOptionList()->at(1), "myoption2=no") == 0); } TEST_CASE("Command line parser: extra option (2)", "[CommandLineParser][Quick]") { const char* argv[] = {"nzbget", "-n", "-o", "myoption1=yes", "-o", "myoption2=no", "-o", "myoption1=no", "-p", NULL}; CommandLineParser commandLineParser(9, argv); REQUIRE(commandLineParser.GetOptionList()->size() == 3); REQUIRE(strcmp(commandLineParser.GetOptionList()->at(0), "myoption1=yes") == 0); REQUIRE(strcmp(commandLineParser.GetOptionList()->at(1), "myoption2=no") == 0); REQUIRE(strcmp(commandLineParser.GetOptionList()->at(2), "myoption1=no") == 0); } // TESTS: Add more tests for: // - edit-command; // - all other remote commands; // - passing nzb-file in standalone mode; nzbget-16.4/tests/util/0000755000175000017500000000000012630544544014677 5ustar andreasandreasnzbget-16.4/tests/util/UtilTest.cpp0000644000175000017500000000574312630544544017171 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2015 Andrey Prygunkov * * 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 #include #include #include "catch.h" #include "nzbget.h" #include "Util.h" TEST_CASE("WebUtil: XmlStripTags", "[Util][Quick]") { const char* szXml = "

    "; const char* szText = " ID: 12345678 Name: Show name Size: 3.00 GB Attributes: Category - Movies > HD "; char* szTestString = strdup(szXml); WebUtil::XmlStripTags(szTestString); REQUIRE(strcmp(szTestString, szText) == 0); free(szTestString); } TEST_CASE("WebUtil: XmlDecode", "[Util][Quick]") { const char* szXml = "Poster: Bob <bob@home> bad—and there's one thing"; const char* szText = "Poster: Bob bad—and there's one thing"; char* szTestString = strdup(szXml); WebUtil::XmlDecode(szTestString); REQUIRE(strcmp(szTestString, szText) == 0); free(szTestString); } TEST_CASE("WebUtil: XmlRemoveEntities", "[Util][Quick]") { const char* szXml = "Poster: Bob <bob@home> bad—and there's one thing"; const char* szText = "Poster: Bob bob@home bad and there s one thing"; char* szTestString = strdup(szXml); WebUtil::XmlRemoveEntities(szTestString); REQUIRE(strcmp(szTestString, szText) == 0); free(szTestString); } TEST_CASE("WebUtil: URLEncode", "[Util][Quick]") { const char* szBadURL = "http://www.example.com/nzb_get/12344/Debian V7 6 64 bit OS.nzb"; const char* szCorrectedURL = "http://www.example.com/nzb_get/12344/Debian%20V7%206%2064%20bit%20OS.nzb"; char* szTestString = WebUtil::URLEncode(szBadURL); REQUIRE(strcmp(szTestString, szCorrectedURL) == 0); free(szTestString); } nzbget-16.4/configure0000755000175000017500000120411212630544544014470 0ustar andreasandreas#! /bin/sh # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.61 for nzbget 16.4. # # Report bugs to . # # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, # 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc. # This configure script is free software; the Free Software Foundation # gives unlimited permission to copy, distribute and modify it. ## --------------------- ## ## M4sh Initialization. ## ## --------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then emulate sh NULLCMD=: # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case `(set -o) 2>/dev/null` in *posix*) set -o posix ;; esac fi # PATH needs CR # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits # The user is always right. if test "${PATH_SEPARATOR+set}" != set; then echo "#! /bin/sh" >conf$$.sh echo "exit 0" >>conf$$.sh chmod +x conf$$.sh if (PATH="/nonexistent;."; conf$$.sh) >/dev/null 2>&1; then PATH_SEPARATOR=';' else PATH_SEPARATOR=: fi rm -f conf$$.sh fi # Support unset when possible. if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then as_unset=unset else as_unset=false fi # IFS # We need space, tab and new line, in precisely that order. Quoting is # there to prevent editors from complaining about space-tab. # (If _AS_PATH_WALK were called with IFS unset, it would disable word # splitting by setting IFS to empty value.) as_nl=' ' IFS=" "" $as_nl" # Find who we are. Look in the path if we contain no directory separator. case $0 in *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 { (exit 1); exit 1; } fi # Work around bugs in pre-3.0 UWIN ksh. for as_var in ENV MAIL MAILPATH do ($as_unset $as_var) >/dev/null 2>&1 && $as_unset $as_var done PS1='$ ' PS2='> ' PS4='+ ' # NLS nuisances. for as_var in \ LANG LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_CTYPE LC_IDENTIFICATION \ LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER \ LC_TELEPHONE LC_TIME do if (set +x; test -z "`(eval $as_var=C; export $as_var) 2>&1`"); then eval $as_var=C; export $as_var else ($as_unset $as_var) >/dev/null 2>&1 && $as_unset $as_var fi done # Required to use basename. if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi # Name of the executable. as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || echo X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # CDPATH. $as_unset CDPATH if test "x$CONFIG_SHELL" = x; then if (eval ":") 2>/dev/null; then as_have_required=yes else as_have_required=no fi if test $as_have_required = yes && (eval ": (as_func_return () { (exit \$1) } as_func_success () { as_func_return 0 } as_func_failure () { as_func_return 1 } as_func_ret_success () { return 0 } as_func_ret_failure () { return 1 } exitcode=0 if as_func_success; then : else exitcode=1 echo as_func_success failed. fi if as_func_failure; then exitcode=1 echo as_func_failure succeeded. fi if as_func_ret_success; then : else exitcode=1 echo as_func_ret_success failed. fi if as_func_ret_failure; then exitcode=1 echo as_func_ret_failure succeeded. fi if ( set x; as_func_ret_success y && test x = \"\$1\" ); then : else exitcode=1 echo positional parameters were not saved. fi test \$exitcode = 0) || { (exit 1); exit 1; } ( as_lineno_1=\$LINENO as_lineno_2=\$LINENO test \"x\$as_lineno_1\" != \"x\$as_lineno_2\" && test \"x\`expr \$as_lineno_1 + 1\`\" = \"x\$as_lineno_2\") || { (exit 1); exit 1; } ") 2> /dev/null; then : else as_candidate_shells= as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. case $as_dir in /*) for as_base in sh bash ksh sh5; do as_candidate_shells="$as_candidate_shells $as_dir/$as_base" done;; esac done IFS=$as_save_IFS for as_shell in $as_candidate_shells $SHELL; do # Try only shells that exist, to save several forks. if { test -f "$as_shell" || test -f "$as_shell.exe"; } && { ("$as_shell") 2> /dev/null <<\_ASEOF if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then emulate sh NULLCMD=: # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case `(set -o) 2>/dev/null` in *posix*) set -o posix ;; esac fi : _ASEOF }; then CONFIG_SHELL=$as_shell as_have_required=yes if { "$as_shell" 2> /dev/null <<\_ASEOF if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then emulate sh NULLCMD=: # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case `(set -o) 2>/dev/null` in *posix*) set -o posix ;; esac fi : (as_func_return () { (exit $1) } as_func_success () { as_func_return 0 } as_func_failure () { as_func_return 1 } as_func_ret_success () { return 0 } as_func_ret_failure () { return 1 } exitcode=0 if as_func_success; then : else exitcode=1 echo as_func_success failed. fi if as_func_failure; then exitcode=1 echo as_func_failure succeeded. fi if as_func_ret_success; then : else exitcode=1 echo as_func_ret_success failed. fi if as_func_ret_failure; then exitcode=1 echo as_func_ret_failure succeeded. fi if ( set x; as_func_ret_success y && test x = "$1" ); then : else exitcode=1 echo positional parameters were not saved. fi test $exitcode = 0) || { (exit 1); exit 1; } ( as_lineno_1=$LINENO as_lineno_2=$LINENO test "x$as_lineno_1" != "x$as_lineno_2" && test "x`expr $as_lineno_1 + 1`" = "x$as_lineno_2") || { (exit 1); exit 1; } _ASEOF }; then break fi fi done if test "x$CONFIG_SHELL" != x; then for as_var in BASH_ENV ENV do ($as_unset $as_var) >/dev/null 2>&1 && $as_unset $as_var done export CONFIG_SHELL exec "$CONFIG_SHELL" "$as_myself" ${1+"$@"} fi if test $as_have_required = no; then echo This script requires a shell more modern than all the echo shells that I found on your system. Please install a echo modern shell, or manually run the script under such a echo shell if you do have one. { (exit 1); exit 1; } fi fi fi (eval "as_func_return () { (exit \$1) } as_func_success () { as_func_return 0 } as_func_failure () { as_func_return 1 } as_func_ret_success () { return 0 } as_func_ret_failure () { return 1 } exitcode=0 if as_func_success; then : else exitcode=1 echo as_func_success failed. fi if as_func_failure; then exitcode=1 echo as_func_failure succeeded. fi if as_func_ret_success; then : else exitcode=1 echo as_func_ret_success failed. fi if as_func_ret_failure; then exitcode=1 echo as_func_ret_failure succeeded. fi if ( set x; as_func_ret_success y && test x = \"\$1\" ); then : else exitcode=1 echo positional parameters were not saved. fi test \$exitcode = 0") || { echo No shell found that supports shell functions. echo Please tell autoconf@gnu.org about your system, echo including any error possibly output before this echo message } as_lineno_1=$LINENO as_lineno_2=$LINENO test "x$as_lineno_1" != "x$as_lineno_2" && test "x`expr $as_lineno_1 + 1`" = "x$as_lineno_2" || { # Create $as_me.lineno as a copy of $as_myself, but with $LINENO # uniformly replaced by the line number. The first 'sed' inserts a # line-number line after each line using $LINENO; the second 'sed' # does the real work. The second script uses 'N' to pair each # line-number line with the line containing $LINENO, and appends # trailing '-' during substitution so that $LINENO is not a special # case at line end. # (Raja R Harinath suggested sed '=', and Paul Eggert wrote the # scripts with optimization help from Paolo Bonzini. Blame Lee # E. McMahon (1931-1989) for sed's syntax. :-) sed -n ' p /[$]LINENO/= ' <$as_myself | sed ' s/[$]LINENO.*/&-/ t lineno b :lineno N :loop s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ t loop s/-\n.*// ' >$as_me.lineno && chmod +x "$as_me.lineno" || { echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2 { (exit 1); exit 1; }; } # Don't try to exec as it changes $[0], causing all sort of problems # (the dirname of $[0] is not the place where we might find the # original and so on. Autoconf is especially sensitive to this). . "./$as_me.lineno" # Exit status is that of the last command. exit } if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in -n*) case `echo 'x\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. *) ECHO_C='\c';; esac;; *) ECHO_N='-n';; esac if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir fi echo >conf$$.file if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. # In both cases, we have to default to `cp -p'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -p' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -p' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null if mkdir -p . 2>/dev/null; then as_mkdir_p=: else test -d ./-p && rmdir ./-p as_mkdir_p=false fi if test -x / >/dev/null 2>&1; then as_test_x='test -x' else if ls -dL / >/dev/null 2>&1; then as_ls_L_option=L else as_ls_L_option= fi as_test_x=' eval sh -c '\'' if test -d "$1"; then test -d "$1/."; else case $1 in -*)set "./$1";; esac; case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in ???[sx]*):;;*)false;;esac;fi '\'' sh ' fi as_executable_p=$as_test_x # Sed expression to map a string onto a valid CPP name. as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" exec 7<&0 &1 # Name of the host. # hostname on some systems (SVR3.2, Linux) returns a bogus exit status, # so uname gets run too. ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` # # Initializations. # ac_default_prefix=/usr/local ac_clean_files= ac_config_libobj_dir=. LIBOBJS= cross_compiling=no subdirs= MFLAGS= MAKEFLAGS= SHELL=${CONFIG_SHELL-/bin/sh} # Identity of this package. PACKAGE_NAME='nzbget' PACKAGE_TARNAME='nzbget' PACKAGE_VERSION='16.4' PACKAGE_STRING='nzbget 16.4' PACKAGE_BUGREPORT='hugbug@users.sourceforge.net' ac_unique_file="daemon/main/nzbget.cpp" # Factoring default headers for most tests. ac_includes_default="\ #include #ifdef HAVE_SYS_TYPES_H # include #endif #ifdef HAVE_SYS_STAT_H # include #endif #ifdef STDC_HEADERS # include # include #else # ifdef HAVE_STDLIB_H # include # endif #endif #ifdef HAVE_STRING_H # if !defined STDC_HEADERS && defined HAVE_MEMORY_H # include # endif # include #endif #ifdef HAVE_STRINGS_H # include #endif #ifdef HAVE_INTTYPES_H # include #endif #ifdef HAVE_STDINT_H # include #endif #ifdef HAVE_UNISTD_H # include #endif" ac_subst_vars='SHELL PATH_SEPARATOR PACKAGE_NAME PACKAGE_TARNAME PACKAGE_VERSION PACKAGE_STRING PACKAGE_BUGREPORT exec_prefix prefix program_transform_name bindir sbindir libexecdir datarootdir datadir sysconfdir sharedstatedir localstatedir includedir oldincludedir docdir infodir htmldir dvidir pdfdir psdir libdir localedir mandir DEFS ECHO_C ECHO_N ECHO_T LIBS build_alias host_alias target_alias INSTALL_PROGRAM INSTALL_SCRIPT INSTALL_DATA CYGPATH_W PACKAGE VERSION ACLOCAL AUTOCONF AUTOMAKE AUTOHEADER MAKEINFO install_sh STRIP INSTALL_STRIP_PROGRAM mkdir_p AWK SET_MAKE am__leading_dot AMTAR am__tar am__untar CXX CXXFLAGS LDFLAGS CPPFLAGS ac_ct_CXX EXEEXT OBJEXT DEPDIR am__include am__quote AMDEP_TRUE AMDEP_FALSE AMDEPBACKSLASH CXXDEPMODE am__fastdepCXX_TRUE am__fastdepCXX_FALSE TAR MAKE CXXCPP GREP EGREP PKG_CONFIG libxml2_CFLAGS libxml2_LIBS LIBOBJS WITH_PAR2_TRUE WITH_PAR2_FALSE openssl_CFLAGS openssl_LIBS WITH_TESTS_TRUE WITH_TESTS_FALSE LTLIBOBJS' ac_subst_files='' ac_precious_vars='build_alias host_alias target_alias CXX CXXFLAGS LDFLAGS LIBS CPPFLAGS CCC CXXCPP PKG_CONFIG libxml2_CFLAGS libxml2_LIBS openssl_CFLAGS openssl_LIBS' # Initialize some variables set by options. ac_init_help= ac_init_version=false # The variables have the same names as the options, with # dashes changed to underlines. cache_file=/dev/null exec_prefix=NONE no_create= no_recursion= prefix=NONE program_prefix=NONE program_suffix=NONE program_transform_name=s,x,x, silent= site= srcdir= verbose= x_includes=NONE x_libraries=NONE # Installation directory options. # These are left unexpanded so users can "make install exec_prefix=/foo" # and all the variables that are supposed to be based on exec_prefix # by default will actually change. # Use braces instead of parens because sh, perl, etc. also accept them. # (The list follows the same order as the GNU Coding Standards.) bindir='${exec_prefix}/bin' sbindir='${exec_prefix}/sbin' libexecdir='${exec_prefix}/libexec' datarootdir='${prefix}/share' datadir='${datarootdir}' sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' includedir='${prefix}/include' oldincludedir='/usr/include' docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' infodir='${datarootdir}/info' htmldir='${docdir}' dvidir='${docdir}' pdfdir='${docdir}' psdir='${docdir}' libdir='${exec_prefix}/lib' localedir='${datarootdir}/locale' mandir='${datarootdir}/man' ac_prev= ac_dashdash= for ac_option do # If the previous option needs an argument, assign it. if test -n "$ac_prev"; then eval $ac_prev=\$ac_option ac_prev= continue fi case $ac_option in *=*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; *) ac_optarg=yes ;; esac # Accept the important Cygnus configure options, so we can diagnose typos. case $ac_dashdash$ac_option in --) ac_dashdash=yes ;; -bindir | --bindir | --bindi | --bind | --bin | --bi) ac_prev=bindir ;; -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) bindir=$ac_optarg ;; -build | --build | --buil | --bui | --bu) ac_prev=build_alias ;; -build=* | --build=* | --buil=* | --bui=* | --bu=*) build_alias=$ac_optarg ;; -cache-file | --cache-file | --cache-fil | --cache-fi \ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) ac_prev=cache_file ;; -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) cache_file=$ac_optarg ;; --config-cache | -C) cache_file=config.cache ;; -datadir | --datadir | --datadi | --datad) ac_prev=datadir ;; -datadir=* | --datadir=* | --datadi=* | --datad=*) datadir=$ac_optarg ;; -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ | --dataroo | --dataro | --datar) ac_prev=datarootdir ;; -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) datarootdir=$ac_optarg ;; -disable-* | --disable-*) ac_feature=`expr "x$ac_option" : 'x-*disable-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_feature" : ".*[^-._$as_cr_alnum]" >/dev/null && { echo "$as_me: error: invalid feature name: $ac_feature" >&2 { (exit 1); exit 1; }; } ac_feature=`echo $ac_feature | sed 's/[-.]/_/g'` eval enable_$ac_feature=no ;; -docdir | --docdir | --docdi | --doc | --do) ac_prev=docdir ;; -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) docdir=$ac_optarg ;; -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) ac_prev=dvidir ;; -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) dvidir=$ac_optarg ;; -enable-* | --enable-*) ac_feature=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_feature" : ".*[^-._$as_cr_alnum]" >/dev/null && { echo "$as_me: error: invalid feature name: $ac_feature" >&2 { (exit 1); exit 1; }; } ac_feature=`echo $ac_feature | sed 's/[-.]/_/g'` eval enable_$ac_feature=\$ac_optarg ;; -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ | --exec | --exe | --ex) ac_prev=exec_prefix ;; -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ | --exec=* | --exe=* | --ex=*) exec_prefix=$ac_optarg ;; -gas | --gas | --ga | --g) # Obsolete; use --with-gas. with_gas=yes ;; -help | --help | --hel | --he | -h) ac_init_help=long ;; -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) ac_init_help=recursive ;; -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) ac_init_help=short ;; -host | --host | --hos | --ho) ac_prev=host_alias ;; -host=* | --host=* | --hos=* | --ho=*) host_alias=$ac_optarg ;; -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) ac_prev=htmldir ;; -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ | --ht=*) htmldir=$ac_optarg ;; -includedir | --includedir | --includedi | --included | --include \ | --includ | --inclu | --incl | --inc) ac_prev=includedir ;; -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ | --includ=* | --inclu=* | --incl=* | --inc=*) includedir=$ac_optarg ;; -infodir | --infodir | --infodi | --infod | --info | --inf) ac_prev=infodir ;; -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) infodir=$ac_optarg ;; -libdir | --libdir | --libdi | --libd) ac_prev=libdir ;; -libdir=* | --libdir=* | --libdi=* | --libd=*) libdir=$ac_optarg ;; -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ | --libexe | --libex | --libe) ac_prev=libexecdir ;; -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ | --libexe=* | --libex=* | --libe=*) libexecdir=$ac_optarg ;; -localedir | --localedir | --localedi | --localed | --locale) ac_prev=localedir ;; -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) localedir=$ac_optarg ;; -localstatedir | --localstatedir | --localstatedi | --localstated \ | --localstate | --localstat | --localsta | --localst | --locals) ac_prev=localstatedir ;; -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) localstatedir=$ac_optarg ;; -mandir | --mandir | --mandi | --mand | --man | --ma | --m) ac_prev=mandir ;; -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) mandir=$ac_optarg ;; -nfp | --nfp | --nf) # Obsolete; use --without-fp. with_fp=no ;; -no-create | --no-create | --no-creat | --no-crea | --no-cre \ | --no-cr | --no-c | -n) no_create=yes ;; -no-recursion | --no-recursion | --no-recursio | --no-recursi \ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) no_recursion=yes ;; -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ | --oldin | --oldi | --old | --ol | --o) ac_prev=oldincludedir ;; -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) oldincludedir=$ac_optarg ;; -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) ac_prev=prefix ;; -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) prefix=$ac_optarg ;; -program-prefix | --program-prefix | --program-prefi | --program-pref \ | --program-pre | --program-pr | --program-p) ac_prev=program_prefix ;; -program-prefix=* | --program-prefix=* | --program-prefi=* \ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) program_prefix=$ac_optarg ;; -program-suffix | --program-suffix | --program-suffi | --program-suff \ | --program-suf | --program-su | --program-s) ac_prev=program_suffix ;; -program-suffix=* | --program-suffix=* | --program-suffi=* \ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) program_suffix=$ac_optarg ;; -program-transform-name | --program-transform-name \ | --program-transform-nam | --program-transform-na \ | --program-transform-n | --program-transform- \ | --program-transform | --program-transfor \ | --program-transfo | --program-transf \ | --program-trans | --program-tran \ | --progr-tra | --program-tr | --program-t) ac_prev=program_transform_name ;; -program-transform-name=* | --program-transform-name=* \ | --program-transform-nam=* | --program-transform-na=* \ | --program-transform-n=* | --program-transform-=* \ | --program-transform=* | --program-transfor=* \ | --program-transfo=* | --program-transf=* \ | --program-trans=* | --program-tran=* \ | --progr-tra=* | --program-tr=* | --program-t=*) program_transform_name=$ac_optarg ;; -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) ac_prev=pdfdir ;; -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) pdfdir=$ac_optarg ;; -psdir | --psdir | --psdi | --psd | --ps) ac_prev=psdir ;; -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) psdir=$ac_optarg ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) silent=yes ;; -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ | --sbi=* | --sb=*) sbindir=$ac_optarg ;; -sharedstatedir | --sharedstatedir | --sharedstatedi \ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ | --sharedst | --shareds | --shared | --share | --shar \ | --sha | --sh) ac_prev=sharedstatedir ;; -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ | --sha=* | --sh=*) sharedstatedir=$ac_optarg ;; -site | --site | --sit) ac_prev=site ;; -site=* | --site=* | --sit=*) site=$ac_optarg ;; -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) ac_prev=srcdir ;; -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) srcdir=$ac_optarg ;; -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ | --syscon | --sysco | --sysc | --sys | --sy) ac_prev=sysconfdir ;; -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) sysconfdir=$ac_optarg ;; -target | --target | --targe | --targ | --tar | --ta | --t) ac_prev=target_alias ;; -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) target_alias=$ac_optarg ;; -v | -verbose | --verbose | --verbos | --verbo | --verb) verbose=yes ;; -version | --version | --versio | --versi | --vers | -V) ac_init_version=: ;; -with-* | --with-*) ac_package=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_package" : ".*[^-._$as_cr_alnum]" >/dev/null && { echo "$as_me: error: invalid package name: $ac_package" >&2 { (exit 1); exit 1; }; } ac_package=`echo $ac_package | sed 's/[-.]/_/g'` eval with_$ac_package=\$ac_optarg ;; -without-* | --without-*) ac_package=`expr "x$ac_option" : 'x-*without-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_package" : ".*[^-._$as_cr_alnum]" >/dev/null && { echo "$as_me: error: invalid package name: $ac_package" >&2 { (exit 1); exit 1; }; } ac_package=`echo $ac_package | sed 's/[-.]/_/g'` eval with_$ac_package=no ;; --x) # Obsolete; use --with-x. with_x=yes ;; -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ | --x-incl | --x-inc | --x-in | --x-i) ac_prev=x_includes ;; -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) x_includes=$ac_optarg ;; -x-libraries | --x-libraries | --x-librarie | --x-librari \ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) ac_prev=x_libraries ;; -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) x_libraries=$ac_optarg ;; -*) { echo "$as_me: error: unrecognized option: $ac_option Try \`$0 --help' for more information." >&2 { (exit 1); exit 1; }; } ;; *=*) ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` # Reject names that are not valid shell variable names. expr "x$ac_envvar" : ".*[^_$as_cr_alnum]" >/dev/null && { echo "$as_me: error: invalid variable name: $ac_envvar" >&2 { (exit 1); exit 1; }; } eval $ac_envvar=\$ac_optarg export $ac_envvar ;; *) # FIXME: should be removed in autoconf 3.0. echo "$as_me: WARNING: you should use --build, --host, --target" >&2 expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && echo "$as_me: WARNING: invalid host type: $ac_option" >&2 : ${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option} ;; esac done if test -n "$ac_prev"; then ac_option=--`echo $ac_prev | sed 's/_/-/g'` { echo "$as_me: error: missing argument to $ac_option" >&2 { (exit 1); exit 1; }; } fi # Be sure to have absolute directory names. for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ libdir localedir mandir do eval ac_val=\$$ac_var case $ac_val in [\\/$]* | ?:[\\/]* ) continue;; NONE | '' ) case $ac_var in *prefix ) continue;; esac;; esac { echo "$as_me: error: expected an absolute directory name for --$ac_var: $ac_val" >&2 { (exit 1); exit 1; }; } done # There might be people who depend on the old broken behavior: `$host' # used to hold the argument of --host etc. # FIXME: To remove some day. build=$build_alias host=$host_alias target=$target_alias # FIXME: To remove some day. if test "x$host_alias" != x; then if test "x$build_alias" = x; then cross_compiling=maybe echo "$as_me: WARNING: If you wanted to set the --build type, don't use --host. If a cross compiler is detected then cross compile mode will be used." >&2 elif test "x$build_alias" != "x$host_alias"; then cross_compiling=yes fi fi ac_tool_prefix= test -n "$host_alias" && ac_tool_prefix=$host_alias- test "$silent" = yes && exec 6>/dev/null ac_pwd=`pwd` && test -n "$ac_pwd" && ac_ls_di=`ls -di .` && ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || { echo "$as_me: error: Working directory cannot be determined" >&2 { (exit 1); exit 1; }; } test "X$ac_ls_di" = "X$ac_pwd_ls_di" || { echo "$as_me: error: pwd does not report name of working directory" >&2 { (exit 1); exit 1; }; } # Find the source files, if location was not specified. if test -z "$srcdir"; then ac_srcdir_defaulted=yes # Try the directory containing this script, then the parent directory. ac_confdir=`$as_dirname -- "$0" || $as_expr X"$0" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$0" : 'X\(//\)[^/]' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || echo X"$0" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` srcdir=$ac_confdir if test ! -r "$srcdir/$ac_unique_file"; then srcdir=.. fi else ac_srcdir_defaulted=no fi if test ! -r "$srcdir/$ac_unique_file"; then test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." { echo "$as_me: error: cannot find sources ($ac_unique_file) in $srcdir" >&2 { (exit 1); exit 1; }; } fi ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" ac_abs_confdir=`( cd "$srcdir" && test -r "./$ac_unique_file" || { echo "$as_me: error: $ac_msg" >&2 { (exit 1); exit 1; }; } pwd)` # When building in place, set srcdir=. if test "$ac_abs_confdir" = "$ac_pwd"; then srcdir=. fi # Remove unnecessary trailing slashes from srcdir. # Double slashes in file names in object file debugging info # mess up M-x gdb in Emacs. case $srcdir in */) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; esac for ac_var in $ac_precious_vars; do eval ac_env_${ac_var}_set=\${${ac_var}+set} eval ac_env_${ac_var}_value=\$${ac_var} eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} eval ac_cv_env_${ac_var}_value=\$${ac_var} done # # Report the --help message. # 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 16.4 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... To assign environment variables (e.g., CC, CFLAGS...), specify them as VAR=VALUE. See below for descriptions of some of the useful variables. Defaults for the options are specified in brackets. Configuration: -h, --help display this help and exit --help=short display options specific to this package --help=recursive display the short help of all the included packages -V, --version display version information and exit -q, --quiet, --silent do not print \`checking...' messages --cache-file=FILE cache test results in FILE [disabled] -C, --config-cache alias for \`--cache-file=config.cache' -n, --no-create do not create output files --srcdir=DIR find the sources in DIR [configure dir or \`..'] Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX [$ac_default_prefix] --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX [PREFIX] By default, \`make install' will install all the files in \`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify an installation prefix other than \`$ac_default_prefix' using \`--prefix', for instance \`--prefix=\$HOME'. For better control, use the options below. Fine tuning of the installation directories: --bindir=DIR user executables [EPREFIX/bin] --sbindir=DIR system admin executables [EPREFIX/sbin] --libexecdir=DIR program executables [EPREFIX/libexec] --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] --datadir=DIR read-only architecture-independent data [DATAROOTDIR] --infodir=DIR info documentation [DATAROOTDIR/info] --localedir=DIR locale-dependent data [DATAROOTDIR/locale] --mandir=DIR man documentation [DATAROOTDIR/man] --docdir=DIR documentation root [DATAROOTDIR/doc/nzbget] --htmldir=DIR html documentation [DOCDIR] --dvidir=DIR dvi documentation [DOCDIR] --pdfdir=DIR pdf documentation [DOCDIR] --psdir=DIR ps documentation [DOCDIR] _ACEOF cat <<\_ACEOF Program names: --program-prefix=PREFIX prepend PREFIX to installed program names --program-suffix=SUFFIX append SUFFIX to installed program names --program-transform-name=PROGRAM run sed PROGRAM on installed program names _ACEOF fi if test -n "$ac_init_help"; then case $ac_init_help in short | recursive ) echo "Configuration of nzbget 16.4:";; esac cat <<\_ACEOF Optional Features: --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) --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 --disable-tls do not use TLS/SSL (removes dependency from TLS/SSL-libraries) --disable-gzip disable gzip-compression/decompression (removes dependency from zlib-library) --disable-sigchld-handler do not use sigchld-handler (the disabling may be neccessary on 32-Bit BSD) --enable-debug enable debugging --enable-tests enable unit and integration tests Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) --with-libxml2-includes=DIR libxml2 include directory --with-libxml2-libraries=DIR libxml2 library directory --with-libcurses-includes=DIR libcurses include directory --with-libcurses-libraries=DIR libcurses library directory --with-tlslib=(OpenSSL, GnuTLS) TLS/SSL library to use --with-openssl-includes=DIR OpenSSL include directory --with-openssl-libraries=DIR OpenSSL library directory --with-libgnutls-includes=DIR GnuTLS include directory --with-libgnutls-libraries=DIR GnuTLS library directory --with-zlib-includes=DIR zlib include directory --with-zlib-libraries=DIR zlib library directory Some influential environment variables: CXX C++ compiler command CXXFLAGS C++ compiler flags LDFLAGS linker flags, e.g. -L if you have libraries in a nonstandard directory LIBS libraries to pass to the linker, e.g. -l CPPFLAGS C/C++/Objective C preprocessor flags, e.g. -I if you have headers in a nonstandard directory CXXCPP C++ preprocessor PKG_CONFIG path to pkg-config utility libxml2_CFLAGS C compiler flags for libxml2, overriding pkg-config libxml2_LIBS linker flags for libxml2, overriding pkg-config openssl_CFLAGS C compiler flags for openssl, overriding pkg-config openssl_LIBS linker flags for openssl, overriding pkg-config Use these variables to override the choices made by `configure' or to help it to find libraries and programs with nonstandard names/locations. Report bugs to . _ACEOF ac_status=$? fi if test "$ac_init_help" = "recursive"; then # If there are subdirs, report their specific --help. for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue test -d "$ac_dir" || continue ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`echo "$ac_dir" | sed 's,^\.[\\/],,'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`echo "$ac_dir_suffix" | sed 's,/[^\\/]*,/..,g;s,/,,'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix cd "$ac_dir" || { ac_status=$?; continue; } # Check for guested configure. if test -f "$ac_srcdir/configure.gnu"; then echo && $SHELL "$ac_srcdir/configure.gnu" --help=recursive elif test -f "$ac_srcdir/configure"; then echo && $SHELL "$ac_srcdir/configure" --help=recursive else echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 fi || ac_status=$? cd "$ac_pwd" || { ac_status=$?; break; } done fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF nzbget configure 16.4 generated by GNU Autoconf 2.61 Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. _ACEOF exit fi 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 16.4, which was generated by GNU Autoconf 2.61. Invocation command line was $ $0 $@ _ACEOF exec 5>>config.log { cat <<_ASUNAME ## --------- ## ## Platform. ## ## --------- ## hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` uname -m = `(uname -m) 2>/dev/null || echo unknown` uname -r = `(uname -r) 2>/dev/null || echo unknown` uname -s = `(uname -s) 2>/dev/null || echo unknown` uname -v = `(uname -v) 2>/dev/null || echo unknown` /usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` /bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` /bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` /usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` /usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` /usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` /bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` /usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` /bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` _ASUNAME as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. echo "PATH: $as_dir" done IFS=$as_save_IFS } >&5 cat >&5 <<_ACEOF ## ----------- ## ## Core tests. ## ## ----------- ## _ACEOF # Keep a trace of the command line. # Strip out --no-create and --no-recursion so they do not pile up. # Strip out --silent because we don't want to record it for future runs. # Also quote any args containing shell meta-characters. # Make two passes to allow for proper duplicate-argument suppression. ac_configure_args= ac_configure_args0= ac_configure_args1= ac_must_keep_next=false for ac_pass in 1 2 do for ac_arg do case $ac_arg in -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil) continue ;; *\'*) ac_arg=`echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac case $ac_pass in 1) ac_configure_args0="$ac_configure_args0 '$ac_arg'" ;; 2) ac_configure_args1="$ac_configure_args1 '$ac_arg'" if test $ac_must_keep_next = true; then ac_must_keep_next=false # Got value, back to normal. else case $ac_arg in *=* | --config-cache | -C | -disable-* | --disable-* \ | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ | -with-* | --with-* | -without-* | --without-* | --x) case "$ac_configure_args0 " in "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; esac ;; -* ) ac_must_keep_next=true ;; esac fi ac_configure_args="$ac_configure_args '$ac_arg'" ;; esac done done $as_unset ac_configure_args0 || test "${ac_configure_args0+set}" != set || { ac_configure_args0=; export ac_configure_args0; } $as_unset ac_configure_args1 || test "${ac_configure_args1+set}" != set || { ac_configure_args1=; export ac_configure_args1; } # When interrupted or exit'd, cleanup temporary files, and complete # config.log. We remove comments because anyway the quotes in there # would cause problems or look ugly. # WARNING: Use '\'' to represent an apostrophe within the trap. # WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. trap 'exit_status=$? # Save into config.log some information that might help in debugging. { echo cat <<\_ASBOX ## ---------------- ## ## Cache variables. ## ## ---------------- ## _ASBOX echo # The following way of writing the cache mishandles newlines in values, ( for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { echo "$as_me:$LINENO: WARNING: Cache variable $ac_var contains a newline." >&5 echo "$as_me: WARNING: Cache variable $ac_var contains a newline." >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( *) $as_unset $ac_var ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( *${as_nl}ac_space=\ *) sed -n \ "s/'\''/'\''\\\\'\'''\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" ;; #( *) sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) echo cat <<\_ASBOX ## ----------------- ## ## Output variables. ## ## ----------------- ## _ASBOX echo for ac_var in $ac_subst_vars do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac echo "$ac_var='\''$ac_val'\''" done | sort echo if test -n "$ac_subst_files"; then cat <<\_ASBOX ## ------------------- ## ## File substitutions. ## ## ------------------- ## _ASBOX echo for ac_var in $ac_subst_files do eval ac_val=\$$ac_var case $ac_val in *\'\''*) ac_val=`echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac echo "$ac_var='\''$ac_val'\''" done | sort echo fi if test -s confdefs.h; then cat <<\_ASBOX ## ----------- ## ## confdefs.h. ## ## ----------- ## _ASBOX echo cat confdefs.h echo fi test "$ac_signal" != 0 && echo "$as_me: caught signal $ac_signal" echo "$as_me: exit $exit_status" } >&5 rm -f core *.core core.conftest.* && rm -f -r conftest* confdefs* conf$$* $ac_clean_files && exit $exit_status ' 0 for ac_signal in 1 2 13 15; do trap 'ac_signal='$ac_signal'; { (exit 1); exit 1; }' $ac_signal done ac_signal=0 # confdefs.h avoids OS command line length limits that DEFS can exceed. rm -f -r conftest* confdefs.h # Predefined preprocessor variables. cat >>confdefs.h <<_ACEOF #define PACKAGE_NAME "$PACKAGE_NAME" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_TARNAME "$PACKAGE_TARNAME" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_VERSION "$PACKAGE_VERSION" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_STRING "$PACKAGE_STRING" _ACEOF cat >>confdefs.h <<_ACEOF #define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" _ACEOF # Let the site file select an alternate cache file if it wants to. # Prefer explicitly selected file to automatically selected ones. if test -n "$CONFIG_SITE"; then set x "$CONFIG_SITE" elif test "x$prefix" != xNONE; then set x "$prefix/share/config.site" "$prefix/etc/config.site" else set x "$ac_default_prefix/share/config.site" \ "$ac_default_prefix/etc/config.site" fi shift for ac_site_file do if test -r "$ac_site_file"; then { echo "$as_me:$LINENO: loading site script $ac_site_file" >&5 echo "$as_me: loading site script $ac_site_file" >&6;} sed 's/^/| /' "$ac_site_file" >&5 . "$ac_site_file" fi done if test -r "$cache_file"; then # Some versions of bash will fail to source /dev/null (special # files actually), so we avoid doing that. if test -f "$cache_file"; then { echo "$as_me:$LINENO: loading cache $cache_file" >&5 echo "$as_me: loading cache $cache_file" >&6;} case $cache_file in [\\/]* | ?:[\\/]* ) . "$cache_file";; *) . "./$cache_file";; esac fi else { echo "$as_me:$LINENO: creating cache $cache_file" >&5 echo "$as_me: creating cache $cache_file" >&6;} >$cache_file fi # Check that the precious variables saved in the cache have kept the same # value. ac_cache_corrupted=false for ac_var in $ac_precious_vars; do eval ac_old_set=\$ac_cv_env_${ac_var}_set eval ac_new_set=\$ac_env_${ac_var}_set eval ac_old_val=\$ac_cv_env_${ac_var}_value eval ac_new_val=\$ac_env_${ac_var}_value case $ac_old_set,$ac_new_set in set,) { echo "$as_me:$LINENO: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} ac_cache_corrupted=: ;; ,set) { echo "$as_me:$LINENO: error: \`$ac_var' was not set in the previous run" >&5 echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} ac_cache_corrupted=: ;; ,);; *) if test "x$ac_old_val" != "x$ac_new_val"; then { echo "$as_me:$LINENO: error: \`$ac_var' has changed since the previous run:" >&5 echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} { echo "$as_me:$LINENO: former value: $ac_old_val" >&5 echo "$as_me: former value: $ac_old_val" >&2;} { echo "$as_me:$LINENO: current value: $ac_new_val" >&5 echo "$as_me: current value: $ac_new_val" >&2;} ac_cache_corrupted=: fi;; esac # Pass precious variables to config.status. if test "$ac_new_set" = set; then case $ac_new_val in *\'*) ac_arg=$ac_var=`echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; *) ac_arg=$ac_var=$ac_new_val ;; esac case " $ac_configure_args " in *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. *) ac_configure_args="$ac_configure_args '$ac_arg'" ;; esac fi done if $ac_cache_corrupted; then { echo "$as_me:$LINENO: error: changes in the environment can compromise the build" >&5 echo "$as_me: error: changes in the environment can compromise the build" >&2;} { { echo "$as_me:$LINENO: error: run \`make distclean' and/or \`rm $cache_file' and start over" >&5 echo "$as_me: error: run \`make distclean' and/or \`rm $cache_file' and start over" >&2;} { (exit 1); exit 1; }; } fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_aux_dir= for ac_dir in posix "$srcdir"/posix; do if test -f "$ac_dir/install-sh"; then ac_aux_dir=$ac_dir ac_install_sh="$ac_aux_dir/install-sh -c" break elif test -f "$ac_dir/install.sh"; then ac_aux_dir=$ac_dir ac_install_sh="$ac_aux_dir/install.sh -c" break elif test -f "$ac_dir/shtool"; then ac_aux_dir=$ac_dir ac_install_sh="$ac_aux_dir/shtool install -c" break fi done if test -z "$ac_aux_dir"; then { { echo "$as_me:$LINENO: error: cannot find install-sh or install.sh in posix \"$srcdir\"/posix" >&5 echo "$as_me: error: cannot find install-sh or install.sh in posix \"$srcdir\"/posix" >&2;} { (exit 1); exit 1; }; } fi # These three variables are undocumented and unsupported, # and are intended to be withdrawn in a future Autoconf release. # They can cause serious problems if a builder's source tree is in a directory # whose full name contains unusual characters. ac_config_guess="$SHELL $ac_aux_dir/config.guess" # Please don't use this var. ac_config_sub="$SHELL $ac_aux_dir/config.sub" # Please don't use this var. ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var. am__api_version="1.9" # Find a good install program. We prefer a C program (faster), # so one script is as good as another. But avoid the broken or # incompatible versions: # SysV /etc/install, /usr/sbin/install # SunOS /usr/etc/install # IRIX /sbin/install # AIX /bin/install # AmigaOS /C/install, which installs bootblocks on floppy discs # AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag # AFS /usr/afsws/bin/install, which mishandles nonexistent args # SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" # OS/2's system install, which has a completely different semantic # ./install, which can be erroneously created by make from ./install.sh. { echo "$as_me:$LINENO: checking for a BSD-compatible install" >&5 echo $ECHO_N "checking for a BSD-compatible install... $ECHO_C" >&6; } if test -z "$INSTALL"; then if test "${ac_cv_path_install+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. # Account for people who put trailing slashes in PATH elements. case $as_dir/ in ./ | .// | /cC/* | \ /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \ ?:\\/os2\\/install\\/* | ?:\\/OS2\\/INSTALL\\/* | \ /usr/ucb/* ) ;; *) # OSF1 and SCO ODT 3.0 have their own names for install. # Don't use installbsd from OSF since it installs stuff as root # by default. for ac_prog in ginstall scoinst install; do for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_prog$ac_exec_ext" && $as_test_x "$as_dir/$ac_prog$ac_exec_ext"; }; then if test $ac_prog = install && grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # AIX install. It has an incompatible calling convention. : elif test $ac_prog = install && grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # program-specific install script used by HP pwplus--don't use. : else ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c" break 3 fi fi done done ;; esac done IFS=$as_save_IFS fi if test "${ac_cv_path_install+set}" = set; then INSTALL=$ac_cv_path_install else # As a last resort, use the slow shell script. Don't cache a # value for INSTALL within a source directory, because that will # break other packages using the cache if that directory is # removed, or if the value is a relative name. INSTALL=$ac_install_sh fi fi { echo "$as_me:$LINENO: result: $INSTALL" >&5 echo "${ECHO_T}$INSTALL" >&6; } # Use test -z because SunOS4 sh mishandles braces in ${var-val}. # It thinks the first close brace ends the variable substitution. test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' { echo "$as_me:$LINENO: checking whether build environment is sane" >&5 echo $ECHO_N "checking whether build environment is sane... $ECHO_C" >&6; } # Just in case sleep 1 echo timestamp > conftest.file # Do `set' in a subshell so we don't clobber the current shell's # arguments. Must try -L first in case configure is actually a # symlink; some systems play weird games with the mod time of symlinks # (eg FreeBSD returns the mod time of the symlink's containing # directory). if ( set X `ls -Lt $srcdir/configure conftest.file 2> /dev/null` if test "$*" = "X"; then # -L didn't work. set X `ls -t $srcdir/configure conftest.file` fi rm -f conftest.file if test "$*" != "X $srcdir/configure conftest.file" \ && test "$*" != "X conftest.file $srcdir/configure"; then # If neither matched, then we have a broken ls. This can happen # if, for instance, CONFIG_SHELL is bash and it inherits a # broken ls alias from the environment. This has actually # happened. Such a system could not be considered "sane". { { echo "$as_me:$LINENO: error: ls -t appears to fail. Make sure there is not a broken alias in your environment" >&5 echo "$as_me: error: ls -t appears to fail. Make sure there is not a broken alias in your environment" >&2;} { (exit 1); exit 1; }; } fi test "$2" = conftest.file ) then # Ok. : else { { echo "$as_me:$LINENO: error: newly created file is older than distributed files! Check your system clock" >&5 echo "$as_me: error: newly created file is older than distributed files! Check your system clock" >&2;} { (exit 1); exit 1; }; } fi { echo "$as_me:$LINENO: result: yes" >&5 echo "${ECHO_T}yes" >&6; } test "$program_prefix" != NONE && program_transform_name="s&^&$program_prefix&;$program_transform_name" # Use a double $ so make ignores it. test "$program_suffix" != NONE && program_transform_name="s&\$&$program_suffix&;$program_transform_name" # Double any \ or $. echo might interpret backslashes. # By default was `s,x,x', remove it if useless. cat <<\_ACEOF >conftest.sed s/[\\$]/&&/g;s/;s,x,x,$// _ACEOF program_transform_name=`echo $program_transform_name | sed -f conftest.sed` rm -f conftest.sed # expand $ac_aux_dir to an absolute path am_aux_dir=`cd $ac_aux_dir && pwd` test x"${MISSING+set}" = xset || MISSING="\${SHELL} $am_aux_dir/missing" # Use eval to expand $SHELL if eval "$MISSING --run true"; then am_missing_run="$MISSING --run " else am_missing_run= { echo "$as_me:$LINENO: WARNING: \`missing' script is too old or missing" >&5 echo "$as_me: WARNING: \`missing' script is too old or missing" >&2;} fi if mkdir -p --version . >/dev/null 2>&1 && test ! -d ./--version; then # We used to keeping the `.' as first argument, in order to # allow $(mkdir_p) to be used without argument. As in # $(mkdir_p) $(somedir) # where $(somedir) is conditionally defined. However this is wrong # for two reasons: # 1. if the package is installed by a user who cannot write `.' # make install will fail, # 2. the above comment should most certainly read # $(mkdir_p) $(DESTDIR)$(somedir) # so it does not work when $(somedir) is undefined and # $(DESTDIR) is not. # To support the latter case, we have to write # test -z "$(somedir)" || $(mkdir_p) $(DESTDIR)$(somedir), # so the `.' trick is pointless. mkdir_p='mkdir -p --' else # On NextStep and OpenStep, the `mkdir' command does not # recognize any option. It will interpret all options as # directories to create, and then abort because `.' already # exists. for d in ./-p ./--version; do test -d $d && rmdir $d done # $(mkinstalldirs) is defined by Automake if mkinstalldirs exists. if test -f "$ac_aux_dir/mkinstalldirs"; then mkdir_p='$(mkinstalldirs)' else mkdir_p='$(install_sh) -d' fi fi for ac_prog in gawk mawk nawk awk do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; } if test "${ac_cv_prog_AWK+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if test -n "$AWK"; then ac_cv_prog_AWK="$AWK" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_prog_AWK="$ac_prog" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi AWK=$ac_cv_prog_AWK if test -n "$AWK"; then { echo "$as_me:$LINENO: result: $AWK" >&5 echo "${ECHO_T}$AWK" >&6; } else { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } fi test -n "$AWK" && break done { echo "$as_me:$LINENO: checking whether ${MAKE-make} sets \$(MAKE)" >&5 echo $ECHO_N "checking whether ${MAKE-make} sets \$(MAKE)... $ECHO_C" >&6; } set x ${MAKE-make}; ac_make=`echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'` if { as_var=ac_cv_prog_make_${ac_make}_set; eval "test \"\${$as_var+set}\" = set"; }; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.make <<\_ACEOF SHELL = /bin/sh all: @echo '@@@%%%=$(MAKE)=@@@%%%' _ACEOF # GNU make sometimes prints "make[1]: Entering...", which would confuse us. case `${MAKE-make} -f conftest.make 2>/dev/null` in *@@@%%%=?*=@@@%%%*) eval ac_cv_prog_make_${ac_make}_set=yes;; *) eval ac_cv_prog_make_${ac_make}_set=no;; esac rm -f conftest.make fi if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then { echo "$as_me:$LINENO: result: yes" >&5 echo "${ECHO_T}yes" >&6; } SET_MAKE= else { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } SET_MAKE="MAKE=${MAKE-make}" fi rm -rf .tst 2>/dev/null mkdir .tst 2>/dev/null if test -d .tst; then am__leading_dot=. else am__leading_dot=_ fi rmdir .tst 2>/dev/null # test to see if srcdir already configured if test "`cd $srcdir && pwd`" != "`pwd`" && test -f $srcdir/config.status; then { { echo "$as_me:$LINENO: error: source directory already configured; run \"make distclean\" there first" >&5 echo "$as_me: error: source directory already configured; run \"make distclean\" there first" >&2;} { (exit 1); exit 1; }; } fi # test whether we have cygpath if test -z "$CYGPATH_W"; then if (cygpath --version) >/dev/null 2>/dev/null; then CYGPATH_W='cygpath -w' else CYGPATH_W=echo fi fi # Define the identity of the package. PACKAGE='nzbget' VERSION='16.4' cat >>confdefs.h <<_ACEOF #define PACKAGE "$PACKAGE" _ACEOF cat >>confdefs.h <<_ACEOF #define VERSION "$VERSION" _ACEOF # Some tools Automake needs. ACLOCAL=${ACLOCAL-"${am_missing_run}aclocal-${am__api_version}"} AUTOCONF=${AUTOCONF-"${am_missing_run}autoconf"} AUTOMAKE=${AUTOMAKE-"${am_missing_run}automake-${am__api_version}"} AUTOHEADER=${AUTOHEADER-"${am_missing_run}autoheader"} MAKEINFO=${MAKEINFO-"${am_missing_run}makeinfo"} install_sh=${install_sh-"$am_aux_dir/install-sh"} # Installed binaries are usually stripped using `strip' when the user # run `make install-strip'. However `strip' might not be the right # tool to use in cross-compilation environments, therefore Automake # will honor the `STRIP' environment variable to overrule this program. if test "$cross_compiling" != no; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args. set dummy ${ac_tool_prefix}strip; ac_word=$2 { echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; } if test "${ac_cv_prog_STRIP+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if test -n "$STRIP"; then ac_cv_prog_STRIP="$STRIP" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_prog_STRIP="${ac_tool_prefix}strip" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi STRIP=$ac_cv_prog_STRIP if test -n "$STRIP"; then { echo "$as_me:$LINENO: result: $STRIP" >&5 echo "${ECHO_T}$STRIP" >&6; } else { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } fi fi if test -z "$ac_cv_prog_STRIP"; then ac_ct_STRIP=$STRIP # Extract the first word of "strip", so it can be a program name with args. set dummy strip; ac_word=$2 { echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; } if test "${ac_cv_prog_ac_ct_STRIP+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if test -n "$ac_ct_STRIP"; then ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_prog_ac_ct_STRIP="strip" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP if test -n "$ac_ct_STRIP"; then { echo "$as_me:$LINENO: result: $ac_ct_STRIP" >&5 echo "${ECHO_T}$ac_ct_STRIP" >&6; } else { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } fi if test "x$ac_ct_STRIP" = x; then STRIP=":" else case $cross_compiling:$ac_tool_warned in yes:) { echo "$as_me:$LINENO: WARNING: In the future, Autoconf will not detect cross-tools whose name does not start with the host triplet. If you think this configuration is useful to you, please write to autoconf@gnu.org." >&5 echo "$as_me: WARNING: In the future, Autoconf will not detect cross-tools whose name does not start with the host triplet. If you think this configuration is useful to you, please write to autoconf@gnu.org." >&2;} ac_tool_warned=yes ;; esac STRIP=$ac_ct_STRIP fi else STRIP="$ac_cv_prog_STRIP" fi fi INSTALL_STRIP_PROGRAM="\${SHELL} \$(install_sh) -c -s" # We need awk for the "check" target. The system "awk" is bad on # some platforms. # Always define AMTAR for backward compatibility. AMTAR=${AMTAR-"${am_missing_run}tar"} am__tar='${AMTAR} chof - "$$tardir"'; am__untar='${AMTAR} xf -' ac_config_headers="$ac_config_headers config.h" if test "$LIBPREF" = ""; then LIBPREF="/usr" fi ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu if test -z "$CXX"; then if test -n "$CCC"; then CXX=$CCC else if test -n "$ac_tool_prefix"; then for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC do # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. set dummy $ac_tool_prefix$ac_prog; ac_word=$2 { echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; } if test "${ac_cv_prog_CXX+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if test -n "$CXX"; then ac_cv_prog_CXX="$CXX" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_prog_CXX="$ac_tool_prefix$ac_prog" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi CXX=$ac_cv_prog_CXX if test -n "$CXX"; then { echo "$as_me:$LINENO: result: $CXX" >&5 echo "${ECHO_T}$CXX" >&6; } else { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } fi test -n "$CXX" && break done fi if test -z "$CXX"; then ac_ct_CXX=$CXX for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 { echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; } if test "${ac_cv_prog_ac_ct_CXX+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if test -n "$ac_ct_CXX"; then ac_cv_prog_ac_ct_CXX="$ac_ct_CXX" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_prog_ac_ct_CXX="$ac_prog" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS fi fi ac_ct_CXX=$ac_cv_prog_ac_ct_CXX if test -n "$ac_ct_CXX"; then { echo "$as_me:$LINENO: result: $ac_ct_CXX" >&5 echo "${ECHO_T}$ac_ct_CXX" >&6; } else { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } fi test -n "$ac_ct_CXX" && break done if test "x$ac_ct_CXX" = x; then CXX="g++" else case $cross_compiling:$ac_tool_warned in yes:) { echo "$as_me:$LINENO: WARNING: In the future, Autoconf will not detect cross-tools whose name does not start with the host triplet. If you think this configuration is useful to you, please write to autoconf@gnu.org." >&5 echo "$as_me: WARNING: In the future, Autoconf will not detect cross-tools whose name does not start with the host triplet. If you think this configuration is useful to you, please write to autoconf@gnu.org." >&2;} ac_tool_warned=yes ;; esac CXX=$ac_ct_CXX fi fi fi fi # Provide some information about the compiler. echo "$as_me:$LINENO: checking for C++ compiler version" >&5 ac_compiler=`set X $ac_compile; echo $2` { (ac_try="$ac_compiler --version >&5" 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_compiler --version >&5") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } { (ac_try="$ac_compiler -v >&5" 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_compiler -v >&5") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } { (ac_try="$ac_compiler -V >&5" 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_compiler -V >&5") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ int main () { ; return 0; } _ACEOF ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files a.out a.exe b.out" # Try to create an executable without -o first, disregard a.out. # It will help us diagnose broken compilers, and finding out an intuition # of exeext. { echo "$as_me:$LINENO: checking for C++ compiler default output file name" >&5 echo $ECHO_N "checking for C++ compiler default output file name... $ECHO_C" >&6; } ac_link_default=`echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'` # # List of possible output files, starting from the most likely. # The algorithm is not robust to junk in `.', hence go to wildcards (a.*) # only as a last resort. b.out is created by i960 compilers. ac_files='a_out.exe a.exe conftest.exe a.out conftest a.* conftest.* b.out' # # The IRIX 6 linker writes into existing files which may not be # executable, retaining their permissions. Remove them first so a # subsequent execution test works. ac_rmfiles= for ac_file in $ac_files do case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.o | *.obj ) ;; * ) ac_rmfiles="$ac_rmfiles $ac_file";; esac done rm -f $ac_rmfiles if { (ac_try="$ac_link_default" 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_default") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; then # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. # So ignore a value of `no', otherwise this would lead to `EXEEXT = no' # in a Makefile. We should not override ac_cv_exeext if it was cached, # so that the user can short-circuit this test for compilers unknown to # Autoconf. for ac_file in $ac_files '' do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.o | *.obj ) ;; [ab].out ) # We found the default executable, but exeext='' is most # certainly right. break;; *.* ) if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no; then :; else ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` fi # We set ac_cv_exeext here because the later test for it is not # safe: cross compilers may not add the suffix if given an `-o' # argument, so we may need to know it at that point already. # Even if this section looks crufty: it has the advantage of # actually working. break;; * ) break;; esac done test "$ac_cv_exeext" = no && ac_cv_exeext= else ac_file='' fi { echo "$as_me:$LINENO: result: $ac_file" >&5 echo "${ECHO_T}$ac_file" >&6; } if test -z "$ac_file"; then echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { echo "$as_me:$LINENO: error: C++ compiler cannot create executables See \`config.log' for more details." >&5 echo "$as_me: error: C++ compiler cannot create executables See \`config.log' for more details." >&2;} { (exit 77); exit 77; }; } fi ac_exeext=$ac_cv_exeext # Check that the compiler produces executables we can run. If not, either # the compiler is broken, or we cross compile. { echo "$as_me:$LINENO: checking whether the C++ compiler works" >&5 echo $ECHO_N "checking whether the C++ compiler works... $ECHO_C" >&6; } # FIXME: These cross compiler hacks should be removed for Autoconf 3.0 # If not cross compiling, check that we can run a simple program. if test "$cross_compiling" != yes; then if { ac_try='./$ac_file' { (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_try") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then cross_compiling=no else if test "$cross_compiling" = maybe; then cross_compiling=yes else { { echo "$as_me:$LINENO: error: cannot run C++ compiled programs. If you meant to cross compile, use \`--host'. See \`config.log' for more details." >&5 echo "$as_me: error: cannot run C++ compiled programs. If you meant to cross compile, use \`--host'. See \`config.log' for more details." >&2;} { (exit 1); exit 1; }; } fi fi fi { echo "$as_me:$LINENO: result: yes" >&5 echo "${ECHO_T}yes" >&6; } rm -f a.out a.exe conftest$ac_cv_exeext b.out ac_clean_files=$ac_clean_files_save # Check that the compiler produces executables we can run. If not, either # the compiler is broken, or we cross compile. { echo "$as_me:$LINENO: checking whether we are cross compiling" >&5 echo $ECHO_N "checking whether we are cross compiling... $ECHO_C" >&6; } { echo "$as_me:$LINENO: result: $cross_compiling" >&5 echo "${ECHO_T}$cross_compiling" >&6; } { echo "$as_me:$LINENO: checking for suffix of executables" >&5 echo $ECHO_N "checking for suffix of executables... $ECHO_C" >&6; } if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; then # If both `conftest.exe' and `conftest' are `present' (well, observable) # catch `conftest.exe'. For instance with Cygwin, `ls conftest' will # work properly (i.e., refer to `conftest.exe'), while it won't with # `rm'. for ac_file in conftest.exe conftest conftest.*; do test -f "$ac_file" || continue case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.o | *.obj ) ;; *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` break;; * ) break;; esac done else { { echo "$as_me:$LINENO: error: cannot compute suffix of executables: cannot compile and link See \`config.log' for more details." >&5 echo "$as_me: error: cannot compute suffix of executables: cannot compile and link See \`config.log' for more details." >&2;} { (exit 1); exit 1; }; } fi rm -f conftest$ac_cv_exeext { echo "$as_me:$LINENO: result: $ac_cv_exeext" >&5 echo "${ECHO_T}$ac_cv_exeext" >&6; } rm -f conftest.$ac_ext EXEEXT=$ac_cv_exeext ac_exeext=$EXEEXT { echo "$as_me:$LINENO: checking for suffix of object files" >&5 echo $ECHO_N "checking for suffix of object files... $ECHO_C" >&6; } if test "${ac_cv_objext+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ int main () { ; return 0; } _ACEOF rm -f conftest.o conftest.obj 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>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; then for ac_file in conftest.o conftest.obj conftest.*; do test -f "$ac_file" || continue; case $ac_file in *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf ) ;; *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` break;; esac done else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { { echo "$as_me:$LINENO: error: cannot compute suffix of object files: cannot compile See \`config.log' for more details." >&5 echo "$as_me: error: cannot compute suffix of object files: cannot compile See \`config.log' for more details." >&2;} { (exit 1); exit 1; }; } fi rm -f conftest.$ac_cv_objext conftest.$ac_ext fi { echo "$as_me:$LINENO: result: $ac_cv_objext" >&5 echo "${ECHO_T}$ac_cv_objext" >&6; } OBJEXT=$ac_cv_objext ac_objext=$OBJEXT { echo "$as_me:$LINENO: checking whether we are using the GNU C++ compiler" >&5 echo $ECHO_N "checking whether we are using the GNU C++ compiler... $ECHO_C" >&6; } if test "${ac_cv_cxx_compiler_gnu+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ int main () { #ifndef __GNUC__ choke me #endif ; 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_compiler_gnu=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_compiler_gnu=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext ac_cv_cxx_compiler_gnu=$ac_compiler_gnu fi { echo "$as_me:$LINENO: result: $ac_cv_cxx_compiler_gnu" >&5 echo "${ECHO_T}$ac_cv_cxx_compiler_gnu" >&6; } GXX=`test $ac_compiler_gnu = yes && echo yes` ac_test_CXXFLAGS=${CXXFLAGS+set} ac_save_CXXFLAGS=$CXXFLAGS { echo "$as_me:$LINENO: checking whether $CXX accepts -g" >&5 echo $ECHO_N "checking whether $CXX accepts -g... $ECHO_C" >&6; } if test "${ac_cv_prog_cxx_g+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_save_cxx_werror_flag=$ac_cxx_werror_flag ac_cxx_werror_flag=yes ac_cv_prog_cxx_g=no CXXFLAGS="-g" cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ 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_prog_cxx_g=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 CXXFLAGS="" cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ 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 : else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_cxx_werror_flag=$ac_save_cxx_werror_flag CXXFLAGS="-g" cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ 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_prog_cxx_g=yes 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 fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext ac_cxx_werror_flag=$ac_save_cxx_werror_flag fi { echo "$as_me:$LINENO: result: $ac_cv_prog_cxx_g" >&5 echo "${ECHO_T}$ac_cv_prog_cxx_g" >&6; } if test "$ac_test_CXXFLAGS" = set; then CXXFLAGS=$ac_save_CXXFLAGS elif test $ac_cv_prog_cxx_g = yes; then if test "$GXX" = yes; then CXXFLAGS="-g -O2" else CXXFLAGS="-g" fi else if test "$GXX" = yes; then CXXFLAGS="-O2" else CXXFLAGS= fi fi ac_ext=c ac_cpp='$CPP $CPPFLAGS' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_c_compiler_gnu DEPDIR="${am__leading_dot}deps" ac_config_commands="$ac_config_commands depfiles" am_make=${MAKE-make} cat > confinc << 'END' am__doit: @echo done .PHONY: am__doit END # If we don't find an include directive, just comment out the code. { echo "$as_me:$LINENO: checking for style of include used by $am_make" >&5 echo $ECHO_N "checking for style of include used by $am_make... $ECHO_C" >&6; } am__include="#" am__quote= _am_result=none # First try GNU make style include. echo "include confinc" > confmf # We grep out `Entering directory' and `Leaving directory' # messages which can occur if `w' ends up in MAKEFLAGS. # In particular we don't look at `^make:' because GNU make might # be invoked under some other name (usually "gmake"), in which # case it prints its new name instead of `make'. if test "`$am_make -s -f confmf 2> /dev/null | grep -v 'ing directory'`" = "done"; then am__include=include am__quote= _am_result=GNU fi # Now try BSD make style include. if test "$am__include" = "#"; then echo '.include "confinc"' > confmf if test "`$am_make -s -f confmf 2> /dev/null`" = "done"; then am__include=.include am__quote="\"" _am_result=BSD fi fi { echo "$as_me:$LINENO: result: $_am_result" >&5 echo "${ECHO_T}$_am_result" >&6; } rm -f confinc confmf # Check whether --enable-dependency-tracking was given. if test "${enable_dependency_tracking+set}" = set; then enableval=$enable_dependency_tracking; fi if test "x$enable_dependency_tracking" != xno; then am_depcomp="$ac_aux_dir/depcomp" AMDEPBACKSLASH='\' fi if test "x$enable_dependency_tracking" != xno; then AMDEP_TRUE= AMDEP_FALSE='#' else AMDEP_TRUE='#' AMDEP_FALSE= fi depcc="$CXX" am_compiler_list= { echo "$as_me:$LINENO: checking dependency style of $depcc" >&5 echo $ECHO_N "checking dependency style of $depcc... $ECHO_C" >&6; } if test "${am_cv_CXX_dependencies_compiler_type+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then # We make a subdir and do the tests there. Otherwise we can end up # making bogus files that we don't know about and never remove. For # instance it was reported that on HP-UX the gcc test will end up # making a dummy file named `D' -- because `-MD' means `put the output # in D'. mkdir conftest.dir # Copy depcomp to subdir because otherwise we won't find it if we're # using a relative directory. cp "$am_depcomp" conftest.dir cd conftest.dir # We will build objects and dependencies in a subdirectory because # it helps to detect inapplicable dependency modes. For instance # both Tru64's cc and ICC support -MD to output dependencies as a # side effect of compilation, but ICC will put the dependencies in # the current directory while Tru64 will put them in the object # directory. mkdir sub am_cv_CXX_dependencies_compiler_type=none if test "$am_compiler_list" = ""; then am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp` fi for depmode in $am_compiler_list; do # Setup a source with many dependencies, because some compilers # like to wrap large dependency lists on column 80 (with \), and # we should not choose a depcomp mode which is confused by this. # # We need to recreate these files for each test, as the compiler may # overwrite some of them when testing with obscure command lines. # This happens at least with the AIX C compiler. : > sub/conftest.c for i in 1 2 3 4 5 6; do echo '#include "conftst'$i'.h"' >> sub/conftest.c # Using `: > sub/conftst$i.h' creates only sub/conftst1.h with # Solaris 8's {/usr,}/bin/sh. touch sub/conftst$i.h done echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf case $depmode in nosideeffect) # after this tag, mechanisms are not by side-effect, so they'll # only be used when explicitly requested if test "x$enable_dependency_tracking" = xyes; then continue else break fi ;; none) break ;; esac # We check with `-c' and `-o' for the sake of the "dashmstdout" # mode. It turns out that the SunPro C++ compiler does not properly # handle `-M -o', and we need to detect this. if depmode=$depmode \ source=sub/conftest.c object=sub/conftest.${OBJEXT-o} \ depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ $SHELL ./depcomp $depcc -c -o sub/conftest.${OBJEXT-o} sub/conftest.c \ >/dev/null 2>conftest.err && grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && grep sub/conftest.${OBJEXT-o} sub/conftest.Po > /dev/null 2>&1 && ${MAKE-make} -s -f confmf > /dev/null 2>&1; then # icc doesn't choke on unknown options, it will just issue warnings # or remarks (even with -Werror). So we grep stderr for any message # that says an option was ignored or not supported. # When given -MP, icc 7.0 and 7.1 complain thusly: # icc: Command line warning: ignoring option '-M'; no argument required # The diagnosis changed in icc 8.0: # icc: Command line remark: option '-MP' not supported if (grep 'ignoring option' conftest.err || grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else am_cv_CXX_dependencies_compiler_type=$depmode break fi fi done cd .. rm -rf conftest.dir else am_cv_CXX_dependencies_compiler_type=none fi fi { echo "$as_me:$LINENO: result: $am_cv_CXX_dependencies_compiler_type" >&5 echo "${ECHO_T}$am_cv_CXX_dependencies_compiler_type" >&6; } CXXDEPMODE=depmode=$am_cv_CXX_dependencies_compiler_type if test "x$enable_dependency_tracking" != xno \ && test "$am_cv_CXX_dependencies_compiler_type" = gcc3; then am__fastdepCXX_TRUE= am__fastdepCXX_FALSE='#' else am__fastdepCXX_TRUE='#' am__fastdepCXX_FALSE= fi # Extract the first word of "tar", so it can be a program name with args. set dummy tar; ac_word=$2 { echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; } if test "${ac_cv_path_TAR+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else case $TAR in [\\/]* | ?:[\\/]*) ac_cv_path_TAR="$TAR" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_path_TAR="$as_dir/$ac_word$ac_exec_ext" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_TAR" && ac_cv_path_TAR="$FALSE" ;; esac fi TAR=$ac_cv_path_TAR if test -n "$TAR"; then { echo "$as_me:$LINENO: result: $TAR" >&5 echo "${ECHO_T}$TAR" >&6; } else { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } fi # Extract the first word of "make", so it can be a program name with args. set dummy make; ac_word=$2 { echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; } if test "${ac_cv_path_MAKE+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else case $MAKE in [\\/]* | ?:[\\/]*) ac_cv_path_MAKE="$MAKE" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_path_MAKE="$as_dir/$ac_word$ac_exec_ext" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS test -z "$ac_cv_path_MAKE" && ac_cv_path_MAKE="$FALSE" ;; esac fi MAKE=$ac_cv_path_MAKE if test -n "$MAKE"; then { echo "$as_me:$LINENO: result: $MAKE" >&5 echo "${ECHO_T}$MAKE" >&6; } else { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } fi # Find a good install program. We prefer a C program (faster), # so one script is as good as another. But avoid the broken or # incompatible versions: # SysV /etc/install, /usr/sbin/install # SunOS /usr/etc/install # IRIX /sbin/install # AIX /bin/install # AmigaOS /C/install, which installs bootblocks on floppy discs # AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag # AFS /usr/afsws/bin/install, which mishandles nonexistent args # SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" # OS/2's system install, which has a completely different semantic # ./install, which can be erroneously created by make from ./install.sh. { echo "$as_me:$LINENO: checking for a BSD-compatible install" >&5 echo $ECHO_N "checking for a BSD-compatible install... $ECHO_C" >&6; } if test -z "$INSTALL"; then if test "${ac_cv_path_install+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. # Account for people who put trailing slashes in PATH elements. case $as_dir/ in ./ | .// | /cC/* | \ /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \ ?:\\/os2\\/install\\/* | ?:\\/OS2\\/INSTALL\\/* | \ /usr/ucb/* ) ;; *) # OSF1 and SCO ODT 3.0 have their own names for install. # Don't use installbsd from OSF since it installs stuff as root # by default. for ac_prog in ginstall scoinst install; do for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_prog$ac_exec_ext" && $as_test_x "$as_dir/$ac_prog$ac_exec_ext"; }; then if test $ac_prog = install && grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # AIX install. It has an incompatible calling convention. : elif test $ac_prog = install && grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # program-specific install script used by HP pwplus--don't use. : else ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c" break 3 fi fi done done ;; esac done IFS=$as_save_IFS fi if test "${ac_cv_path_install+set}" = set; then INSTALL=$ac_cv_path_install else # As a last resort, use the slow shell script. Don't cache a # value for INSTALL within a source directory, because that will # break other packages using the cache if that directory is # removed, or if the value is a relative name. INSTALL=$ac_install_sh fi fi { echo "$as_me:$LINENO: result: $INSTALL" >&5 echo "${ECHO_T}$INSTALL" >&6; } # Use test -z because SunOS4 sh mishandles braces in ${var-val}. # It thinks the first close brace ends the variable substitution. test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu { echo "$as_me:$LINENO: checking how to run the C++ preprocessor" >&5 echo $ECHO_N "checking how to run the C++ preprocessor... $ECHO_C" >&6; } if test -z "$CXXCPP"; then if test "${ac_cv_prog_CXXCPP+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else # Double quotes because CXXCPP needs to be expanded for CXXCPP in "$CXX -E" "/lib/cpp" do ac_preproc_ok=false for ac_cxx_preproc_warn_flag in '' yes do # Use a header file that comes with gcc, so configuring glibc # with a fresh cross-compiler works. # Prefer to if __STDC__ is defined, since # exists even on freestanding compilers. # On the NeXT, cc -E runs the code through the compiler's parser, # not just through cpp. "Syntax error" is here to catch this case. cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #ifdef __STDC__ # include #else # include #endif Syntax error _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" 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_cpp conftest.$ac_ext") 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); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then : else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 # Broken: fails on valid input. continue fi rm -f conftest.err conftest.$ac_ext # OK, works on sane cases. Now check whether nonexistent headers # can be detected and how. cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" 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_cpp conftest.$ac_ext") 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); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then # Broken: success on invalid input. continue else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 # Passes both tests. ac_preproc_ok=: break fi rm -f conftest.err conftest.$ac_ext done # Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. rm -f conftest.err conftest.$ac_ext if $ac_preproc_ok; then break fi done ac_cv_prog_CXXCPP=$CXXCPP fi CXXCPP=$ac_cv_prog_CXXCPP else ac_cv_prog_CXXCPP=$CXXCPP fi { echo "$as_me:$LINENO: result: $CXXCPP" >&5 echo "${ECHO_T}$CXXCPP" >&6; } ac_preproc_ok=false for ac_cxx_preproc_warn_flag in '' yes do # Use a header file that comes with gcc, so configuring glibc # with a fresh cross-compiler works. # Prefer to if __STDC__ is defined, since # exists even on freestanding compilers. # On the NeXT, cc -E runs the code through the compiler's parser, # not just through cpp. "Syntax error" is here to catch this case. cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #ifdef __STDC__ # include #else # include #endif Syntax error _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" 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_cpp conftest.$ac_ext") 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); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then : else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 # Broken: fails on valid input. continue fi rm -f conftest.err conftest.$ac_ext # OK, works on sane cases. Now check whether nonexistent headers # can be detected and how. cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" 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_cpp conftest.$ac_ext") 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); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then # Broken: success on invalid input. continue else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 # Passes both tests. ac_preproc_ok=: break fi rm -f conftest.err conftest.$ac_ext done # Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. rm -f conftest.err conftest.$ac_ext if $ac_preproc_ok; then : else { { echo "$as_me:$LINENO: error: C++ preprocessor \"$CXXCPP\" fails sanity check See \`config.log' for more details." >&5 echo "$as_me: error: C++ preprocessor \"$CXXCPP\" fails sanity check See \`config.log' for more details." >&2;} { (exit 1); exit 1; }; } fi ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu { echo "$as_me:$LINENO: checking for grep that handles long lines and -e" >&5 echo $ECHO_N "checking for grep that handles long lines and -e... $ECHO_C" >&6; } if test "${ac_cv_path_GREP+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else # Extract the first word of "grep ggrep" to use in msg output if test -z "$GREP"; then set dummy grep ggrep; ac_prog_name=$2 if test "${ac_cv_path_GREP+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_path_GREP_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_prog in grep ggrep; do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext" { test -f "$ac_path_GREP" && $as_test_x "$ac_path_GREP"; } || continue # Check for GNU ac_path_GREP and select it if it is found. # Check for GNU $ac_path_GREP case `"$ac_path_GREP" --version 2>&1` in *GNU*) ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;; *) ac_count=0 echo $ECHO_N "0123456789$ECHO_C" >"conftest.in" while : do cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" echo 'GREP' >> "conftest.nl" "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break ac_count=`expr $ac_count + 1` if test $ac_count -gt ${ac_path_GREP_max-0}; then # Best one so far, save it but keep looking for a better one ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac $ac_path_GREP_found && break 3 done done done IFS=$as_save_IFS fi GREP="$ac_cv_path_GREP" if test -z "$GREP"; then { { echo "$as_me:$LINENO: error: no acceptable $ac_prog_name could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" >&5 echo "$as_me: error: no acceptable $ac_prog_name could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" >&2;} { (exit 1); exit 1; }; } fi else ac_cv_path_GREP=$GREP fi fi { echo "$as_me:$LINENO: result: $ac_cv_path_GREP" >&5 echo "${ECHO_T}$ac_cv_path_GREP" >&6; } GREP="$ac_cv_path_GREP" { echo "$as_me:$LINENO: checking for egrep" >&5 echo $ECHO_N "checking for egrep... $ECHO_C" >&6; } if test "${ac_cv_path_EGREP+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 then ac_cv_path_EGREP="$GREP -E" else # Extract the first word of "egrep" to use in msg output if test -z "$EGREP"; then set dummy egrep; ac_prog_name=$2 if test "${ac_cv_path_EGREP+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_path_EGREP_found=false # Loop through the user's path and test for each of PROGNAME-LIST as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_prog in egrep; do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext" { test -f "$ac_path_EGREP" && $as_test_x "$ac_path_EGREP"; } || continue # Check for GNU ac_path_EGREP and select it if it is found. # Check for GNU $ac_path_EGREP case `"$ac_path_EGREP" --version 2>&1` in *GNU*) ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;; *) ac_count=0 echo $ECHO_N "0123456789$ECHO_C" >"conftest.in" while : do cat "conftest.in" "conftest.in" >"conftest.tmp" mv "conftest.tmp" "conftest.in" cp "conftest.in" "conftest.nl" echo 'EGREP' >> "conftest.nl" "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break ac_count=`expr $ac_count + 1` if test $ac_count -gt ${ac_path_EGREP_max-0}; then # Best one so far, save it but keep looking for a better one ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_max=$ac_count fi # 10*(2^10) chars as input seems more than enough test $ac_count -gt 10 && break done rm -f conftest.in conftest.tmp conftest.nl conftest.out;; esac $ac_path_EGREP_found && break 3 done done done IFS=$as_save_IFS fi EGREP="$ac_cv_path_EGREP" if test -z "$EGREP"; then { { echo "$as_me:$LINENO: error: no acceptable $ac_prog_name could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" >&5 echo "$as_me: error: no acceptable $ac_prog_name could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" >&2;} { (exit 1); exit 1; }; } fi else ac_cv_path_EGREP=$EGREP fi fi fi { echo "$as_me:$LINENO: result: $ac_cv_path_EGREP" >&5 echo "${ECHO_T}$ac_cv_path_EGREP" >&6; } EGREP="$ac_cv_path_EGREP" { echo "$as_me:$LINENO: checking for ANSI C header files" >&5 echo $ECHO_N "checking for ANSI C header files... $ECHO_C" >&6; } if test "${ac_cv_header_stdc+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #include #include #include 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_header_stdc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_cv_header_stdc=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext if test $ac_cv_header_stdc = yes; then # SunOS 4.x string.h does not declare mem*, contrary to ANSI. cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP "memchr" >/dev/null 2>&1; then : else ac_cv_header_stdc=no fi rm -f conftest* fi if test $ac_cv_header_stdc = yes; then # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP "free" >/dev/null 2>&1; then : else ac_cv_header_stdc=no fi rm -f conftest* fi if test $ac_cv_header_stdc = yes; then # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. if test "$cross_compiling" = yes; then : else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #include #if ((' ' & 0x0FF) == 0x020) # define ISLOWER(c) ('a' <= (c) && (c) <= 'z') # define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) #else # define ISLOWER(c) \ (('a' <= (c) && (c) <= 'i') \ || ('j' <= (c) && (c) <= 'r') \ || ('s' <= (c) && (c) <= 'z')) # define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) #endif #define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) int main () { int i; for (i = 0; i < 256; i++) if (XOR (islower (i), ISLOWER (i)) || toupper (i) != TOUPPER (i)) return 2; return 0; } _ACEOF rm -f conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { ac_try='./conftest$ac_exeext' { (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_try") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then : else echo "$as_me: program exited with status $ac_status" >&5 echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ( exit $ac_status ) ac_cv_header_stdc=no fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext conftest.$ac_objext conftest.$ac_ext fi fi fi { echo "$as_me:$LINENO: result: $ac_cv_header_stdc" >&5 echo "${ECHO_T}$ac_cv_header_stdc" >&6; } if test $ac_cv_header_stdc = yes; then cat >>confdefs.h <<\_ACEOF #define STDC_HEADERS 1 _ACEOF fi # On IRIX 5.3, sys/types and inttypes.h are conflicting. for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \ inttypes.h stdint.h unistd.h do as_ac_Header=`echo "ac_cv_header_$ac_header" | $as_tr_sh` { echo "$as_me:$LINENO: checking for $ac_header" >&5 echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6; } if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default #include <$ac_header> _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 eval "$as_ac_Header=yes" else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 eval "$as_ac_Header=no" fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi ac_res=`eval echo '${'$as_ac_Header'}'` { echo "$as_me:$LINENO: result: $ac_res" >&5 echo "${ECHO_T}$ac_res" >&6; } if test `eval echo '${'$as_ac_Header'}'` = yes; then cat >>confdefs.h <<_ACEOF #define `echo "HAVE_$ac_header" | $as_tr_cpp` 1 _ACEOF fi done for ac_header in sys/prctl.h do as_ac_Header=`echo "ac_cv_header_$ac_header" | $as_tr_sh` if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then { echo "$as_me:$LINENO: checking for $ac_header" >&5 echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6; } if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then echo $ECHO_N "(cached) $ECHO_C" >&6 fi ac_res=`eval echo '${'$as_ac_Header'}'` { echo "$as_me:$LINENO: result: $ac_res" >&5 echo "${ECHO_T}$ac_res" >&6; } else # Is the header compilable? { echo "$as_me:$LINENO: checking $ac_header usability" >&5 echo $ECHO_N "checking $ac_header usability... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default #include <$ac_header> _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_header_compiler=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_compiler=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 echo "${ECHO_T}$ac_header_compiler" >&6; } # Is the header present? { echo "$as_me:$LINENO: checking $ac_header presence" >&5 echo $ECHO_N "checking $ac_header presence... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include <$ac_header> _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" 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_cpp conftest.$ac_ext") 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); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then ac_header_preproc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_preproc=no fi rm -f conftest.err conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 echo "${ECHO_T}$ac_header_preproc" >&6; } # So? What about this header? case $ac_header_compiler:$ac_header_preproc:$ac_cxx_preproc_warn_flag in yes:no: ) { echo "$as_me:$LINENO: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&5 echo "$as_me: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the compiler's result" >&5 echo "$as_me: WARNING: $ac_header: proceeding with the compiler's result" >&2;} ac_header_preproc=yes ;; no:yes:* ) { echo "$as_me:$LINENO: WARNING: $ac_header: present but cannot be compiled" >&5 echo "$as_me: WARNING: $ac_header: present but cannot be compiled" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: check for missing prerequisite headers?" >&5 echo "$as_me: WARNING: $ac_header: check for missing prerequisite headers?" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: see the Autoconf documentation" >&5 echo "$as_me: WARNING: $ac_header: see the Autoconf documentation" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: section \"Present But Cannot Be Compiled\"" >&5 echo "$as_me: WARNING: $ac_header: section \"Present But Cannot Be Compiled\"" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: $ac_header: proceeding with the preprocessor's result" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: in the future, the compiler will take precedence" >&5 echo "$as_me: WARNING: $ac_header: in the future, the compiler will take precedence" >&2;} ( cat <<\_ASBOX ## ------------------------------------------- ## ## Report this to hugbug@users.sourceforge.net ## ## ------------------------------------------- ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; esac { echo "$as_me:$LINENO: checking for $ac_header" >&5 echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6; } if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then echo $ECHO_N "(cached) $ECHO_C" >&6 else eval "$as_ac_Header=\$ac_header_preproc" fi ac_res=`eval echo '${'$as_ac_Header'}'` { echo "$as_me:$LINENO: result: $ac_res" >&5 echo "${ECHO_T}$ac_res" >&6; } fi if test `eval echo '${'$as_ac_Header'}'` = yes; then cat >>confdefs.h <<_ACEOF #define `echo "HAVE_$ac_header" | $as_tr_cpp` 1 _ACEOF fi done for ac_header in regex.h do as_ac_Header=`echo "ac_cv_header_$ac_header" | $as_tr_sh` if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then { echo "$as_me:$LINENO: checking for $ac_header" >&5 echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6; } if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then echo $ECHO_N "(cached) $ECHO_C" >&6 fi ac_res=`eval echo '${'$as_ac_Header'}'` { echo "$as_me:$LINENO: result: $ac_res" >&5 echo "${ECHO_T}$ac_res" >&6; } else # Is the header compilable? { echo "$as_me:$LINENO: checking $ac_header usability" >&5 echo $ECHO_N "checking $ac_header usability... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default #include <$ac_header> _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_header_compiler=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_compiler=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 echo "${ECHO_T}$ac_header_compiler" >&6; } # Is the header present? { echo "$as_me:$LINENO: checking $ac_header presence" >&5 echo $ECHO_N "checking $ac_header presence... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include <$ac_header> _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" 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_cpp conftest.$ac_ext") 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); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then ac_header_preproc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_preproc=no fi rm -f conftest.err conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 echo "${ECHO_T}$ac_header_preproc" >&6; } # So? What about this header? case $ac_header_compiler:$ac_header_preproc:$ac_cxx_preproc_warn_flag in yes:no: ) { echo "$as_me:$LINENO: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&5 echo "$as_me: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the compiler's result" >&5 echo "$as_me: WARNING: $ac_header: proceeding with the compiler's result" >&2;} ac_header_preproc=yes ;; no:yes:* ) { echo "$as_me:$LINENO: WARNING: $ac_header: present but cannot be compiled" >&5 echo "$as_me: WARNING: $ac_header: present but cannot be compiled" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: check for missing prerequisite headers?" >&5 echo "$as_me: WARNING: $ac_header: check for missing prerequisite headers?" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: see the Autoconf documentation" >&5 echo "$as_me: WARNING: $ac_header: see the Autoconf documentation" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: section \"Present But Cannot Be Compiled\"" >&5 echo "$as_me: WARNING: $ac_header: section \"Present But Cannot Be Compiled\"" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: $ac_header: proceeding with the preprocessor's result" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: in the future, the compiler will take precedence" >&5 echo "$as_me: WARNING: $ac_header: in the future, the compiler will take precedence" >&2;} ( cat <<\_ASBOX ## ------------------------------------------- ## ## Report this to hugbug@users.sourceforge.net ## ## ------------------------------------------- ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; esac { echo "$as_me:$LINENO: checking for $ac_header" >&5 echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6; } if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then echo $ECHO_N "(cached) $ECHO_C" >&6 else eval "$as_ac_Header=\$ac_header_preproc" fi ac_res=`eval echo '${'$as_ac_Header'}'` { echo "$as_me:$LINENO: result: $ac_res" >&5 echo "${ECHO_T}$ac_res" >&6; } fi if test `eval echo '${'$as_ac_Header'}'` = yes; then cat >>confdefs.h <<_ACEOF #define `echo "HAVE_$ac_header" | $as_tr_cpp` 1 _ACEOF fi done { echo "$as_me:$LINENO: checking for library containing pthread_create" >&5 echo $ECHO_N "checking for library containing pthread_create... $ECHO_C" >&6; } if test "${ac_cv_search_pthread_create+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char pthread_create (); int main () { return pthread_create (); ; return 0; } _ACEOF for ac_lib in '' pthread; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search_pthread_create=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search_pthread_create+set}" = set; then break fi done if test "${ac_cv_search_pthread_create+set}" = set; then : else ac_cv_search_pthread_create=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search_pthread_create" >&5 echo "${ECHO_T}$ac_cv_search_pthread_create" >&6; } ac_res=$ac_cv_search_pthread_create if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" fi { echo "$as_me:$LINENO: checking for library containing socket" >&5 echo $ECHO_N "checking for library containing socket... $ECHO_C" >&6; } if test "${ac_cv_search_socket+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char socket (); int main () { return socket (); ; return 0; } _ACEOF for ac_lib in '' socket; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search_socket=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search_socket+set}" = set; then break fi done if test "${ac_cv_search_socket+set}" = set; then : else ac_cv_search_socket=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search_socket" >&5 echo "${ECHO_T}$ac_cv_search_socket" >&6; } ac_res=$ac_cv_search_socket if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" fi { echo "$as_me:$LINENO: checking for library containing inet_addr" >&5 echo $ECHO_N "checking for library containing inet_addr... $ECHO_C" >&6; } if test "${ac_cv_search_inet_addr+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char inet_addr (); int main () { return inet_addr (); ; return 0; } _ACEOF for ac_lib in '' nsl; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search_inet_addr=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search_inet_addr+set}" = set; then break fi done if test "${ac_cv_search_inet_addr+set}" = set; then : else ac_cv_search_inet_addr=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search_inet_addr" >&5 echo "${ECHO_T}$ac_cv_search_inet_addr" >&6; } ac_res=$ac_cv_search_inet_addr if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" fi { echo "$as_me:$LINENO: checking for library containing hstrerror" >&5 echo $ECHO_N "checking for library containing hstrerror... $ECHO_C" >&6; } if test "${ac_cv_search_hstrerror+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char hstrerror (); int main () { return hstrerror (); ; return 0; } _ACEOF for ac_lib in '' resolv; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search_hstrerror=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search_hstrerror+set}" = set; then break fi done if test "${ac_cv_search_hstrerror+set}" = set; then : else ac_cv_search_hstrerror=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search_hstrerror" >&5 echo "${ECHO_T}$ac_cv_search_hstrerror" >&6; } ac_res=$ac_cv_search_hstrerror if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" fi { echo "$as_me:$LINENO: checking for getopt_long" >&5 echo $ECHO_N "checking for getopt_long... $ECHO_C" >&6; } if test "${ac_cv_func_getopt_long+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Define getopt_long to an innocuous variant, in case declares getopt_long. For example, HP-UX 11i declares gettimeofday. */ #define getopt_long innocuous_getopt_long /* System header to define __stub macros and hopefully few prototypes, which can conflict with char getopt_long (); below. Prefer to if __STDC__ is defined, since exists even on freestanding compilers. */ #ifdef __STDC__ # include #else # include #endif #undef getopt_long /* 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 getopt_long (); /* 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_getopt_long || defined __stub___getopt_long choke me #endif int main () { return getopt_long (); ; return 0; } _ACEOF rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_func_getopt_long=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_cv_func_getopt_long=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_getopt_long" >&5 echo "${ECHO_T}$ac_cv_func_getopt_long" >&6; } if test $ac_cv_func_getopt_long = yes; then cat >>confdefs.h <<\_ACEOF #define HAVE_GETOPT_LONG 1 _ACEOF fi { echo "$as_me:$LINENO: checking for fdatasync" >&5 echo $ECHO_N "checking for fdatasync... $ECHO_C" >&6; } if test "${ac_cv_func_fdatasync+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Define fdatasync to an innocuous variant, in case declares fdatasync. For example, HP-UX 11i declares gettimeofday. */ #define fdatasync innocuous_fdatasync /* System header to define __stub macros and hopefully few prototypes, which can conflict with char fdatasync (); below. Prefer to if __STDC__ is defined, since exists even on freestanding compilers. */ #ifdef __STDC__ # include #else # include #endif #undef fdatasync /* 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 fdatasync (); /* 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_fdatasync || defined __stub___fdatasync choke me #endif int main () { return fdatasync (); ; return 0; } _ACEOF rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_func_fdatasync=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_cv_func_fdatasync=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_fdatasync" >&5 echo "${ECHO_T}$ac_cv_func_fdatasync" >&6; } if test $ac_cv_func_fdatasync = yes; then cat >>confdefs.h <<\_ACEOF #define HAVE_FDATASYNC 1 _ACEOF fi { echo "$as_me:$LINENO: checking whether F_FULLFSYNC is declared" >&5 echo $ECHO_N "checking whether F_FULLFSYNC is declared... $ECHO_C" >&6; } if test "${ac_cv_have_decl_F_FULLFSYNC+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include int main () { #ifndef F_FULLFSYNC (void) F_FULLFSYNC; #endif ; 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_have_decl_F_FULLFSYNC=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_cv_have_decl_F_FULLFSYNC=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi { echo "$as_me:$LINENO: result: $ac_cv_have_decl_F_FULLFSYNC" >&5 echo "${ECHO_T}$ac_cv_have_decl_F_FULLFSYNC" >&6; } if test $ac_cv_have_decl_F_FULLFSYNC = yes; then cat >>confdefs.h <<\_ACEOF #define HAVE_FULLFSYNC 1 _ACEOF fi # 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 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. */ #include /* 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 break else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi 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 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 /* 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 /* 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 /* 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 /* 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 { echo "$as_me:$LINENO: checking for ctime_r" >&5 echo $ECHO_N "checking for ctime_r... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include int main () { time_t clock; char buf[26]; ctime_r(&clock, buf, 26); ; 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 { echo "$as_me:$LINENO: result: yes, and it takes 3 arguments" >&5 echo "${ECHO_T}yes, and it takes 3 arguments" >&6; } FOUND="yes" cat >>confdefs.h <<\_ACEOF #define HAVE_CTIME_R_3 1 _ACEOF else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 FOUND="no" fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext if test "$FOUND" = "no"; then cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include int main () { time_t clock; char buf[26]; ctime_r(&clock, buf); ; 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 { echo "$as_me:$LINENO: result: yes, and it takes 2 arguments" >&5 echo "${ECHO_T}yes, and it takes 2 arguments" >&6; } FOUND="yes" cat >>confdefs.h <<\_ACEOF #define HAVE_CTIME_R_2 1 _ACEOF else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 FOUND="no" fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi if test "$FOUND" = "no"; then { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } { { echo "$as_me:$LINENO: error: \"function ctime_r not found\"" >&5 echo "$as_me: error: \"function ctime_r not found\"" >&2;} { (exit 1); exit 1; }; } fi { echo "$as_me:$LINENO: checking for getaddrinfo" >&5 echo $ECHO_N "checking for getaddrinfo... $ECHO_C" >&6; } if test "${ac_cv_func_getaddrinfo+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Define getaddrinfo to an innocuous variant, in case declares getaddrinfo. For example, HP-UX 11i declares gettimeofday. */ #define getaddrinfo innocuous_getaddrinfo /* System header to define __stub macros and hopefully few prototypes, which can conflict with char getaddrinfo (); below. Prefer to if __STDC__ is defined, since exists even on freestanding compilers. */ #ifdef __STDC__ # include #else # include #endif #undef getaddrinfo /* 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 getaddrinfo (); /* 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_getaddrinfo || defined __stub___getaddrinfo choke me #endif int main () { return getaddrinfo (); ; return 0; } _ACEOF rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_func_getaddrinfo=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_cv_func_getaddrinfo=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_getaddrinfo" >&5 echo "${ECHO_T}$ac_cv_func_getaddrinfo" >&6; } if test $ac_cv_func_getaddrinfo = yes; then FOUND="yes" cat >>confdefs.h <<\_ACEOF #define HAVE_GETADDRINFO 1 _ACEOF { echo "$as_me:$LINENO: checking for library containing getaddrinfo" >&5 echo $ECHO_N "checking for library containing getaddrinfo... $ECHO_C" >&6; } if test "${ac_cv_search_getaddrinfo+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char getaddrinfo (); int main () { return getaddrinfo (); ; return 0; } _ACEOF for ac_lib in '' nsl; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search_getaddrinfo=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search_getaddrinfo+set}" = set; then break fi done if test "${ac_cv_search_getaddrinfo+set}" = set; then : else ac_cv_search_getaddrinfo=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search_getaddrinfo" >&5 echo "${ECHO_T}$ac_cv_search_getaddrinfo" >&6; } ac_res=$ac_cv_search_getaddrinfo if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" fi else FOUND="no" fi if test "$FOUND" = "no"; then { echo "$as_me:$LINENO: checking for gethostbyname_r" >&5 echo $ECHO_N "checking for gethostbyname_r... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include int main () { char* szHost; struct hostent hinfobuf; char* strbuf; int h_errnop; struct hostent* hinfo = gethostbyname_r(szHost, &hinfobuf, strbuf, 1024, &h_errnop); ; 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 { echo "$as_me:$LINENO: result: yes, and it takes 5 arguments" >&5 echo "${ECHO_T}yes, and it takes 5 arguments" >&6; } FOUND="yes" cat >>confdefs.h <<\_ACEOF #define HAVE_GETHOSTBYNAME_R_5 1 _ACEOF else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 FOUND="no" fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext if test "$FOUND" = "no"; then cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include int main () { char* szHost; struct hostent* hinfo; struct hostent hinfobuf; char* strbuf; int h_errnop; int err = gethostbyname_r(szHost, &hinfobuf, strbuf, 1024, &hinfo, &h_errnop); ; 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 { echo "$as_me:$LINENO: result: yes, and it takes 6 arguments" >&5 echo "${ECHO_T}yes, and it takes 6 arguments" >&6; } FOUND="yes" cat >>confdefs.h <<\_ACEOF #define HAVE_GETHOSTBYNAME_R_6 1 _ACEOF else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 FOUND="no" fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi if test "$FOUND" = "no"; then cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include int main () { char* szHost; struct hostent hinfo; struct hostent_data hinfobuf; int err = gethostbyname_r(szHost, &hinfo, &hinfobuf); ; 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 { echo "$as_me:$LINENO: result: yes, and it takes 3 arguments" >&5 echo "${ECHO_T}yes, and it takes 3 arguments" >&6; } FOUND="yes" cat >>confdefs.h <<\_ACEOF #define HAVE_GETHOSTBYNAME_R_3 1 _ACEOF else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } FOUND="no" fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi if test "$FOUND" = "yes"; then cat >>confdefs.h <<\_ACEOF #define HAVE_GETHOSTBYNAME_R 1 _ACEOF { echo "$as_me:$LINENO: checking for library containing gethostbyname_r" >&5 echo $ECHO_N "checking for library containing gethostbyname_r... $ECHO_C" >&6; } if test "${ac_cv_search_gethostbyname_r+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char gethostbyname_r (); int main () { return gethostbyname_r (); ; return 0; } _ACEOF for ac_lib in '' nsl; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search_gethostbyname_r=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search_gethostbyname_r+set}" = set; then break fi done if test "${ac_cv_search_gethostbyname_r+set}" = set; then : else ac_cv_search_gethostbyname_r=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search_gethostbyname_r" >&5 echo "${ECHO_T}$ac_cv_search_gethostbyname_r" >&6; } ac_res=$ac_cv_search_gethostbyname_r if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" fi fi fi { echo "$as_me:$LINENO: checking for type of socket length (socklen_t)" >&5 echo $ECHO_N "checking for type of socket length (socklen_t)... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #include #include int main () { (void)getsockopt (1, 1, 1, NULL, (socklen_t*)NULL) ; 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 { echo "$as_me:$LINENO: result: socklen_t" >&5 echo "${ECHO_T}socklen_t" >&6; } SOCKLEN_T=socklen_t else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #include #include int main () { (void)getsockopt (1, 1, 1, NULL, (size_t*)NULL) ; 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 { echo "$as_me:$LINENO: result: size_t" >&5 echo "${ECHO_T}size_t" >&6; } SOCKLEN_T=size_t else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #include #include int main () { (void)getsockopt (1, 1, 1, NULL, (int*)NULL) ; 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 { echo "$as_me:$LINENO: result: int" >&5 echo "${ECHO_T}int" >&6; } SOCKLEN_T=int else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { echo "$as_me:$LINENO: WARNING: could not determine" >&5 echo "$as_me: WARNING: could not determine" >&2;} SOCKLEN_T=int fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext cat >>confdefs.h <<_ACEOF #define SOCKLEN_T $SOCKLEN_T _ACEOF { echo "$as_me:$LINENO: checking whether dir-browser snapshot workaround is needed" >&5 echo $ECHO_N "checking whether dir-browser snapshot workaround is needed... $ECHO_C" >&6; } if test "$target_vendor" == "apple"; then { echo "$as_me:$LINENO: result: yes" >&5 echo "${ECHO_T}yes" >&6; } cat >>confdefs.h <<\_ACEOF #define DIRBROWSER_SNAPSHOT 1 _ACEOF else { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } fi { echo "$as_me:$LINENO: checking for cpu cores via sysconf" >&5 echo $ECHO_N "checking for cpu cores via sysconf... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include int main () { int a = _SC_NPROCESSORS_ONLN; ; 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 FOUND="yes" { echo "$as_me:$LINENO: result: yes" >&5 echo "${ECHO_T}yes" >&6; } cat >>confdefs.h <<\_ACEOF #define HAVE_SC_NPROCESSORS_ONLN 1 _ACEOF else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 FOUND="no" fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext # Check whether --with-libxml2_includes was given. if test "${with_libxml2_includes+set}" = set; then withval=$with_libxml2_includes; CPPFLAGS="${CPPFLAGS} -I${withval}" INCVAL="yes" else INCVAL="no" fi # Check whether --with-libxml2_libraries was given. if test "${with_libxml2_libraries+set}" = set; then withval=$with_libxml2_libraries; LDFLAGS="${LDFLAGS} -L${withval}" LIBVAL="yes" else LIBVAL="no" fi if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then if test -n "$ac_tool_prefix"; then # Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args. set dummy ${ac_tool_prefix}pkg-config; ac_word=$2 { echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; } if test "${ac_cv_path_PKG_CONFIG+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else case $PKG_CONFIG in [\\/]* | ?:[\\/]*) ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_path_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi PKG_CONFIG=$ac_cv_path_PKG_CONFIG if test -n "$PKG_CONFIG"; then { echo "$as_me:$LINENO: result: $PKG_CONFIG" >&5 echo "${ECHO_T}$PKG_CONFIG" >&6; } else { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } fi fi if test -z "$ac_cv_path_PKG_CONFIG"; then ac_pt_PKG_CONFIG=$PKG_CONFIG # Extract the first word of "pkg-config", so it can be a program name with args. set dummy pkg-config; ac_word=$2 { echo "$as_me:$LINENO: checking for $ac_word" >&5 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; } if test "${ac_cv_path_ac_pt_PKG_CONFIG+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else case $ac_pt_PKG_CONFIG in [\\/]* | ?:[\\/]*) ac_cv_path_ac_pt_PKG_CONFIG="$ac_pt_PKG_CONFIG" # Let the user override the test with a path. ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ac_cv_path_ac_pt_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext" echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS ;; esac fi ac_pt_PKG_CONFIG=$ac_cv_path_ac_pt_PKG_CONFIG if test -n "$ac_pt_PKG_CONFIG"; then { echo "$as_me:$LINENO: result: $ac_pt_PKG_CONFIG" >&5 echo "${ECHO_T}$ac_pt_PKG_CONFIG" >&6; } else { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } fi if test "x$ac_pt_PKG_CONFIG" = x; then PKG_CONFIG="" else case $cross_compiling:$ac_tool_warned in yes:) { echo "$as_me:$LINENO: WARNING: In the future, Autoconf will not detect cross-tools whose name does not start with the host triplet. If you think this configuration is useful to you, please write to autoconf@gnu.org." >&5 echo "$as_me: WARNING: In the future, Autoconf will not detect cross-tools whose name does not start with the host triplet. If you think this configuration is useful to you, please write to autoconf@gnu.org." >&2;} ac_tool_warned=yes ;; esac PKG_CONFIG=$ac_pt_PKG_CONFIG fi else PKG_CONFIG="$ac_cv_path_PKG_CONFIG" fi fi if test -n "$PKG_CONFIG"; then _pkg_min_version=0.9.0 { echo "$as_me:$LINENO: checking pkg-config is at least version $_pkg_min_version" >&5 echo $ECHO_N "checking pkg-config is at least version $_pkg_min_version... $ECHO_C" >&6; } if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then { echo "$as_me:$LINENO: result: yes" >&5 echo "${ECHO_T}yes" >&6; } else { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } PKG_CONFIG="" fi fi pkg_failed=no { echo "$as_me:$LINENO: checking for libxml2" >&5 echo $ECHO_N "checking for libxml2... $ECHO_C" >&6; } if test -n "$PKG_CONFIG"; then if test -n "$libxml2_CFLAGS"; then pkg_cv_libxml2_CFLAGS="$libxml2_CFLAGS" else if test -n "$PKG_CONFIG" && \ { (echo "$as_me:$LINENO: \$PKG_CONFIG --exists --print-errors \"libxml-2.0\"") >&5 ($PKG_CONFIG --exists --print-errors "libxml-2.0") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; then pkg_cv_libxml2_CFLAGS=`$PKG_CONFIG --cflags "libxml-2.0" 2>/dev/null` else pkg_failed=yes fi fi else pkg_failed=untried fi if test -n "$PKG_CONFIG"; then if test -n "$libxml2_LIBS"; then pkg_cv_libxml2_LIBS="$libxml2_LIBS" else if test -n "$PKG_CONFIG" && \ { (echo "$as_me:$LINENO: \$PKG_CONFIG --exists --print-errors \"libxml-2.0\"") >&5 ($PKG_CONFIG --exists --print-errors "libxml-2.0") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; then pkg_cv_libxml2_LIBS=`$PKG_CONFIG --libs "libxml-2.0" 2>/dev/null` else pkg_failed=yes fi fi else pkg_failed=untried fi if test $pkg_failed = yes; then if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then libxml2_PKG_ERRORS=`$PKG_CONFIG --short-errors --errors-to-stdout --print-errors "libxml-2.0"` else libxml2_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "libxml-2.0"` fi # Put the nasty error message in config.log where it belongs echo "$libxml2_PKG_ERRORS" >&5 { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } { { echo "$as_me:$LINENO: error: \"libxml2 library not found\"" >&5 echo "$as_me: error: \"libxml2 library not found\"" >&2;} { (exit 1); exit 1; }; } elif test $pkg_failed = untried; then { { echo "$as_me:$LINENO: error: \"libxml2 library not found\"" >&5 echo "$as_me: error: \"libxml2 library not found\"" >&2;} { (exit 1); exit 1; }; } else libxml2_CFLAGS=$pkg_cv_libxml2_CFLAGS libxml2_LIBS=$pkg_cv_libxml2_LIBS { echo "$as_me:$LINENO: result: yes" >&5 echo "${ECHO_T}yes" >&6; } LIBS="${LIBS} $libxml2_LIBS" CPPFLAGS="${CPPFLAGS} $libxml2_CFLAGS" fi fi if test "${ac_cv_header_libxml_tree_h+set}" = set; then { echo "$as_me:$LINENO: checking for libxml/tree.h" >&5 echo $ECHO_N "checking for libxml/tree.h... $ECHO_C" >&6; } if test "${ac_cv_header_libxml_tree_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 fi { echo "$as_me:$LINENO: result: $ac_cv_header_libxml_tree_h" >&5 echo "${ECHO_T}$ac_cv_header_libxml_tree_h" >&6; } else # Is the header compilable? { echo "$as_me:$LINENO: checking libxml/tree.h usability" >&5 echo $ECHO_N "checking libxml/tree.h usability... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default #include _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_header_compiler=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_compiler=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 echo "${ECHO_T}$ac_header_compiler" >&6; } # Is the header present? { echo "$as_me:$LINENO: checking libxml/tree.h presence" >&5 echo $ECHO_N "checking libxml/tree.h presence... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" 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_cpp conftest.$ac_ext") 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); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then ac_header_preproc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_preproc=no fi rm -f conftest.err conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 echo "${ECHO_T}$ac_header_preproc" >&6; } # So? What about this header? case $ac_header_compiler:$ac_header_preproc:$ac_cxx_preproc_warn_flag in yes:no: ) { echo "$as_me:$LINENO: WARNING: libxml/tree.h: accepted by the compiler, rejected by the preprocessor!" >&5 echo "$as_me: WARNING: libxml/tree.h: accepted by the compiler, rejected by the preprocessor!" >&2;} { echo "$as_me:$LINENO: WARNING: libxml/tree.h: proceeding with the compiler's result" >&5 echo "$as_me: WARNING: libxml/tree.h: proceeding with the compiler's result" >&2;} ac_header_preproc=yes ;; no:yes:* ) { echo "$as_me:$LINENO: WARNING: libxml/tree.h: present but cannot be compiled" >&5 echo "$as_me: WARNING: libxml/tree.h: present but cannot be compiled" >&2;} { echo "$as_me:$LINENO: WARNING: libxml/tree.h: check for missing prerequisite headers?" >&5 echo "$as_me: WARNING: libxml/tree.h: check for missing prerequisite headers?" >&2;} { echo "$as_me:$LINENO: WARNING: libxml/tree.h: see the Autoconf documentation" >&5 echo "$as_me: WARNING: libxml/tree.h: see the Autoconf documentation" >&2;} { echo "$as_me:$LINENO: WARNING: libxml/tree.h: section \"Present But Cannot Be Compiled\"" >&5 echo "$as_me: WARNING: libxml/tree.h: section \"Present But Cannot Be Compiled\"" >&2;} { echo "$as_me:$LINENO: WARNING: libxml/tree.h: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: libxml/tree.h: proceeding with the preprocessor's result" >&2;} { echo "$as_me:$LINENO: WARNING: libxml/tree.h: in the future, the compiler will take precedence" >&5 echo "$as_me: WARNING: libxml/tree.h: in the future, the compiler will take precedence" >&2;} ( cat <<\_ASBOX ## ------------------------------------------- ## ## Report this to hugbug@users.sourceforge.net ## ## ------------------------------------------- ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; esac { echo "$as_me:$LINENO: checking for libxml/tree.h" >&5 echo $ECHO_N "checking for libxml/tree.h... $ECHO_C" >&6; } if test "${ac_cv_header_libxml_tree_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_cv_header_libxml_tree_h=$ac_header_preproc fi { echo "$as_me:$LINENO: result: $ac_cv_header_libxml_tree_h" >&5 echo "${ECHO_T}$ac_cv_header_libxml_tree_h" >&6; } fi if test $ac_cv_header_libxml_tree_h = yes; then : else { { echo "$as_me:$LINENO: error: \"libxml2 header files not found\"" >&5 echo "$as_me: error: \"libxml2 header files not found\"" >&2;} { (exit 1); exit 1; }; } fi { echo "$as_me:$LINENO: checking for library containing xmlNewNode" >&5 echo $ECHO_N "checking for library containing xmlNewNode... $ECHO_C" >&6; } if test "${ac_cv_search_xmlNewNode+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char xmlNewNode (); int main () { return xmlNewNode (); ; return 0; } _ACEOF for ac_lib in '' xml2; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search_xmlNewNode=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search_xmlNewNode+set}" = set; then break fi done if test "${ac_cv_search_xmlNewNode+set}" = set; then : else ac_cv_search_xmlNewNode=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search_xmlNewNode" >&5 echo "${ECHO_T}$ac_cv_search_xmlNewNode" >&6; } ac_res=$ac_cv_search_xmlNewNode if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" else { { echo "$as_me:$LINENO: error: \"libxml2 library not found\"" >&5 echo "$as_me: error: \"libxml2 library not found\"" >&2;} { (exit 1); exit 1; }; } fi { echo "$as_me:$LINENO: checking whether to use curses" >&5 echo $ECHO_N "checking whether to use curses... $ECHO_C" >&6; } # Check whether --enable-curses was given. if test "${enable_curses+set}" = set; then enableval=$enable_curses; USECURSES=$enableval else USECURSES=yes fi { echo "$as_me:$LINENO: result: $USECURSES" >&5 echo "${ECHO_T}$USECURSES" >&6; } if test "$USECURSES" = "yes"; then INCVAL="${LIBPREF}/include" LIBVAL="${LIBPREF}/lib" # Check whether --with-libcurses_includes was given. if test "${with_libcurses_includes+set}" = set; then withval=$with_libcurses_includes; INCVAL="$withval" fi CPPFLAGS="${CPPFLAGS} -I${INCVAL}" # Check whether --with-libcurses_libraries was given. if test "${with_libcurses_libraries+set}" = set; then withval=$with_libcurses_libraries; LIBVAL="$withval" fi LDFLAGS="${LDFLAGS} -L${LIBVAL}" if test "${ac_cv_header_ncurses_h+set}" = set; then { echo "$as_me:$LINENO: checking for ncurses.h" >&5 echo $ECHO_N "checking for ncurses.h... $ECHO_C" >&6; } if test "${ac_cv_header_ncurses_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 fi { echo "$as_me:$LINENO: result: $ac_cv_header_ncurses_h" >&5 echo "${ECHO_T}$ac_cv_header_ncurses_h" >&6; } else # Is the header compilable? { echo "$as_me:$LINENO: checking ncurses.h usability" >&5 echo $ECHO_N "checking ncurses.h usability... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default #include _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_header_compiler=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_compiler=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 echo "${ECHO_T}$ac_header_compiler" >&6; } # Is the header present? { echo "$as_me:$LINENO: checking ncurses.h presence" >&5 echo $ECHO_N "checking ncurses.h presence... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" 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_cpp conftest.$ac_ext") 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); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then ac_header_preproc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_preproc=no fi rm -f conftest.err conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 echo "${ECHO_T}$ac_header_preproc" >&6; } # So? What about this header? case $ac_header_compiler:$ac_header_preproc:$ac_cxx_preproc_warn_flag in yes:no: ) { echo "$as_me:$LINENO: WARNING: ncurses.h: accepted by the compiler, rejected by the preprocessor!" >&5 echo "$as_me: WARNING: ncurses.h: accepted by the compiler, rejected by the preprocessor!" >&2;} { echo "$as_me:$LINENO: WARNING: ncurses.h: proceeding with the compiler's result" >&5 echo "$as_me: WARNING: ncurses.h: proceeding with the compiler's result" >&2;} ac_header_preproc=yes ;; no:yes:* ) { echo "$as_me:$LINENO: WARNING: ncurses.h: present but cannot be compiled" >&5 echo "$as_me: WARNING: ncurses.h: present but cannot be compiled" >&2;} { echo "$as_me:$LINENO: WARNING: ncurses.h: check for missing prerequisite headers?" >&5 echo "$as_me: WARNING: ncurses.h: check for missing prerequisite headers?" >&2;} { echo "$as_me:$LINENO: WARNING: ncurses.h: see the Autoconf documentation" >&5 echo "$as_me: WARNING: ncurses.h: see the Autoconf documentation" >&2;} { echo "$as_me:$LINENO: WARNING: ncurses.h: section \"Present But Cannot Be Compiled\"" >&5 echo "$as_me: WARNING: ncurses.h: section \"Present But Cannot Be Compiled\"" >&2;} { echo "$as_me:$LINENO: WARNING: ncurses.h: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: ncurses.h: proceeding with the preprocessor's result" >&2;} { echo "$as_me:$LINENO: WARNING: ncurses.h: in the future, the compiler will take precedence" >&5 echo "$as_me: WARNING: ncurses.h: in the future, the compiler will take precedence" >&2;} ( cat <<\_ASBOX ## ------------------------------------------- ## ## Report this to hugbug@users.sourceforge.net ## ## ------------------------------------------- ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; esac { echo "$as_me:$LINENO: checking for ncurses.h" >&5 echo $ECHO_N "checking for ncurses.h... $ECHO_C" >&6; } if test "${ac_cv_header_ncurses_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_cv_header_ncurses_h=$ac_header_preproc fi { echo "$as_me:$LINENO: result: $ac_cv_header_ncurses_h" >&5 echo "${ECHO_T}$ac_cv_header_ncurses_h" >&6; } fi if test $ac_cv_header_ncurses_h = yes; then FOUND=yes cat >>confdefs.h <<\_ACEOF #define HAVE_NCURSES_H 1 _ACEOF else FOUND=no fi if test "$FOUND" = "no"; then if test "${ac_cv_header_ncurses_ncurses_h+set}" = set; then { echo "$as_me:$LINENO: checking for ncurses/ncurses.h" >&5 echo $ECHO_N "checking for ncurses/ncurses.h... $ECHO_C" >&6; } if test "${ac_cv_header_ncurses_ncurses_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 fi { echo "$as_me:$LINENO: result: $ac_cv_header_ncurses_ncurses_h" >&5 echo "${ECHO_T}$ac_cv_header_ncurses_ncurses_h" >&6; } else # Is the header compilable? { echo "$as_me:$LINENO: checking ncurses/ncurses.h usability" >&5 echo $ECHO_N "checking ncurses/ncurses.h usability... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default #include _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_header_compiler=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_compiler=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 echo "${ECHO_T}$ac_header_compiler" >&6; } # Is the header present? { echo "$as_me:$LINENO: checking ncurses/ncurses.h presence" >&5 echo $ECHO_N "checking ncurses/ncurses.h presence... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" 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_cpp conftest.$ac_ext") 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); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then ac_header_preproc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_preproc=no fi rm -f conftest.err conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 echo "${ECHO_T}$ac_header_preproc" >&6; } # So? What about this header? case $ac_header_compiler:$ac_header_preproc:$ac_cxx_preproc_warn_flag in yes:no: ) { echo "$as_me:$LINENO: WARNING: ncurses/ncurses.h: accepted by the compiler, rejected by the preprocessor!" >&5 echo "$as_me: WARNING: ncurses/ncurses.h: accepted by the compiler, rejected by the preprocessor!" >&2;} { echo "$as_me:$LINENO: WARNING: ncurses/ncurses.h: proceeding with the compiler's result" >&5 echo "$as_me: WARNING: ncurses/ncurses.h: proceeding with the compiler's result" >&2;} ac_header_preproc=yes ;; no:yes:* ) { echo "$as_me:$LINENO: WARNING: ncurses/ncurses.h: present but cannot be compiled" >&5 echo "$as_me: WARNING: ncurses/ncurses.h: present but cannot be compiled" >&2;} { echo "$as_me:$LINENO: WARNING: ncurses/ncurses.h: check for missing prerequisite headers?" >&5 echo "$as_me: WARNING: ncurses/ncurses.h: check for missing prerequisite headers?" >&2;} { echo "$as_me:$LINENO: WARNING: ncurses/ncurses.h: see the Autoconf documentation" >&5 echo "$as_me: WARNING: ncurses/ncurses.h: see the Autoconf documentation" >&2;} { echo "$as_me:$LINENO: WARNING: ncurses/ncurses.h: section \"Present But Cannot Be Compiled\"" >&5 echo "$as_me: WARNING: ncurses/ncurses.h: section \"Present But Cannot Be Compiled\"" >&2;} { echo "$as_me:$LINENO: WARNING: ncurses/ncurses.h: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: ncurses/ncurses.h: proceeding with the preprocessor's result" >&2;} { echo "$as_me:$LINENO: WARNING: ncurses/ncurses.h: in the future, the compiler will take precedence" >&5 echo "$as_me: WARNING: ncurses/ncurses.h: in the future, the compiler will take precedence" >&2;} ( cat <<\_ASBOX ## ------------------------------------------- ## ## Report this to hugbug@users.sourceforge.net ## ## ------------------------------------------- ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; esac { echo "$as_me:$LINENO: checking for ncurses/ncurses.h" >&5 echo $ECHO_N "checking for ncurses/ncurses.h... $ECHO_C" >&6; } if test "${ac_cv_header_ncurses_ncurses_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_cv_header_ncurses_ncurses_h=$ac_header_preproc fi { echo "$as_me:$LINENO: result: $ac_cv_header_ncurses_ncurses_h" >&5 echo "${ECHO_T}$ac_cv_header_ncurses_ncurses_h" >&6; } fi if test $ac_cv_header_ncurses_ncurses_h = yes; then FOUND=yes cat >>confdefs.h <<\_ACEOF #define HAVE_NCURSES_NCURSES_H 1 _ACEOF else FOUND=no fi fi if test "$FOUND" = "no"; then if test "${ac_cv_header_curses_h+set}" = set; then { echo "$as_me:$LINENO: checking for curses.h" >&5 echo $ECHO_N "checking for curses.h... $ECHO_C" >&6; } if test "${ac_cv_header_curses_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 fi { echo "$as_me:$LINENO: result: $ac_cv_header_curses_h" >&5 echo "${ECHO_T}$ac_cv_header_curses_h" >&6; } else # Is the header compilable? { echo "$as_me:$LINENO: checking curses.h usability" >&5 echo $ECHO_N "checking curses.h usability... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default #include _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_header_compiler=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_compiler=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 echo "${ECHO_T}$ac_header_compiler" >&6; } # Is the header present? { echo "$as_me:$LINENO: checking curses.h presence" >&5 echo $ECHO_N "checking curses.h presence... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" 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_cpp conftest.$ac_ext") 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); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then ac_header_preproc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_preproc=no fi rm -f conftest.err conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 echo "${ECHO_T}$ac_header_preproc" >&6; } # So? What about this header? case $ac_header_compiler:$ac_header_preproc:$ac_cxx_preproc_warn_flag in yes:no: ) { echo "$as_me:$LINENO: WARNING: curses.h: accepted by the compiler, rejected by the preprocessor!" >&5 echo "$as_me: WARNING: curses.h: accepted by the compiler, rejected by the preprocessor!" >&2;} { echo "$as_me:$LINENO: WARNING: curses.h: proceeding with the compiler's result" >&5 echo "$as_me: WARNING: curses.h: proceeding with the compiler's result" >&2;} ac_header_preproc=yes ;; no:yes:* ) { echo "$as_me:$LINENO: WARNING: curses.h: present but cannot be compiled" >&5 echo "$as_me: WARNING: curses.h: present but cannot be compiled" >&2;} { echo "$as_me:$LINENO: WARNING: curses.h: check for missing prerequisite headers?" >&5 echo "$as_me: WARNING: curses.h: check for missing prerequisite headers?" >&2;} { echo "$as_me:$LINENO: WARNING: curses.h: see the Autoconf documentation" >&5 echo "$as_me: WARNING: curses.h: see the Autoconf documentation" >&2;} { echo "$as_me:$LINENO: WARNING: curses.h: section \"Present But Cannot Be Compiled\"" >&5 echo "$as_me: WARNING: curses.h: section \"Present But Cannot Be Compiled\"" >&2;} { echo "$as_me:$LINENO: WARNING: curses.h: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: curses.h: proceeding with the preprocessor's result" >&2;} { echo "$as_me:$LINENO: WARNING: curses.h: in the future, the compiler will take precedence" >&5 echo "$as_me: WARNING: curses.h: in the future, the compiler will take precedence" >&2;} ( cat <<\_ASBOX ## ------------------------------------------- ## ## Report this to hugbug@users.sourceforge.net ## ## ------------------------------------------- ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; esac { echo "$as_me:$LINENO: checking for curses.h" >&5 echo $ECHO_N "checking for curses.h... $ECHO_C" >&6; } if test "${ac_cv_header_curses_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_cv_header_curses_h=$ac_header_preproc fi { echo "$as_me:$LINENO: result: $ac_cv_header_curses_h" >&5 echo "${ECHO_T}$ac_cv_header_curses_h" >&6; } fi if test $ac_cv_header_curses_h = yes; then FOUND=yes cat >>confdefs.h <<\_ACEOF #define HAVE_CURSES_H 1 _ACEOF else FOUND=no fi fi if test "$FOUND" = "no"; then { { echo "$as_me:$LINENO: error: Couldn't find curses headers (ncurses.h or curses.h)" >&5 echo "$as_me: error: Couldn't find curses headers (ncurses.h or curses.h)" >&2;} { (exit 1); exit 1; }; } fi { echo "$as_me:$LINENO: checking for library containing refresh" >&5 echo $ECHO_N "checking for library containing refresh... $ECHO_C" >&6; } if test "${ac_cv_search_refresh+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char refresh (); int main () { return refresh (); ; return 0; } _ACEOF for ac_lib in '' ncurses curses; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search_refresh=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search_refresh+set}" = set; then break fi done if test "${ac_cv_search_refresh+set}" = set; then : else ac_cv_search_refresh=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search_refresh" >&5 echo "${ECHO_T}$ac_cv_search_refresh" >&6; } ac_res=$ac_cv_search_refresh if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" else { { echo "$as_me:$LINENO: error: Couldn't find curses library" >&5 echo "$as_me: error: Couldn't find curses library" >&2;} { (exit 1); exit 1; }; } fi else cat >>confdefs.h <<\_ACEOF #define DISABLE_CURSES 1 _ACEOF fi { echo "$as_me:$LINENO: checking whether to include code for par-checking" >&5 echo $ECHO_N "checking whether to include code for par-checking... $ECHO_C" >&6; } # Check whether --enable-parcheck was given. if test "${enable_parcheck+set}" = set; then enableval=$enable_parcheck; ENABLEPARCHECK=$enableval else ENABLEPARCHECK=yes fi { echo "$as_me:$LINENO: result: $ENABLEPARCHECK" >&5 echo "${ECHO_T}$ENABLEPARCHECK" >&6; } if test "$ENABLEPARCHECK" = "yes"; then ac_header_dirent=no for ac_hdr in dirent.h sys/ndir.h sys/dir.h ndir.h; do as_ac_Header=`echo "ac_cv_header_dirent_$ac_hdr" | $as_tr_sh` { echo "$as_me:$LINENO: checking for $ac_hdr that defines DIR" >&5 echo $ECHO_N "checking for $ac_hdr that defines DIR... $ECHO_C" >&6; } if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #include <$ac_hdr> int main () { if ((DIR *) 0) return 0; ; 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 eval "$as_ac_Header=yes" else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 eval "$as_ac_Header=no" fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi ac_res=`eval echo '${'$as_ac_Header'}'` { echo "$as_me:$LINENO: result: $ac_res" >&5 echo "${ECHO_T}$ac_res" >&6; } if test `eval echo '${'$as_ac_Header'}'` = yes; then cat >>confdefs.h <<_ACEOF #define `echo "HAVE_$ac_hdr" | $as_tr_cpp` 1 _ACEOF ac_header_dirent=$ac_hdr; break fi done # Two versions of opendir et al. are in -ldir and -lx on SCO Xenix. if test $ac_header_dirent = dirent.h; then { echo "$as_me:$LINENO: checking for library containing opendir" >&5 echo $ECHO_N "checking for library containing opendir... $ECHO_C" >&6; } if test "${ac_cv_search_opendir+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char opendir (); int main () { return opendir (); ; return 0; } _ACEOF for ac_lib in '' dir; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search_opendir=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search_opendir+set}" = set; then break fi done if test "${ac_cv_search_opendir+set}" = set; then : else ac_cv_search_opendir=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search_opendir" >&5 echo "${ECHO_T}$ac_cv_search_opendir" >&6; } ac_res=$ac_cv_search_opendir if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" fi else { echo "$as_me:$LINENO: checking for library containing opendir" >&5 echo $ECHO_N "checking for library containing opendir... $ECHO_C" >&6; } if test "${ac_cv_search_opendir+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char opendir (); int main () { return opendir (); ; return 0; } _ACEOF for ac_lib in '' x; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search_opendir=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search_opendir+set}" = set; then break fi done if test "${ac_cv_search_opendir+set}" = set; then : else ac_cv_search_opendir=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search_opendir" >&5 echo "${ECHO_T}$ac_cv_search_opendir" >&6; } ac_res=$ac_cv_search_opendir if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" fi fi { echo "$as_me:$LINENO: checking for stdbool.h that conforms to C99" >&5 echo $ECHO_N "checking for stdbool.h that conforms to C99... $ECHO_C" >&6; } if test "${ac_cv_header_stdbool_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #ifndef bool "error: bool is not defined" #endif #ifndef false "error: false is not defined" #endif #if false "error: false is not 0" #endif #ifndef true "error: true is not defined" #endif #if true != 1 "error: true is not 1" #endif #ifndef __bool_true_false_are_defined "error: __bool_true_false_are_defined is not defined" #endif struct s { _Bool s: 1; _Bool t; } s; char a[true == 1 ? 1 : -1]; char b[false == 0 ? 1 : -1]; char c[__bool_true_false_are_defined == 1 ? 1 : -1]; char d[(bool) 0.5 == true ? 1 : -1]; bool e = &s; char f[(_Bool) 0.0 == false ? 1 : -1]; char g[true]; char h[sizeof (_Bool)]; char i[sizeof s.t]; enum { j = false, k = true, l = false * true, m = true * 256 }; _Bool n[m]; char o[sizeof n == m * sizeof n[0] ? 1 : -1]; char p[-1 - (_Bool) 0 < 0 && -1 - (bool) 0 < 0 ? 1 : -1]; # if defined __xlc__ || defined __GNUC__ /* Catch a bug in IBM AIX xlc compiler version 6.0.0.0 reported by James Lemley on 2005-10-05; see http://lists.gnu.org/archive/html/bug-coreutils/2005-10/msg00086.html This test is not quite right, since xlc is allowed to reject this program, as the initializer for xlcbug is not one of the forms that C requires support for. However, doing the test right would require a runtime test, and that would make cross-compilation harder. Let us hope that IBM fixes the xlc bug, and also adds support for this kind of constant expression. In the meantime, this test will reject xlc, which is OK, since our stdbool.h substitute should suffice. We also test this with GCC, where it should work, to detect more quickly whether someone messes up the test in the future. */ char digs[] = "0123456789"; int xlcbug = 1 / (&(digs + 5)[-2 + (bool) 1] == &digs[4] ? 1 : -1); # endif /* Catch a bug in an HP-UX C compiler. See http://gcc.gnu.org/ml/gcc-patches/2003-12/msg02303.html http://lists.gnu.org/archive/html/bug-coreutils/2005-11/msg00161.html */ _Bool q = true; _Bool *pq = &q; int main () { *pq |= q; *pq |= ! q; /* Refer to every declared value, to avoid compiler optimizations. */ return (!a + !b + !c + !d + !e + !f + !g + !h + !i + !!j + !k + !!l + !m + !n + !o + !p + !q + !pq); ; 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_header_stdbool_h=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_cv_header_stdbool_h=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi { echo "$as_me:$LINENO: result: $ac_cv_header_stdbool_h" >&5 echo "${ECHO_T}$ac_cv_header_stdbool_h" >&6; } { echo "$as_me:$LINENO: checking for _Bool" >&5 echo $ECHO_N "checking for _Bool... $ECHO_C" >&6; } if test "${ac_cv_type__Bool+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default typedef _Bool ac__type_new_; int main () { if ((ac__type_new_ *) 0) return 0; if (sizeof (ac__type_new_)) return 0; ; 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_type__Bool=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_cv_type__Bool=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi { echo "$as_me:$LINENO: result: $ac_cv_type__Bool" >&5 echo "${ECHO_T}$ac_cv_type__Bool" >&6; } if test $ac_cv_type__Bool = yes; then cat >>confdefs.h <<_ACEOF #define HAVE__BOOL 1 _ACEOF fi if test $ac_cv_header_stdbool_h = yes; then cat >>confdefs.h <<\_ACEOF #define HAVE_STDBOOL_H 1 _ACEOF fi { echo "$as_me:$LINENO: checking for ANSI C header files" >&5 echo $ECHO_N "checking for ANSI C header files... $ECHO_C" >&6; } if test "${ac_cv_header_stdc+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #include #include #include 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_header_stdc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_cv_header_stdc=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext if test $ac_cv_header_stdc = yes; then # SunOS 4.x string.h does not declare mem*, contrary to ANSI. cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP "memchr" >/dev/null 2>&1; then : else ac_cv_header_stdc=no fi rm -f conftest* fi if test $ac_cv_header_stdc = yes; then # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | $EGREP "free" >/dev/null 2>&1; then : else ac_cv_header_stdc=no fi rm -f conftest* fi if test $ac_cv_header_stdc = yes; then # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. if test "$cross_compiling" = yes; then : else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #include #if ((' ' & 0x0FF) == 0x020) # define ISLOWER(c) ('a' <= (c) && (c) <= 'z') # define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) #else # define ISLOWER(c) \ (('a' <= (c) && (c) <= 'i') \ || ('j' <= (c) && (c) <= 'r') \ || ('s' <= (c) && (c) <= 'z')) # define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) #endif #define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) int main () { int i; for (i = 0; i < 256; i++) if (XOR (islower (i), ISLOWER (i)) || toupper (i) != TOUPPER (i)) return 2; return 0; } _ACEOF rm -f conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { ac_try='./conftest$ac_exeext' { (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_try") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then : else echo "$as_me: program exited with status $ac_status" >&5 echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ( exit $ac_status ) ac_cv_header_stdc=no fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext conftest.$ac_objext conftest.$ac_ext fi fi fi { echo "$as_me:$LINENO: result: $ac_cv_header_stdc" >&5 echo "${ECHO_T}$ac_cv_header_stdc" >&6; } if test $ac_cv_header_stdc = yes; then cat >>confdefs.h <<\_ACEOF #define STDC_HEADERS 1 _ACEOF fi for ac_header in stdio.h endian.h getopt.h do as_ac_Header=`echo "ac_cv_header_$ac_header" | $as_tr_sh` if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then { echo "$as_me:$LINENO: checking for $ac_header" >&5 echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6; } if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then echo $ECHO_N "(cached) $ECHO_C" >&6 fi ac_res=`eval echo '${'$as_ac_Header'}'` { echo "$as_me:$LINENO: result: $ac_res" >&5 echo "${ECHO_T}$ac_res" >&6; } else # Is the header compilable? { echo "$as_me:$LINENO: checking $ac_header usability" >&5 echo $ECHO_N "checking $ac_header usability... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default #include <$ac_header> _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_header_compiler=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_compiler=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 echo "${ECHO_T}$ac_header_compiler" >&6; } # Is the header present? { echo "$as_me:$LINENO: checking $ac_header presence" >&5 echo $ECHO_N "checking $ac_header presence... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include <$ac_header> _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" 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_cpp conftest.$ac_ext") 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); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then ac_header_preproc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_preproc=no fi rm -f conftest.err conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 echo "${ECHO_T}$ac_header_preproc" >&6; } # So? What about this header? case $ac_header_compiler:$ac_header_preproc:$ac_cxx_preproc_warn_flag in yes:no: ) { echo "$as_me:$LINENO: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&5 echo "$as_me: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the compiler's result" >&5 echo "$as_me: WARNING: $ac_header: proceeding with the compiler's result" >&2;} ac_header_preproc=yes ;; no:yes:* ) { echo "$as_me:$LINENO: WARNING: $ac_header: present but cannot be compiled" >&5 echo "$as_me: WARNING: $ac_header: present but cannot be compiled" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: check for missing prerequisite headers?" >&5 echo "$as_me: WARNING: $ac_header: check for missing prerequisite headers?" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: see the Autoconf documentation" >&5 echo "$as_me: WARNING: $ac_header: see the Autoconf documentation" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: section \"Present But Cannot Be Compiled\"" >&5 echo "$as_me: WARNING: $ac_header: section \"Present But Cannot Be Compiled\"" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: $ac_header: proceeding with the preprocessor's result" >&2;} { echo "$as_me:$LINENO: WARNING: $ac_header: in the future, the compiler will take precedence" >&5 echo "$as_me: WARNING: $ac_header: in the future, the compiler will take precedence" >&2;} ( cat <<\_ASBOX ## ------------------------------------------- ## ## Report this to hugbug@users.sourceforge.net ## ## ------------------------------------------- ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; esac { echo "$as_me:$LINENO: checking for $ac_header" >&5 echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6; } if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then echo $ECHO_N "(cached) $ECHO_C" >&6 else eval "$as_ac_Header=\$ac_header_preproc" fi ac_res=`eval echo '${'$as_ac_Header'}'` { echo "$as_me:$LINENO: result: $ac_res" >&5 echo "${ECHO_T}$ac_res" >&6; } fi if test `eval echo '${'$as_ac_Header'}'` = yes; then cat >>confdefs.h <<_ACEOF #define `echo "HAVE_$ac_header" | $as_tr_cpp` 1 _ACEOF fi done { echo "$as_me:$LINENO: checking for size_t" >&5 echo $ECHO_N "checking for size_t... $ECHO_C" >&6; } if test "${ac_cv_type_size_t+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default typedef size_t ac__type_new_; int main () { if ((ac__type_new_ *) 0) return 0; if (sizeof (ac__type_new_)) return 0; ; 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_type_size_t=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_cv_type_size_t=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi { echo "$as_me:$LINENO: result: $ac_cv_type_size_t" >&5 echo "${ECHO_T}$ac_cv_type_size_t" >&6; } if test $ac_cv_type_size_t = yes; then : else cat >>confdefs.h <<_ACEOF #define size_t unsigned int _ACEOF fi { echo "$as_me:$LINENO: checking whether byte ordering is bigendian" >&5 echo $ECHO_N "checking whether byte ordering is bigendian... $ECHO_C" >&6; } if test "${ac_cv_c_bigendian+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else # See if sys/param.h defines the BYTE_ORDER macro. cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #include int main () { #if ! (defined BYTE_ORDER && defined BIG_ENDIAN && defined LITTLE_ENDIAN \ && BYTE_ORDER && BIG_ENDIAN && LITTLE_ENDIAN) bogus endian macros #endif ; 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 # It does; now see whether it defined to BIG_ENDIAN or not. cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #include int main () { #if BYTE_ORDER != BIG_ENDIAN not big endian #endif ; 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_c_bigendian=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_cv_c_bigendian=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 # It does not; compile a test program. if test "$cross_compiling" = yes; then # try to guess the endianness by grepping values into an object file ac_cv_c_bigendian=unknown cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ short int ascii_mm[] = { 0x4249, 0x4765, 0x6E44, 0x6961, 0x6E53, 0x7953, 0 }; short int ascii_ii[] = { 0x694C, 0x5454, 0x656C, 0x6E45, 0x6944, 0x6E61, 0 }; void _ascii () { char *s = (char *) ascii_mm; s = (char *) ascii_ii; } short int ebcdic_ii[] = { 0x89D3, 0xE3E3, 0x8593, 0x95C5, 0x89C4, 0x9581, 0 }; short int ebcdic_mm[] = { 0xC2C9, 0xC785, 0x95C4, 0x8981, 0x95E2, 0xA8E2, 0 }; void _ebcdic () { char *s = (char *) ebcdic_mm; s = (char *) ebcdic_ii; } int main () { _ascii (); _ebcdic (); ; 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 if grep BIGenDianSyS conftest.$ac_objext >/dev/null ; then ac_cv_c_bigendian=yes fi if grep LiTTleEnDian conftest.$ac_objext >/dev/null ; then if test "$ac_cv_c_bigendian" = unknown; then ac_cv_c_bigendian=no else # finding both strings is unlikely to happen, but who knows? ac_cv_c_bigendian=unknown fi fi 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 else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default int main () { /* Are we little or big endian? From Harbison&Steele. */ union { long int l; char c[sizeof (long int)]; } u; u.l = 1; return u.c[sizeof (long int) - 1] == 1; ; return 0; } _ACEOF rm -f conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { ac_try='./conftest$ac_exeext' { (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_try") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then ac_cv_c_bigendian=no else echo "$as_me: program exited with status $ac_status" >&5 echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ( exit $ac_status ) ac_cv_c_bigendian=yes fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext conftest.$ac_objext conftest.$ac_ext fi fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi { echo "$as_me:$LINENO: result: $ac_cv_c_bigendian" >&5 echo "${ECHO_T}$ac_cv_c_bigendian" >&6; } case $ac_cv_c_bigendian in yes) cat >>confdefs.h <<\_ACEOF #define WORDS_BIGENDIAN 1 _ACEOF ;; no) ;; *) { { echo "$as_me:$LINENO: error: unknown endianness presetting ac_cv_c_bigendian=no (or yes) will help" >&5 echo "$as_me: error: unknown endianness presetting ac_cv_c_bigendian=no (or yes) will help" >&2;} { (exit 1); exit 1; }; } ;; esac { echo "$as_me:$LINENO: checking for an ANSI C-conforming const" >&5 echo $ECHO_N "checking for an ANSI C-conforming const... $ECHO_C" >&6; } if test "${ac_cv_c_const+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ int main () { /* FIXME: Include the comments suggested by Paul. */ #ifndef __cplusplus /* Ultrix mips cc rejects this. */ typedef int charset[2]; const charset cs; /* SunOS 4.1.1 cc rejects this. */ char const *const *pcpcc; char **ppc; /* NEC SVR4.0.2 mips cc rejects this. */ struct point {int x, y;}; static struct point const zero = {0,0}; /* AIX XL C 1.02.0.0 rejects this. It does not let you subtract one const X* pointer from another in an arm of an if-expression whose if-part is not a constant expression */ const char *g = "string"; pcpcc = &g + (g ? g-g : 0); /* HPUX 7.0 cc rejects these. */ ++pcpcc; ppc = (char**) pcpcc; pcpcc = (char const *const *) ppc; { /* SCO 3.2v4 cc rejects this. */ char *t; char const *s = 0 ? (char *) 0 : (char const *) 0; *t++ = 0; if (s) return 0; } { /* Someone thinks the Sun supposedly-ANSI compiler will reject this. */ int x[] = {25, 17}; const int *foo = &x[0]; ++foo; } { /* Sun SC1.0 ANSI compiler rejects this -- but not the above. */ typedef const int *iptr; iptr p = 0; ++p; } { /* AIX XL C 1.02.0.0 rejects this saying "k.c", line 2.27: 1506-025 (S) Operand must be a modifiable lvalue. */ struct s { int j; const int *ap[3]; }; struct s *b; b->j = 5; } { /* ULTRIX-32 V3.1 (Rev 9) vcc rejects this */ const int foo = 10; if (!foo) return 0; } return !cs[0] && !zero.x; #endif ; 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_c_const=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_cv_c_const=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi { echo "$as_me:$LINENO: result: $ac_cv_c_const" >&5 echo "${ECHO_T}$ac_cv_c_const" >&6; } if test $ac_cv_c_const = no; then cat >>confdefs.h <<\_ACEOF #define const _ACEOF fi { echo "$as_me:$LINENO: checking for inline" >&5 echo $ECHO_N "checking for inline... $ECHO_C" >&6; } if test "${ac_cv_c_inline+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_cv_c_inline=no for ac_kw in inline __inline__ __inline; do cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #ifndef __cplusplus typedef int foo_t; static $ac_kw foo_t static_foo () {return 0; } $ac_kw foo_t foo () {return 0; } #endif _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_c_inline=$ac_kw 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 test "$ac_cv_c_inline" != no && break done fi { echo "$as_me:$LINENO: result: $ac_cv_c_inline" >&5 echo "${ECHO_T}$ac_cv_c_inline" >&6; } case $ac_cv_c_inline in inline | yes) ;; *) case $ac_cv_c_inline in no) ac_val=;; *) ac_val=$ac_cv_c_inline;; esac cat >>confdefs.h <<_ACEOF #ifndef __cplusplus #define inline $ac_val #endif _ACEOF ;; esac { echo "$as_me:$LINENO: checking for _LARGEFILE_SOURCE value needed for large files" >&5 echo $ECHO_N "checking for _LARGEFILE_SOURCE value needed for large files... $ECHO_C" >&6; } if test "${ac_cv_sys_largefile_source+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 int main () { return fseeko (stdin, 0, 0) && (fseeko) (stdin, 0, 0); ; return 0; } _ACEOF rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_sys_largefile_source=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_ipa8_conftest.oo \ conftest$ac_exeext 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 _LARGEFILE_SOURCE 1 #include int main () { return fseeko (stdin, 0, 0) && (fseeko) (stdin, 0, 0); ; return 0; } _ACEOF rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_sys_largefile_source=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_ipa8_conftest.oo \ conftest$ac_exeext conftest.$ac_ext ac_cv_sys_largefile_source=unknown break done fi { echo "$as_me:$LINENO: result: $ac_cv_sys_largefile_source" >&5 echo "${ECHO_T}$ac_cv_sys_largefile_source" >&6; } case $ac_cv_sys_largefile_source in #( no | unknown) ;; *) cat >>confdefs.h <<_ACEOF #define _LARGEFILE_SOURCE $ac_cv_sys_largefile_source _ACEOF ;; esac rm -f conftest* # We used to try defining _XOPEN_SOURCE=500 too, to work around a bug # in glibc 2.1.3, but that breaks too many other things. # If you want fseeko and ftello with glibc, upgrade to a fixed glibc. if test $ac_cv_sys_largefile_source != unknown; then cat >>confdefs.h <<\_ACEOF #define HAVE_FSEEKO 1 _ACEOF fi { echo "$as_me:$LINENO: checking for working memcmp" >&5 echo $ECHO_N "checking for working memcmp... $ECHO_C" >&6; } if test "${ac_cv_func_memcmp_working+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else if test "$cross_compiling" = yes; then ac_cv_func_memcmp_working=no else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default int main () { /* Some versions of memcmp are not 8-bit clean. */ char c0 = '\100', c1 = '\200', c2 = '\201'; if (memcmp(&c0, &c2, 1) >= 0 || memcmp(&c1, &c2, 1) >= 0) return 1; /* The Next x86 OpenStep bug shows up only when comparing 16 bytes or more and with at least one buffer not starting on a 4-byte boundary. William Lewis provided this test program. */ { char foo[21]; char bar[21]; int i; for (i = 0; i < 4; i++) { char *a = foo + i; char *b = bar + i; strcpy (a, "--------01111111"); strcpy (b, "--------10000000"); if (memcmp (a, b, 16) >= 0) return 1; } return 0; } ; return 0; } _ACEOF rm -f conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { ac_try='./conftest$ac_exeext' { (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_try") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; }; then ac_cv_func_memcmp_working=yes else echo "$as_me: program exited with status $ac_status" >&5 echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ( exit $ac_status ) ac_cv_func_memcmp_working=no fi rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext conftest.$ac_objext conftest.$ac_ext fi fi { echo "$as_me:$LINENO: result: $ac_cv_func_memcmp_working" >&5 echo "${ECHO_T}$ac_cv_func_memcmp_working" >&6; } test $ac_cv_func_memcmp_working = no && case " $LIBOBJS " in *" memcmp.$ac_objext "* ) ;; *) LIBOBJS="$LIBOBJS memcmp.$ac_objext" ;; esac for ac_func in stricmp strcasecmp do as_ac_var=`echo "ac_cv_func_$ac_func" | $as_tr_sh` { echo "$as_me:$LINENO: checking for $ac_func" >&5 echo $ECHO_N "checking for $ac_func... $ECHO_C" >&6; } if { as_var=$as_ac_var; eval "test \"\${$as_var+set}\" = set"; }; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Define $ac_func to an innocuous variant, in case declares $ac_func. For example, HP-UX 11i declares gettimeofday. */ #define $ac_func innocuous_$ac_func /* System header to define __stub macros and hopefully few prototypes, which can conflict with char $ac_func (); below. Prefer to if __STDC__ is defined, since exists even on freestanding compilers. */ #ifdef __STDC__ # include #else # include #endif #undef $ac_func /* 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 $ac_func (); /* 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_$ac_func || defined __stub___$ac_func choke me #endif int main () { return $ac_func (); ; return 0; } _ACEOF rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then eval "$as_ac_var=yes" else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 eval "$as_ac_var=no" fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext conftest.$ac_ext fi ac_res=`eval echo '${'$as_ac_var'}'` { echo "$as_me:$LINENO: result: $ac_res" >&5 echo "${ECHO_T}$ac_res" >&6; } if test `eval echo '${'$as_ac_var'}'` = yes; then cat >>confdefs.h <<_ACEOF #define `echo "HAVE_$ac_func" | $as_tr_cpp` 1 _ACEOF fi done for ac_func in strchr memcpy do as_ac_var=`echo "ac_cv_func_$ac_func" | $as_tr_sh` { echo "$as_me:$LINENO: checking for $ac_func" >&5 echo $ECHO_N "checking for $ac_func... $ECHO_C" >&6; } if { as_var=$as_ac_var; eval "test \"\${$as_var+set}\" = set"; }; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Define $ac_func to an innocuous variant, in case declares $ac_func. For example, HP-UX 11i declares gettimeofday. */ #define $ac_func innocuous_$ac_func /* System header to define __stub macros and hopefully few prototypes, which can conflict with char $ac_func (); below. Prefer to if __STDC__ is defined, since exists even on freestanding compilers. */ #ifdef __STDC__ # include #else # include #endif #undef $ac_func /* 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 $ac_func (); /* 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_$ac_func || defined __stub___$ac_func choke me #endif int main () { return $ac_func (); ; return 0; } _ACEOF rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then eval "$as_ac_var=yes" else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 eval "$as_ac_var=no" fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext conftest.$ac_ext fi ac_res=`eval echo '${'$as_ac_var'}'` { echo "$as_me:$LINENO: result: $ac_res" >&5 echo "${ECHO_T}$ac_res" >&6; } if test `eval echo '${'$as_ac_var'}'` = yes; then cat >>confdefs.h <<_ACEOF #define `echo "HAVE_$ac_func" | $as_tr_cpp` 1 _ACEOF fi done for ac_func in getopt do as_ac_var=`echo "ac_cv_func_$ac_func" | $as_tr_sh` { echo "$as_me:$LINENO: checking for $ac_func" >&5 echo $ECHO_N "checking for $ac_func... $ECHO_C" >&6; } if { as_var=$as_ac_var; eval "test \"\${$as_var+set}\" = set"; }; then echo $ECHO_N "(cached) $ECHO_C" >&6 else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Define $ac_func to an innocuous variant, in case declares $ac_func. For example, HP-UX 11i declares gettimeofday. */ #define $ac_func innocuous_$ac_func /* System header to define __stub macros and hopefully few prototypes, which can conflict with char $ac_func (); below. Prefer to if __STDC__ is defined, since exists even on freestanding compilers. */ #ifdef __STDC__ # include #else # include #endif #undef $ac_func /* 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 $ac_func (); /* 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_$ac_func || defined __stub___$ac_func choke me #endif int main () { return $ac_func (); ; return 0; } _ACEOF rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then eval "$as_ac_var=yes" else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 eval "$as_ac_var=no" fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext conftest.$ac_ext fi ac_res=`eval echo '${'$as_ac_var'}'` { echo "$as_me:$LINENO: result: $ac_res" >&5 echo "${ECHO_T}$ac_res" >&6; } if test `eval echo '${'$as_ac_var'}'` = yes; then cat >>confdefs.h <<_ACEOF #define `echo "HAVE_$ac_func" | $as_tr_cpp` 1 _ACEOF fi done if true; then WITH_PAR2_TRUE= WITH_PAR2_FALSE='#' else WITH_PAR2_TRUE='#' WITH_PAR2_FALSE= fi else cat >>confdefs.h <<\_ACEOF #define DISABLE_PARCHECK 1 _ACEOF if false; then WITH_PAR2_TRUE= WITH_PAR2_FALSE='#' else WITH_PAR2_TRUE='#' WITH_PAR2_FALSE= fi fi { echo "$as_me:$LINENO: checking whether to use TLS/SSL" >&5 echo $ECHO_N "checking whether to use TLS/SSL... $ECHO_C" >&6; } # Check whether --enable-tls was given. if test "${enable_tls+set}" = set; then enableval=$enable_tls; USETLS=$enableval else USETLS=yes fi { echo "$as_me:$LINENO: result: $USETLS" >&5 echo "${ECHO_T}$USETLS" >&6; } if test "$USETLS" = "yes"; then # Check whether --with-tlslib was given. if test "${with_tlslib+set}" = set; then withval=$with_tlslib; TLSLIB="$withval" fi if test "$TLSLIB" != "GnuTLS" -a "$TLSLIB" != "OpenSSL" -a "$TLSLIB" != ""; then { { echo "$as_me:$LINENO: error: Invalid argument for option --with-tlslib" >&5 echo "$as_me: error: Invalid argument for option --with-tlslib" >&2;} { (exit 1); exit 1; }; } fi if test "$TLSLIB" = "OpenSSL" -o "$TLSLIB" = ""; then # Check whether --with-openssl_includes was given. if test "${with_openssl_includes+set}" = set; then withval=$with_openssl_includes; CPPFLAGS="${CPPFLAGS} -I${withval}" INCVAL="yes" else INCVAL="no" fi # Check whether --with-openssl_libraries was given. if test "${with_openssl_libraries+set}" = set; then withval=$with_openssl_libraries; LDFLAGS="${LDFLAGS} -L${withval}" LIBVAL="yes" else LIBVAL="no" fi if test "$INCVAL" = "no" -o "$LIBVAL" = "no"; then pkg_failed=no { echo "$as_me:$LINENO: checking for openssl" >&5 echo $ECHO_N "checking for openssl... $ECHO_C" >&6; } if test -n "$PKG_CONFIG"; then if test -n "$openssl_CFLAGS"; then pkg_cv_openssl_CFLAGS="$openssl_CFLAGS" else if test -n "$PKG_CONFIG" && \ { (echo "$as_me:$LINENO: \$PKG_CONFIG --exists --print-errors \"openssl\"") >&5 ($PKG_CONFIG --exists --print-errors "openssl") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; then pkg_cv_openssl_CFLAGS=`$PKG_CONFIG --cflags "openssl" 2>/dev/null` else pkg_failed=yes fi fi else pkg_failed=untried fi if test -n "$PKG_CONFIG"; then if test -n "$openssl_LIBS"; then pkg_cv_openssl_LIBS="$openssl_LIBS" else if test -n "$PKG_CONFIG" && \ { (echo "$as_me:$LINENO: \$PKG_CONFIG --exists --print-errors \"openssl\"") >&5 ($PKG_CONFIG --exists --print-errors "openssl") 2>&5 ac_status=$? echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); }; then pkg_cv_openssl_LIBS=`$PKG_CONFIG --libs "openssl" 2>/dev/null` else pkg_failed=yes fi fi else pkg_failed=untried fi if test $pkg_failed = yes; then if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi if test $_pkg_short_errors_supported = yes; then openssl_PKG_ERRORS=`$PKG_CONFIG --short-errors --errors-to-stdout --print-errors "openssl"` else openssl_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "openssl"` fi # Put the nasty error message in config.log where it belongs echo "$openssl_PKG_ERRORS" >&5 { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } FOUND=no elif test $pkg_failed = untried; then FOUND=no else openssl_CFLAGS=$pkg_cv_openssl_CFLAGS openssl_LIBS=$pkg_cv_openssl_LIBS { echo "$as_me:$LINENO: result: yes" >&5 echo "${ECHO_T}yes" >&6; } LIBS="${LIBS} $openssl_LIBS" CPPFLAGS="${CPPFLAGS} $openssl_CFLAGS" fi fi if test "${ac_cv_header_openssl_ssl_h+set}" = set; then { echo "$as_me:$LINENO: checking for openssl/ssl.h" >&5 echo $ECHO_N "checking for openssl/ssl.h... $ECHO_C" >&6; } if test "${ac_cv_header_openssl_ssl_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 fi { echo "$as_me:$LINENO: result: $ac_cv_header_openssl_ssl_h" >&5 echo "${ECHO_T}$ac_cv_header_openssl_ssl_h" >&6; } else # Is the header compilable? { echo "$as_me:$LINENO: checking openssl/ssl.h usability" >&5 echo $ECHO_N "checking openssl/ssl.h usability... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default #include _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_header_compiler=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_compiler=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 echo "${ECHO_T}$ac_header_compiler" >&6; } # Is the header present? { echo "$as_me:$LINENO: checking openssl/ssl.h presence" >&5 echo $ECHO_N "checking openssl/ssl.h presence... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" 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_cpp conftest.$ac_ext") 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); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then ac_header_preproc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_preproc=no fi rm -f conftest.err conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 echo "${ECHO_T}$ac_header_preproc" >&6; } # So? What about this header? case $ac_header_compiler:$ac_header_preproc:$ac_cxx_preproc_warn_flag in yes:no: ) { echo "$as_me:$LINENO: WARNING: openssl/ssl.h: accepted by the compiler, rejected by the preprocessor!" >&5 echo "$as_me: WARNING: openssl/ssl.h: accepted by the compiler, rejected by the preprocessor!" >&2;} { echo "$as_me:$LINENO: WARNING: openssl/ssl.h: proceeding with the compiler's result" >&5 echo "$as_me: WARNING: openssl/ssl.h: proceeding with the compiler's result" >&2;} ac_header_preproc=yes ;; no:yes:* ) { echo "$as_me:$LINENO: WARNING: openssl/ssl.h: present but cannot be compiled" >&5 echo "$as_me: WARNING: openssl/ssl.h: present but cannot be compiled" >&2;} { echo "$as_me:$LINENO: WARNING: openssl/ssl.h: check for missing prerequisite headers?" >&5 echo "$as_me: WARNING: openssl/ssl.h: check for missing prerequisite headers?" >&2;} { echo "$as_me:$LINENO: WARNING: openssl/ssl.h: see the Autoconf documentation" >&5 echo "$as_me: WARNING: openssl/ssl.h: see the Autoconf documentation" >&2;} { echo "$as_me:$LINENO: WARNING: openssl/ssl.h: section \"Present But Cannot Be Compiled\"" >&5 echo "$as_me: WARNING: openssl/ssl.h: section \"Present But Cannot Be Compiled\"" >&2;} { echo "$as_me:$LINENO: WARNING: openssl/ssl.h: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: openssl/ssl.h: proceeding with the preprocessor's result" >&2;} { echo "$as_me:$LINENO: WARNING: openssl/ssl.h: in the future, the compiler will take precedence" >&5 echo "$as_me: WARNING: openssl/ssl.h: in the future, the compiler will take precedence" >&2;} ( cat <<\_ASBOX ## ------------------------------------------- ## ## Report this to hugbug@users.sourceforge.net ## ## ------------------------------------------- ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; esac { echo "$as_me:$LINENO: checking for openssl/ssl.h" >&5 echo $ECHO_N "checking for openssl/ssl.h... $ECHO_C" >&6; } if test "${ac_cv_header_openssl_ssl_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_cv_header_openssl_ssl_h=$ac_header_preproc fi { echo "$as_me:$LINENO: result: $ac_cv_header_openssl_ssl_h" >&5 echo "${ECHO_T}$ac_cv_header_openssl_ssl_h" >&6; } fi if test $ac_cv_header_openssl_ssl_h = yes; then FOUND=yes TLSHEADERS=yes else FOUND=no fi if test "$FOUND" = "no" -a "$TLSLIB" = "OpenSSL"; then { { echo "$as_me:$LINENO: error: Couldn't find OpenSSL headers (ssl.h)" >&5 echo "$as_me: error: Couldn't find OpenSSL headers (ssl.h)" >&2;} { (exit 1); exit 1; }; } fi if test "$FOUND" = "yes"; then { echo "$as_me:$LINENO: checking for library containing CRYPTO_set_locking_callback" >&5 echo $ECHO_N "checking for library containing CRYPTO_set_locking_callback... $ECHO_C" >&6; } if test "${ac_cv_search_CRYPTO_set_locking_callback+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char CRYPTO_set_locking_callback (); int main () { return CRYPTO_set_locking_callback (); ; return 0; } _ACEOF for ac_lib in '' crypto; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search_CRYPTO_set_locking_callback=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search_CRYPTO_set_locking_callback+set}" = set; then break fi done if test "${ac_cv_search_CRYPTO_set_locking_callback+set}" = set; then : else ac_cv_search_CRYPTO_set_locking_callback=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search_CRYPTO_set_locking_callback" >&5 echo "${ECHO_T}$ac_cv_search_CRYPTO_set_locking_callback" >&6; } ac_res=$ac_cv_search_CRYPTO_set_locking_callback if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" { echo "$as_me:$LINENO: checking for library containing SSL_library_init" >&5 echo $ECHO_N "checking for library containing SSL_library_init... $ECHO_C" >&6; } if test "${ac_cv_search_SSL_library_init+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char SSL_library_init (); int main () { return SSL_library_init (); ; return 0; } _ACEOF for ac_lib in '' ssl; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search_SSL_library_init=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search_SSL_library_init+set}" = set; then break fi done if test "${ac_cv_search_SSL_library_init+set}" = set; then : else ac_cv_search_SSL_library_init=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search_SSL_library_init" >&5 echo "${ECHO_T}$ac_cv_search_SSL_library_init" >&6; } ac_res=$ac_cv_search_SSL_library_init if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" FOUND=yes else FOUND=no fi else FOUND=no fi if test "$FOUND" = "no" -a "$TLSLIB" = "OpenSSL"; then { { echo "$as_me:$LINENO: error: Couldn't find OpenSSL library" >&5 echo "$as_me: error: Couldn't find OpenSSL library" >&2;} { (exit 1); exit 1; }; } fi if test "$FOUND" = "yes"; then TLSLIB="OpenSSL" cat >>confdefs.h <<\_ACEOF #define HAVE_OPENSSL 1 _ACEOF fi fi fi if test "$TLSLIB" = "GnuTLS" -o "$TLSLIB" = ""; then INCVAL="${LIBPREF}/include" LIBVAL="${LIBPREF}/lib" # Check whether --with-libgnutls_includes was given. if test "${with_libgnutls_includes+set}" = set; then withval=$with_libgnutls_includes; INCVAL="$withval" fi CPPFLAGS="${CPPFLAGS} -I${INCVAL}" # Check whether --with-libgnutls_libraries was given. if test "${with_libgnutls_libraries+set}" = set; then withval=$with_libgnutls_libraries; LIBVAL="$withval" fi LDFLAGS="${LDFLAGS} -L${LIBVAL}" if test "${ac_cv_header_gnutls_gnutls_h+set}" = set; then { echo "$as_me:$LINENO: checking for gnutls/gnutls.h" >&5 echo $ECHO_N "checking for gnutls/gnutls.h... $ECHO_C" >&6; } if test "${ac_cv_header_gnutls_gnutls_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 fi { echo "$as_me:$LINENO: result: $ac_cv_header_gnutls_gnutls_h" >&5 echo "${ECHO_T}$ac_cv_header_gnutls_gnutls_h" >&6; } else # Is the header compilable? { echo "$as_me:$LINENO: checking gnutls/gnutls.h usability" >&5 echo $ECHO_N "checking gnutls/gnutls.h usability... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default #include _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_header_compiler=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_compiler=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 echo "${ECHO_T}$ac_header_compiler" >&6; } # Is the header present? { echo "$as_me:$LINENO: checking gnutls/gnutls.h presence" >&5 echo $ECHO_N "checking gnutls/gnutls.h presence... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" 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_cpp conftest.$ac_ext") 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); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then ac_header_preproc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_preproc=no fi rm -f conftest.err conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 echo "${ECHO_T}$ac_header_preproc" >&6; } # So? What about this header? case $ac_header_compiler:$ac_header_preproc:$ac_cxx_preproc_warn_flag in yes:no: ) { echo "$as_me:$LINENO: WARNING: gnutls/gnutls.h: accepted by the compiler, rejected by the preprocessor!" >&5 echo "$as_me: WARNING: gnutls/gnutls.h: accepted by the compiler, rejected by the preprocessor!" >&2;} { echo "$as_me:$LINENO: WARNING: gnutls/gnutls.h: proceeding with the compiler's result" >&5 echo "$as_me: WARNING: gnutls/gnutls.h: proceeding with the compiler's result" >&2;} ac_header_preproc=yes ;; no:yes:* ) { echo "$as_me:$LINENO: WARNING: gnutls/gnutls.h: present but cannot be compiled" >&5 echo "$as_me: WARNING: gnutls/gnutls.h: present but cannot be compiled" >&2;} { echo "$as_me:$LINENO: WARNING: gnutls/gnutls.h: check for missing prerequisite headers?" >&5 echo "$as_me: WARNING: gnutls/gnutls.h: check for missing prerequisite headers?" >&2;} { echo "$as_me:$LINENO: WARNING: gnutls/gnutls.h: see the Autoconf documentation" >&5 echo "$as_me: WARNING: gnutls/gnutls.h: see the Autoconf documentation" >&2;} { echo "$as_me:$LINENO: WARNING: gnutls/gnutls.h: section \"Present But Cannot Be Compiled\"" >&5 echo "$as_me: WARNING: gnutls/gnutls.h: section \"Present But Cannot Be Compiled\"" >&2;} { echo "$as_me:$LINENO: WARNING: gnutls/gnutls.h: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: gnutls/gnutls.h: proceeding with the preprocessor's result" >&2;} { echo "$as_me:$LINENO: WARNING: gnutls/gnutls.h: in the future, the compiler will take precedence" >&5 echo "$as_me: WARNING: gnutls/gnutls.h: in the future, the compiler will take precedence" >&2;} ( cat <<\_ASBOX ## ------------------------------------------- ## ## Report this to hugbug@users.sourceforge.net ## ## ------------------------------------------- ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; esac { echo "$as_me:$LINENO: checking for gnutls/gnutls.h" >&5 echo $ECHO_N "checking for gnutls/gnutls.h... $ECHO_C" >&6; } if test "${ac_cv_header_gnutls_gnutls_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_cv_header_gnutls_gnutls_h=$ac_header_preproc fi { echo "$as_me:$LINENO: result: $ac_cv_header_gnutls_gnutls_h" >&5 echo "${ECHO_T}$ac_cv_header_gnutls_gnutls_h" >&6; } fi if test $ac_cv_header_gnutls_gnutls_h = yes; then FOUND=yes TLSHEADERS=yes else FOUND=no fi if test "$FOUND" = "no" -a "$TLSLIB" = "GnuTLS"; then { { echo "$as_me:$LINENO: error: Couldn't find GnuTLS headers (gnutls.h)" >&5 echo "$as_me: error: Couldn't find GnuTLS headers (gnutls.h)" >&2;} { (exit 1); exit 1; }; } fi if test "$FOUND" = "yes"; then { echo "$as_me:$LINENO: checking for library containing gnutls_global_init" >&5 echo $ECHO_N "checking for library containing gnutls_global_init... $ECHO_C" >&6; } if test "${ac_cv_search_gnutls_global_init+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char gnutls_global_init (); int main () { return gnutls_global_init (); ; return 0; } _ACEOF for ac_lib in '' gnutls; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search_gnutls_global_init=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search_gnutls_global_init+set}" = set; then break fi done if test "${ac_cv_search_gnutls_global_init+set}" = set; then : else ac_cv_search_gnutls_global_init=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search_gnutls_global_init" >&5 echo "${ECHO_T}$ac_cv_search_gnutls_global_init" >&6; } ac_res=$ac_cv_search_gnutls_global_init if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" FOUND=yes else FOUND=no fi if test "$FOUND" = "yes"; then { echo "$as_me:$LINENO: checking whether gcrypt is needed" >&5 echo $ECHO_N "checking whether gcrypt is needed... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #if GNUTLS_VERSION_NUMBER <= 0x020b00 compile error #endif int main () { int a; ; 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 { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } GCRYPT=no else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { echo "$as_me:$LINENO: result: yes" >&5 echo "${ECHO_T}yes" >&6; } GCRYPT=yes fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext if test "$GCRYPT" = "yes"; then if test "${ac_cv_header_gcrypt_h+set}" = set; then { echo "$as_me:$LINENO: checking for gcrypt.h" >&5 echo $ECHO_N "checking for gcrypt.h... $ECHO_C" >&6; } if test "${ac_cv_header_gcrypt_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 fi { echo "$as_me:$LINENO: result: $ac_cv_header_gcrypt_h" >&5 echo "${ECHO_T}$ac_cv_header_gcrypt_h" >&6; } else # Is the header compilable? { echo "$as_me:$LINENO: checking gcrypt.h usability" >&5 echo $ECHO_N "checking gcrypt.h usability... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default #include _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_header_compiler=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_compiler=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 echo "${ECHO_T}$ac_header_compiler" >&6; } # Is the header present? { echo "$as_me:$LINENO: checking gcrypt.h presence" >&5 echo $ECHO_N "checking gcrypt.h presence... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" 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_cpp conftest.$ac_ext") 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); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then ac_header_preproc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_preproc=no fi rm -f conftest.err conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 echo "${ECHO_T}$ac_header_preproc" >&6; } # So? What about this header? case $ac_header_compiler:$ac_header_preproc:$ac_cxx_preproc_warn_flag in yes:no: ) { echo "$as_me:$LINENO: WARNING: gcrypt.h: accepted by the compiler, rejected by the preprocessor!" >&5 echo "$as_me: WARNING: gcrypt.h: accepted by the compiler, rejected by the preprocessor!" >&2;} { echo "$as_me:$LINENO: WARNING: gcrypt.h: proceeding with the compiler's result" >&5 echo "$as_me: WARNING: gcrypt.h: proceeding with the compiler's result" >&2;} ac_header_preproc=yes ;; no:yes:* ) { echo "$as_me:$LINENO: WARNING: gcrypt.h: present but cannot be compiled" >&5 echo "$as_me: WARNING: gcrypt.h: present but cannot be compiled" >&2;} { echo "$as_me:$LINENO: WARNING: gcrypt.h: check for missing prerequisite headers?" >&5 echo "$as_me: WARNING: gcrypt.h: check for missing prerequisite headers?" >&2;} { echo "$as_me:$LINENO: WARNING: gcrypt.h: see the Autoconf documentation" >&5 echo "$as_me: WARNING: gcrypt.h: see the Autoconf documentation" >&2;} { echo "$as_me:$LINENO: WARNING: gcrypt.h: section \"Present But Cannot Be Compiled\"" >&5 echo "$as_me: WARNING: gcrypt.h: section \"Present But Cannot Be Compiled\"" >&2;} { echo "$as_me:$LINENO: WARNING: gcrypt.h: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: gcrypt.h: proceeding with the preprocessor's result" >&2;} { echo "$as_me:$LINENO: WARNING: gcrypt.h: in the future, the compiler will take precedence" >&5 echo "$as_me: WARNING: gcrypt.h: in the future, the compiler will take precedence" >&2;} ( cat <<\_ASBOX ## ------------------------------------------- ## ## Report this to hugbug@users.sourceforge.net ## ## ------------------------------------------- ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; esac { echo "$as_me:$LINENO: checking for gcrypt.h" >&5 echo $ECHO_N "checking for gcrypt.h... $ECHO_C" >&6; } if test "${ac_cv_header_gcrypt_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_cv_header_gcrypt_h=$ac_header_preproc fi { echo "$as_me:$LINENO: result: $ac_cv_header_gcrypt_h" >&5 echo "${ECHO_T}$ac_cv_header_gcrypt_h" >&6; } fi if test $ac_cv_header_gcrypt_h = yes; then { echo "$as_me:$LINENO: checking for library containing gcry_control" >&5 echo $ECHO_N "checking for library containing gcry_control... $ECHO_C" >&6; } if test "${ac_cv_search_gcry_control+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char gcry_control (); int main () { return gcry_control (); ; return 0; } _ACEOF for ac_lib in '' gnutls gcrypt; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search_gcry_control=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search_gcry_control+set}" = set; then break fi done if test "${ac_cv_search_gcry_control+set}" = set; then : else ac_cv_search_gcry_control=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search_gcry_control" >&5 echo "${ECHO_T}$ac_cv_search_gcry_control" >&6; } ac_res=$ac_cv_search_gcry_control if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" FOUND=yes else FOUND=no fi else FOUND=yes fi fi fi if test "$FOUND" = "no" -a "$TLSLIB" = "GnuTLS"; then { { echo "$as_me:$LINENO: error: Couldn't find GnuTLS library" >&5 echo "$as_me: error: Couldn't find GnuTLS library" >&2;} { (exit 1); exit 1; }; } fi if test "$FOUND" = "yes"; then TLSLIB="GnuTLS" cat >>confdefs.h <<\_ACEOF #define HAVE_LIBGNUTLS 1 _ACEOF fi fi fi if test "$TLSLIB" = ""; then if test "$TLSHEADERS" = ""; then { { echo "$as_me:$LINENO: error: Couldn't find neither OpenSSL nor GnuTLS headers (ssl.h or gnutls.h)" >&5 echo "$as_me: error: Couldn't find neither OpenSSL nor GnuTLS headers (ssl.h or gnutls.h)" >&2;} { (exit 1); exit 1; }; } else { { echo "$as_me:$LINENO: error: Couldn't find neither OpenSSL nor GnuTLS library" >&5 echo "$as_me: error: Couldn't find neither OpenSSL nor GnuTLS library" >&2;} { (exit 1); exit 1; }; } fi fi else cat >>confdefs.h <<\_ACEOF #define DISABLE_TLS 1 _ACEOF fi { echo "$as_me:$LINENO: checking whether to use gzip" >&5 echo $ECHO_N "checking whether to use gzip... $ECHO_C" >&6; } # Check whether --enable-gzip was given. if test "${enable_gzip+set}" = set; then enableval=$enable_gzip; USEZLIB=$enableval else USEZLIB=yes fi { echo "$as_me:$LINENO: result: $USEZLIB" >&5 echo "${ECHO_T}$USEZLIB" >&6; } if test "$USEZLIB" = "yes"; then INCVAL="${LIBPREF}/include" LIBVAL="${LIBPREF}/lib" # Check whether --with-zlib_includes was given. if test "${with_zlib_includes+set}" = set; then withval=$with_zlib_includes; INCVAL="$withval" fi CPPFLAGS="${CPPFLAGS} -I${INCVAL}" # Check whether --with-zlib_libraries was given. if test "${with_zlib_libraries+set}" = set; then withval=$with_zlib_libraries; LIBVAL="$withval" fi LDFLAGS="${LDFLAGS} -L${LIBVAL}" if test "${ac_cv_header_zlib_h+set}" = set; then { echo "$as_me:$LINENO: checking for zlib.h" >&5 echo $ECHO_N "checking for zlib.h... $ECHO_C" >&6; } if test "${ac_cv_header_zlib_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 fi { echo "$as_me:$LINENO: result: $ac_cv_header_zlib_h" >&5 echo "${ECHO_T}$ac_cv_header_zlib_h" >&6; } else # Is the header compilable? { echo "$as_me:$LINENO: checking zlib.h usability" >&5 echo $ECHO_N "checking zlib.h usability... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ $ac_includes_default #include _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_header_compiler=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_compiler=no fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 echo "${ECHO_T}$ac_header_compiler" >&6; } # Is the header present? { echo "$as_me:$LINENO: checking zlib.h presence" >&5 echo $ECHO_N "checking zlib.h presence... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include _ACEOF if { (ac_try="$ac_cpp conftest.$ac_ext" 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_cpp conftest.$ac_ext") 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); } >/dev/null && { test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || test ! -s conftest.err }; then ac_header_preproc=yes else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 ac_header_preproc=no fi rm -f conftest.err conftest.$ac_ext { echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 echo "${ECHO_T}$ac_header_preproc" >&6; } # So? What about this header? case $ac_header_compiler:$ac_header_preproc:$ac_cxx_preproc_warn_flag in yes:no: ) { echo "$as_me:$LINENO: WARNING: zlib.h: accepted by the compiler, rejected by the preprocessor!" >&5 echo "$as_me: WARNING: zlib.h: accepted by the compiler, rejected by the preprocessor!" >&2;} { echo "$as_me:$LINENO: WARNING: zlib.h: proceeding with the compiler's result" >&5 echo "$as_me: WARNING: zlib.h: proceeding with the compiler's result" >&2;} ac_header_preproc=yes ;; no:yes:* ) { echo "$as_me:$LINENO: WARNING: zlib.h: present but cannot be compiled" >&5 echo "$as_me: WARNING: zlib.h: present but cannot be compiled" >&2;} { echo "$as_me:$LINENO: WARNING: zlib.h: check for missing prerequisite headers?" >&5 echo "$as_me: WARNING: zlib.h: check for missing prerequisite headers?" >&2;} { echo "$as_me:$LINENO: WARNING: zlib.h: see the Autoconf documentation" >&5 echo "$as_me: WARNING: zlib.h: see the Autoconf documentation" >&2;} { echo "$as_me:$LINENO: WARNING: zlib.h: section \"Present But Cannot Be Compiled\"" >&5 echo "$as_me: WARNING: zlib.h: section \"Present But Cannot Be Compiled\"" >&2;} { echo "$as_me:$LINENO: WARNING: zlib.h: proceeding with the preprocessor's result" >&5 echo "$as_me: WARNING: zlib.h: proceeding with the preprocessor's result" >&2;} { echo "$as_me:$LINENO: WARNING: zlib.h: in the future, the compiler will take precedence" >&5 echo "$as_me: WARNING: zlib.h: in the future, the compiler will take precedence" >&2;} ( cat <<\_ASBOX ## ------------------------------------------- ## ## Report this to hugbug@users.sourceforge.net ## ## ------------------------------------------- ## _ASBOX ) | sed "s/^/$as_me: WARNING: /" >&2 ;; esac { echo "$as_me:$LINENO: checking for zlib.h" >&5 echo $ECHO_N "checking for zlib.h... $ECHO_C" >&6; } if test "${ac_cv_header_zlib_h+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_cv_header_zlib_h=$ac_header_preproc fi { echo "$as_me:$LINENO: result: $ac_cv_header_zlib_h" >&5 echo "${ECHO_T}$ac_cv_header_zlib_h" >&6; } fi if test $ac_cv_header_zlib_h = yes; then : else { { echo "$as_me:$LINENO: error: \"zlib header files not found\"" >&5 echo "$as_me: error: \"zlib header files not found\"" >&2;} { (exit 1); exit 1; }; } fi { echo "$as_me:$LINENO: checking for library containing deflateBound" >&5 echo $ECHO_N "checking for library containing deflateBound... $ECHO_C" >&6; } if test "${ac_cv_search_deflateBound+set}" = set; then echo $ECHO_N "(cached) $ECHO_C" >&6 else ac_func_search_save_LIBS=$LIBS cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ /* Override any GCC internal prototype to avoid an error. Use char because int might match the return type of a GCC builtin and then its argument prototype would still apply. */ #ifdef __cplusplus extern "C" #endif char deflateBound (); int main () { return deflateBound (); ; return 0; } _ACEOF for ac_lib in '' z; do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then ac_cv_search_deflateBound=$ac_res else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext if test "${ac_cv_search_deflateBound+set}" = set; then break fi done if test "${ac_cv_search_deflateBound+set}" = set; then : else ac_cv_search_deflateBound=no fi rm conftest.$ac_ext LIBS=$ac_func_search_save_LIBS fi { echo "$as_me:$LINENO: result: $ac_cv_search_deflateBound" >&5 echo "${ECHO_T}$ac_cv_search_deflateBound" >&6; } ac_res=$ac_cv_search_deflateBound if test "$ac_res" != no; then test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" else { { echo "$as_me:$LINENO: error: \"zlib library not found\"" >&5 echo "$as_me: error: \"zlib library not found\"" >&2;} { (exit 1); exit 1; }; } fi else cat >>confdefs.h <<\_ACEOF #define DISABLE_GZIP 1 _ACEOF fi { echo "$as_me:$LINENO: checking whether to use an empty SIGCHLD handler" >&5 echo $ECHO_N "checking whether to use an empty SIGCHLD handler... $ECHO_C" >&6; } # Check whether --enable-sigchld-handler was given. if test "${enable_sigchld_handler+set}" = set; then enableval=$enable_sigchld_handler; SIGCHLDHANDLER=$enableval else SIGCHLDHANDLER=yes fi { echo "$as_me:$LINENO: result: $SIGCHLDHANDLER" >&5 echo "${ECHO_T}$SIGCHLDHANDLER" >&6; } if test "$SIGCHLDHANDLER" = "yes"; then cat >>confdefs.h <<\_ACEOF #define SIGCHLD_HANDLER 1 _ACEOF fi { echo "$as_me:$LINENO: checking whether to include all debugging code" >&5 echo $ECHO_N "checking whether to include all debugging code... $ECHO_C" >&6; } # Check whether --enable-debug was given. if test "${enable_debug+set}" = set; then enableval=$enable_debug; ENABLEDEBUG=$enableval else ENABLEDEBUG=no fi { echo "$as_me:$LINENO: result: $ENABLEDEBUG" >&5 echo "${ECHO_T}$ENABLEDEBUG" >&6; } if test "$ENABLEDEBUG" = "yes"; then cat >>confdefs.h <<\_ACEOF #define DEBUG 1 _ACEOF { echo "$as_me:$LINENO: checking for macro returning current function name" >&5 echo $ECHO_N "checking for macro returning current function name... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include int main () { printf("%s\n", __FUNCTION__); ; 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 { echo "$as_me:$LINENO: result: __FUNCTION__" >&5 echo "${ECHO_T}__FUNCTION__" >&6; } FUNCTION_MACRO_NAME=__FUNCTION__ else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include int main () { printf("%s\n", __func__); ; 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 { echo "$as_me:$LINENO: result: __func__" >&5 echo "${ECHO_T}__func__" >&6; } FUNCTION_MACRO_NAME=__func__ else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { echo "$as_me:$LINENO: result: none" >&5 echo "${ECHO_T}none" >&6; } fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext if test "$FUNCTION_MACRO_NAME" != ""; then cat >>confdefs.h <<_ACEOF #define FUNCTION_MACRO_NAME $FUNCTION_MACRO_NAME _ACEOF fi { echo "$as_me:$LINENO: checking for variadic macros" >&5 echo $ECHO_N "checking for variadic macros... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF #define macro(...) macrofunc(__VA_ARGS__) int macrofunc(int a, int b) { return a + b; } int test() { return macro(1, 2); } _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 { echo "$as_me:$LINENO: result: yes" >&5 echo "${ECHO_T}yes" >&6; } cat >>confdefs.h <<\_ACEOF #define HAVE_VARIADIC_MACROS 1 _ACEOF else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { echo "$as_me:$LINENO: checking for backtrace" >&5 echo $ECHO_N "checking for backtrace... $ECHO_C" >&6; } cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ #include #include #include int main () { void *array[100]; size_t size; char **strings; size = backtrace(array, 100); strings = backtrace_symbols(array, size); ; 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 FOUND=yes { echo "$as_me:$LINENO: result: yes" >&5 echo "${ECHO_T}yes" >&6; } cat >>confdefs.h <<\_ACEOF #define HAVE_BACKTRACE 1 _ACEOF else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 FOUND=no { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } fi rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext { echo "$as_me:$LINENO: checking for rdynamic linker flag" >&5 echo $ECHO_N "checking for rdynamic linker flag... $ECHO_C" >&6; } old_LDFLAGS="$LDFLAGS" LDFLAGS="$LDFLAGS -rdynamic" cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ _ACEOF cat confdefs.h >>conftest.$ac_ext cat >>conftest.$ac_ext <<_ACEOF /* end confdefs.h. */ int main () { ; return 0; } _ACEOF rm -f conftest.$ac_objext conftest$ac_exeext if { (ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 (eval "$ac_link") 2>conftest.er1 ac_status=$? grep -v '^ *+' conftest.er1 >conftest.err rm -f conftest.er1 cat conftest.err >&5 echo "$as_me:$LINENO: \$? = $ac_status" >&5 (exit $ac_status); } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && $as_test_x conftest$ac_exeext; then { echo "$as_me:$LINENO: result: yes" >&5 echo "${ECHO_T}yes" >&6; } else echo "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 { echo "$as_me:$LINENO: result: no" >&5 echo "${ECHO_T}no" >&6; } LDFLAGS="$old_LDFLAGS" fi rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ conftest$ac_exeext conftest.$ac_ext else cat >>confdefs.h <<\_ACEOF #define NDEBUG 1 _ACEOF fi { echo "$as_me:$LINENO: checking whether to enable unit and integration tests" >&5 echo $ECHO_N "checking whether to enable unit and integration tests... $ECHO_C" >&6; } # Check whether --enable-tests was given. if test "${enable_tests+set}" = set; then enableval=$enable_tests; ENABLETESTS=$enableval else ENABLETESTS=no fi { echo "$as_me:$LINENO: result: $ENABLETESTS" >&5 echo "${ECHO_T}$ENABLETESTS" >&6; } if test "$ENABLETESTS" = "yes"; then cat >>confdefs.h <<\_ACEOF #define ENABLE_TESTS 1 _ACEOF if true; then WITH_TESTS_TRUE= WITH_TESTS_FALSE='#' else WITH_TESTS_TRUE='#' WITH_TESTS_FALSE= fi else if false; then WITH_TESTS_TRUE= WITH_TESTS_FALSE='#' else WITH_TESTS_TRUE='#' WITH_TESTS_FALSE= fi fi ac_config_files="$ac_config_files Makefile" cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure # tests run on this system so they can be shared between configure # scripts and configure runs, see configure's option --config-cache. # It is not useful on other systems. If it contains results you don't # want to keep, you may remove or edit it. # # config.status only pays attention to the cache file if you give it # the --recheck option to rerun configure. # # `ac_cv_env_foo' variables (set or unset) will be overridden when # loading this file, other *unset* `ac_cv_foo' will be assigned the # following values. _ACEOF # The following way of writing the cache mishandles newlines in values, # but we know of no workaround that is simple, portable, and efficient. # So, we kill variables containing newlines. # Ultrix sh set writes to stderr and can't be redirected directly, # and sets the high bit in the cache file unless we assign to the vars. ( for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do eval ac_val=\$$ac_var case $ac_val in #( *${as_nl}*) case $ac_var in #( *_cv_*) { echo "$as_me:$LINENO: WARNING: Cache variable $ac_var contains a newline." >&5 echo "$as_me: WARNING: Cache variable $ac_var contains a newline." >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( *) $as_unset $ac_var ;; esac ;; esac done (set) 2>&1 | case $as_nl`(ac_space=' '; set) 2>&1` in #( *${as_nl}ac_space=\ *) # `set' does not quote correctly, so add quotes (double-quote # substitution turns \\\\ into \\, and sed turns \\ into \). sed -n \ "s/'/'\\\\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" ;; #( *) # `set' quotes correctly as required by POSIX, so do not add quotes. sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | sort ) | sed ' /^ac_cv_env_/b end t clear :clear s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ t end s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ :end' >>confcache if diff "$cache_file" confcache >/dev/null 2>&1; then :; else if test -w "$cache_file"; then test "x$cache_file" != "x/dev/null" && { echo "$as_me:$LINENO: updating cache $cache_file" >&5 echo "$as_me: updating cache $cache_file" >&6;} cat confcache >$cache_file else { echo "$as_me:$LINENO: not updating unwritable cache $cache_file" >&5 echo "$as_me: not updating unwritable cache $cache_file" >&6;} fi fi rm -f confcache test "x$prefix" = xNONE && prefix=$ac_default_prefix # Let make expand exec_prefix. test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' DEFS=-DHAVE_CONFIG_H ac_libobjs= ac_ltlibobjs= for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue # 1. Remove the extension, and $U if already installed. ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' ac_i=`echo "$ac_i" | sed "$ac_script"` # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR # will be set to the directory where LIBOBJS objects are built. ac_libobjs="$ac_libobjs \${LIBOBJDIR}$ac_i\$U.$ac_objext" ac_ltlibobjs="$ac_ltlibobjs \${LIBOBJDIR}$ac_i"'$U.lo' done LIBOBJS=$ac_libobjs LTLIBOBJS=$ac_ltlibobjs if test -z "${AMDEP_TRUE}" && test -z "${AMDEP_FALSE}"; then { { echo "$as_me:$LINENO: error: conditional \"AMDEP\" was never defined. Usually this means the macro was only invoked conditionally." >&5 echo "$as_me: error: conditional \"AMDEP\" was never defined. Usually this means the macro was only invoked conditionally." >&2;} { (exit 1); exit 1; }; } fi if test -z "${am__fastdepCXX_TRUE}" && test -z "${am__fastdepCXX_FALSE}"; then { { echo "$as_me:$LINENO: error: conditional \"am__fastdepCXX\" was never defined. Usually this means the macro was only invoked conditionally." >&5 echo "$as_me: error: conditional \"am__fastdepCXX\" was never defined. Usually this means the macro was only invoked conditionally." >&2;} { (exit 1); exit 1; }; } fi if test -z "${WITH_PAR2_TRUE}" && test -z "${WITH_PAR2_FALSE}"; then { { echo "$as_me:$LINENO: error: conditional \"WITH_PAR2\" was never defined. Usually this means the macro was only invoked conditionally." >&5 echo "$as_me: error: conditional \"WITH_PAR2\" was never defined. Usually this means the macro was only invoked conditionally." >&2;} { (exit 1); exit 1; }; } fi if test -z "${WITH_PAR2_TRUE}" && test -z "${WITH_PAR2_FALSE}"; then { { echo "$as_me:$LINENO: error: conditional \"WITH_PAR2\" was never defined. Usually this means the macro was only invoked conditionally." >&5 echo "$as_me: error: conditional \"WITH_PAR2\" was never defined. Usually this means the macro was only invoked conditionally." >&2;} { (exit 1); exit 1; }; } fi if test -z "${WITH_TESTS_TRUE}" && test -z "${WITH_TESTS_FALSE}"; then { { echo "$as_me:$LINENO: error: conditional \"WITH_TESTS\" was never defined. Usually this means the macro was only invoked conditionally." >&5 echo "$as_me: error: conditional \"WITH_TESTS\" was never defined. Usually this means the macro was only invoked conditionally." >&2;} { (exit 1); exit 1; }; } fi if test -z "${WITH_TESTS_TRUE}" && test -z "${WITH_TESTS_FALSE}"; then { { echo "$as_me:$LINENO: error: conditional \"WITH_TESTS\" was never defined. Usually this means the macro was only invoked conditionally." >&5 echo "$as_me: error: conditional \"WITH_TESTS\" was never defined. Usually this means the macro was only invoked conditionally." >&2;} { (exit 1); exit 1; }; } fi : ${CONFIG_STATUS=./config.status} ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files $CONFIG_STATUS" { echo "$as_me:$LINENO: creating $CONFIG_STATUS" >&5 echo "$as_me: creating $CONFIG_STATUS" >&6;} cat >$CONFIG_STATUS <<_ACEOF #! $SHELL # Generated by $as_me. # Run this file to recreate the current configuration. # Compiler output produced by configure, useful for debugging # configure, is in config.log if it exists. debug=false ac_cs_recheck=false ac_cs_silent=false SHELL=\${CONFIG_SHELL-$SHELL} _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF ## --------------------- ## ## M4sh Initialization. ## ## --------------------- ## # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then emulate sh NULLCMD=: # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST else case `(set -o) 2>/dev/null` in *posix*) set -o posix ;; esac fi # PATH needs CR # Avoid depending upon Character Ranges. as_cr_letters='abcdefghijklmnopqrstuvwxyz' as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits # The user is always right. if test "${PATH_SEPARATOR+set}" != set; then echo "#! /bin/sh" >conf$$.sh echo "exit 0" >>conf$$.sh chmod +x conf$$.sh if (PATH="/nonexistent;."; conf$$.sh) >/dev/null 2>&1; then PATH_SEPARATOR=';' else PATH_SEPARATOR=: fi rm -f conf$$.sh fi # Support unset when possible. if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then as_unset=unset else as_unset=false fi # IFS # We need space, tab and new line, in precisely that order. Quoting is # there to prevent editors from complaining about space-tab. # (If _AS_PATH_WALK were called with IFS unset, it would disable word # splitting by setting IFS to empty value.) as_nl=' ' IFS=" "" $as_nl" # Find who we are. Look in the path if we contain no directory separator. case $0 in *[\\/]* ) as_myself=$0 ;; *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break done IFS=$as_save_IFS ;; esac # We did not find ourselves, most probably we were run as `sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 { (exit 1); exit 1; } fi # Work around bugs in pre-3.0 UWIN ksh. for as_var in ENV MAIL MAILPATH do ($as_unset $as_var) >/dev/null 2>&1 && $as_unset $as_var done PS1='$ ' PS2='> ' PS4='+ ' # NLS nuisances. for as_var in \ LANG LANGUAGE LC_ADDRESS LC_ALL LC_COLLATE LC_CTYPE LC_IDENTIFICATION \ LC_MEASUREMENT LC_MESSAGES LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER \ LC_TELEPHONE LC_TIME do if (set +x; test -z "`(eval $as_var=C; export $as_var) 2>&1`"); then eval $as_var=C; export $as_var else ($as_unset $as_var) >/dev/null 2>&1 && $as_unset $as_var fi done # Required to use basename. if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then as_basename=basename else as_basename=false fi # Name of the executable. as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || echo X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q } /^X\/\(\/\/\)$/{ s//\1/ q } /^X\/\(\/\).*/{ s//\1/ q } s/.*/./; q'` # CDPATH. $as_unset CDPATH as_lineno_1=$LINENO as_lineno_2=$LINENO test "x$as_lineno_1" != "x$as_lineno_2" && test "x`expr $as_lineno_1 + 1`" = "x$as_lineno_2" || { # Create $as_me.lineno as a copy of $as_myself, but with $LINENO # uniformly replaced by the line number. The first 'sed' inserts a # line-number line after each line using $LINENO; the second 'sed' # does the real work. The second script uses 'N' to pair each # line-number line with the line containing $LINENO, and appends # trailing '-' during substitution so that $LINENO is not a special # case at line end. # (Raja R Harinath suggested sed '=', and Paul Eggert wrote the # scripts with optimization help from Paolo Bonzini. Blame Lee # E. McMahon (1931-1989) for sed's syntax. :-) sed -n ' p /[$]LINENO/= ' <$as_myself | sed ' s/[$]LINENO.*/&-/ t lineno b :lineno N :loop s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ t loop s/-\n.*// ' >$as_me.lineno && chmod +x "$as_me.lineno" || { echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2 { (exit 1); exit 1; }; } # Don't try to exec as it changes $[0], causing all sort of problems # (the dirname of $[0] is not the place where we might find the # original and so on. Autoconf is especially sensitive to this). . "./$as_me.lineno" # Exit status is that of the last command. exit } if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then as_dirname=dirname else as_dirname=false fi ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in -n*) case `echo 'x\c'` in *c*) ECHO_T=' ';; # ECHO_T is single tab character. *) ECHO_C='\c';; esac;; *) ECHO_N='-n';; esac if expr a : '\(a\)' >/dev/null 2>&1 && test "X`expr 00001 : '.*\(...\)'`" = X001; then as_expr=expr else as_expr=false fi rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file else rm -f conf$$.dir mkdir conf$$.dir fi echo >conf$$.file if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. # In both cases, we have to default to `cp -p'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -p' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else as_ln_s='cp -p' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null if mkdir -p . 2>/dev/null; then as_mkdir_p=: else test -d ./-p && rmdir ./-p as_mkdir_p=false fi if test -x / >/dev/null 2>&1; then as_test_x='test -x' else if ls -dL / >/dev/null 2>&1; then as_ls_L_option=L else as_ls_L_option= fi as_test_x=' eval sh -c '\'' if test -d "$1"; then test -d "$1/."; else case $1 in -*)set "./$1";; esac; case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in ???[sx]*):;;*)false;;esac;fi '\'' sh ' fi as_executable_p=$as_test_x # Sed expression to map a string onto a valid CPP name. as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" # Sed expression to map a string onto a valid variable name. as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" exec 6>&1 # Save the log message, to keep $[0] and so on meaningful, and to # 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 16.4, which was generated by GNU Autoconf 2.61. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS CONFIG_LINKS = $CONFIG_LINKS CONFIG_COMMANDS = $CONFIG_COMMANDS $ $0 $@ on `(hostname || uname -n) 2>/dev/null | sed 1q` " _ACEOF cat >>$CONFIG_STATUS <<_ACEOF # Files that config.status was made for. config_files="$ac_config_files" config_headers="$ac_config_headers" config_commands="$ac_config_commands" _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF ac_cs_usage="\ \`$as_me' instantiates files from templates according to the current configuration. Usage: $0 [OPTIONS] [FILE]... -h, --help print this help, then exit -V, --version print version number and configuration settings, then exit -q, --quiet do not print progress messages -d, --debug don't remove temporary files --recheck update $as_me by reconfiguring in the same conditions --file=FILE[:TEMPLATE] instantiate the configuration file FILE --header=FILE[:TEMPLATE] instantiate the configuration header FILE Configuration files: $config_files Configuration headers: $config_headers Configuration commands: $config_commands Report bugs to ." _ACEOF cat >>$CONFIG_STATUS <<_ACEOF ac_cs_version="\\ nzbget config.status 16.4 configured by $0, generated by GNU Autoconf 2.61, with options \\"`echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`\\" Copyright (C) 2006 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." ac_pwd='$ac_pwd' srcdir='$srcdir' INSTALL='$INSTALL' _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF # If no file are specified by the user, then we need to provide default # value. By we need to know if files were specified by the user. ac_need_defaults=: while test $# != 0 do case $1 in --*=*) ac_option=`expr "X$1" : 'X\([^=]*\)='` ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` ac_shift=: ;; *) ac_option=$1 ac_optarg=$2 ac_shift=shift ;; esac case $ac_option in # Handling of the options. -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) ac_cs_recheck=: ;; --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) echo "$ac_cs_version"; exit ;; --debug | --debu | --deb | --de | --d | -d ) debug=: ;; --file | --fil | --fi | --f ) $ac_shift CONFIG_FILES="$CONFIG_FILES $ac_optarg" ac_need_defaults=false;; --header | --heade | --head | --hea ) $ac_shift CONFIG_HEADERS="$CONFIG_HEADERS $ac_optarg" ac_need_defaults=false;; --he | --h) # Conflict between --help and --header { echo "$as_me: error: ambiguous option: $1 Try \`$0 --help' for more information." >&2 { (exit 1); exit 1; }; };; --help | --hel | -h ) echo "$ac_cs_usage"; exit ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil | --si | --s) ac_cs_silent=: ;; # This is an error. -*) { echo "$as_me: error: unrecognized option: $1 Try \`$0 --help' for more information." >&2 { (exit 1); exit 1; }; } ;; *) ac_config_targets="$ac_config_targets $1" ac_need_defaults=false ;; esac shift done ac_configure_extra_args= if $ac_cs_silent; then exec 6>/dev/null ac_configure_extra_args="$ac_configure_extra_args --silent" fi _ACEOF cat >>$CONFIG_STATUS <<_ACEOF if \$ac_cs_recheck; then echo "running CONFIG_SHELL=$SHELL $SHELL $0 "$ac_configure_args \$ac_configure_extra_args " --no-create --no-recursion" >&6 CONFIG_SHELL=$SHELL export CONFIG_SHELL exec $SHELL "$0"$ac_configure_args \$ac_configure_extra_args --no-create --no-recursion fi _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF exec 5>>config.log { echo sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX ## Running $as_me. ## _ASBOX echo "$ac_log" } >&5 _ACEOF cat >>$CONFIG_STATUS <<_ACEOF # # INIT-COMMANDS # AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir" _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF # Handling of arguments. for ac_config_target in $ac_config_targets do case $ac_config_target in "config.h") CONFIG_HEADERS="$CONFIG_HEADERS config.h" ;; "depfiles") CONFIG_COMMANDS="$CONFIG_COMMANDS depfiles" ;; "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; *) { { echo "$as_me:$LINENO: error: invalid argument: $ac_config_target" >&5 echo "$as_me: error: invalid argument: $ac_config_target" >&2;} { (exit 1); exit 1; }; };; esac done # If the user did not use the arguments to specify the items to instantiate, # then the envvar interface is used. Set only those that are not. # We use the long form for the default assignment because of an extremely # bizarre bug on SunOS 4.1.3. if $ac_need_defaults; then test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files test "${CONFIG_HEADERS+set}" = set || CONFIG_HEADERS=$config_headers test "${CONFIG_COMMANDS+set}" = set || CONFIG_COMMANDS=$config_commands fi # Have a temporary directory for convenience. Make it in the build tree # simply because there is no reason against having it here, and in addition, # creating and moving files from /tmp can sometimes cause problems. # Hook for its removal unless debugging. # Note that there is a small window in which the directory will not be cleaned: # after its creation but before its name has been assigned to `$tmp'. $debug || { tmp= trap 'exit_status=$? { test -z "$tmp" || test ! -d "$tmp" || rm -fr "$tmp"; } && exit $exit_status ' 0 trap '{ (exit 1); exit 1; }' 1 2 13 15 } # Create a (secure) tmp directory for tmp files. { tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" } || { tmp=./conf$$-$RANDOM (umask 077 && mkdir "$tmp") } || { echo "$me: cannot create a temporary directory in ." >&2 { (exit 1); exit 1; } } # # Set up the sed scripts for CONFIG_FILES section. # # No need to generate the scripts if there are no CONFIG_FILES. # This happens for instance when ./config.status config.h if test -n "$CONFIG_FILES"; then _ACEOF ac_delim='%!_!# ' for ac_last_try in false false false false false :; do cat >conf$$subs.sed <<_ACEOF SHELL!$SHELL$ac_delim PATH_SEPARATOR!$PATH_SEPARATOR$ac_delim PACKAGE_NAME!$PACKAGE_NAME$ac_delim PACKAGE_TARNAME!$PACKAGE_TARNAME$ac_delim PACKAGE_VERSION!$PACKAGE_VERSION$ac_delim PACKAGE_STRING!$PACKAGE_STRING$ac_delim PACKAGE_BUGREPORT!$PACKAGE_BUGREPORT$ac_delim exec_prefix!$exec_prefix$ac_delim prefix!$prefix$ac_delim program_transform_name!$program_transform_name$ac_delim bindir!$bindir$ac_delim sbindir!$sbindir$ac_delim libexecdir!$libexecdir$ac_delim datarootdir!$datarootdir$ac_delim datadir!$datadir$ac_delim sysconfdir!$sysconfdir$ac_delim sharedstatedir!$sharedstatedir$ac_delim localstatedir!$localstatedir$ac_delim includedir!$includedir$ac_delim oldincludedir!$oldincludedir$ac_delim docdir!$docdir$ac_delim infodir!$infodir$ac_delim htmldir!$htmldir$ac_delim dvidir!$dvidir$ac_delim pdfdir!$pdfdir$ac_delim psdir!$psdir$ac_delim libdir!$libdir$ac_delim localedir!$localedir$ac_delim mandir!$mandir$ac_delim DEFS!$DEFS$ac_delim ECHO_C!$ECHO_C$ac_delim ECHO_N!$ECHO_N$ac_delim ECHO_T!$ECHO_T$ac_delim LIBS!$LIBS$ac_delim build_alias!$build_alias$ac_delim host_alias!$host_alias$ac_delim target_alias!$target_alias$ac_delim INSTALL_PROGRAM!$INSTALL_PROGRAM$ac_delim INSTALL_SCRIPT!$INSTALL_SCRIPT$ac_delim INSTALL_DATA!$INSTALL_DATA$ac_delim CYGPATH_W!$CYGPATH_W$ac_delim PACKAGE!$PACKAGE$ac_delim VERSION!$VERSION$ac_delim ACLOCAL!$ACLOCAL$ac_delim AUTOCONF!$AUTOCONF$ac_delim AUTOMAKE!$AUTOMAKE$ac_delim AUTOHEADER!$AUTOHEADER$ac_delim MAKEINFO!$MAKEINFO$ac_delim install_sh!$install_sh$ac_delim STRIP!$STRIP$ac_delim INSTALL_STRIP_PROGRAM!$INSTALL_STRIP_PROGRAM$ac_delim mkdir_p!$mkdir_p$ac_delim AWK!$AWK$ac_delim SET_MAKE!$SET_MAKE$ac_delim am__leading_dot!$am__leading_dot$ac_delim AMTAR!$AMTAR$ac_delim am__tar!$am__tar$ac_delim am__untar!$am__untar$ac_delim CXX!$CXX$ac_delim CXXFLAGS!$CXXFLAGS$ac_delim LDFLAGS!$LDFLAGS$ac_delim CPPFLAGS!$CPPFLAGS$ac_delim ac_ct_CXX!$ac_ct_CXX$ac_delim EXEEXT!$EXEEXT$ac_delim OBJEXT!$OBJEXT$ac_delim DEPDIR!$DEPDIR$ac_delim am__include!$am__include$ac_delim am__quote!$am__quote$ac_delim AMDEP_TRUE!$AMDEP_TRUE$ac_delim AMDEP_FALSE!$AMDEP_FALSE$ac_delim AMDEPBACKSLASH!$AMDEPBACKSLASH$ac_delim CXXDEPMODE!$CXXDEPMODE$ac_delim am__fastdepCXX_TRUE!$am__fastdepCXX_TRUE$ac_delim am__fastdepCXX_FALSE!$am__fastdepCXX_FALSE$ac_delim TAR!$TAR$ac_delim MAKE!$MAKE$ac_delim CXXCPP!$CXXCPP$ac_delim GREP!$GREP$ac_delim EGREP!$EGREP$ac_delim PKG_CONFIG!$PKG_CONFIG$ac_delim libxml2_CFLAGS!$libxml2_CFLAGS$ac_delim libxml2_LIBS!$libxml2_LIBS$ac_delim LIBOBJS!$LIBOBJS$ac_delim WITH_PAR2_TRUE!$WITH_PAR2_TRUE$ac_delim WITH_PAR2_FALSE!$WITH_PAR2_FALSE$ac_delim openssl_CFLAGS!$openssl_CFLAGS$ac_delim openssl_LIBS!$openssl_LIBS$ac_delim WITH_TESTS_TRUE!$WITH_TESTS_TRUE$ac_delim WITH_TESTS_FALSE!$WITH_TESTS_FALSE$ac_delim LTLIBOBJS!$LTLIBOBJS$ac_delim _ACEOF if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 90; then break elif $ac_last_try; then { { echo "$as_me:$LINENO: error: could not make $CONFIG_STATUS" >&5 echo "$as_me: error: could not make $CONFIG_STATUS" >&2;} { (exit 1); exit 1; }; } else ac_delim="$ac_delim!$ac_delim _$ac_delim!! " fi done ac_eof=`sed -n '/^CEOF[0-9]*$/s/CEOF/0/p' conf$$subs.sed` if test -n "$ac_eof"; then ac_eof=`echo "$ac_eof" | sort -nru | sed 1q` ac_eof=`expr $ac_eof + 1` fi cat >>$CONFIG_STATUS <<_ACEOF cat >"\$tmp/subs-1.sed" <<\CEOF$ac_eof /@[a-zA-Z_][a-zA-Z_0-9]*@/!b end _ACEOF sed ' s/[,\\&]/\\&/g; s/@/@|#_!!_#|/g s/^/s,@/; s/!/@,|#_!!_#|/ :n t n s/'"$ac_delim"'$/,g/; t s/$/\\/; p N; s/^.*\n//; s/[,\\&]/\\&/g; s/@/@|#_!!_#|/g; b n ' >>$CONFIG_STATUS >$CONFIG_STATUS <<_ACEOF :end s/|#_!!_#|//g CEOF$ac_eof _ACEOF # VPATH may cause trouble with some makes, so we remove $(srcdir), # ${srcdir} and @srcdir@ from VPATH if srcdir is ".", strip leading and # trailing colons and then remove the whole line if VPATH becomes empty # (actually we leave an empty line to preserve line numbers). if test "x$srcdir" = x.; then ac_vpsub='/^[ ]*VPATH[ ]*=/{ s/:*\$(srcdir):*/:/ s/:*\${srcdir}:*/:/ s/:*@srcdir@:*/:/ s/^\([^=]*=[ ]*\):*/\1/ s/:*$// s/^[^=]*=[ ]*$// }' fi cat >>$CONFIG_STATUS <<\_ACEOF fi # test -n "$CONFIG_FILES" for ac_tag in :F $CONFIG_FILES :H $CONFIG_HEADERS :C $CONFIG_COMMANDS do case $ac_tag in :[FHLC]) ac_mode=$ac_tag; continue;; esac case $ac_mode$ac_tag in :[FHL]*:*);; :L* | :C*:*) { { echo "$as_me:$LINENO: error: Invalid tag $ac_tag." >&5 echo "$as_me: error: Invalid tag $ac_tag." >&2;} { (exit 1); exit 1; }; };; :[FH]-) ac_tag=-:-;; :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; esac ac_save_IFS=$IFS IFS=: set x $ac_tag IFS=$ac_save_IFS shift ac_file=$1 shift case $ac_mode in :L) ac_source=$1;; :[FH]) ac_file_inputs= for ac_f do case $ac_f in -) ac_f="$tmp/stdin";; *) # Look for the file first in the build tree, then in the source tree # (if the path is not absolute). The absolute path cannot be DOS-style, # because $ac_f cannot contain `:'. test -f "$ac_f" || case $ac_f in [\\/$]*) false;; *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; esac || { { echo "$as_me:$LINENO: error: cannot find input file: $ac_f" >&5 echo "$as_me: error: cannot find input file: $ac_f" >&2;} { (exit 1); exit 1; }; };; esac ac_file_inputs="$ac_file_inputs $ac_f" done # Let's still pretend it is `configure' which instantiates (i.e., don't # use $as_me), people would be surprised to read: # /* config.h. Generated by config.status. */ configure_input="Generated from "`IFS=: echo $* | sed 's|^[^:]*/||;s|:[^:]*/|, |g'`" by configure." if test x"$ac_file" != x-; then configure_input="$ac_file. $configure_input" { echo "$as_me:$LINENO: creating $ac_file" >&5 echo "$as_me: creating $ac_file" >&6;} fi case $ac_tag in *:-:* | *:-) cat >"$tmp/stdin";; esac ;; esac ac_dir=`$as_dirname -- "$ac_file" || $as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$ac_file" : 'X\(//\)[^/]' \| \ X"$ac_file" : 'X\(//\)$' \| \ X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || echo X"$ac_file" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` { as_dir="$ac_dir" case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || { $as_mkdir_p && mkdir -p "$as_dir"; } || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || echo X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || { { echo "$as_me:$LINENO: error: cannot create directory $as_dir" >&5 echo "$as_me: error: cannot create directory $as_dir" >&2;} { (exit 1); exit 1; }; }; } ac_builddir=. case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_dir_suffix=/`echo "$ac_dir" | sed 's,^\.[\\/],,'` # A ".." for each directory in $ac_dir_suffix. ac_top_builddir_sub=`echo "$ac_dir_suffix" | sed 's,/[^\\/]*,/..,g;s,/,,'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; esac ;; esac ac_abs_top_builddir=$ac_pwd ac_abs_builddir=$ac_pwd$ac_dir_suffix # for backward compatibility: ac_top_builddir=$ac_top_build_prefix case $srcdir in .) # We are building in place. ac_srcdir=. ac_top_srcdir=$ac_top_builddir_sub ac_abs_top_srcdir=$ac_pwd ;; [\\/]* | ?:[\\/]* ) # Absolute name. ac_srcdir=$srcdir$ac_dir_suffix; ac_top_srcdir=$srcdir ac_abs_top_srcdir=$srcdir ;; *) # Relative name. ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix ac_top_srcdir=$ac_top_build_prefix$srcdir ac_abs_top_srcdir=$ac_pwd/$srcdir ;; esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix case $ac_mode in :F) # # CONFIG_FILE # case $INSTALL in [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;; *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;; esac _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF # If the template does not know about datarootdir, expand it. # FIXME: This hack should be removed a few years after 2.60. ac_datarootdir_hack=; ac_datarootdir_seen= case `sed -n '/datarootdir/ { p q } /@datadir@/p /@docdir@/p /@infodir@/p /@localedir@/p /@mandir@/p ' $ac_file_inputs` in *datarootdir*) ac_datarootdir_seen=yes;; *@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) { echo "$as_me:$LINENO: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} _ACEOF cat >>$CONFIG_STATUS <<_ACEOF ac_datarootdir_hack=' s&@datadir@&$datadir&g s&@docdir@&$docdir&g s&@infodir@&$infodir&g s&@localedir@&$localedir&g s&@mandir@&$mandir&g s&\\\${datarootdir}&$datarootdir&g' ;; esac _ACEOF # Neutralize VPATH when `$srcdir' = `.'. # Shell code in configure.ac might set extrasub. # FIXME: do we really want to maintain this feature? cat >>$CONFIG_STATUS <<_ACEOF sed "$ac_vpsub $extrasub _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF :t /@[a-zA-Z_][a-zA-Z_0-9]*@/!b s&@configure_input@&$configure_input&;t t s&@top_builddir@&$ac_top_builddir_sub&;t t s&@srcdir@&$ac_srcdir&;t t s&@abs_srcdir@&$ac_abs_srcdir&;t t s&@top_srcdir@&$ac_top_srcdir&;t t s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t s&@builddir@&$ac_builddir&;t t s&@abs_builddir@&$ac_abs_builddir&;t t s&@abs_top_builddir@&$ac_abs_top_builddir&;t t s&@INSTALL@&$ac_INSTALL&;t t $ac_datarootdir_hack " $ac_file_inputs | sed -f "$tmp/subs-1.sed" >$tmp/out test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && { ac_out=`sed -n '/\${datarootdir}/p' "$tmp/out"`; test -n "$ac_out"; } && { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' "$tmp/out"`; test -z "$ac_out"; } && { echo "$as_me:$LINENO: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined." >&5 echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' which seems to be undefined. Please make sure it is defined." >&2;} rm -f "$tmp/stdin" case $ac_file in -) cat "$tmp/out"; rm -f "$tmp/out";; *) rm -f "$ac_file"; mv "$tmp/out" $ac_file;; esac ;; :H) # # CONFIG_HEADER # _ACEOF # Transform confdefs.h into a sed script `conftest.defines', that # substitutes the proper values into config.h.in to produce config.h. rm -f conftest.defines conftest.tail # First, append a space to every undef/define line, to ease matching. echo 's/$/ /' >conftest.defines # Then, protect against being on the right side of a sed subst, or in # an unquoted here document, in config.status. If some macros were # called several times there might be several #defines for the same # symbol, which is useless. But do not sort them, since the last # AC_DEFINE must be honored. ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]* # These sed commands are passed to sed as "A NAME B PARAMS C VALUE D", where # NAME is the cpp macro being defined, VALUE is the value it is being given. # PARAMS is the parameter list in the macro definition--in most cases, it's # just an empty string. ac_dA='s,^\\([ #]*\\)[^ ]*\\([ ]*' ac_dB='\\)[ (].*,\\1define\\2' ac_dC=' ' ac_dD=' ,' uniq confdefs.h | sed -n ' t rset :rset s/^[ ]*#[ ]*define[ ][ ]*// t ok d :ok s/[\\&,]/\\&/g s/^\('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/ '"$ac_dA"'\1'"$ac_dB"'\2'"${ac_dC}"'\3'"$ac_dD"'/p s/^\('"$ac_word_re"'\)[ ]*\(.*\)/'"$ac_dA"'\1'"$ac_dB$ac_dC"'\2'"$ac_dD"'/p ' >>conftest.defines # Remove the space that was appended to ease matching. # Then replace #undef with comments. This is necessary, for # example, in the case of _POSIX_SOURCE, which is predefined and required # on some systems where configure will not decide to define it. # (The regexp can be short, since the line contains either #define or #undef.) echo 's/ $// s,^[ #]*u.*,/* & */,' >>conftest.defines # Break up conftest.defines: ac_max_sed_lines=50 # First sed command is: sed -f defines.sed $ac_file_inputs >"$tmp/out1" # Second one is: sed -f defines.sed "$tmp/out1" >"$tmp/out2" # Third one will be: sed -f defines.sed "$tmp/out2" >"$tmp/out1" # et cetera. ac_in='$ac_file_inputs' ac_out='"$tmp/out1"' ac_nxt='"$tmp/out2"' while : do # Write a here document: cat >>$CONFIG_STATUS <<_ACEOF # First, check the format of the line: cat >"\$tmp/defines.sed" <<\\CEOF /^[ ]*#[ ]*undef[ ][ ]*$ac_word_re[ ]*\$/b def /^[ ]*#[ ]*define[ ][ ]*$ac_word_re[( ]/b def b :def _ACEOF sed ${ac_max_sed_lines}q conftest.defines >>$CONFIG_STATUS echo 'CEOF sed -f "$tmp/defines.sed"' "$ac_in >$ac_out" >>$CONFIG_STATUS ac_in=$ac_out; ac_out=$ac_nxt; ac_nxt=$ac_in sed 1,${ac_max_sed_lines}d conftest.defines >conftest.tail grep . conftest.tail >/dev/null || break rm -f conftest.defines mv conftest.tail conftest.defines done rm -f conftest.defines conftest.tail echo "ac_result=$ac_in" >>$CONFIG_STATUS cat >>$CONFIG_STATUS <<\_ACEOF if test x"$ac_file" != x-; then echo "/* $configure_input */" >"$tmp/config.h" cat "$ac_result" >>"$tmp/config.h" if diff $ac_file "$tmp/config.h" >/dev/null 2>&1; then { echo "$as_me:$LINENO: $ac_file is unchanged" >&5 echo "$as_me: $ac_file is unchanged" >&6;} else rm -f $ac_file mv "$tmp/config.h" $ac_file fi else echo "/* $configure_input */" cat "$ac_result" fi rm -f "$tmp/out12" # Compute $ac_file's index in $config_headers. _am_stamp_count=1 for _am_header in $config_headers :; do case $_am_header in $ac_file | $ac_file:* ) break ;; * ) _am_stamp_count=`expr $_am_stamp_count + 1` ;; esac done echo "timestamp for $ac_file" >`$as_dirname -- $ac_file || $as_expr X$ac_file : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X$ac_file : 'X\(//\)[^/]' \| \ X$ac_file : 'X\(//\)$' \| \ X$ac_file : 'X\(/\)' \| . 2>/dev/null || echo X$ac_file | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'`/stamp-h$_am_stamp_count ;; :C) { echo "$as_me:$LINENO: executing $ac_file commands" >&5 echo "$as_me: executing $ac_file commands" >&6;} ;; esac case $ac_file$ac_mode in "depfiles":C) test x"$AMDEP_TRUE" != x"" || for mf in $CONFIG_FILES; do # Strip MF so we end up with the name of the file. mf=`echo "$mf" | sed -e 's/:.*$//'` # Check whether this is an Automake generated Makefile or not. # We used to match only the files named `Makefile.in', but # some people rename them; so instead we look at the file content. # Grep'ing the first line is not enough: some people post-process # each Makefile.in and add a new line on top of each file to say so. # So let's grep whole file. if grep '^#.*generated by automake' $mf > /dev/null 2>&1; then dirpart=`$as_dirname -- "$mf" || $as_expr X"$mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$mf" : 'X\(//\)[^/]' \| \ X"$mf" : 'X\(//\)$' \| \ X"$mf" : 'X\(/\)' \| . 2>/dev/null || echo X"$mf" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` else continue fi # Extract the definition of DEPDIR, am__include, and am__quote # from the Makefile without running `make'. DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"` test -z "$DEPDIR" && continue am__include=`sed -n 's/^am__include = //p' < "$mf"` test -z "am__include" && continue am__quote=`sed -n 's/^am__quote = //p' < "$mf"` # When using ansi2knr, U may be empty or an underscore; expand it U=`sed -n 's/^U = //p' < "$mf"` # Find all dependency output files, they are included files with # $(DEPDIR) in their names. We invoke sed twice because it is the # simplest approach to changing $(DEPDIR) to its actual value in the # expansion. for file in `sed -n " s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \ sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g' -e 's/\$U/'"$U"'/g'`; do # Make sure the directory exists. test -f "$dirpart/$file" && continue fdir=`$as_dirname -- "$file" || $as_expr X"$file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$file" : 'X\(//\)[^/]' \| \ X"$file" : 'X\(//\)$' \| \ X"$file" : 'X\(/\)' \| . 2>/dev/null || echo X"$file" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` { as_dir=$dirpart/$fdir case $as_dir in #( -*) as_dir=./$as_dir;; esac test -d "$as_dir" || { $as_mkdir_p && mkdir -p "$as_dir"; } || { as_dirs= while :; do case $as_dir in #( *\'*) as_qdir=`echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" as_dir=`$as_dirname -- "$as_dir" || $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || echo X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } /^X\(\/\/\)[^/].*/{ s//\1/ q } /^X\(\/\/\)$/{ s//\1/ q } /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` test -d "$as_dir" && break done test -z "$as_dirs" || eval "mkdir $as_dirs" } || test -d "$as_dir" || { { echo "$as_me:$LINENO: error: cannot create directory $as_dir" >&5 echo "$as_me: error: cannot create directory $as_dir" >&2;} { (exit 1); exit 1; }; }; } # echo "creating $dirpart/$file" echo '# dummy' > "$dirpart/$file" done done ;; esac done # for ac_tag { (exit 0); exit 0; } _ACEOF chmod +x $CONFIG_STATUS ac_clean_files=$ac_clean_files_save # configure is writing to config.log, and then calls config.status. # config.status does its own redirection, appending to config.log. # Unfortunately, on DOS this fails, as config.log is still kept open # by configure, so config.status won't be able to write to it; its # output is simply discarded. So we exec the FD to /dev/null, # effectively closing config.log, so it can be properly (re)opened and # appended to by config.status. When coming back to configure, we # need to make the FD available again. if test "$no_create" != yes; then ac_cs_success=: ac_config_status_args= test "$silent" = yes && ac_config_status_args="$ac_config_status_args --quiet" exec 5>/dev/null $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false exec 5>>config.log # Use ||, not &&, to avoid exiting from the if with $? = 1, which # would make configure fail if this is the last instruction. $ac_cs_success || { (exit 1); exit 1; } fi nzbget-16.4/README.md0000644000175000017500000000154312630544544014042 0ustar andreasandreas# NZBGet # NZBGet is a binary downloader, which downloads files from Usenet based on information given in nzb-files. NZBGet is written in C++ and is known for its extraordinary performance and efficiency. NZBGet can be run at almost every platform - classic PCs, NAS, media players, SAT-receivers, WLAN-routers, etc. The download area provides precompiled binaries for Windows, Mac OS X and Linux (compatible with many CPUs and platform variants). For other platforms the program can be compiled from sources. - [Home page (nzbget.net)](http://nzbget.net) - for first time visitors, learn more about NZBGet; - [Downloads](http://nzbget.net/download) - get the binaries and sources; - [Documentation](https://github.com/nzbget/nzbget/wiki) - installation manuals, HOW-TOs, API; - [Forum](http://forum.nzbget.net) - get support, share your ideas, scripts, add-ons. nzbget-16.4/Makefile.in0000644000175000017500000070471112630544544014637 0ustar andreasandreas# Makefile.in generated by automake 1.9.6 from Makefile.am. # @configure_input@ # Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, # 2003, 2004, 2005 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ # # This file is part of nzbget # # Copyright (C) 2008-2015 Andrey Prygunkov # # 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. # # srcdir = @srcdir@ top_srcdir = @top_srcdir@ VPATH = @srcdir@ pkgdatadir = $(datadir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ top_builddir = . am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd INSTALL = @INSTALL@ install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : bin_PROGRAMS = nzbget$(EXEEXT) @WITH_PAR2_TRUE@am__append_1 = \ @WITH_PAR2_TRUE@ lib/par2/commandline.cpp \ @WITH_PAR2_TRUE@ lib/par2/commandline.h \ @WITH_PAR2_TRUE@ lib/par2/crc.cpp \ @WITH_PAR2_TRUE@ lib/par2/crc.h \ @WITH_PAR2_TRUE@ lib/par2/creatorpacket.cpp \ @WITH_PAR2_TRUE@ lib/par2/creatorpacket.h \ @WITH_PAR2_TRUE@ lib/par2/criticalpacket.cpp \ @WITH_PAR2_TRUE@ lib/par2/criticalpacket.h \ @WITH_PAR2_TRUE@ lib/par2/datablock.cpp \ @WITH_PAR2_TRUE@ lib/par2/datablock.h \ @WITH_PAR2_TRUE@ lib/par2/descriptionpacket.cpp \ @WITH_PAR2_TRUE@ lib/par2/descriptionpacket.h \ @WITH_PAR2_TRUE@ lib/par2/diskfile.cpp \ @WITH_PAR2_TRUE@ lib/par2/diskfile.h \ @WITH_PAR2_TRUE@ lib/par2/filechecksummer.cpp \ @WITH_PAR2_TRUE@ lib/par2/filechecksummer.h \ @WITH_PAR2_TRUE@ lib/par2/galois.cpp \ @WITH_PAR2_TRUE@ lib/par2/galois.h \ @WITH_PAR2_TRUE@ lib/par2/letype.h \ @WITH_PAR2_TRUE@ lib/par2/mainpacket.cpp \ @WITH_PAR2_TRUE@ lib/par2/mainpacket.h \ @WITH_PAR2_TRUE@ lib/par2/md5.cpp \ @WITH_PAR2_TRUE@ lib/par2/md5.h \ @WITH_PAR2_TRUE@ lib/par2/par2cmdline.h \ @WITH_PAR2_TRUE@ lib/par2/par2creatorsourcefile.cpp \ @WITH_PAR2_TRUE@ lib/par2/par2creatorsourcefile.h \ @WITH_PAR2_TRUE@ lib/par2/par2fileformat.cpp \ @WITH_PAR2_TRUE@ lib/par2/par2fileformat.h \ @WITH_PAR2_TRUE@ lib/par2/par2repairer.cpp \ @WITH_PAR2_TRUE@ lib/par2/par2repairer.h \ @WITH_PAR2_TRUE@ lib/par2/par2repairersourcefile.cpp \ @WITH_PAR2_TRUE@ lib/par2/par2repairersourcefile.h \ @WITH_PAR2_TRUE@ lib/par2/parheaders.cpp \ @WITH_PAR2_TRUE@ lib/par2/parheaders.h \ @WITH_PAR2_TRUE@ lib/par2/recoverypacket.cpp \ @WITH_PAR2_TRUE@ lib/par2/recoverypacket.h \ @WITH_PAR2_TRUE@ lib/par2/reedsolomon.cpp \ @WITH_PAR2_TRUE@ lib/par2/reedsolomon.h \ @WITH_PAR2_TRUE@ lib/par2/verificationhashtable.cpp \ @WITH_PAR2_TRUE@ lib/par2/verificationhashtable.h \ @WITH_PAR2_TRUE@ lib/par2/verificationpacket.cpp \ @WITH_PAR2_TRUE@ lib/par2/verificationpacket.h @WITH_TESTS_TRUE@am__append_2 = \ @WITH_TESTS_TRUE@ lib/catch/catch.h \ @WITH_TESTS_TRUE@ tests/suite/TestMain.cpp \ @WITH_TESTS_TRUE@ tests/suite/TestMain.h \ @WITH_TESTS_TRUE@ tests/suite/TestUtil.cpp \ @WITH_TESTS_TRUE@ tests/suite/TestUtil.h \ @WITH_TESTS_TRUE@ tests/main/CommandLineParserTest.cpp \ @WITH_TESTS_TRUE@ tests/main/OptionsTest.cpp \ @WITH_TESTS_TRUE@ tests/feed/FeedFilterTest.cpp \ @WITH_TESTS_TRUE@ tests/postprocess/ParCheckerTest.cpp \ @WITH_TESTS_TRUE@ tests/postprocess/ParRenamerTest.cpp \ @WITH_TESTS_TRUE@ tests/queue/NZBFileTest.cpp \ @WITH_TESTS_TRUE@ tests/util/UtilTest.cpp @WITH_TESTS_TRUE@am__append_3 = \ @WITH_TESTS_TRUE@ -I$(srcdir)/lib/catch \ @WITH_TESTS_TRUE@ -I$(srcdir)/tests/suite subdir = . DIST_COMMON = README $(am__configure_deps) $(dist_doc_DATA) \ $(dist_exampleconf_DATA) $(nobase_dist_scripts_SCRIPTS) \ $(nobase_dist_webui_DATA) $(srcdir)/Makefile.am \ $(srcdir)/Makefile.in $(srcdir)/config.h.in \ $(top_srcdir)/configure COPYING ChangeLog posix/depcomp \ posix/install-sh posix/missing ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \ configure.lineno configure.status.lineno mkinstalldirs = $(install_sh) -d CONFIG_HEADER = config.h CONFIG_CLEAN_FILES = am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(scriptsdir)" \ "$(DESTDIR)$(docdir)" "$(DESTDIR)$(exampleconfdir)" \ "$(DESTDIR)$(webuidir)" binPROGRAMS_INSTALL = $(INSTALL_PROGRAM) PROGRAMS = $(bin_PROGRAMS) am__nzbget_SOURCES_DIST = daemon/connect/Connection.cpp \ daemon/connect/Connection.h daemon/connect/TLS.cpp \ daemon/connect/TLS.h daemon/connect/WebDownloader.cpp \ daemon/connect/WebDownloader.h daemon/extension/FeedScript.cpp \ daemon/extension/FeedScript.h daemon/extension/NzbScript.cpp \ daemon/extension/NzbScript.h daemon/extension/PostScript.cpp \ daemon/extension/PostScript.h daemon/extension/QueueScript.cpp \ daemon/extension/QueueScript.h daemon/extension/ScanScript.cpp \ daemon/extension/ScanScript.h \ daemon/extension/SchedulerScript.cpp \ daemon/extension/SchedulerScript.h \ daemon/extension/ScriptConfig.cpp \ daemon/extension/ScriptConfig.h \ daemon/feed/FeedCoordinator.cpp daemon/feed/FeedCoordinator.h \ daemon/feed/FeedFile.cpp daemon/feed/FeedFile.h \ daemon/feed/FeedFilter.cpp daemon/feed/FeedFilter.h \ daemon/feed/FeedInfo.cpp daemon/feed/FeedInfo.h \ daemon/frontend/ColoredFrontend.cpp \ daemon/frontend/ColoredFrontend.h daemon/frontend/Frontend.cpp \ daemon/frontend/Frontend.h \ daemon/frontend/LoggableFrontend.cpp \ daemon/frontend/LoggableFrontend.h \ daemon/frontend/NCursesFrontend.cpp \ daemon/frontend/NCursesFrontend.h \ daemon/main/CommandLineParser.cpp \ daemon/main/CommandLineParser.h daemon/main/DiskService.cpp \ daemon/main/DiskService.h daemon/main/Maintenance.cpp \ daemon/main/Maintenance.h daemon/main/nzbget.cpp \ daemon/main/nzbget.h daemon/main/Options.cpp \ daemon/main/Options.h daemon/main/Scheduler.cpp \ daemon/main/Scheduler.h daemon/main/StackTrace.cpp \ daemon/main/StackTrace.h daemon/nntp/ArticleDownloader.cpp \ daemon/nntp/ArticleDownloader.h daemon/nntp/ArticleWriter.cpp \ daemon/nntp/ArticleWriter.h daemon/nntp/Decoder.cpp \ daemon/nntp/Decoder.h daemon/nntp/NewsServer.cpp \ daemon/nntp/NewsServer.h daemon/nntp/NNTPConnection.cpp \ daemon/nntp/NNTPConnection.h daemon/nntp/ServerPool.cpp \ daemon/nntp/ServerPool.h daemon/nntp/StatMeter.cpp \ daemon/nntp/StatMeter.h daemon/postprocess/Cleanup.cpp \ daemon/postprocess/Cleanup.h \ daemon/postprocess/DupeMatcher.cpp \ daemon/postprocess/DupeMatcher.h \ daemon/postprocess/ParChecker.cpp \ daemon/postprocess/ParChecker.h \ daemon/postprocess/ParCoordinator.cpp \ daemon/postprocess/ParCoordinator.h \ daemon/postprocess/ParParser.cpp \ daemon/postprocess/ParParser.h \ daemon/postprocess/ParRenamer.cpp \ daemon/postprocess/ParRenamer.h \ daemon/postprocess/PrePostProcessor.cpp \ daemon/postprocess/PrePostProcessor.h \ daemon/postprocess/Unpack.cpp daemon/postprocess/Unpack.h \ daemon/queue/DiskState.cpp daemon/queue/DiskState.h \ daemon/queue/DownloadInfo.cpp daemon/queue/DownloadInfo.h \ daemon/queue/DupeCoordinator.cpp \ daemon/queue/DupeCoordinator.h \ daemon/queue/HistoryCoordinator.cpp \ daemon/queue/HistoryCoordinator.h daemon/queue/NZBFile.cpp \ daemon/queue/NZBFile.h daemon/queue/QueueCoordinator.cpp \ daemon/queue/QueueCoordinator.h daemon/queue/QueueEditor.cpp \ daemon/queue/QueueEditor.h daemon/queue/Scanner.cpp \ daemon/queue/Scanner.h daemon/queue/UrlCoordinator.cpp \ daemon/queue/UrlCoordinator.h daemon/remote/BinRpc.cpp \ daemon/remote/BinRpc.h daemon/remote/MessageBase.h \ daemon/remote/RemoteClient.cpp daemon/remote/RemoteClient.h \ daemon/remote/RemoteServer.cpp daemon/remote/RemoteServer.h \ daemon/remote/WebServer.cpp daemon/remote/WebServer.h \ daemon/remote/XmlRpc.cpp daemon/remote/XmlRpc.h \ daemon/util/Log.cpp daemon/util/Log.h daemon/util/Observer.cpp \ daemon/util/Observer.h daemon/util/Script.cpp \ daemon/util/Script.h daemon/util/Thread.cpp \ daemon/util/Thread.h daemon/util/Service.cpp \ daemon/util/Service.h daemon/util/Util.cpp daemon/util/Util.h \ code_revision.cpp lib/par2/commandline.cpp \ lib/par2/commandline.h lib/par2/crc.cpp lib/par2/crc.h \ lib/par2/creatorpacket.cpp lib/par2/creatorpacket.h \ lib/par2/criticalpacket.cpp lib/par2/criticalpacket.h \ lib/par2/datablock.cpp lib/par2/datablock.h \ lib/par2/descriptionpacket.cpp lib/par2/descriptionpacket.h \ lib/par2/diskfile.cpp lib/par2/diskfile.h \ lib/par2/filechecksummer.cpp lib/par2/filechecksummer.h \ lib/par2/galois.cpp lib/par2/galois.h lib/par2/letype.h \ lib/par2/mainpacket.cpp lib/par2/mainpacket.h lib/par2/md5.cpp \ lib/par2/md5.h lib/par2/par2cmdline.h \ lib/par2/par2creatorsourcefile.cpp \ lib/par2/par2creatorsourcefile.h lib/par2/par2fileformat.cpp \ lib/par2/par2fileformat.h lib/par2/par2repairer.cpp \ lib/par2/par2repairer.h lib/par2/par2repairersourcefile.cpp \ lib/par2/par2repairersourcefile.h lib/par2/parheaders.cpp \ lib/par2/parheaders.h lib/par2/recoverypacket.cpp \ lib/par2/recoverypacket.h lib/par2/reedsolomon.cpp \ lib/par2/reedsolomon.h lib/par2/verificationhashtable.cpp \ lib/par2/verificationhashtable.h \ lib/par2/verificationpacket.cpp lib/par2/verificationpacket.h \ lib/catch/catch.h tests/suite/TestMain.cpp \ tests/suite/TestMain.h tests/suite/TestUtil.cpp \ tests/suite/TestUtil.h tests/main/CommandLineParserTest.cpp \ tests/main/OptionsTest.cpp tests/feed/FeedFilterTest.cpp \ tests/postprocess/ParCheckerTest.cpp \ tests/postprocess/ParRenamerTest.cpp \ tests/queue/NZBFileTest.cpp tests/util/UtilTest.cpp @WITH_PAR2_TRUE@am__objects_1 = commandline.$(OBJEXT) crc.$(OBJEXT) \ @WITH_PAR2_TRUE@ creatorpacket.$(OBJEXT) \ @WITH_PAR2_TRUE@ criticalpacket.$(OBJEXT) datablock.$(OBJEXT) \ @WITH_PAR2_TRUE@ descriptionpacket.$(OBJEXT) diskfile.$(OBJEXT) \ @WITH_PAR2_TRUE@ filechecksummer.$(OBJEXT) galois.$(OBJEXT) \ @WITH_PAR2_TRUE@ mainpacket.$(OBJEXT) md5.$(OBJEXT) \ @WITH_PAR2_TRUE@ par2creatorsourcefile.$(OBJEXT) \ @WITH_PAR2_TRUE@ par2fileformat.$(OBJEXT) \ @WITH_PAR2_TRUE@ par2repairer.$(OBJEXT) \ @WITH_PAR2_TRUE@ par2repairersourcefile.$(OBJEXT) \ @WITH_PAR2_TRUE@ parheaders.$(OBJEXT) recoverypacket.$(OBJEXT) \ @WITH_PAR2_TRUE@ reedsolomon.$(OBJEXT) \ @WITH_PAR2_TRUE@ verificationhashtable.$(OBJEXT) \ @WITH_PAR2_TRUE@ verificationpacket.$(OBJEXT) @WITH_TESTS_TRUE@am__objects_2 = TestMain.$(OBJEXT) TestUtil.$(OBJEXT) \ @WITH_TESTS_TRUE@ CommandLineParserTest.$(OBJEXT) \ @WITH_TESTS_TRUE@ OptionsTest.$(OBJEXT) \ @WITH_TESTS_TRUE@ FeedFilterTest.$(OBJEXT) \ @WITH_TESTS_TRUE@ ParCheckerTest.$(OBJEXT) \ @WITH_TESTS_TRUE@ ParRenamerTest.$(OBJEXT) \ @WITH_TESTS_TRUE@ NZBFileTest.$(OBJEXT) UtilTest.$(OBJEXT) am_nzbget_OBJECTS = Connection.$(OBJEXT) TLS.$(OBJEXT) \ WebDownloader.$(OBJEXT) FeedScript.$(OBJEXT) \ NzbScript.$(OBJEXT) PostScript.$(OBJEXT) QueueScript.$(OBJEXT) \ ScanScript.$(OBJEXT) SchedulerScript.$(OBJEXT) \ ScriptConfig.$(OBJEXT) FeedCoordinator.$(OBJEXT) \ FeedFile.$(OBJEXT) FeedFilter.$(OBJEXT) FeedInfo.$(OBJEXT) \ ColoredFrontend.$(OBJEXT) Frontend.$(OBJEXT) \ LoggableFrontend.$(OBJEXT) NCursesFrontend.$(OBJEXT) \ CommandLineParser.$(OBJEXT) DiskService.$(OBJEXT) \ Maintenance.$(OBJEXT) nzbget.$(OBJEXT) Options.$(OBJEXT) \ Scheduler.$(OBJEXT) StackTrace.$(OBJEXT) \ ArticleDownloader.$(OBJEXT) ArticleWriter.$(OBJEXT) \ Decoder.$(OBJEXT) NewsServer.$(OBJEXT) \ NNTPConnection.$(OBJEXT) ServerPool.$(OBJEXT) \ StatMeter.$(OBJEXT) Cleanup.$(OBJEXT) DupeMatcher.$(OBJEXT) \ ParChecker.$(OBJEXT) ParCoordinator.$(OBJEXT) \ ParParser.$(OBJEXT) ParRenamer.$(OBJEXT) \ PrePostProcessor.$(OBJEXT) Unpack.$(OBJEXT) \ DiskState.$(OBJEXT) DownloadInfo.$(OBJEXT) \ DupeCoordinator.$(OBJEXT) HistoryCoordinator.$(OBJEXT) \ NZBFile.$(OBJEXT) QueueCoordinator.$(OBJEXT) \ QueueEditor.$(OBJEXT) Scanner.$(OBJEXT) \ UrlCoordinator.$(OBJEXT) BinRpc.$(OBJEXT) \ RemoteClient.$(OBJEXT) RemoteServer.$(OBJEXT) \ WebServer.$(OBJEXT) XmlRpc.$(OBJEXT) Log.$(OBJEXT) \ Observer.$(OBJEXT) Script.$(OBJEXT) Thread.$(OBJEXT) \ Service.$(OBJEXT) Util.$(OBJEXT) code_revision.$(OBJEXT) \ $(am__objects_1) $(am__objects_2) nzbget_OBJECTS = $(am_nzbget_OBJECTS) nzbget_LDADD = $(LDADD) 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_scriptsSCRIPT_INSTALL = $(install_sh_SCRIPT) SCRIPTS = $(nobase_dist_scripts_SCRIPTS) DEFAULT_INCLUDES = -I. -I$(srcdir) -I. depcomp = $(SHELL) $(top_srcdir)/posix/depcomp am__depfiles_maybe = depfiles CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) CXXLD = $(CXX) CXXLINK = $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) \ -o $@ COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) CCLD = $(CC) LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ SOURCES = $(nzbget_SOURCES) DIST_SOURCES = $(am__nzbget_SOURCES_DIST) dist_docDATA_INSTALL = $(INSTALL_DATA) dist_exampleconfDATA_INSTALL = $(INSTALL_DATA) nobase_dist_webuiDATA_INSTALL = $(install_sh_DATA) DATA = $(dist_doc_DATA) $(dist_exampleconf_DATA) \ $(nobase_dist_webui_DATA) ETAGS = etags CTAGS = ctags DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) distdir = $(PACKAGE)-$(VERSION) top_distdir = $(distdir) am__remove_distdir = \ { test ! -d $(distdir) \ || { find $(distdir) -type d ! -perm -200 -exec chmod u+w {} ';' \ && rm -fr $(distdir); }; } DIST_ARCHIVES = $(distdir).tar.gz GZIP_ENV = --best distuninstallcheck_listfiles = find . -type f -print ACLOCAL = @ACLOCAL@ AMDEP_FALSE = @AMDEP_FALSE@ AMDEP_TRUE = @AMDEP_TRUE@ AMTAR = @AMTAR@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ CPPFLAGS = @CPPFLAGS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EXEEXT = @EXEEXT@ GREP = @GREP@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ LDFLAGS = @LDFLAGS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LTLIBOBJS = @LTLIBOBJS@ MAKE = @MAKE@ MAKEINFO = @MAKEINFO@ OBJEXT = @OBJEXT@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_VERSION = @PACKAGE_VERSION@ PATH_SEPARATOR = @PATH_SEPARATOR@ PKG_CONFIG = @PKG_CONFIG@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ STRIP = @STRIP@ TAR = @TAR@ VERSION = @VERSION@ WITH_PAR2_FALSE = @WITH_PAR2_FALSE@ WITH_PAR2_TRUE = @WITH_PAR2_TRUE@ WITH_TESTS_FALSE = @WITH_TESTS_FALSE@ WITH_TESTS_TRUE = @WITH_TESTS_TRUE@ ac_ct_CXX = @ac_ct_CXX@ am__fastdepCXX_FALSE = @am__fastdepCXX_FALSE@ am__fastdepCXX_TRUE = @am__fastdepCXX_TRUE@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build_alias = @build_alias@ datadir = @datadir@ datarootdir = @datarootdir@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host_alias = @host_alias@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ libxml2_CFLAGS = @libxml2_CFLAGS@ libxml2_LIBS = @libxml2_LIBS@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ oldincludedir = @oldincludedir@ openssl_CFLAGS = @openssl_CFLAGS@ openssl_LIBS = @openssl_LIBS@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ sysconfdir = @sysconfdir@ target_alias = @target_alias@ nzbget_SOURCES = daemon/connect/Connection.cpp \ daemon/connect/Connection.h daemon/connect/TLS.cpp \ daemon/connect/TLS.h daemon/connect/WebDownloader.cpp \ daemon/connect/WebDownloader.h daemon/extension/FeedScript.cpp \ daemon/extension/FeedScript.h daemon/extension/NzbScript.cpp \ daemon/extension/NzbScript.h daemon/extension/PostScript.cpp \ daemon/extension/PostScript.h daemon/extension/QueueScript.cpp \ daemon/extension/QueueScript.h daemon/extension/ScanScript.cpp \ daemon/extension/ScanScript.h \ daemon/extension/SchedulerScript.cpp \ daemon/extension/SchedulerScript.h \ daemon/extension/ScriptConfig.cpp \ daemon/extension/ScriptConfig.h \ daemon/feed/FeedCoordinator.cpp daemon/feed/FeedCoordinator.h \ daemon/feed/FeedFile.cpp daemon/feed/FeedFile.h \ daemon/feed/FeedFilter.cpp daemon/feed/FeedFilter.h \ daemon/feed/FeedInfo.cpp daemon/feed/FeedInfo.h \ daemon/frontend/ColoredFrontend.cpp \ daemon/frontend/ColoredFrontend.h daemon/frontend/Frontend.cpp \ daemon/frontend/Frontend.h \ daemon/frontend/LoggableFrontend.cpp \ daemon/frontend/LoggableFrontend.h \ daemon/frontend/NCursesFrontend.cpp \ daemon/frontend/NCursesFrontend.h \ daemon/main/CommandLineParser.cpp \ daemon/main/CommandLineParser.h daemon/main/DiskService.cpp \ daemon/main/DiskService.h daemon/main/Maintenance.cpp \ daemon/main/Maintenance.h daemon/main/nzbget.cpp \ daemon/main/nzbget.h daemon/main/Options.cpp \ daemon/main/Options.h daemon/main/Scheduler.cpp \ daemon/main/Scheduler.h daemon/main/StackTrace.cpp \ daemon/main/StackTrace.h daemon/nntp/ArticleDownloader.cpp \ daemon/nntp/ArticleDownloader.h daemon/nntp/ArticleWriter.cpp \ daemon/nntp/ArticleWriter.h daemon/nntp/Decoder.cpp \ daemon/nntp/Decoder.h daemon/nntp/NewsServer.cpp \ daemon/nntp/NewsServer.h daemon/nntp/NNTPConnection.cpp \ daemon/nntp/NNTPConnection.h daemon/nntp/ServerPool.cpp \ daemon/nntp/ServerPool.h daemon/nntp/StatMeter.cpp \ daemon/nntp/StatMeter.h daemon/postprocess/Cleanup.cpp \ daemon/postprocess/Cleanup.h \ daemon/postprocess/DupeMatcher.cpp \ daemon/postprocess/DupeMatcher.h \ daemon/postprocess/ParChecker.cpp \ daemon/postprocess/ParChecker.h \ daemon/postprocess/ParCoordinator.cpp \ daemon/postprocess/ParCoordinator.h \ daemon/postprocess/ParParser.cpp \ daemon/postprocess/ParParser.h \ daemon/postprocess/ParRenamer.cpp \ daemon/postprocess/ParRenamer.h \ daemon/postprocess/PrePostProcessor.cpp \ daemon/postprocess/PrePostProcessor.h \ daemon/postprocess/Unpack.cpp daemon/postprocess/Unpack.h \ daemon/queue/DiskState.cpp daemon/queue/DiskState.h \ daemon/queue/DownloadInfo.cpp daemon/queue/DownloadInfo.h \ daemon/queue/DupeCoordinator.cpp \ daemon/queue/DupeCoordinator.h \ daemon/queue/HistoryCoordinator.cpp \ daemon/queue/HistoryCoordinator.h daemon/queue/NZBFile.cpp \ daemon/queue/NZBFile.h daemon/queue/QueueCoordinator.cpp \ daemon/queue/QueueCoordinator.h daemon/queue/QueueEditor.cpp \ daemon/queue/QueueEditor.h daemon/queue/Scanner.cpp \ daemon/queue/Scanner.h daemon/queue/UrlCoordinator.cpp \ daemon/queue/UrlCoordinator.h daemon/remote/BinRpc.cpp \ daemon/remote/BinRpc.h daemon/remote/MessageBase.h \ daemon/remote/RemoteClient.cpp daemon/remote/RemoteClient.h \ daemon/remote/RemoteServer.cpp daemon/remote/RemoteServer.h \ daemon/remote/WebServer.cpp daemon/remote/WebServer.h \ daemon/remote/XmlRpc.cpp daemon/remote/XmlRpc.h \ daemon/util/Log.cpp daemon/util/Log.h daemon/util/Observer.cpp \ daemon/util/Observer.h daemon/util/Script.cpp \ daemon/util/Script.h daemon/util/Thread.cpp \ daemon/util/Thread.h daemon/util/Service.cpp \ daemon/util/Service.h daemon/util/Util.cpp daemon/util/Util.h \ code_revision.cpp $(am__append_1) $(am__append_2) AM_CPPFLAGS = -I$(srcdir)/daemon/connect -I$(srcdir)/daemon/extension \ -I$(srcdir)/daemon/feed -I$(srcdir)/daemon/frontend \ -I$(srcdir)/daemon/main -I$(srcdir)/daemon/nntp \ -I$(srcdir)/daemon/postprocess -I$(srcdir)/daemon/queue \ -I$(srcdir)/daemon/remote -I$(srcdir)/daemon/util \ -I$(srcdir)/lib/par2 $(am__append_3) EXTRA_DIST = \ $(windows_FILES) \ $(osx_FILES) \ $(linux_FILES) \ $(testdata_FILES) windows_FILES = \ daemon/windows/NTService.cpp \ daemon/windows/NTService.h \ daemon/windows/win32.h \ daemon/windows/WinConsole.cpp \ daemon/windows/WinConsole.h \ nzbget.vcproj \ windows/nzbget-command-shell.bat \ windows/install-update.bat \ windows/README-WINDOWS.txt \ windows/package-info.json \ windows/resources/mainicon.ico \ windows/resources/nzbget.rc \ windows/resources/resource.h \ windows/resources/trayicon_idle.ico \ windows/resources/trayicon_paused.ico \ windows/resources/trayicon_working.ico \ windows/setup/nzbget-setup.nsi \ windows/setup/install.bmp \ windows/setup/uninstall.bmp 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/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 linux_FILES = \ linux/installer.sh \ linux/install-update.sh \ linux/package-info.json \ linux/build-info.txt \ linux/build-nzbget \ linux/build-unpack doc_FILES = \ lib/par2/AUTHORS \ lib/par2/README \ README \ ChangeLog \ COPYING exampleconf_FILES = \ 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/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/lib/raphael.js \ webui/lib/raphael.min.js \ webui/lib/elycharts.js \ webui/lib/elycharts.min.js \ webui/img/icons.png \ webui/img/icons-2x.png \ webui/img/transmit.gif \ webui/img/transmit-file.gif \ webui/img/favicon.ico \ webui/img/download-anim-green-2x.png \ webui/img/download-anim-orange-2x.png \ webui/img/transmit-reload-2x.gif scripts_FILES = \ scripts/EMail.py \ scripts/Logger.py testdata_FILES = \ tests/testdata/dupematcher1/testfile.part01.rar \ tests/testdata/dupematcher1/testfile.part24.rar \ tests/testdata/dupematcher2/testfile.part04.rar \ tests/testdata/dupematcher2/testfile.part43.rar \ tests/testdata/nzbfile/dotless.nzb \ tests/testdata/nzbfile/dotless.txt \ tests/testdata/nzbfile/plain.nzb \ tests/testdata/nzbfile/plain.txt \ tests/testdata/parchecker/crc.txt \ tests/testdata/parchecker/testfile.dat \ tests/testdata/parchecker/testfile.nfo \ tests/testdata/parchecker/testfile.par2 \ tests/testdata/parchecker/testfile.vol00+1.PAR2 \ tests/testdata/parchecker/testfile.vol01+2.PAR2 \ tests/testdata/parchecker/testfile.vol03+3.PAR2 # Install dist_doc_DATA = $(doc_FILES) exampleconfdir = $(datadir)/nzbget dist_exampleconf_DATA = $(exampleconf_FILES) webuidir = $(datadir)/nzbget nobase_dist_webui_DATA = $(webui_FILES) scriptsdir = $(datadir)/nzbget nobase_dist_scripts_SCRIPTS = $(scripts_FILES) # Ignore "code_revision.cpp" in distcleancheck distcleancheck_listfiles = \ find . -type f -exec sh -c 'test -f $(srcdir)/$$1 || echo $$1' \ sh '{}' ';' all: config.h $(MAKE) $(AM_MAKEFLAGS) all-am .SUFFIXES: .SUFFIXES: .cpp .o .obj am--refresh: @: $(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ echo ' cd $(srcdir) && $(AUTOMAKE) --foreign '; \ cd $(srcdir) && $(AUTOMAKE) --foreign \ && exit 0; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign Makefile'; \ cd $(top_srcdir) && \ $(AUTOMAKE) --foreign Makefile .PRECIOUS: Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ echo ' $(SHELL) ./config.status'; \ $(SHELL) ./config.status;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe)'; \ cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) $(SHELL) ./config.status --recheck $(top_srcdir)/configure: $(am__configure_deps) cd $(srcdir) && $(AUTOCONF) $(ACLOCAL_M4): $(am__aclocal_m4_deps) cd $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS) config.h: stamp-h1 @if test ! -f $@; then \ rm -f stamp-h1; \ $(MAKE) stamp-h1; \ else :; fi stamp-h1: $(srcdir)/config.h.in $(top_builddir)/config.status @rm -f stamp-h1 cd $(top_builddir) && $(SHELL) ./config.status config.h $(srcdir)/config.h.in: $(am__configure_deps) cd $(top_srcdir) && $(AUTOHEADER) rm -f stamp-h1 touch $@ distclean-hdr: -rm -f config.h stamp-h1 install-binPROGRAMS: $(bin_PROGRAMS) @$(NORMAL_INSTALL) test -z "$(bindir)" || $(mkdir_p) "$(DESTDIR)$(bindir)" @list='$(bin_PROGRAMS)'; for p in $$list; do \ p1=`echo $$p|sed 's/$(EXEEXT)$$//'`; \ if test -f $$p \ ; then \ f=`echo "$$p1" | sed 's,^.*/,,;$(transform);s/$$/$(EXEEXT)/'`; \ echo " $(INSTALL_PROGRAM_ENV) $(binPROGRAMS_INSTALL) '$$p' '$(DESTDIR)$(bindir)/$$f'"; \ $(INSTALL_PROGRAM_ENV) $(binPROGRAMS_INSTALL) "$$p" "$(DESTDIR)$(bindir)/$$f" || exit 1; \ else :; fi; \ done uninstall-binPROGRAMS: @$(NORMAL_UNINSTALL) @list='$(bin_PROGRAMS)'; for p in $$list; do \ f=`echo "$$p" | sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \ echo " rm -f '$(DESTDIR)$(bindir)/$$f'"; \ rm -f "$(DESTDIR)$(bindir)/$$f"; \ done clean-binPROGRAMS: -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS) nzbget$(EXEEXT): $(nzbget_OBJECTS) $(nzbget_DEPENDENCIES) @rm -f nzbget$(EXEEXT) $(CXXLINK) $(nzbget_LDFLAGS) $(nzbget_OBJECTS) $(nzbget_LDADD) $(LIBS) install-nobase_dist_scriptsSCRIPTS: $(nobase_dist_scripts_SCRIPTS) @$(NORMAL_INSTALL) test -z "$(scriptsdir)" || $(mkdir_p) "$(DESTDIR)$(scriptsdir)" @$(am__vpath_adj_setup) \ list='$(nobase_dist_scripts_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)'`; \ f=`echo "$$p" | sed 's|[^/]*$$||'`"$$f"; \ echo " $(nobase_dist_scriptsSCRIPT_INSTALL) '$$d$$p' '$(DESTDIR)$(scriptsdir)/$$f'"; \ $(nobase_dist_scriptsSCRIPT_INSTALL) "$$d$$p" "$(DESTDIR)$(scriptsdir)/$$f"; \ else :; fi; \ done uninstall-nobase_dist_scriptsSCRIPTS: @$(NORMAL_UNINSTALL) @$(am__vpath_adj_setup) \ list='$(nobase_dist_scripts_SCRIPTS)'; for p in $$list; do \ $(am__vpath_adj) p=$$f; \ f=`echo "$$p" | sed 's|^.*/||;$(transform)'`; \ f=`echo "$$p" | sed 's|[^/]*$$||'`"$$f"; \ echo " rm -f '$(DESTDIR)$(scriptsdir)/$$f'"; \ rm -f "$(DESTDIR)$(scriptsdir)/$$f"; \ done mostlyclean-compile: -rm -f *.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ArticleDownloader.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ArticleWriter.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/BinRpc.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Cleanup.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ColoredFrontend.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/CommandLineParser.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/CommandLineParserTest.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Connection.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Decoder.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DiskService.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DiskState.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DownloadInfo.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DupeCoordinator.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DupeMatcher.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)/FeedFilterTest.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/FeedInfo.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/FeedScript.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Frontend.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/HistoryCoordinator.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@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NZBFileTest.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NewsServer.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/NzbScript.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Observer.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Options.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/OptionsTest.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ParChecker.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ParCheckerTest.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ParCoordinator.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ParParser.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ParRenamer.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ParRenamerTest.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PostScript.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PrePostProcessor.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/QueueCoordinator.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/QueueEditor.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/QueueScript.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/RemoteClient.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/RemoteServer.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ScanScript.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Scanner.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Scheduler.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SchedulerScript.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Script.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ScriptConfig.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ServerPool.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Service.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/StackTrace.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/StatMeter.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/TLS.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/TestMain.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/TestUtil.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Thread.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Unpack.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/UrlCoordinator.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Util.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/UtilTest.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/WebDownloader.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/WebServer.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/XmlRpc.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/code_revision.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/commandline.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crc.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/creatorpacket.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/criticalpacket.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/datablock.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/descriptionpacket.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/diskfile.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/filechecksummer.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/galois.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mainpacket.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/md5.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nzbget.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/par2creatorsourcefile.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/par2fileformat.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/par2repairer.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/par2repairersourcefile.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/parheaders.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/recoverypacket.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/reedsolomon.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/verificationhashtable.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/verificationpacket.Po@am__quote@ .cpp.o: @am__fastdepCXX_TRUE@ if $(CXXCOMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ $<; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXXCOMPILE) -c -o $@ $< .cpp.obj: @am__fastdepCXX_TRUE@ if $(CXXCOMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ `$(CYGPATH_W) '$<'`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` Connection.o: daemon/connect/Connection.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Connection.o -MD -MP -MF "$(DEPDIR)/Connection.Tpo" -c -o Connection.o `test -f 'daemon/connect/Connection.cpp' || echo '$(srcdir)/'`daemon/connect/Connection.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Connection.Tpo" "$(DEPDIR)/Connection.Po"; else rm -f "$(DEPDIR)/Connection.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/connect/Connection.cpp' object='Connection.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Connection.o `test -f 'daemon/connect/Connection.cpp' || echo '$(srcdir)/'`daemon/connect/Connection.cpp Connection.obj: daemon/connect/Connection.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Connection.obj -MD -MP -MF "$(DEPDIR)/Connection.Tpo" -c -o Connection.obj `if test -f 'daemon/connect/Connection.cpp'; then $(CYGPATH_W) 'daemon/connect/Connection.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/connect/Connection.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Connection.Tpo" "$(DEPDIR)/Connection.Po"; else rm -f "$(DEPDIR)/Connection.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/connect/Connection.cpp' object='Connection.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Connection.obj `if test -f 'daemon/connect/Connection.cpp'; then $(CYGPATH_W) 'daemon/connect/Connection.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/connect/Connection.cpp'; fi` TLS.o: daemon/connect/TLS.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT TLS.o -MD -MP -MF "$(DEPDIR)/TLS.Tpo" -c -o TLS.o `test -f 'daemon/connect/TLS.cpp' || echo '$(srcdir)/'`daemon/connect/TLS.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/TLS.Tpo" "$(DEPDIR)/TLS.Po"; else rm -f "$(DEPDIR)/TLS.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/connect/TLS.cpp' object='TLS.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o TLS.o `test -f 'daemon/connect/TLS.cpp' || echo '$(srcdir)/'`daemon/connect/TLS.cpp TLS.obj: daemon/connect/TLS.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT TLS.obj -MD -MP -MF "$(DEPDIR)/TLS.Tpo" -c -o TLS.obj `if test -f 'daemon/connect/TLS.cpp'; then $(CYGPATH_W) 'daemon/connect/TLS.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/connect/TLS.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/TLS.Tpo" "$(DEPDIR)/TLS.Po"; else rm -f "$(DEPDIR)/TLS.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/connect/TLS.cpp' object='TLS.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o TLS.obj `if test -f 'daemon/connect/TLS.cpp'; then $(CYGPATH_W) 'daemon/connect/TLS.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/connect/TLS.cpp'; fi` WebDownloader.o: daemon/connect/WebDownloader.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT WebDownloader.o -MD -MP -MF "$(DEPDIR)/WebDownloader.Tpo" -c -o WebDownloader.o `test -f 'daemon/connect/WebDownloader.cpp' || echo '$(srcdir)/'`daemon/connect/WebDownloader.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/WebDownloader.Tpo" "$(DEPDIR)/WebDownloader.Po"; else rm -f "$(DEPDIR)/WebDownloader.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/connect/WebDownloader.cpp' object='WebDownloader.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o WebDownloader.o `test -f 'daemon/connect/WebDownloader.cpp' || echo '$(srcdir)/'`daemon/connect/WebDownloader.cpp WebDownloader.obj: daemon/connect/WebDownloader.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT WebDownloader.obj -MD -MP -MF "$(DEPDIR)/WebDownloader.Tpo" -c -o WebDownloader.obj `if test -f 'daemon/connect/WebDownloader.cpp'; then $(CYGPATH_W) 'daemon/connect/WebDownloader.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/connect/WebDownloader.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/WebDownloader.Tpo" "$(DEPDIR)/WebDownloader.Po"; else rm -f "$(DEPDIR)/WebDownloader.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/connect/WebDownloader.cpp' object='WebDownloader.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o WebDownloader.obj `if test -f 'daemon/connect/WebDownloader.cpp'; then $(CYGPATH_W) 'daemon/connect/WebDownloader.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/connect/WebDownloader.cpp'; fi` FeedScript.o: daemon/extension/FeedScript.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT FeedScript.o -MD -MP -MF "$(DEPDIR)/FeedScript.Tpo" -c -o FeedScript.o `test -f 'daemon/extension/FeedScript.cpp' || echo '$(srcdir)/'`daemon/extension/FeedScript.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/FeedScript.Tpo" "$(DEPDIR)/FeedScript.Po"; else rm -f "$(DEPDIR)/FeedScript.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/extension/FeedScript.cpp' object='FeedScript.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o FeedScript.o `test -f 'daemon/extension/FeedScript.cpp' || echo '$(srcdir)/'`daemon/extension/FeedScript.cpp FeedScript.obj: daemon/extension/FeedScript.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT FeedScript.obj -MD -MP -MF "$(DEPDIR)/FeedScript.Tpo" -c -o FeedScript.obj `if test -f 'daemon/extension/FeedScript.cpp'; then $(CYGPATH_W) 'daemon/extension/FeedScript.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/extension/FeedScript.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/FeedScript.Tpo" "$(DEPDIR)/FeedScript.Po"; else rm -f "$(DEPDIR)/FeedScript.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/extension/FeedScript.cpp' object='FeedScript.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o FeedScript.obj `if test -f 'daemon/extension/FeedScript.cpp'; then $(CYGPATH_W) 'daemon/extension/FeedScript.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/extension/FeedScript.cpp'; fi` NzbScript.o: daemon/extension/NzbScript.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NzbScript.o -MD -MP -MF "$(DEPDIR)/NzbScript.Tpo" -c -o NzbScript.o `test -f 'daemon/extension/NzbScript.cpp' || echo '$(srcdir)/'`daemon/extension/NzbScript.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NzbScript.Tpo" "$(DEPDIR)/NzbScript.Po"; else rm -f "$(DEPDIR)/NzbScript.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/extension/NzbScript.cpp' object='NzbScript.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NzbScript.o `test -f 'daemon/extension/NzbScript.cpp' || echo '$(srcdir)/'`daemon/extension/NzbScript.cpp NzbScript.obj: daemon/extension/NzbScript.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NzbScript.obj -MD -MP -MF "$(DEPDIR)/NzbScript.Tpo" -c -o NzbScript.obj `if test -f 'daemon/extension/NzbScript.cpp'; then $(CYGPATH_W) 'daemon/extension/NzbScript.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/extension/NzbScript.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NzbScript.Tpo" "$(DEPDIR)/NzbScript.Po"; else rm -f "$(DEPDIR)/NzbScript.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/extension/NzbScript.cpp' object='NzbScript.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NzbScript.obj `if test -f 'daemon/extension/NzbScript.cpp'; then $(CYGPATH_W) 'daemon/extension/NzbScript.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/extension/NzbScript.cpp'; fi` PostScript.o: daemon/extension/PostScript.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT PostScript.o -MD -MP -MF "$(DEPDIR)/PostScript.Tpo" -c -o PostScript.o `test -f 'daemon/extension/PostScript.cpp' || echo '$(srcdir)/'`daemon/extension/PostScript.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/PostScript.Tpo" "$(DEPDIR)/PostScript.Po"; else rm -f "$(DEPDIR)/PostScript.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/extension/PostScript.cpp' object='PostScript.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o PostScript.o `test -f 'daemon/extension/PostScript.cpp' || echo '$(srcdir)/'`daemon/extension/PostScript.cpp PostScript.obj: daemon/extension/PostScript.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT PostScript.obj -MD -MP -MF "$(DEPDIR)/PostScript.Tpo" -c -o PostScript.obj `if test -f 'daemon/extension/PostScript.cpp'; then $(CYGPATH_W) 'daemon/extension/PostScript.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/extension/PostScript.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/PostScript.Tpo" "$(DEPDIR)/PostScript.Po"; else rm -f "$(DEPDIR)/PostScript.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/extension/PostScript.cpp' object='PostScript.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o PostScript.obj `if test -f 'daemon/extension/PostScript.cpp'; then $(CYGPATH_W) 'daemon/extension/PostScript.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/extension/PostScript.cpp'; fi` QueueScript.o: daemon/extension/QueueScript.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT QueueScript.o -MD -MP -MF "$(DEPDIR)/QueueScript.Tpo" -c -o QueueScript.o `test -f 'daemon/extension/QueueScript.cpp' || echo '$(srcdir)/'`daemon/extension/QueueScript.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/QueueScript.Tpo" "$(DEPDIR)/QueueScript.Po"; else rm -f "$(DEPDIR)/QueueScript.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/extension/QueueScript.cpp' object='QueueScript.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o QueueScript.o `test -f 'daemon/extension/QueueScript.cpp' || echo '$(srcdir)/'`daemon/extension/QueueScript.cpp QueueScript.obj: daemon/extension/QueueScript.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT QueueScript.obj -MD -MP -MF "$(DEPDIR)/QueueScript.Tpo" -c -o QueueScript.obj `if test -f 'daemon/extension/QueueScript.cpp'; then $(CYGPATH_W) 'daemon/extension/QueueScript.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/extension/QueueScript.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/QueueScript.Tpo" "$(DEPDIR)/QueueScript.Po"; else rm -f "$(DEPDIR)/QueueScript.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/extension/QueueScript.cpp' object='QueueScript.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o QueueScript.obj `if test -f 'daemon/extension/QueueScript.cpp'; then $(CYGPATH_W) 'daemon/extension/QueueScript.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/extension/QueueScript.cpp'; fi` ScanScript.o: daemon/extension/ScanScript.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ScanScript.o -MD -MP -MF "$(DEPDIR)/ScanScript.Tpo" -c -o ScanScript.o `test -f 'daemon/extension/ScanScript.cpp' || echo '$(srcdir)/'`daemon/extension/ScanScript.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ScanScript.Tpo" "$(DEPDIR)/ScanScript.Po"; else rm -f "$(DEPDIR)/ScanScript.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/extension/ScanScript.cpp' object='ScanScript.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ScanScript.o `test -f 'daemon/extension/ScanScript.cpp' || echo '$(srcdir)/'`daemon/extension/ScanScript.cpp ScanScript.obj: daemon/extension/ScanScript.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ScanScript.obj -MD -MP -MF "$(DEPDIR)/ScanScript.Tpo" -c -o ScanScript.obj `if test -f 'daemon/extension/ScanScript.cpp'; then $(CYGPATH_W) 'daemon/extension/ScanScript.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/extension/ScanScript.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ScanScript.Tpo" "$(DEPDIR)/ScanScript.Po"; else rm -f "$(DEPDIR)/ScanScript.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/extension/ScanScript.cpp' object='ScanScript.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ScanScript.obj `if test -f 'daemon/extension/ScanScript.cpp'; then $(CYGPATH_W) 'daemon/extension/ScanScript.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/extension/ScanScript.cpp'; fi` SchedulerScript.o: daemon/extension/SchedulerScript.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT SchedulerScript.o -MD -MP -MF "$(DEPDIR)/SchedulerScript.Tpo" -c -o SchedulerScript.o `test -f 'daemon/extension/SchedulerScript.cpp' || echo '$(srcdir)/'`daemon/extension/SchedulerScript.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/SchedulerScript.Tpo" "$(DEPDIR)/SchedulerScript.Po"; else rm -f "$(DEPDIR)/SchedulerScript.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/extension/SchedulerScript.cpp' object='SchedulerScript.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o SchedulerScript.o `test -f 'daemon/extension/SchedulerScript.cpp' || echo '$(srcdir)/'`daemon/extension/SchedulerScript.cpp SchedulerScript.obj: daemon/extension/SchedulerScript.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT SchedulerScript.obj -MD -MP -MF "$(DEPDIR)/SchedulerScript.Tpo" -c -o SchedulerScript.obj `if test -f 'daemon/extension/SchedulerScript.cpp'; then $(CYGPATH_W) 'daemon/extension/SchedulerScript.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/extension/SchedulerScript.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/SchedulerScript.Tpo" "$(DEPDIR)/SchedulerScript.Po"; else rm -f "$(DEPDIR)/SchedulerScript.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/extension/SchedulerScript.cpp' object='SchedulerScript.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o SchedulerScript.obj `if test -f 'daemon/extension/SchedulerScript.cpp'; then $(CYGPATH_W) 'daemon/extension/SchedulerScript.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/extension/SchedulerScript.cpp'; fi` ScriptConfig.o: daemon/extension/ScriptConfig.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ScriptConfig.o -MD -MP -MF "$(DEPDIR)/ScriptConfig.Tpo" -c -o ScriptConfig.o `test -f 'daemon/extension/ScriptConfig.cpp' || echo '$(srcdir)/'`daemon/extension/ScriptConfig.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ScriptConfig.Tpo" "$(DEPDIR)/ScriptConfig.Po"; else rm -f "$(DEPDIR)/ScriptConfig.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/extension/ScriptConfig.cpp' object='ScriptConfig.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ScriptConfig.o `test -f 'daemon/extension/ScriptConfig.cpp' || echo '$(srcdir)/'`daemon/extension/ScriptConfig.cpp ScriptConfig.obj: daemon/extension/ScriptConfig.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ScriptConfig.obj -MD -MP -MF "$(DEPDIR)/ScriptConfig.Tpo" -c -o ScriptConfig.obj `if test -f 'daemon/extension/ScriptConfig.cpp'; then $(CYGPATH_W) 'daemon/extension/ScriptConfig.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/extension/ScriptConfig.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ScriptConfig.Tpo" "$(DEPDIR)/ScriptConfig.Po"; else rm -f "$(DEPDIR)/ScriptConfig.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/extension/ScriptConfig.cpp' object='ScriptConfig.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ScriptConfig.obj `if test -f 'daemon/extension/ScriptConfig.cpp'; then $(CYGPATH_W) 'daemon/extension/ScriptConfig.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/extension/ScriptConfig.cpp'; fi` FeedCoordinator.o: daemon/feed/FeedCoordinator.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT FeedCoordinator.o -MD -MP -MF "$(DEPDIR)/FeedCoordinator.Tpo" -c -o FeedCoordinator.o `test -f 'daemon/feed/FeedCoordinator.cpp' || echo '$(srcdir)/'`daemon/feed/FeedCoordinator.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/FeedCoordinator.Tpo" "$(DEPDIR)/FeedCoordinator.Po"; else rm -f "$(DEPDIR)/FeedCoordinator.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/feed/FeedCoordinator.cpp' object='FeedCoordinator.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o FeedCoordinator.o `test -f 'daemon/feed/FeedCoordinator.cpp' || echo '$(srcdir)/'`daemon/feed/FeedCoordinator.cpp FeedCoordinator.obj: daemon/feed/FeedCoordinator.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT FeedCoordinator.obj -MD -MP -MF "$(DEPDIR)/FeedCoordinator.Tpo" -c -o FeedCoordinator.obj `if test -f 'daemon/feed/FeedCoordinator.cpp'; then $(CYGPATH_W) 'daemon/feed/FeedCoordinator.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/feed/FeedCoordinator.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/FeedCoordinator.Tpo" "$(DEPDIR)/FeedCoordinator.Po"; else rm -f "$(DEPDIR)/FeedCoordinator.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/feed/FeedCoordinator.cpp' object='FeedCoordinator.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o FeedCoordinator.obj `if test -f 'daemon/feed/FeedCoordinator.cpp'; then $(CYGPATH_W) 'daemon/feed/FeedCoordinator.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/feed/FeedCoordinator.cpp'; fi` FeedFile.o: daemon/feed/FeedFile.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT FeedFile.o -MD -MP -MF "$(DEPDIR)/FeedFile.Tpo" -c -o FeedFile.o `test -f 'daemon/feed/FeedFile.cpp' || echo '$(srcdir)/'`daemon/feed/FeedFile.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/FeedFile.Tpo" "$(DEPDIR)/FeedFile.Po"; else rm -f "$(DEPDIR)/FeedFile.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/feed/FeedFile.cpp' object='FeedFile.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o FeedFile.o `test -f 'daemon/feed/FeedFile.cpp' || echo '$(srcdir)/'`daemon/feed/FeedFile.cpp FeedFile.obj: daemon/feed/FeedFile.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT FeedFile.obj -MD -MP -MF "$(DEPDIR)/FeedFile.Tpo" -c -o FeedFile.obj `if test -f 'daemon/feed/FeedFile.cpp'; then $(CYGPATH_W) 'daemon/feed/FeedFile.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/feed/FeedFile.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/FeedFile.Tpo" "$(DEPDIR)/FeedFile.Po"; else rm -f "$(DEPDIR)/FeedFile.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/feed/FeedFile.cpp' object='FeedFile.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o FeedFile.obj `if test -f 'daemon/feed/FeedFile.cpp'; then $(CYGPATH_W) 'daemon/feed/FeedFile.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/feed/FeedFile.cpp'; fi` FeedFilter.o: daemon/feed/FeedFilter.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT FeedFilter.o -MD -MP -MF "$(DEPDIR)/FeedFilter.Tpo" -c -o FeedFilter.o `test -f 'daemon/feed/FeedFilter.cpp' || echo '$(srcdir)/'`daemon/feed/FeedFilter.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/FeedFilter.Tpo" "$(DEPDIR)/FeedFilter.Po"; else rm -f "$(DEPDIR)/FeedFilter.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/feed/FeedFilter.cpp' object='FeedFilter.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o FeedFilter.o `test -f 'daemon/feed/FeedFilter.cpp' || echo '$(srcdir)/'`daemon/feed/FeedFilter.cpp FeedFilter.obj: daemon/feed/FeedFilter.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT FeedFilter.obj -MD -MP -MF "$(DEPDIR)/FeedFilter.Tpo" -c -o FeedFilter.obj `if test -f 'daemon/feed/FeedFilter.cpp'; then $(CYGPATH_W) 'daemon/feed/FeedFilter.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/feed/FeedFilter.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/FeedFilter.Tpo" "$(DEPDIR)/FeedFilter.Po"; else rm -f "$(DEPDIR)/FeedFilter.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/feed/FeedFilter.cpp' object='FeedFilter.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o FeedFilter.obj `if test -f 'daemon/feed/FeedFilter.cpp'; then $(CYGPATH_W) 'daemon/feed/FeedFilter.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/feed/FeedFilter.cpp'; fi` FeedInfo.o: daemon/feed/FeedInfo.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT FeedInfo.o -MD -MP -MF "$(DEPDIR)/FeedInfo.Tpo" -c -o FeedInfo.o `test -f 'daemon/feed/FeedInfo.cpp' || echo '$(srcdir)/'`daemon/feed/FeedInfo.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/FeedInfo.Tpo" "$(DEPDIR)/FeedInfo.Po"; else rm -f "$(DEPDIR)/FeedInfo.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/feed/FeedInfo.cpp' object='FeedInfo.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o FeedInfo.o `test -f 'daemon/feed/FeedInfo.cpp' || echo '$(srcdir)/'`daemon/feed/FeedInfo.cpp FeedInfo.obj: daemon/feed/FeedInfo.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT FeedInfo.obj -MD -MP -MF "$(DEPDIR)/FeedInfo.Tpo" -c -o FeedInfo.obj `if test -f 'daemon/feed/FeedInfo.cpp'; then $(CYGPATH_W) 'daemon/feed/FeedInfo.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/feed/FeedInfo.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/FeedInfo.Tpo" "$(DEPDIR)/FeedInfo.Po"; else rm -f "$(DEPDIR)/FeedInfo.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/feed/FeedInfo.cpp' object='FeedInfo.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o FeedInfo.obj `if test -f 'daemon/feed/FeedInfo.cpp'; then $(CYGPATH_W) 'daemon/feed/FeedInfo.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/feed/FeedInfo.cpp'; fi` ColoredFrontend.o: daemon/frontend/ColoredFrontend.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ColoredFrontend.o -MD -MP -MF "$(DEPDIR)/ColoredFrontend.Tpo" -c -o ColoredFrontend.o `test -f 'daemon/frontend/ColoredFrontend.cpp' || echo '$(srcdir)/'`daemon/frontend/ColoredFrontend.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ColoredFrontend.Tpo" "$(DEPDIR)/ColoredFrontend.Po"; else rm -f "$(DEPDIR)/ColoredFrontend.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/frontend/ColoredFrontend.cpp' object='ColoredFrontend.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ColoredFrontend.o `test -f 'daemon/frontend/ColoredFrontend.cpp' || echo '$(srcdir)/'`daemon/frontend/ColoredFrontend.cpp ColoredFrontend.obj: daemon/frontend/ColoredFrontend.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ColoredFrontend.obj -MD -MP -MF "$(DEPDIR)/ColoredFrontend.Tpo" -c -o ColoredFrontend.obj `if test -f 'daemon/frontend/ColoredFrontend.cpp'; then $(CYGPATH_W) 'daemon/frontend/ColoredFrontend.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/frontend/ColoredFrontend.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ColoredFrontend.Tpo" "$(DEPDIR)/ColoredFrontend.Po"; else rm -f "$(DEPDIR)/ColoredFrontend.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/frontend/ColoredFrontend.cpp' object='ColoredFrontend.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ColoredFrontend.obj `if test -f 'daemon/frontend/ColoredFrontend.cpp'; then $(CYGPATH_W) 'daemon/frontend/ColoredFrontend.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/frontend/ColoredFrontend.cpp'; fi` Frontend.o: daemon/frontend/Frontend.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Frontend.o -MD -MP -MF "$(DEPDIR)/Frontend.Tpo" -c -o Frontend.o `test -f 'daemon/frontend/Frontend.cpp' || echo '$(srcdir)/'`daemon/frontend/Frontend.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Frontend.Tpo" "$(DEPDIR)/Frontend.Po"; else rm -f "$(DEPDIR)/Frontend.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/frontend/Frontend.cpp' object='Frontend.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Frontend.o `test -f 'daemon/frontend/Frontend.cpp' || echo '$(srcdir)/'`daemon/frontend/Frontend.cpp Frontend.obj: daemon/frontend/Frontend.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Frontend.obj -MD -MP -MF "$(DEPDIR)/Frontend.Tpo" -c -o Frontend.obj `if test -f 'daemon/frontend/Frontend.cpp'; then $(CYGPATH_W) 'daemon/frontend/Frontend.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/frontend/Frontend.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Frontend.Tpo" "$(DEPDIR)/Frontend.Po"; else rm -f "$(DEPDIR)/Frontend.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/frontend/Frontend.cpp' object='Frontend.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Frontend.obj `if test -f 'daemon/frontend/Frontend.cpp'; then $(CYGPATH_W) 'daemon/frontend/Frontend.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/frontend/Frontend.cpp'; fi` LoggableFrontend.o: daemon/frontend/LoggableFrontend.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT LoggableFrontend.o -MD -MP -MF "$(DEPDIR)/LoggableFrontend.Tpo" -c -o LoggableFrontend.o `test -f 'daemon/frontend/LoggableFrontend.cpp' || echo '$(srcdir)/'`daemon/frontend/LoggableFrontend.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/LoggableFrontend.Tpo" "$(DEPDIR)/LoggableFrontend.Po"; else rm -f "$(DEPDIR)/LoggableFrontend.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/frontend/LoggableFrontend.cpp' object='LoggableFrontend.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o LoggableFrontend.o `test -f 'daemon/frontend/LoggableFrontend.cpp' || echo '$(srcdir)/'`daemon/frontend/LoggableFrontend.cpp LoggableFrontend.obj: daemon/frontend/LoggableFrontend.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT LoggableFrontend.obj -MD -MP -MF "$(DEPDIR)/LoggableFrontend.Tpo" -c -o LoggableFrontend.obj `if test -f 'daemon/frontend/LoggableFrontend.cpp'; then $(CYGPATH_W) 'daemon/frontend/LoggableFrontend.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/frontend/LoggableFrontend.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/LoggableFrontend.Tpo" "$(DEPDIR)/LoggableFrontend.Po"; else rm -f "$(DEPDIR)/LoggableFrontend.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/frontend/LoggableFrontend.cpp' object='LoggableFrontend.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o LoggableFrontend.obj `if test -f 'daemon/frontend/LoggableFrontend.cpp'; then $(CYGPATH_W) 'daemon/frontend/LoggableFrontend.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/frontend/LoggableFrontend.cpp'; fi` NCursesFrontend.o: daemon/frontend/NCursesFrontend.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NCursesFrontend.o -MD -MP -MF "$(DEPDIR)/NCursesFrontend.Tpo" -c -o NCursesFrontend.o `test -f 'daemon/frontend/NCursesFrontend.cpp' || echo '$(srcdir)/'`daemon/frontend/NCursesFrontend.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NCursesFrontend.Tpo" "$(DEPDIR)/NCursesFrontend.Po"; else rm -f "$(DEPDIR)/NCursesFrontend.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/frontend/NCursesFrontend.cpp' object='NCursesFrontend.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NCursesFrontend.o `test -f 'daemon/frontend/NCursesFrontend.cpp' || echo '$(srcdir)/'`daemon/frontend/NCursesFrontend.cpp NCursesFrontend.obj: daemon/frontend/NCursesFrontend.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NCursesFrontend.obj -MD -MP -MF "$(DEPDIR)/NCursesFrontend.Tpo" -c -o NCursesFrontend.obj `if test -f 'daemon/frontend/NCursesFrontend.cpp'; then $(CYGPATH_W) 'daemon/frontend/NCursesFrontend.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/frontend/NCursesFrontend.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NCursesFrontend.Tpo" "$(DEPDIR)/NCursesFrontend.Po"; else rm -f "$(DEPDIR)/NCursesFrontend.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/frontend/NCursesFrontend.cpp' object='NCursesFrontend.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NCursesFrontend.obj `if test -f 'daemon/frontend/NCursesFrontend.cpp'; then $(CYGPATH_W) 'daemon/frontend/NCursesFrontend.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/frontend/NCursesFrontend.cpp'; fi` CommandLineParser.o: daemon/main/CommandLineParser.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT CommandLineParser.o -MD -MP -MF "$(DEPDIR)/CommandLineParser.Tpo" -c -o CommandLineParser.o `test -f 'daemon/main/CommandLineParser.cpp' || echo '$(srcdir)/'`daemon/main/CommandLineParser.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/CommandLineParser.Tpo" "$(DEPDIR)/CommandLineParser.Po"; else rm -f "$(DEPDIR)/CommandLineParser.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/main/CommandLineParser.cpp' object='CommandLineParser.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o CommandLineParser.o `test -f 'daemon/main/CommandLineParser.cpp' || echo '$(srcdir)/'`daemon/main/CommandLineParser.cpp CommandLineParser.obj: daemon/main/CommandLineParser.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT CommandLineParser.obj -MD -MP -MF "$(DEPDIR)/CommandLineParser.Tpo" -c -o CommandLineParser.obj `if test -f 'daemon/main/CommandLineParser.cpp'; then $(CYGPATH_W) 'daemon/main/CommandLineParser.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/main/CommandLineParser.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/CommandLineParser.Tpo" "$(DEPDIR)/CommandLineParser.Po"; else rm -f "$(DEPDIR)/CommandLineParser.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/main/CommandLineParser.cpp' object='CommandLineParser.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o CommandLineParser.obj `if test -f 'daemon/main/CommandLineParser.cpp'; then $(CYGPATH_W) 'daemon/main/CommandLineParser.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/main/CommandLineParser.cpp'; fi` DiskService.o: daemon/main/DiskService.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT DiskService.o -MD -MP -MF "$(DEPDIR)/DiskService.Tpo" -c -o DiskService.o `test -f 'daemon/main/DiskService.cpp' || echo '$(srcdir)/'`daemon/main/DiskService.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/DiskService.Tpo" "$(DEPDIR)/DiskService.Po"; else rm -f "$(DEPDIR)/DiskService.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/main/DiskService.cpp' object='DiskService.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o DiskService.o `test -f 'daemon/main/DiskService.cpp' || echo '$(srcdir)/'`daemon/main/DiskService.cpp DiskService.obj: daemon/main/DiskService.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT DiskService.obj -MD -MP -MF "$(DEPDIR)/DiskService.Tpo" -c -o DiskService.obj `if test -f 'daemon/main/DiskService.cpp'; then $(CYGPATH_W) 'daemon/main/DiskService.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/main/DiskService.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/DiskService.Tpo" "$(DEPDIR)/DiskService.Po"; else rm -f "$(DEPDIR)/DiskService.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/main/DiskService.cpp' object='DiskService.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o DiskService.obj `if test -f 'daemon/main/DiskService.cpp'; then $(CYGPATH_W) 'daemon/main/DiskService.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/main/DiskService.cpp'; fi` Maintenance.o: daemon/main/Maintenance.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Maintenance.o -MD -MP -MF "$(DEPDIR)/Maintenance.Tpo" -c -o Maintenance.o `test -f 'daemon/main/Maintenance.cpp' || echo '$(srcdir)/'`daemon/main/Maintenance.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Maintenance.Tpo" "$(DEPDIR)/Maintenance.Po"; else rm -f "$(DEPDIR)/Maintenance.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/main/Maintenance.cpp' object='Maintenance.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Maintenance.o `test -f 'daemon/main/Maintenance.cpp' || echo '$(srcdir)/'`daemon/main/Maintenance.cpp Maintenance.obj: daemon/main/Maintenance.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Maintenance.obj -MD -MP -MF "$(DEPDIR)/Maintenance.Tpo" -c -o Maintenance.obj `if test -f 'daemon/main/Maintenance.cpp'; then $(CYGPATH_W) 'daemon/main/Maintenance.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/main/Maintenance.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Maintenance.Tpo" "$(DEPDIR)/Maintenance.Po"; else rm -f "$(DEPDIR)/Maintenance.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/main/Maintenance.cpp' object='Maintenance.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Maintenance.obj `if test -f 'daemon/main/Maintenance.cpp'; then $(CYGPATH_W) 'daemon/main/Maintenance.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/main/Maintenance.cpp'; fi` nzbget.o: daemon/main/nzbget.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT nzbget.o -MD -MP -MF "$(DEPDIR)/nzbget.Tpo" -c -o nzbget.o `test -f 'daemon/main/nzbget.cpp' || echo '$(srcdir)/'`daemon/main/nzbget.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/nzbget.Tpo" "$(DEPDIR)/nzbget.Po"; else rm -f "$(DEPDIR)/nzbget.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/main/nzbget.cpp' object='nzbget.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o nzbget.o `test -f 'daemon/main/nzbget.cpp' || echo '$(srcdir)/'`daemon/main/nzbget.cpp nzbget.obj: daemon/main/nzbget.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT nzbget.obj -MD -MP -MF "$(DEPDIR)/nzbget.Tpo" -c -o nzbget.obj `if test -f 'daemon/main/nzbget.cpp'; then $(CYGPATH_W) 'daemon/main/nzbget.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/main/nzbget.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/nzbget.Tpo" "$(DEPDIR)/nzbget.Po"; else rm -f "$(DEPDIR)/nzbget.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/main/nzbget.cpp' object='nzbget.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o nzbget.obj `if test -f 'daemon/main/nzbget.cpp'; then $(CYGPATH_W) 'daemon/main/nzbget.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/main/nzbget.cpp'; fi` Options.o: daemon/main/Options.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Options.o -MD -MP -MF "$(DEPDIR)/Options.Tpo" -c -o Options.o `test -f 'daemon/main/Options.cpp' || echo '$(srcdir)/'`daemon/main/Options.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Options.Tpo" "$(DEPDIR)/Options.Po"; else rm -f "$(DEPDIR)/Options.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/main/Options.cpp' object='Options.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Options.o `test -f 'daemon/main/Options.cpp' || echo '$(srcdir)/'`daemon/main/Options.cpp Options.obj: daemon/main/Options.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Options.obj -MD -MP -MF "$(DEPDIR)/Options.Tpo" -c -o Options.obj `if test -f 'daemon/main/Options.cpp'; then $(CYGPATH_W) 'daemon/main/Options.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/main/Options.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Options.Tpo" "$(DEPDIR)/Options.Po"; else rm -f "$(DEPDIR)/Options.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/main/Options.cpp' object='Options.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Options.obj `if test -f 'daemon/main/Options.cpp'; then $(CYGPATH_W) 'daemon/main/Options.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/main/Options.cpp'; fi` Scheduler.o: daemon/main/Scheduler.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Scheduler.o -MD -MP -MF "$(DEPDIR)/Scheduler.Tpo" -c -o Scheduler.o `test -f 'daemon/main/Scheduler.cpp' || echo '$(srcdir)/'`daemon/main/Scheduler.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Scheduler.Tpo" "$(DEPDIR)/Scheduler.Po"; else rm -f "$(DEPDIR)/Scheduler.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/main/Scheduler.cpp' object='Scheduler.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Scheduler.o `test -f 'daemon/main/Scheduler.cpp' || echo '$(srcdir)/'`daemon/main/Scheduler.cpp Scheduler.obj: daemon/main/Scheduler.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Scheduler.obj -MD -MP -MF "$(DEPDIR)/Scheduler.Tpo" -c -o Scheduler.obj `if test -f 'daemon/main/Scheduler.cpp'; then $(CYGPATH_W) 'daemon/main/Scheduler.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/main/Scheduler.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Scheduler.Tpo" "$(DEPDIR)/Scheduler.Po"; else rm -f "$(DEPDIR)/Scheduler.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/main/Scheduler.cpp' object='Scheduler.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Scheduler.obj `if test -f 'daemon/main/Scheduler.cpp'; then $(CYGPATH_W) 'daemon/main/Scheduler.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/main/Scheduler.cpp'; fi` StackTrace.o: daemon/main/StackTrace.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT StackTrace.o -MD -MP -MF "$(DEPDIR)/StackTrace.Tpo" -c -o StackTrace.o `test -f 'daemon/main/StackTrace.cpp' || echo '$(srcdir)/'`daemon/main/StackTrace.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/StackTrace.Tpo" "$(DEPDIR)/StackTrace.Po"; else rm -f "$(DEPDIR)/StackTrace.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/main/StackTrace.cpp' object='StackTrace.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o StackTrace.o `test -f 'daemon/main/StackTrace.cpp' || echo '$(srcdir)/'`daemon/main/StackTrace.cpp StackTrace.obj: daemon/main/StackTrace.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT StackTrace.obj -MD -MP -MF "$(DEPDIR)/StackTrace.Tpo" -c -o StackTrace.obj `if test -f 'daemon/main/StackTrace.cpp'; then $(CYGPATH_W) 'daemon/main/StackTrace.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/main/StackTrace.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/StackTrace.Tpo" "$(DEPDIR)/StackTrace.Po"; else rm -f "$(DEPDIR)/StackTrace.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/main/StackTrace.cpp' object='StackTrace.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o StackTrace.obj `if test -f 'daemon/main/StackTrace.cpp'; then $(CYGPATH_W) 'daemon/main/StackTrace.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/main/StackTrace.cpp'; fi` ArticleDownloader.o: daemon/nntp/ArticleDownloader.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ArticleDownloader.o -MD -MP -MF "$(DEPDIR)/ArticleDownloader.Tpo" -c -o ArticleDownloader.o `test -f 'daemon/nntp/ArticleDownloader.cpp' || echo '$(srcdir)/'`daemon/nntp/ArticleDownloader.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ArticleDownloader.Tpo" "$(DEPDIR)/ArticleDownloader.Po"; else rm -f "$(DEPDIR)/ArticleDownloader.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nntp/ArticleDownloader.cpp' object='ArticleDownloader.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ArticleDownloader.o `test -f 'daemon/nntp/ArticleDownloader.cpp' || echo '$(srcdir)/'`daemon/nntp/ArticleDownloader.cpp ArticleDownloader.obj: daemon/nntp/ArticleDownloader.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ArticleDownloader.obj -MD -MP -MF "$(DEPDIR)/ArticleDownloader.Tpo" -c -o ArticleDownloader.obj `if test -f 'daemon/nntp/ArticleDownloader.cpp'; then $(CYGPATH_W) 'daemon/nntp/ArticleDownloader.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nntp/ArticleDownloader.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ArticleDownloader.Tpo" "$(DEPDIR)/ArticleDownloader.Po"; else rm -f "$(DEPDIR)/ArticleDownloader.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nntp/ArticleDownloader.cpp' object='ArticleDownloader.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ArticleDownloader.obj `if test -f 'daemon/nntp/ArticleDownloader.cpp'; then $(CYGPATH_W) 'daemon/nntp/ArticleDownloader.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nntp/ArticleDownloader.cpp'; fi` ArticleWriter.o: daemon/nntp/ArticleWriter.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ArticleWriter.o -MD -MP -MF "$(DEPDIR)/ArticleWriter.Tpo" -c -o ArticleWriter.o `test -f 'daemon/nntp/ArticleWriter.cpp' || echo '$(srcdir)/'`daemon/nntp/ArticleWriter.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ArticleWriter.Tpo" "$(DEPDIR)/ArticleWriter.Po"; else rm -f "$(DEPDIR)/ArticleWriter.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nntp/ArticleWriter.cpp' object='ArticleWriter.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ArticleWriter.o `test -f 'daemon/nntp/ArticleWriter.cpp' || echo '$(srcdir)/'`daemon/nntp/ArticleWriter.cpp ArticleWriter.obj: daemon/nntp/ArticleWriter.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ArticleWriter.obj -MD -MP -MF "$(DEPDIR)/ArticleWriter.Tpo" -c -o ArticleWriter.obj `if test -f 'daemon/nntp/ArticleWriter.cpp'; then $(CYGPATH_W) 'daemon/nntp/ArticleWriter.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nntp/ArticleWriter.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ArticleWriter.Tpo" "$(DEPDIR)/ArticleWriter.Po"; else rm -f "$(DEPDIR)/ArticleWriter.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nntp/ArticleWriter.cpp' object='ArticleWriter.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ArticleWriter.obj `if test -f 'daemon/nntp/ArticleWriter.cpp'; then $(CYGPATH_W) 'daemon/nntp/ArticleWriter.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nntp/ArticleWriter.cpp'; fi` Decoder.o: daemon/nntp/Decoder.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Decoder.o -MD -MP -MF "$(DEPDIR)/Decoder.Tpo" -c -o Decoder.o `test -f 'daemon/nntp/Decoder.cpp' || echo '$(srcdir)/'`daemon/nntp/Decoder.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Decoder.Tpo" "$(DEPDIR)/Decoder.Po"; else rm -f "$(DEPDIR)/Decoder.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nntp/Decoder.cpp' object='Decoder.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Decoder.o `test -f 'daemon/nntp/Decoder.cpp' || echo '$(srcdir)/'`daemon/nntp/Decoder.cpp Decoder.obj: daemon/nntp/Decoder.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Decoder.obj -MD -MP -MF "$(DEPDIR)/Decoder.Tpo" -c -o Decoder.obj `if test -f 'daemon/nntp/Decoder.cpp'; then $(CYGPATH_W) 'daemon/nntp/Decoder.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nntp/Decoder.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Decoder.Tpo" "$(DEPDIR)/Decoder.Po"; else rm -f "$(DEPDIR)/Decoder.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nntp/Decoder.cpp' object='Decoder.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Decoder.obj `if test -f 'daemon/nntp/Decoder.cpp'; then $(CYGPATH_W) 'daemon/nntp/Decoder.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nntp/Decoder.cpp'; fi` NewsServer.o: daemon/nntp/NewsServer.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NewsServer.o -MD -MP -MF "$(DEPDIR)/NewsServer.Tpo" -c -o NewsServer.o `test -f 'daemon/nntp/NewsServer.cpp' || echo '$(srcdir)/'`daemon/nntp/NewsServer.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NewsServer.Tpo" "$(DEPDIR)/NewsServer.Po"; else rm -f "$(DEPDIR)/NewsServer.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nntp/NewsServer.cpp' object='NewsServer.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NewsServer.o `test -f 'daemon/nntp/NewsServer.cpp' || echo '$(srcdir)/'`daemon/nntp/NewsServer.cpp NewsServer.obj: daemon/nntp/NewsServer.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NewsServer.obj -MD -MP -MF "$(DEPDIR)/NewsServer.Tpo" -c -o NewsServer.obj `if test -f 'daemon/nntp/NewsServer.cpp'; then $(CYGPATH_W) 'daemon/nntp/NewsServer.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nntp/NewsServer.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NewsServer.Tpo" "$(DEPDIR)/NewsServer.Po"; else rm -f "$(DEPDIR)/NewsServer.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nntp/NewsServer.cpp' object='NewsServer.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NewsServer.obj `if test -f 'daemon/nntp/NewsServer.cpp'; then $(CYGPATH_W) 'daemon/nntp/NewsServer.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nntp/NewsServer.cpp'; fi` NNTPConnection.o: daemon/nntp/NNTPConnection.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NNTPConnection.o -MD -MP -MF "$(DEPDIR)/NNTPConnection.Tpo" -c -o NNTPConnection.o `test -f 'daemon/nntp/NNTPConnection.cpp' || echo '$(srcdir)/'`daemon/nntp/NNTPConnection.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NNTPConnection.Tpo" "$(DEPDIR)/NNTPConnection.Po"; else rm -f "$(DEPDIR)/NNTPConnection.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nntp/NNTPConnection.cpp' object='NNTPConnection.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NNTPConnection.o `test -f 'daemon/nntp/NNTPConnection.cpp' || echo '$(srcdir)/'`daemon/nntp/NNTPConnection.cpp NNTPConnection.obj: daemon/nntp/NNTPConnection.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NNTPConnection.obj -MD -MP -MF "$(DEPDIR)/NNTPConnection.Tpo" -c -o NNTPConnection.obj `if test -f 'daemon/nntp/NNTPConnection.cpp'; then $(CYGPATH_W) 'daemon/nntp/NNTPConnection.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nntp/NNTPConnection.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NNTPConnection.Tpo" "$(DEPDIR)/NNTPConnection.Po"; else rm -f "$(DEPDIR)/NNTPConnection.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nntp/NNTPConnection.cpp' object='NNTPConnection.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NNTPConnection.obj `if test -f 'daemon/nntp/NNTPConnection.cpp'; then $(CYGPATH_W) 'daemon/nntp/NNTPConnection.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nntp/NNTPConnection.cpp'; fi` ServerPool.o: daemon/nntp/ServerPool.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ServerPool.o -MD -MP -MF "$(DEPDIR)/ServerPool.Tpo" -c -o ServerPool.o `test -f 'daemon/nntp/ServerPool.cpp' || echo '$(srcdir)/'`daemon/nntp/ServerPool.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ServerPool.Tpo" "$(DEPDIR)/ServerPool.Po"; else rm -f "$(DEPDIR)/ServerPool.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nntp/ServerPool.cpp' object='ServerPool.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ServerPool.o `test -f 'daemon/nntp/ServerPool.cpp' || echo '$(srcdir)/'`daemon/nntp/ServerPool.cpp ServerPool.obj: daemon/nntp/ServerPool.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ServerPool.obj -MD -MP -MF "$(DEPDIR)/ServerPool.Tpo" -c -o ServerPool.obj `if test -f 'daemon/nntp/ServerPool.cpp'; then $(CYGPATH_W) 'daemon/nntp/ServerPool.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nntp/ServerPool.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ServerPool.Tpo" "$(DEPDIR)/ServerPool.Po"; else rm -f "$(DEPDIR)/ServerPool.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nntp/ServerPool.cpp' object='ServerPool.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ServerPool.obj `if test -f 'daemon/nntp/ServerPool.cpp'; then $(CYGPATH_W) 'daemon/nntp/ServerPool.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nntp/ServerPool.cpp'; fi` StatMeter.o: daemon/nntp/StatMeter.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT StatMeter.o -MD -MP -MF "$(DEPDIR)/StatMeter.Tpo" -c -o StatMeter.o `test -f 'daemon/nntp/StatMeter.cpp' || echo '$(srcdir)/'`daemon/nntp/StatMeter.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/StatMeter.Tpo" "$(DEPDIR)/StatMeter.Po"; else rm -f "$(DEPDIR)/StatMeter.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nntp/StatMeter.cpp' object='StatMeter.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o StatMeter.o `test -f 'daemon/nntp/StatMeter.cpp' || echo '$(srcdir)/'`daemon/nntp/StatMeter.cpp StatMeter.obj: daemon/nntp/StatMeter.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT StatMeter.obj -MD -MP -MF "$(DEPDIR)/StatMeter.Tpo" -c -o StatMeter.obj `if test -f 'daemon/nntp/StatMeter.cpp'; then $(CYGPATH_W) 'daemon/nntp/StatMeter.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nntp/StatMeter.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/StatMeter.Tpo" "$(DEPDIR)/StatMeter.Po"; else rm -f "$(DEPDIR)/StatMeter.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/nntp/StatMeter.cpp' object='StatMeter.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o StatMeter.obj `if test -f 'daemon/nntp/StatMeter.cpp'; then $(CYGPATH_W) 'daemon/nntp/StatMeter.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/nntp/StatMeter.cpp'; fi` Cleanup.o: daemon/postprocess/Cleanup.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Cleanup.o -MD -MP -MF "$(DEPDIR)/Cleanup.Tpo" -c -o Cleanup.o `test -f 'daemon/postprocess/Cleanup.cpp' || echo '$(srcdir)/'`daemon/postprocess/Cleanup.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Cleanup.Tpo" "$(DEPDIR)/Cleanup.Po"; else rm -f "$(DEPDIR)/Cleanup.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/Cleanup.cpp' object='Cleanup.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Cleanup.o `test -f 'daemon/postprocess/Cleanup.cpp' || echo '$(srcdir)/'`daemon/postprocess/Cleanup.cpp Cleanup.obj: daemon/postprocess/Cleanup.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Cleanup.obj -MD -MP -MF "$(DEPDIR)/Cleanup.Tpo" -c -o Cleanup.obj `if test -f 'daemon/postprocess/Cleanup.cpp'; then $(CYGPATH_W) 'daemon/postprocess/Cleanup.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/Cleanup.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Cleanup.Tpo" "$(DEPDIR)/Cleanup.Po"; else rm -f "$(DEPDIR)/Cleanup.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/Cleanup.cpp' object='Cleanup.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Cleanup.obj `if test -f 'daemon/postprocess/Cleanup.cpp'; then $(CYGPATH_W) 'daemon/postprocess/Cleanup.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/Cleanup.cpp'; fi` DupeMatcher.o: daemon/postprocess/DupeMatcher.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT DupeMatcher.o -MD -MP -MF "$(DEPDIR)/DupeMatcher.Tpo" -c -o DupeMatcher.o `test -f 'daemon/postprocess/DupeMatcher.cpp' || echo '$(srcdir)/'`daemon/postprocess/DupeMatcher.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/DupeMatcher.Tpo" "$(DEPDIR)/DupeMatcher.Po"; else rm -f "$(DEPDIR)/DupeMatcher.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/DupeMatcher.cpp' object='DupeMatcher.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o DupeMatcher.o `test -f 'daemon/postprocess/DupeMatcher.cpp' || echo '$(srcdir)/'`daemon/postprocess/DupeMatcher.cpp DupeMatcher.obj: daemon/postprocess/DupeMatcher.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT DupeMatcher.obj -MD -MP -MF "$(DEPDIR)/DupeMatcher.Tpo" -c -o DupeMatcher.obj `if test -f 'daemon/postprocess/DupeMatcher.cpp'; then $(CYGPATH_W) 'daemon/postprocess/DupeMatcher.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/DupeMatcher.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/DupeMatcher.Tpo" "$(DEPDIR)/DupeMatcher.Po"; else rm -f "$(DEPDIR)/DupeMatcher.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/DupeMatcher.cpp' object='DupeMatcher.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o DupeMatcher.obj `if test -f 'daemon/postprocess/DupeMatcher.cpp'; then $(CYGPATH_W) 'daemon/postprocess/DupeMatcher.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/DupeMatcher.cpp'; fi` ParChecker.o: daemon/postprocess/ParChecker.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ParChecker.o -MD -MP -MF "$(DEPDIR)/ParChecker.Tpo" -c -o ParChecker.o `test -f 'daemon/postprocess/ParChecker.cpp' || echo '$(srcdir)/'`daemon/postprocess/ParChecker.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ParChecker.Tpo" "$(DEPDIR)/ParChecker.Po"; else rm -f "$(DEPDIR)/ParChecker.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/ParChecker.cpp' object='ParChecker.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ParChecker.o `test -f 'daemon/postprocess/ParChecker.cpp' || echo '$(srcdir)/'`daemon/postprocess/ParChecker.cpp ParChecker.obj: daemon/postprocess/ParChecker.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ParChecker.obj -MD -MP -MF "$(DEPDIR)/ParChecker.Tpo" -c -o ParChecker.obj `if test -f 'daemon/postprocess/ParChecker.cpp'; then $(CYGPATH_W) 'daemon/postprocess/ParChecker.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/ParChecker.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ParChecker.Tpo" "$(DEPDIR)/ParChecker.Po"; else rm -f "$(DEPDIR)/ParChecker.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/ParChecker.cpp' object='ParChecker.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ParChecker.obj `if test -f 'daemon/postprocess/ParChecker.cpp'; then $(CYGPATH_W) 'daemon/postprocess/ParChecker.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/ParChecker.cpp'; fi` ParCoordinator.o: daemon/postprocess/ParCoordinator.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ParCoordinator.o -MD -MP -MF "$(DEPDIR)/ParCoordinator.Tpo" -c -o ParCoordinator.o `test -f 'daemon/postprocess/ParCoordinator.cpp' || echo '$(srcdir)/'`daemon/postprocess/ParCoordinator.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ParCoordinator.Tpo" "$(DEPDIR)/ParCoordinator.Po"; else rm -f "$(DEPDIR)/ParCoordinator.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/ParCoordinator.cpp' object='ParCoordinator.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ParCoordinator.o `test -f 'daemon/postprocess/ParCoordinator.cpp' || echo '$(srcdir)/'`daemon/postprocess/ParCoordinator.cpp ParCoordinator.obj: daemon/postprocess/ParCoordinator.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ParCoordinator.obj -MD -MP -MF "$(DEPDIR)/ParCoordinator.Tpo" -c -o ParCoordinator.obj `if test -f 'daemon/postprocess/ParCoordinator.cpp'; then $(CYGPATH_W) 'daemon/postprocess/ParCoordinator.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/ParCoordinator.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ParCoordinator.Tpo" "$(DEPDIR)/ParCoordinator.Po"; else rm -f "$(DEPDIR)/ParCoordinator.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/ParCoordinator.cpp' object='ParCoordinator.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ParCoordinator.obj `if test -f 'daemon/postprocess/ParCoordinator.cpp'; then $(CYGPATH_W) 'daemon/postprocess/ParCoordinator.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/ParCoordinator.cpp'; fi` ParParser.o: daemon/postprocess/ParParser.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ParParser.o -MD -MP -MF "$(DEPDIR)/ParParser.Tpo" -c -o ParParser.o `test -f 'daemon/postprocess/ParParser.cpp' || echo '$(srcdir)/'`daemon/postprocess/ParParser.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ParParser.Tpo" "$(DEPDIR)/ParParser.Po"; else rm -f "$(DEPDIR)/ParParser.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/ParParser.cpp' object='ParParser.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ParParser.o `test -f 'daemon/postprocess/ParParser.cpp' || echo '$(srcdir)/'`daemon/postprocess/ParParser.cpp ParParser.obj: daemon/postprocess/ParParser.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ParParser.obj -MD -MP -MF "$(DEPDIR)/ParParser.Tpo" -c -o ParParser.obj `if test -f 'daemon/postprocess/ParParser.cpp'; then $(CYGPATH_W) 'daemon/postprocess/ParParser.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/ParParser.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ParParser.Tpo" "$(DEPDIR)/ParParser.Po"; else rm -f "$(DEPDIR)/ParParser.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/ParParser.cpp' object='ParParser.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ParParser.obj `if test -f 'daemon/postprocess/ParParser.cpp'; then $(CYGPATH_W) 'daemon/postprocess/ParParser.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/ParParser.cpp'; fi` ParRenamer.o: daemon/postprocess/ParRenamer.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ParRenamer.o -MD -MP -MF "$(DEPDIR)/ParRenamer.Tpo" -c -o ParRenamer.o `test -f 'daemon/postprocess/ParRenamer.cpp' || echo '$(srcdir)/'`daemon/postprocess/ParRenamer.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ParRenamer.Tpo" "$(DEPDIR)/ParRenamer.Po"; else rm -f "$(DEPDIR)/ParRenamer.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/ParRenamer.cpp' object='ParRenamer.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ParRenamer.o `test -f 'daemon/postprocess/ParRenamer.cpp' || echo '$(srcdir)/'`daemon/postprocess/ParRenamer.cpp ParRenamer.obj: daemon/postprocess/ParRenamer.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ParRenamer.obj -MD -MP -MF "$(DEPDIR)/ParRenamer.Tpo" -c -o ParRenamer.obj `if test -f 'daemon/postprocess/ParRenamer.cpp'; then $(CYGPATH_W) 'daemon/postprocess/ParRenamer.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/ParRenamer.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ParRenamer.Tpo" "$(DEPDIR)/ParRenamer.Po"; else rm -f "$(DEPDIR)/ParRenamer.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/ParRenamer.cpp' object='ParRenamer.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ParRenamer.obj `if test -f 'daemon/postprocess/ParRenamer.cpp'; then $(CYGPATH_W) 'daemon/postprocess/ParRenamer.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/ParRenamer.cpp'; fi` PrePostProcessor.o: daemon/postprocess/PrePostProcessor.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT PrePostProcessor.o -MD -MP -MF "$(DEPDIR)/PrePostProcessor.Tpo" -c -o PrePostProcessor.o `test -f 'daemon/postprocess/PrePostProcessor.cpp' || echo '$(srcdir)/'`daemon/postprocess/PrePostProcessor.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/PrePostProcessor.Tpo" "$(DEPDIR)/PrePostProcessor.Po"; else rm -f "$(DEPDIR)/PrePostProcessor.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/PrePostProcessor.cpp' object='PrePostProcessor.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o PrePostProcessor.o `test -f 'daemon/postprocess/PrePostProcessor.cpp' || echo '$(srcdir)/'`daemon/postprocess/PrePostProcessor.cpp PrePostProcessor.obj: daemon/postprocess/PrePostProcessor.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT PrePostProcessor.obj -MD -MP -MF "$(DEPDIR)/PrePostProcessor.Tpo" -c -o PrePostProcessor.obj `if test -f 'daemon/postprocess/PrePostProcessor.cpp'; then $(CYGPATH_W) 'daemon/postprocess/PrePostProcessor.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/PrePostProcessor.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/PrePostProcessor.Tpo" "$(DEPDIR)/PrePostProcessor.Po"; else rm -f "$(DEPDIR)/PrePostProcessor.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/PrePostProcessor.cpp' object='PrePostProcessor.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o PrePostProcessor.obj `if test -f 'daemon/postprocess/PrePostProcessor.cpp'; then $(CYGPATH_W) 'daemon/postprocess/PrePostProcessor.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/PrePostProcessor.cpp'; fi` Unpack.o: daemon/postprocess/Unpack.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Unpack.o -MD -MP -MF "$(DEPDIR)/Unpack.Tpo" -c -o Unpack.o `test -f 'daemon/postprocess/Unpack.cpp' || echo '$(srcdir)/'`daemon/postprocess/Unpack.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Unpack.Tpo" "$(DEPDIR)/Unpack.Po"; else rm -f "$(DEPDIR)/Unpack.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/Unpack.cpp' object='Unpack.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Unpack.o `test -f 'daemon/postprocess/Unpack.cpp' || echo '$(srcdir)/'`daemon/postprocess/Unpack.cpp Unpack.obj: daemon/postprocess/Unpack.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Unpack.obj -MD -MP -MF "$(DEPDIR)/Unpack.Tpo" -c -o Unpack.obj `if test -f 'daemon/postprocess/Unpack.cpp'; then $(CYGPATH_W) 'daemon/postprocess/Unpack.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/Unpack.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Unpack.Tpo" "$(DEPDIR)/Unpack.Po"; else rm -f "$(DEPDIR)/Unpack.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/postprocess/Unpack.cpp' object='Unpack.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Unpack.obj `if test -f 'daemon/postprocess/Unpack.cpp'; then $(CYGPATH_W) 'daemon/postprocess/Unpack.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/postprocess/Unpack.cpp'; fi` DiskState.o: daemon/queue/DiskState.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT DiskState.o -MD -MP -MF "$(DEPDIR)/DiskState.Tpo" -c -o DiskState.o `test -f 'daemon/queue/DiskState.cpp' || echo '$(srcdir)/'`daemon/queue/DiskState.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/DiskState.Tpo" "$(DEPDIR)/DiskState.Po"; else rm -f "$(DEPDIR)/DiskState.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/queue/DiskState.cpp' object='DiskState.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o DiskState.o `test -f 'daemon/queue/DiskState.cpp' || echo '$(srcdir)/'`daemon/queue/DiskState.cpp DiskState.obj: daemon/queue/DiskState.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT DiskState.obj -MD -MP -MF "$(DEPDIR)/DiskState.Tpo" -c -o DiskState.obj `if test -f 'daemon/queue/DiskState.cpp'; then $(CYGPATH_W) 'daemon/queue/DiskState.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/queue/DiskState.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/DiskState.Tpo" "$(DEPDIR)/DiskState.Po"; else rm -f "$(DEPDIR)/DiskState.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/queue/DiskState.cpp' object='DiskState.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o DiskState.obj `if test -f 'daemon/queue/DiskState.cpp'; then $(CYGPATH_W) 'daemon/queue/DiskState.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/queue/DiskState.cpp'; fi` DownloadInfo.o: daemon/queue/DownloadInfo.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT DownloadInfo.o -MD -MP -MF "$(DEPDIR)/DownloadInfo.Tpo" -c -o DownloadInfo.o `test -f 'daemon/queue/DownloadInfo.cpp' || echo '$(srcdir)/'`daemon/queue/DownloadInfo.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/DownloadInfo.Tpo" "$(DEPDIR)/DownloadInfo.Po"; else rm -f "$(DEPDIR)/DownloadInfo.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/queue/DownloadInfo.cpp' object='DownloadInfo.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o DownloadInfo.o `test -f 'daemon/queue/DownloadInfo.cpp' || echo '$(srcdir)/'`daemon/queue/DownloadInfo.cpp DownloadInfo.obj: daemon/queue/DownloadInfo.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT DownloadInfo.obj -MD -MP -MF "$(DEPDIR)/DownloadInfo.Tpo" -c -o DownloadInfo.obj `if test -f 'daemon/queue/DownloadInfo.cpp'; then $(CYGPATH_W) 'daemon/queue/DownloadInfo.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/queue/DownloadInfo.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/DownloadInfo.Tpo" "$(DEPDIR)/DownloadInfo.Po"; else rm -f "$(DEPDIR)/DownloadInfo.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/queue/DownloadInfo.cpp' object='DownloadInfo.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o DownloadInfo.obj `if test -f 'daemon/queue/DownloadInfo.cpp'; then $(CYGPATH_W) 'daemon/queue/DownloadInfo.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/queue/DownloadInfo.cpp'; fi` DupeCoordinator.o: daemon/queue/DupeCoordinator.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT DupeCoordinator.o -MD -MP -MF "$(DEPDIR)/DupeCoordinator.Tpo" -c -o DupeCoordinator.o `test -f 'daemon/queue/DupeCoordinator.cpp' || echo '$(srcdir)/'`daemon/queue/DupeCoordinator.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/DupeCoordinator.Tpo" "$(DEPDIR)/DupeCoordinator.Po"; else rm -f "$(DEPDIR)/DupeCoordinator.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/queue/DupeCoordinator.cpp' object='DupeCoordinator.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o DupeCoordinator.o `test -f 'daemon/queue/DupeCoordinator.cpp' || echo '$(srcdir)/'`daemon/queue/DupeCoordinator.cpp DupeCoordinator.obj: daemon/queue/DupeCoordinator.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT DupeCoordinator.obj -MD -MP -MF "$(DEPDIR)/DupeCoordinator.Tpo" -c -o DupeCoordinator.obj `if test -f 'daemon/queue/DupeCoordinator.cpp'; then $(CYGPATH_W) 'daemon/queue/DupeCoordinator.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/queue/DupeCoordinator.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/DupeCoordinator.Tpo" "$(DEPDIR)/DupeCoordinator.Po"; else rm -f "$(DEPDIR)/DupeCoordinator.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/queue/DupeCoordinator.cpp' object='DupeCoordinator.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o DupeCoordinator.obj `if test -f 'daemon/queue/DupeCoordinator.cpp'; then $(CYGPATH_W) 'daemon/queue/DupeCoordinator.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/queue/DupeCoordinator.cpp'; fi` HistoryCoordinator.o: daemon/queue/HistoryCoordinator.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT HistoryCoordinator.o -MD -MP -MF "$(DEPDIR)/HistoryCoordinator.Tpo" -c -o HistoryCoordinator.o `test -f 'daemon/queue/HistoryCoordinator.cpp' || echo '$(srcdir)/'`daemon/queue/HistoryCoordinator.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/HistoryCoordinator.Tpo" "$(DEPDIR)/HistoryCoordinator.Po"; else rm -f "$(DEPDIR)/HistoryCoordinator.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/queue/HistoryCoordinator.cpp' object='HistoryCoordinator.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o HistoryCoordinator.o `test -f 'daemon/queue/HistoryCoordinator.cpp' || echo '$(srcdir)/'`daemon/queue/HistoryCoordinator.cpp HistoryCoordinator.obj: daemon/queue/HistoryCoordinator.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT HistoryCoordinator.obj -MD -MP -MF "$(DEPDIR)/HistoryCoordinator.Tpo" -c -o HistoryCoordinator.obj `if test -f 'daemon/queue/HistoryCoordinator.cpp'; then $(CYGPATH_W) 'daemon/queue/HistoryCoordinator.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/queue/HistoryCoordinator.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/HistoryCoordinator.Tpo" "$(DEPDIR)/HistoryCoordinator.Po"; else rm -f "$(DEPDIR)/HistoryCoordinator.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/queue/HistoryCoordinator.cpp' object='HistoryCoordinator.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o HistoryCoordinator.obj `if test -f 'daemon/queue/HistoryCoordinator.cpp'; then $(CYGPATH_W) 'daemon/queue/HistoryCoordinator.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/queue/HistoryCoordinator.cpp'; fi` NZBFile.o: daemon/queue/NZBFile.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NZBFile.o -MD -MP -MF "$(DEPDIR)/NZBFile.Tpo" -c -o NZBFile.o `test -f 'daemon/queue/NZBFile.cpp' || echo '$(srcdir)/'`daemon/queue/NZBFile.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NZBFile.Tpo" "$(DEPDIR)/NZBFile.Po"; else rm -f "$(DEPDIR)/NZBFile.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/queue/NZBFile.cpp' object='NZBFile.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NZBFile.o `test -f 'daemon/queue/NZBFile.cpp' || echo '$(srcdir)/'`daemon/queue/NZBFile.cpp NZBFile.obj: daemon/queue/NZBFile.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NZBFile.obj -MD -MP -MF "$(DEPDIR)/NZBFile.Tpo" -c -o NZBFile.obj `if test -f 'daemon/queue/NZBFile.cpp'; then $(CYGPATH_W) 'daemon/queue/NZBFile.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/queue/NZBFile.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NZBFile.Tpo" "$(DEPDIR)/NZBFile.Po"; else rm -f "$(DEPDIR)/NZBFile.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/queue/NZBFile.cpp' object='NZBFile.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NZBFile.obj `if test -f 'daemon/queue/NZBFile.cpp'; then $(CYGPATH_W) 'daemon/queue/NZBFile.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/queue/NZBFile.cpp'; fi` QueueCoordinator.o: daemon/queue/QueueCoordinator.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT QueueCoordinator.o -MD -MP -MF "$(DEPDIR)/QueueCoordinator.Tpo" -c -o QueueCoordinator.o `test -f 'daemon/queue/QueueCoordinator.cpp' || echo '$(srcdir)/'`daemon/queue/QueueCoordinator.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/QueueCoordinator.Tpo" "$(DEPDIR)/QueueCoordinator.Po"; else rm -f "$(DEPDIR)/QueueCoordinator.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/queue/QueueCoordinator.cpp' object='QueueCoordinator.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o QueueCoordinator.o `test -f 'daemon/queue/QueueCoordinator.cpp' || echo '$(srcdir)/'`daemon/queue/QueueCoordinator.cpp QueueCoordinator.obj: daemon/queue/QueueCoordinator.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT QueueCoordinator.obj -MD -MP -MF "$(DEPDIR)/QueueCoordinator.Tpo" -c -o QueueCoordinator.obj `if test -f 'daemon/queue/QueueCoordinator.cpp'; then $(CYGPATH_W) 'daemon/queue/QueueCoordinator.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/queue/QueueCoordinator.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/QueueCoordinator.Tpo" "$(DEPDIR)/QueueCoordinator.Po"; else rm -f "$(DEPDIR)/QueueCoordinator.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/queue/QueueCoordinator.cpp' object='QueueCoordinator.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o QueueCoordinator.obj `if test -f 'daemon/queue/QueueCoordinator.cpp'; then $(CYGPATH_W) 'daemon/queue/QueueCoordinator.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/queue/QueueCoordinator.cpp'; fi` QueueEditor.o: daemon/queue/QueueEditor.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT QueueEditor.o -MD -MP -MF "$(DEPDIR)/QueueEditor.Tpo" -c -o QueueEditor.o `test -f 'daemon/queue/QueueEditor.cpp' || echo '$(srcdir)/'`daemon/queue/QueueEditor.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/QueueEditor.Tpo" "$(DEPDIR)/QueueEditor.Po"; else rm -f "$(DEPDIR)/QueueEditor.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/queue/QueueEditor.cpp' object='QueueEditor.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o QueueEditor.o `test -f 'daemon/queue/QueueEditor.cpp' || echo '$(srcdir)/'`daemon/queue/QueueEditor.cpp QueueEditor.obj: daemon/queue/QueueEditor.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT QueueEditor.obj -MD -MP -MF "$(DEPDIR)/QueueEditor.Tpo" -c -o QueueEditor.obj `if test -f 'daemon/queue/QueueEditor.cpp'; then $(CYGPATH_W) 'daemon/queue/QueueEditor.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/queue/QueueEditor.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/QueueEditor.Tpo" "$(DEPDIR)/QueueEditor.Po"; else rm -f "$(DEPDIR)/QueueEditor.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/queue/QueueEditor.cpp' object='QueueEditor.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o QueueEditor.obj `if test -f 'daemon/queue/QueueEditor.cpp'; then $(CYGPATH_W) 'daemon/queue/QueueEditor.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/queue/QueueEditor.cpp'; fi` Scanner.o: daemon/queue/Scanner.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Scanner.o -MD -MP -MF "$(DEPDIR)/Scanner.Tpo" -c -o Scanner.o `test -f 'daemon/queue/Scanner.cpp' || echo '$(srcdir)/'`daemon/queue/Scanner.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Scanner.Tpo" "$(DEPDIR)/Scanner.Po"; else rm -f "$(DEPDIR)/Scanner.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/queue/Scanner.cpp' object='Scanner.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Scanner.o `test -f 'daemon/queue/Scanner.cpp' || echo '$(srcdir)/'`daemon/queue/Scanner.cpp Scanner.obj: daemon/queue/Scanner.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Scanner.obj -MD -MP -MF "$(DEPDIR)/Scanner.Tpo" -c -o Scanner.obj `if test -f 'daemon/queue/Scanner.cpp'; then $(CYGPATH_W) 'daemon/queue/Scanner.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/queue/Scanner.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Scanner.Tpo" "$(DEPDIR)/Scanner.Po"; else rm -f "$(DEPDIR)/Scanner.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/queue/Scanner.cpp' object='Scanner.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Scanner.obj `if test -f 'daemon/queue/Scanner.cpp'; then $(CYGPATH_W) 'daemon/queue/Scanner.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/queue/Scanner.cpp'; fi` UrlCoordinator.o: daemon/queue/UrlCoordinator.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT UrlCoordinator.o -MD -MP -MF "$(DEPDIR)/UrlCoordinator.Tpo" -c -o UrlCoordinator.o `test -f 'daemon/queue/UrlCoordinator.cpp' || echo '$(srcdir)/'`daemon/queue/UrlCoordinator.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/UrlCoordinator.Tpo" "$(DEPDIR)/UrlCoordinator.Po"; else rm -f "$(DEPDIR)/UrlCoordinator.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/queue/UrlCoordinator.cpp' object='UrlCoordinator.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o UrlCoordinator.o `test -f 'daemon/queue/UrlCoordinator.cpp' || echo '$(srcdir)/'`daemon/queue/UrlCoordinator.cpp UrlCoordinator.obj: daemon/queue/UrlCoordinator.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT UrlCoordinator.obj -MD -MP -MF "$(DEPDIR)/UrlCoordinator.Tpo" -c -o UrlCoordinator.obj `if test -f 'daemon/queue/UrlCoordinator.cpp'; then $(CYGPATH_W) 'daemon/queue/UrlCoordinator.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/queue/UrlCoordinator.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/UrlCoordinator.Tpo" "$(DEPDIR)/UrlCoordinator.Po"; else rm -f "$(DEPDIR)/UrlCoordinator.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/queue/UrlCoordinator.cpp' object='UrlCoordinator.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o UrlCoordinator.obj `if test -f 'daemon/queue/UrlCoordinator.cpp'; then $(CYGPATH_W) 'daemon/queue/UrlCoordinator.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/queue/UrlCoordinator.cpp'; fi` BinRpc.o: daemon/remote/BinRpc.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT BinRpc.o -MD -MP -MF "$(DEPDIR)/BinRpc.Tpo" -c -o BinRpc.o `test -f 'daemon/remote/BinRpc.cpp' || echo '$(srcdir)/'`daemon/remote/BinRpc.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/BinRpc.Tpo" "$(DEPDIR)/BinRpc.Po"; else rm -f "$(DEPDIR)/BinRpc.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/remote/BinRpc.cpp' object='BinRpc.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o BinRpc.o `test -f 'daemon/remote/BinRpc.cpp' || echo '$(srcdir)/'`daemon/remote/BinRpc.cpp BinRpc.obj: daemon/remote/BinRpc.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT BinRpc.obj -MD -MP -MF "$(DEPDIR)/BinRpc.Tpo" -c -o BinRpc.obj `if test -f 'daemon/remote/BinRpc.cpp'; then $(CYGPATH_W) 'daemon/remote/BinRpc.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/remote/BinRpc.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/BinRpc.Tpo" "$(DEPDIR)/BinRpc.Po"; else rm -f "$(DEPDIR)/BinRpc.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/remote/BinRpc.cpp' object='BinRpc.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o BinRpc.obj `if test -f 'daemon/remote/BinRpc.cpp'; then $(CYGPATH_W) 'daemon/remote/BinRpc.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/remote/BinRpc.cpp'; fi` RemoteClient.o: daemon/remote/RemoteClient.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT RemoteClient.o -MD -MP -MF "$(DEPDIR)/RemoteClient.Tpo" -c -o RemoteClient.o `test -f 'daemon/remote/RemoteClient.cpp' || echo '$(srcdir)/'`daemon/remote/RemoteClient.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/RemoteClient.Tpo" "$(DEPDIR)/RemoteClient.Po"; else rm -f "$(DEPDIR)/RemoteClient.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/remote/RemoteClient.cpp' object='RemoteClient.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o RemoteClient.o `test -f 'daemon/remote/RemoteClient.cpp' || echo '$(srcdir)/'`daemon/remote/RemoteClient.cpp RemoteClient.obj: daemon/remote/RemoteClient.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT RemoteClient.obj -MD -MP -MF "$(DEPDIR)/RemoteClient.Tpo" -c -o RemoteClient.obj `if test -f 'daemon/remote/RemoteClient.cpp'; then $(CYGPATH_W) 'daemon/remote/RemoteClient.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/remote/RemoteClient.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/RemoteClient.Tpo" "$(DEPDIR)/RemoteClient.Po"; else rm -f "$(DEPDIR)/RemoteClient.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/remote/RemoteClient.cpp' object='RemoteClient.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o RemoteClient.obj `if test -f 'daemon/remote/RemoteClient.cpp'; then $(CYGPATH_W) 'daemon/remote/RemoteClient.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/remote/RemoteClient.cpp'; fi` RemoteServer.o: daemon/remote/RemoteServer.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT RemoteServer.o -MD -MP -MF "$(DEPDIR)/RemoteServer.Tpo" -c -o RemoteServer.o `test -f 'daemon/remote/RemoteServer.cpp' || echo '$(srcdir)/'`daemon/remote/RemoteServer.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/RemoteServer.Tpo" "$(DEPDIR)/RemoteServer.Po"; else rm -f "$(DEPDIR)/RemoteServer.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/remote/RemoteServer.cpp' object='RemoteServer.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o RemoteServer.o `test -f 'daemon/remote/RemoteServer.cpp' || echo '$(srcdir)/'`daemon/remote/RemoteServer.cpp RemoteServer.obj: daemon/remote/RemoteServer.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT RemoteServer.obj -MD -MP -MF "$(DEPDIR)/RemoteServer.Tpo" -c -o RemoteServer.obj `if test -f 'daemon/remote/RemoteServer.cpp'; then $(CYGPATH_W) 'daemon/remote/RemoteServer.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/remote/RemoteServer.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/RemoteServer.Tpo" "$(DEPDIR)/RemoteServer.Po"; else rm -f "$(DEPDIR)/RemoteServer.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/remote/RemoteServer.cpp' object='RemoteServer.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o RemoteServer.obj `if test -f 'daemon/remote/RemoteServer.cpp'; then $(CYGPATH_W) 'daemon/remote/RemoteServer.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/remote/RemoteServer.cpp'; fi` WebServer.o: daemon/remote/WebServer.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT WebServer.o -MD -MP -MF "$(DEPDIR)/WebServer.Tpo" -c -o WebServer.o `test -f 'daemon/remote/WebServer.cpp' || echo '$(srcdir)/'`daemon/remote/WebServer.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/WebServer.Tpo" "$(DEPDIR)/WebServer.Po"; else rm -f "$(DEPDIR)/WebServer.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/remote/WebServer.cpp' object='WebServer.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o WebServer.o `test -f 'daemon/remote/WebServer.cpp' || echo '$(srcdir)/'`daemon/remote/WebServer.cpp WebServer.obj: daemon/remote/WebServer.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT WebServer.obj -MD -MP -MF "$(DEPDIR)/WebServer.Tpo" -c -o WebServer.obj `if test -f 'daemon/remote/WebServer.cpp'; then $(CYGPATH_W) 'daemon/remote/WebServer.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/remote/WebServer.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/WebServer.Tpo" "$(DEPDIR)/WebServer.Po"; else rm -f "$(DEPDIR)/WebServer.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/remote/WebServer.cpp' object='WebServer.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o WebServer.obj `if test -f 'daemon/remote/WebServer.cpp'; then $(CYGPATH_W) 'daemon/remote/WebServer.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/remote/WebServer.cpp'; fi` XmlRpc.o: daemon/remote/XmlRpc.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT XmlRpc.o -MD -MP -MF "$(DEPDIR)/XmlRpc.Tpo" -c -o XmlRpc.o `test -f 'daemon/remote/XmlRpc.cpp' || echo '$(srcdir)/'`daemon/remote/XmlRpc.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/XmlRpc.Tpo" "$(DEPDIR)/XmlRpc.Po"; else rm -f "$(DEPDIR)/XmlRpc.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/remote/XmlRpc.cpp' object='XmlRpc.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o XmlRpc.o `test -f 'daemon/remote/XmlRpc.cpp' || echo '$(srcdir)/'`daemon/remote/XmlRpc.cpp XmlRpc.obj: daemon/remote/XmlRpc.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT XmlRpc.obj -MD -MP -MF "$(DEPDIR)/XmlRpc.Tpo" -c -o XmlRpc.obj `if test -f 'daemon/remote/XmlRpc.cpp'; then $(CYGPATH_W) 'daemon/remote/XmlRpc.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/remote/XmlRpc.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/XmlRpc.Tpo" "$(DEPDIR)/XmlRpc.Po"; else rm -f "$(DEPDIR)/XmlRpc.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/remote/XmlRpc.cpp' object='XmlRpc.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o XmlRpc.obj `if test -f 'daemon/remote/XmlRpc.cpp'; then $(CYGPATH_W) 'daemon/remote/XmlRpc.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/remote/XmlRpc.cpp'; fi` Log.o: daemon/util/Log.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Log.o -MD -MP -MF "$(DEPDIR)/Log.Tpo" -c -o Log.o `test -f 'daemon/util/Log.cpp' || echo '$(srcdir)/'`daemon/util/Log.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Log.Tpo" "$(DEPDIR)/Log.Po"; else rm -f "$(DEPDIR)/Log.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/util/Log.cpp' object='Log.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Log.o `test -f 'daemon/util/Log.cpp' || echo '$(srcdir)/'`daemon/util/Log.cpp Log.obj: daemon/util/Log.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Log.obj -MD -MP -MF "$(DEPDIR)/Log.Tpo" -c -o Log.obj `if test -f 'daemon/util/Log.cpp'; then $(CYGPATH_W) 'daemon/util/Log.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/util/Log.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Log.Tpo" "$(DEPDIR)/Log.Po"; else rm -f "$(DEPDIR)/Log.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/util/Log.cpp' object='Log.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Log.obj `if test -f 'daemon/util/Log.cpp'; then $(CYGPATH_W) 'daemon/util/Log.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/util/Log.cpp'; fi` Observer.o: daemon/util/Observer.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Observer.o -MD -MP -MF "$(DEPDIR)/Observer.Tpo" -c -o Observer.o `test -f 'daemon/util/Observer.cpp' || echo '$(srcdir)/'`daemon/util/Observer.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Observer.Tpo" "$(DEPDIR)/Observer.Po"; else rm -f "$(DEPDIR)/Observer.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/util/Observer.cpp' object='Observer.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Observer.o `test -f 'daemon/util/Observer.cpp' || echo '$(srcdir)/'`daemon/util/Observer.cpp Observer.obj: daemon/util/Observer.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Observer.obj -MD -MP -MF "$(DEPDIR)/Observer.Tpo" -c -o Observer.obj `if test -f 'daemon/util/Observer.cpp'; then $(CYGPATH_W) 'daemon/util/Observer.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/util/Observer.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Observer.Tpo" "$(DEPDIR)/Observer.Po"; else rm -f "$(DEPDIR)/Observer.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/util/Observer.cpp' object='Observer.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Observer.obj `if test -f 'daemon/util/Observer.cpp'; then $(CYGPATH_W) 'daemon/util/Observer.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/util/Observer.cpp'; fi` Script.o: daemon/util/Script.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Script.o -MD -MP -MF "$(DEPDIR)/Script.Tpo" -c -o Script.o `test -f 'daemon/util/Script.cpp' || echo '$(srcdir)/'`daemon/util/Script.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Script.Tpo" "$(DEPDIR)/Script.Po"; else rm -f "$(DEPDIR)/Script.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/util/Script.cpp' object='Script.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Script.o `test -f 'daemon/util/Script.cpp' || echo '$(srcdir)/'`daemon/util/Script.cpp Script.obj: daemon/util/Script.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Script.obj -MD -MP -MF "$(DEPDIR)/Script.Tpo" -c -o Script.obj `if test -f 'daemon/util/Script.cpp'; then $(CYGPATH_W) 'daemon/util/Script.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/util/Script.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Script.Tpo" "$(DEPDIR)/Script.Po"; else rm -f "$(DEPDIR)/Script.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/util/Script.cpp' object='Script.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Script.obj `if test -f 'daemon/util/Script.cpp'; then $(CYGPATH_W) 'daemon/util/Script.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/util/Script.cpp'; fi` Thread.o: daemon/util/Thread.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Thread.o -MD -MP -MF "$(DEPDIR)/Thread.Tpo" -c -o Thread.o `test -f 'daemon/util/Thread.cpp' || echo '$(srcdir)/'`daemon/util/Thread.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Thread.Tpo" "$(DEPDIR)/Thread.Po"; else rm -f "$(DEPDIR)/Thread.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/util/Thread.cpp' object='Thread.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Thread.o `test -f 'daemon/util/Thread.cpp' || echo '$(srcdir)/'`daemon/util/Thread.cpp Thread.obj: daemon/util/Thread.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Thread.obj -MD -MP -MF "$(DEPDIR)/Thread.Tpo" -c -o Thread.obj `if test -f 'daemon/util/Thread.cpp'; then $(CYGPATH_W) 'daemon/util/Thread.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/util/Thread.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Thread.Tpo" "$(DEPDIR)/Thread.Po"; else rm -f "$(DEPDIR)/Thread.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/util/Thread.cpp' object='Thread.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Thread.obj `if test -f 'daemon/util/Thread.cpp'; then $(CYGPATH_W) 'daemon/util/Thread.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/util/Thread.cpp'; fi` Service.o: daemon/util/Service.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Service.o -MD -MP -MF "$(DEPDIR)/Service.Tpo" -c -o Service.o `test -f 'daemon/util/Service.cpp' || echo '$(srcdir)/'`daemon/util/Service.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Service.Tpo" "$(DEPDIR)/Service.Po"; else rm -f "$(DEPDIR)/Service.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/util/Service.cpp' object='Service.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Service.o `test -f 'daemon/util/Service.cpp' || echo '$(srcdir)/'`daemon/util/Service.cpp Service.obj: daemon/util/Service.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Service.obj -MD -MP -MF "$(DEPDIR)/Service.Tpo" -c -o Service.obj `if test -f 'daemon/util/Service.cpp'; then $(CYGPATH_W) 'daemon/util/Service.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/util/Service.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Service.Tpo" "$(DEPDIR)/Service.Po"; else rm -f "$(DEPDIR)/Service.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/util/Service.cpp' object='Service.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Service.obj `if test -f 'daemon/util/Service.cpp'; then $(CYGPATH_W) 'daemon/util/Service.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/util/Service.cpp'; fi` Util.o: daemon/util/Util.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Util.o -MD -MP -MF "$(DEPDIR)/Util.Tpo" -c -o Util.o `test -f 'daemon/util/Util.cpp' || echo '$(srcdir)/'`daemon/util/Util.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Util.Tpo" "$(DEPDIR)/Util.Po"; else rm -f "$(DEPDIR)/Util.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/util/Util.cpp' object='Util.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Util.o `test -f 'daemon/util/Util.cpp' || echo '$(srcdir)/'`daemon/util/Util.cpp Util.obj: daemon/util/Util.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT Util.obj -MD -MP -MF "$(DEPDIR)/Util.Tpo" -c -o Util.obj `if test -f 'daemon/util/Util.cpp'; then $(CYGPATH_W) 'daemon/util/Util.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/util/Util.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/Util.Tpo" "$(DEPDIR)/Util.Po"; else rm -f "$(DEPDIR)/Util.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='daemon/util/Util.cpp' object='Util.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o Util.obj `if test -f 'daemon/util/Util.cpp'; then $(CYGPATH_W) 'daemon/util/Util.cpp'; else $(CYGPATH_W) '$(srcdir)/daemon/util/Util.cpp'; fi` commandline.o: lib/par2/commandline.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT commandline.o -MD -MP -MF "$(DEPDIR)/commandline.Tpo" -c -o commandline.o `test -f 'lib/par2/commandline.cpp' || echo '$(srcdir)/'`lib/par2/commandline.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/commandline.Tpo" "$(DEPDIR)/commandline.Po"; else rm -f "$(DEPDIR)/commandline.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/commandline.cpp' object='commandline.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o commandline.o `test -f 'lib/par2/commandline.cpp' || echo '$(srcdir)/'`lib/par2/commandline.cpp commandline.obj: lib/par2/commandline.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT commandline.obj -MD -MP -MF "$(DEPDIR)/commandline.Tpo" -c -o commandline.obj `if test -f 'lib/par2/commandline.cpp'; then $(CYGPATH_W) 'lib/par2/commandline.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/commandline.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/commandline.Tpo" "$(DEPDIR)/commandline.Po"; else rm -f "$(DEPDIR)/commandline.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/commandline.cpp' object='commandline.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o commandline.obj `if test -f 'lib/par2/commandline.cpp'; then $(CYGPATH_W) 'lib/par2/commandline.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/commandline.cpp'; fi` crc.o: lib/par2/crc.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT crc.o -MD -MP -MF "$(DEPDIR)/crc.Tpo" -c -o crc.o `test -f 'lib/par2/crc.cpp' || echo '$(srcdir)/'`lib/par2/crc.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/crc.Tpo" "$(DEPDIR)/crc.Po"; else rm -f "$(DEPDIR)/crc.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/crc.cpp' object='crc.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o crc.o `test -f 'lib/par2/crc.cpp' || echo '$(srcdir)/'`lib/par2/crc.cpp crc.obj: lib/par2/crc.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT crc.obj -MD -MP -MF "$(DEPDIR)/crc.Tpo" -c -o crc.obj `if test -f 'lib/par2/crc.cpp'; then $(CYGPATH_W) 'lib/par2/crc.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/crc.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/crc.Tpo" "$(DEPDIR)/crc.Po"; else rm -f "$(DEPDIR)/crc.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/crc.cpp' object='crc.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o crc.obj `if test -f 'lib/par2/crc.cpp'; then $(CYGPATH_W) 'lib/par2/crc.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/crc.cpp'; fi` creatorpacket.o: lib/par2/creatorpacket.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT creatorpacket.o -MD -MP -MF "$(DEPDIR)/creatorpacket.Tpo" -c -o creatorpacket.o `test -f 'lib/par2/creatorpacket.cpp' || echo '$(srcdir)/'`lib/par2/creatorpacket.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/creatorpacket.Tpo" "$(DEPDIR)/creatorpacket.Po"; else rm -f "$(DEPDIR)/creatorpacket.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/creatorpacket.cpp' object='creatorpacket.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o creatorpacket.o `test -f 'lib/par2/creatorpacket.cpp' || echo '$(srcdir)/'`lib/par2/creatorpacket.cpp creatorpacket.obj: lib/par2/creatorpacket.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT creatorpacket.obj -MD -MP -MF "$(DEPDIR)/creatorpacket.Tpo" -c -o creatorpacket.obj `if test -f 'lib/par2/creatorpacket.cpp'; then $(CYGPATH_W) 'lib/par2/creatorpacket.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/creatorpacket.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/creatorpacket.Tpo" "$(DEPDIR)/creatorpacket.Po"; else rm -f "$(DEPDIR)/creatorpacket.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/creatorpacket.cpp' object='creatorpacket.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o creatorpacket.obj `if test -f 'lib/par2/creatorpacket.cpp'; then $(CYGPATH_W) 'lib/par2/creatorpacket.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/creatorpacket.cpp'; fi` criticalpacket.o: lib/par2/criticalpacket.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT criticalpacket.o -MD -MP -MF "$(DEPDIR)/criticalpacket.Tpo" -c -o criticalpacket.o `test -f 'lib/par2/criticalpacket.cpp' || echo '$(srcdir)/'`lib/par2/criticalpacket.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/criticalpacket.Tpo" "$(DEPDIR)/criticalpacket.Po"; else rm -f "$(DEPDIR)/criticalpacket.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/criticalpacket.cpp' object='criticalpacket.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o criticalpacket.o `test -f 'lib/par2/criticalpacket.cpp' || echo '$(srcdir)/'`lib/par2/criticalpacket.cpp criticalpacket.obj: lib/par2/criticalpacket.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT criticalpacket.obj -MD -MP -MF "$(DEPDIR)/criticalpacket.Tpo" -c -o criticalpacket.obj `if test -f 'lib/par2/criticalpacket.cpp'; then $(CYGPATH_W) 'lib/par2/criticalpacket.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/criticalpacket.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/criticalpacket.Tpo" "$(DEPDIR)/criticalpacket.Po"; else rm -f "$(DEPDIR)/criticalpacket.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/criticalpacket.cpp' object='criticalpacket.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o criticalpacket.obj `if test -f 'lib/par2/criticalpacket.cpp'; then $(CYGPATH_W) 'lib/par2/criticalpacket.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/criticalpacket.cpp'; fi` datablock.o: lib/par2/datablock.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT datablock.o -MD -MP -MF "$(DEPDIR)/datablock.Tpo" -c -o datablock.o `test -f 'lib/par2/datablock.cpp' || echo '$(srcdir)/'`lib/par2/datablock.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/datablock.Tpo" "$(DEPDIR)/datablock.Po"; else rm -f "$(DEPDIR)/datablock.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/datablock.cpp' object='datablock.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o datablock.o `test -f 'lib/par2/datablock.cpp' || echo '$(srcdir)/'`lib/par2/datablock.cpp datablock.obj: lib/par2/datablock.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT datablock.obj -MD -MP -MF "$(DEPDIR)/datablock.Tpo" -c -o datablock.obj `if test -f 'lib/par2/datablock.cpp'; then $(CYGPATH_W) 'lib/par2/datablock.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/datablock.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/datablock.Tpo" "$(DEPDIR)/datablock.Po"; else rm -f "$(DEPDIR)/datablock.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/datablock.cpp' object='datablock.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o datablock.obj `if test -f 'lib/par2/datablock.cpp'; then $(CYGPATH_W) 'lib/par2/datablock.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/datablock.cpp'; fi` descriptionpacket.o: lib/par2/descriptionpacket.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT descriptionpacket.o -MD -MP -MF "$(DEPDIR)/descriptionpacket.Tpo" -c -o descriptionpacket.o `test -f 'lib/par2/descriptionpacket.cpp' || echo '$(srcdir)/'`lib/par2/descriptionpacket.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/descriptionpacket.Tpo" "$(DEPDIR)/descriptionpacket.Po"; else rm -f "$(DEPDIR)/descriptionpacket.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/descriptionpacket.cpp' object='descriptionpacket.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o descriptionpacket.o `test -f 'lib/par2/descriptionpacket.cpp' || echo '$(srcdir)/'`lib/par2/descriptionpacket.cpp descriptionpacket.obj: lib/par2/descriptionpacket.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT descriptionpacket.obj -MD -MP -MF "$(DEPDIR)/descriptionpacket.Tpo" -c -o descriptionpacket.obj `if test -f 'lib/par2/descriptionpacket.cpp'; then $(CYGPATH_W) 'lib/par2/descriptionpacket.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/descriptionpacket.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/descriptionpacket.Tpo" "$(DEPDIR)/descriptionpacket.Po"; else rm -f "$(DEPDIR)/descriptionpacket.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/descriptionpacket.cpp' object='descriptionpacket.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o descriptionpacket.obj `if test -f 'lib/par2/descriptionpacket.cpp'; then $(CYGPATH_W) 'lib/par2/descriptionpacket.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/descriptionpacket.cpp'; fi` diskfile.o: lib/par2/diskfile.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT diskfile.o -MD -MP -MF "$(DEPDIR)/diskfile.Tpo" -c -o diskfile.o `test -f 'lib/par2/diskfile.cpp' || echo '$(srcdir)/'`lib/par2/diskfile.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/diskfile.Tpo" "$(DEPDIR)/diskfile.Po"; else rm -f "$(DEPDIR)/diskfile.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/diskfile.cpp' object='diskfile.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o diskfile.o `test -f 'lib/par2/diskfile.cpp' || echo '$(srcdir)/'`lib/par2/diskfile.cpp diskfile.obj: lib/par2/diskfile.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT diskfile.obj -MD -MP -MF "$(DEPDIR)/diskfile.Tpo" -c -o diskfile.obj `if test -f 'lib/par2/diskfile.cpp'; then $(CYGPATH_W) 'lib/par2/diskfile.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/diskfile.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/diskfile.Tpo" "$(DEPDIR)/diskfile.Po"; else rm -f "$(DEPDIR)/diskfile.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/diskfile.cpp' object='diskfile.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o diskfile.obj `if test -f 'lib/par2/diskfile.cpp'; then $(CYGPATH_W) 'lib/par2/diskfile.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/diskfile.cpp'; fi` filechecksummer.o: lib/par2/filechecksummer.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT filechecksummer.o -MD -MP -MF "$(DEPDIR)/filechecksummer.Tpo" -c -o filechecksummer.o `test -f 'lib/par2/filechecksummer.cpp' || echo '$(srcdir)/'`lib/par2/filechecksummer.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/filechecksummer.Tpo" "$(DEPDIR)/filechecksummer.Po"; else rm -f "$(DEPDIR)/filechecksummer.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/filechecksummer.cpp' object='filechecksummer.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o filechecksummer.o `test -f 'lib/par2/filechecksummer.cpp' || echo '$(srcdir)/'`lib/par2/filechecksummer.cpp filechecksummer.obj: lib/par2/filechecksummer.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT filechecksummer.obj -MD -MP -MF "$(DEPDIR)/filechecksummer.Tpo" -c -o filechecksummer.obj `if test -f 'lib/par2/filechecksummer.cpp'; then $(CYGPATH_W) 'lib/par2/filechecksummer.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/filechecksummer.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/filechecksummer.Tpo" "$(DEPDIR)/filechecksummer.Po"; else rm -f "$(DEPDIR)/filechecksummer.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/filechecksummer.cpp' object='filechecksummer.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o filechecksummer.obj `if test -f 'lib/par2/filechecksummer.cpp'; then $(CYGPATH_W) 'lib/par2/filechecksummer.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/filechecksummer.cpp'; fi` galois.o: lib/par2/galois.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT galois.o -MD -MP -MF "$(DEPDIR)/galois.Tpo" -c -o galois.o `test -f 'lib/par2/galois.cpp' || echo '$(srcdir)/'`lib/par2/galois.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/galois.Tpo" "$(DEPDIR)/galois.Po"; else rm -f "$(DEPDIR)/galois.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/galois.cpp' object='galois.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o galois.o `test -f 'lib/par2/galois.cpp' || echo '$(srcdir)/'`lib/par2/galois.cpp galois.obj: lib/par2/galois.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT galois.obj -MD -MP -MF "$(DEPDIR)/galois.Tpo" -c -o galois.obj `if test -f 'lib/par2/galois.cpp'; then $(CYGPATH_W) 'lib/par2/galois.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/galois.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/galois.Tpo" "$(DEPDIR)/galois.Po"; else rm -f "$(DEPDIR)/galois.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/galois.cpp' object='galois.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o galois.obj `if test -f 'lib/par2/galois.cpp'; then $(CYGPATH_W) 'lib/par2/galois.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/galois.cpp'; fi` mainpacket.o: lib/par2/mainpacket.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT mainpacket.o -MD -MP -MF "$(DEPDIR)/mainpacket.Tpo" -c -o mainpacket.o `test -f 'lib/par2/mainpacket.cpp' || echo '$(srcdir)/'`lib/par2/mainpacket.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/mainpacket.Tpo" "$(DEPDIR)/mainpacket.Po"; else rm -f "$(DEPDIR)/mainpacket.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/mainpacket.cpp' object='mainpacket.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o mainpacket.o `test -f 'lib/par2/mainpacket.cpp' || echo '$(srcdir)/'`lib/par2/mainpacket.cpp mainpacket.obj: lib/par2/mainpacket.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT mainpacket.obj -MD -MP -MF "$(DEPDIR)/mainpacket.Tpo" -c -o mainpacket.obj `if test -f 'lib/par2/mainpacket.cpp'; then $(CYGPATH_W) 'lib/par2/mainpacket.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/mainpacket.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/mainpacket.Tpo" "$(DEPDIR)/mainpacket.Po"; else rm -f "$(DEPDIR)/mainpacket.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/mainpacket.cpp' object='mainpacket.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o mainpacket.obj `if test -f 'lib/par2/mainpacket.cpp'; then $(CYGPATH_W) 'lib/par2/mainpacket.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/mainpacket.cpp'; fi` md5.o: lib/par2/md5.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT md5.o -MD -MP -MF "$(DEPDIR)/md5.Tpo" -c -o md5.o `test -f 'lib/par2/md5.cpp' || echo '$(srcdir)/'`lib/par2/md5.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/md5.Tpo" "$(DEPDIR)/md5.Po"; else rm -f "$(DEPDIR)/md5.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/md5.cpp' object='md5.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o md5.o `test -f 'lib/par2/md5.cpp' || echo '$(srcdir)/'`lib/par2/md5.cpp md5.obj: lib/par2/md5.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT md5.obj -MD -MP -MF "$(DEPDIR)/md5.Tpo" -c -o md5.obj `if test -f 'lib/par2/md5.cpp'; then $(CYGPATH_W) 'lib/par2/md5.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/md5.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/md5.Tpo" "$(DEPDIR)/md5.Po"; else rm -f "$(DEPDIR)/md5.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/md5.cpp' object='md5.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o md5.obj `if test -f 'lib/par2/md5.cpp'; then $(CYGPATH_W) 'lib/par2/md5.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/md5.cpp'; fi` par2creatorsourcefile.o: lib/par2/par2creatorsourcefile.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT par2creatorsourcefile.o -MD -MP -MF "$(DEPDIR)/par2creatorsourcefile.Tpo" -c -o par2creatorsourcefile.o `test -f 'lib/par2/par2creatorsourcefile.cpp' || echo '$(srcdir)/'`lib/par2/par2creatorsourcefile.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/par2creatorsourcefile.Tpo" "$(DEPDIR)/par2creatorsourcefile.Po"; else rm -f "$(DEPDIR)/par2creatorsourcefile.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/par2creatorsourcefile.cpp' object='par2creatorsourcefile.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o par2creatorsourcefile.o `test -f 'lib/par2/par2creatorsourcefile.cpp' || echo '$(srcdir)/'`lib/par2/par2creatorsourcefile.cpp par2creatorsourcefile.obj: lib/par2/par2creatorsourcefile.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT par2creatorsourcefile.obj -MD -MP -MF "$(DEPDIR)/par2creatorsourcefile.Tpo" -c -o par2creatorsourcefile.obj `if test -f 'lib/par2/par2creatorsourcefile.cpp'; then $(CYGPATH_W) 'lib/par2/par2creatorsourcefile.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/par2creatorsourcefile.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/par2creatorsourcefile.Tpo" "$(DEPDIR)/par2creatorsourcefile.Po"; else rm -f "$(DEPDIR)/par2creatorsourcefile.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/par2creatorsourcefile.cpp' object='par2creatorsourcefile.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o par2creatorsourcefile.obj `if test -f 'lib/par2/par2creatorsourcefile.cpp'; then $(CYGPATH_W) 'lib/par2/par2creatorsourcefile.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/par2creatorsourcefile.cpp'; fi` par2fileformat.o: lib/par2/par2fileformat.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT par2fileformat.o -MD -MP -MF "$(DEPDIR)/par2fileformat.Tpo" -c -o par2fileformat.o `test -f 'lib/par2/par2fileformat.cpp' || echo '$(srcdir)/'`lib/par2/par2fileformat.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/par2fileformat.Tpo" "$(DEPDIR)/par2fileformat.Po"; else rm -f "$(DEPDIR)/par2fileformat.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/par2fileformat.cpp' object='par2fileformat.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o par2fileformat.o `test -f 'lib/par2/par2fileformat.cpp' || echo '$(srcdir)/'`lib/par2/par2fileformat.cpp par2fileformat.obj: lib/par2/par2fileformat.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT par2fileformat.obj -MD -MP -MF "$(DEPDIR)/par2fileformat.Tpo" -c -o par2fileformat.obj `if test -f 'lib/par2/par2fileformat.cpp'; then $(CYGPATH_W) 'lib/par2/par2fileformat.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/par2fileformat.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/par2fileformat.Tpo" "$(DEPDIR)/par2fileformat.Po"; else rm -f "$(DEPDIR)/par2fileformat.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/par2fileformat.cpp' object='par2fileformat.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o par2fileformat.obj `if test -f 'lib/par2/par2fileformat.cpp'; then $(CYGPATH_W) 'lib/par2/par2fileformat.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/par2fileformat.cpp'; fi` par2repairer.o: lib/par2/par2repairer.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT par2repairer.o -MD -MP -MF "$(DEPDIR)/par2repairer.Tpo" -c -o par2repairer.o `test -f 'lib/par2/par2repairer.cpp' || echo '$(srcdir)/'`lib/par2/par2repairer.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/par2repairer.Tpo" "$(DEPDIR)/par2repairer.Po"; else rm -f "$(DEPDIR)/par2repairer.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/par2repairer.cpp' object='par2repairer.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o par2repairer.o `test -f 'lib/par2/par2repairer.cpp' || echo '$(srcdir)/'`lib/par2/par2repairer.cpp par2repairer.obj: lib/par2/par2repairer.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT par2repairer.obj -MD -MP -MF "$(DEPDIR)/par2repairer.Tpo" -c -o par2repairer.obj `if test -f 'lib/par2/par2repairer.cpp'; then $(CYGPATH_W) 'lib/par2/par2repairer.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/par2repairer.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/par2repairer.Tpo" "$(DEPDIR)/par2repairer.Po"; else rm -f "$(DEPDIR)/par2repairer.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/par2repairer.cpp' object='par2repairer.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o par2repairer.obj `if test -f 'lib/par2/par2repairer.cpp'; then $(CYGPATH_W) 'lib/par2/par2repairer.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/par2repairer.cpp'; fi` par2repairersourcefile.o: lib/par2/par2repairersourcefile.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT par2repairersourcefile.o -MD -MP -MF "$(DEPDIR)/par2repairersourcefile.Tpo" -c -o par2repairersourcefile.o `test -f 'lib/par2/par2repairersourcefile.cpp' || echo '$(srcdir)/'`lib/par2/par2repairersourcefile.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/par2repairersourcefile.Tpo" "$(DEPDIR)/par2repairersourcefile.Po"; else rm -f "$(DEPDIR)/par2repairersourcefile.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/par2repairersourcefile.cpp' object='par2repairersourcefile.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o par2repairersourcefile.o `test -f 'lib/par2/par2repairersourcefile.cpp' || echo '$(srcdir)/'`lib/par2/par2repairersourcefile.cpp par2repairersourcefile.obj: lib/par2/par2repairersourcefile.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT par2repairersourcefile.obj -MD -MP -MF "$(DEPDIR)/par2repairersourcefile.Tpo" -c -o par2repairersourcefile.obj `if test -f 'lib/par2/par2repairersourcefile.cpp'; then $(CYGPATH_W) 'lib/par2/par2repairersourcefile.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/par2repairersourcefile.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/par2repairersourcefile.Tpo" "$(DEPDIR)/par2repairersourcefile.Po"; else rm -f "$(DEPDIR)/par2repairersourcefile.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/par2repairersourcefile.cpp' object='par2repairersourcefile.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o par2repairersourcefile.obj `if test -f 'lib/par2/par2repairersourcefile.cpp'; then $(CYGPATH_W) 'lib/par2/par2repairersourcefile.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/par2repairersourcefile.cpp'; fi` parheaders.o: lib/par2/parheaders.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT parheaders.o -MD -MP -MF "$(DEPDIR)/parheaders.Tpo" -c -o parheaders.o `test -f 'lib/par2/parheaders.cpp' || echo '$(srcdir)/'`lib/par2/parheaders.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/parheaders.Tpo" "$(DEPDIR)/parheaders.Po"; else rm -f "$(DEPDIR)/parheaders.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/parheaders.cpp' object='parheaders.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o parheaders.o `test -f 'lib/par2/parheaders.cpp' || echo '$(srcdir)/'`lib/par2/parheaders.cpp parheaders.obj: lib/par2/parheaders.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT parheaders.obj -MD -MP -MF "$(DEPDIR)/parheaders.Tpo" -c -o parheaders.obj `if test -f 'lib/par2/parheaders.cpp'; then $(CYGPATH_W) 'lib/par2/parheaders.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/parheaders.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/parheaders.Tpo" "$(DEPDIR)/parheaders.Po"; else rm -f "$(DEPDIR)/parheaders.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/parheaders.cpp' object='parheaders.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o parheaders.obj `if test -f 'lib/par2/parheaders.cpp'; then $(CYGPATH_W) 'lib/par2/parheaders.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/parheaders.cpp'; fi` recoverypacket.o: lib/par2/recoverypacket.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT recoverypacket.o -MD -MP -MF "$(DEPDIR)/recoverypacket.Tpo" -c -o recoverypacket.o `test -f 'lib/par2/recoverypacket.cpp' || echo '$(srcdir)/'`lib/par2/recoverypacket.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/recoverypacket.Tpo" "$(DEPDIR)/recoverypacket.Po"; else rm -f "$(DEPDIR)/recoverypacket.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/recoverypacket.cpp' object='recoverypacket.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o recoverypacket.o `test -f 'lib/par2/recoverypacket.cpp' || echo '$(srcdir)/'`lib/par2/recoverypacket.cpp recoverypacket.obj: lib/par2/recoverypacket.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT recoverypacket.obj -MD -MP -MF "$(DEPDIR)/recoverypacket.Tpo" -c -o recoverypacket.obj `if test -f 'lib/par2/recoverypacket.cpp'; then $(CYGPATH_W) 'lib/par2/recoverypacket.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/recoverypacket.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/recoverypacket.Tpo" "$(DEPDIR)/recoverypacket.Po"; else rm -f "$(DEPDIR)/recoverypacket.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/recoverypacket.cpp' object='recoverypacket.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o recoverypacket.obj `if test -f 'lib/par2/recoverypacket.cpp'; then $(CYGPATH_W) 'lib/par2/recoverypacket.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/recoverypacket.cpp'; fi` reedsolomon.o: lib/par2/reedsolomon.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT reedsolomon.o -MD -MP -MF "$(DEPDIR)/reedsolomon.Tpo" -c -o reedsolomon.o `test -f 'lib/par2/reedsolomon.cpp' || echo '$(srcdir)/'`lib/par2/reedsolomon.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/reedsolomon.Tpo" "$(DEPDIR)/reedsolomon.Po"; else rm -f "$(DEPDIR)/reedsolomon.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/reedsolomon.cpp' object='reedsolomon.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o reedsolomon.o `test -f 'lib/par2/reedsolomon.cpp' || echo '$(srcdir)/'`lib/par2/reedsolomon.cpp reedsolomon.obj: lib/par2/reedsolomon.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT reedsolomon.obj -MD -MP -MF "$(DEPDIR)/reedsolomon.Tpo" -c -o reedsolomon.obj `if test -f 'lib/par2/reedsolomon.cpp'; then $(CYGPATH_W) 'lib/par2/reedsolomon.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/reedsolomon.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/reedsolomon.Tpo" "$(DEPDIR)/reedsolomon.Po"; else rm -f "$(DEPDIR)/reedsolomon.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/reedsolomon.cpp' object='reedsolomon.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o reedsolomon.obj `if test -f 'lib/par2/reedsolomon.cpp'; then $(CYGPATH_W) 'lib/par2/reedsolomon.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/reedsolomon.cpp'; fi` verificationhashtable.o: lib/par2/verificationhashtable.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT verificationhashtable.o -MD -MP -MF "$(DEPDIR)/verificationhashtable.Tpo" -c -o verificationhashtable.o `test -f 'lib/par2/verificationhashtable.cpp' || echo '$(srcdir)/'`lib/par2/verificationhashtable.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/verificationhashtable.Tpo" "$(DEPDIR)/verificationhashtable.Po"; else rm -f "$(DEPDIR)/verificationhashtable.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/verificationhashtable.cpp' object='verificationhashtable.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o verificationhashtable.o `test -f 'lib/par2/verificationhashtable.cpp' || echo '$(srcdir)/'`lib/par2/verificationhashtable.cpp verificationhashtable.obj: lib/par2/verificationhashtable.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT verificationhashtable.obj -MD -MP -MF "$(DEPDIR)/verificationhashtable.Tpo" -c -o verificationhashtable.obj `if test -f 'lib/par2/verificationhashtable.cpp'; then $(CYGPATH_W) 'lib/par2/verificationhashtable.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/verificationhashtable.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/verificationhashtable.Tpo" "$(DEPDIR)/verificationhashtable.Po"; else rm -f "$(DEPDIR)/verificationhashtable.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/verificationhashtable.cpp' object='verificationhashtable.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o verificationhashtable.obj `if test -f 'lib/par2/verificationhashtable.cpp'; then $(CYGPATH_W) 'lib/par2/verificationhashtable.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/verificationhashtable.cpp'; fi` verificationpacket.o: lib/par2/verificationpacket.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT verificationpacket.o -MD -MP -MF "$(DEPDIR)/verificationpacket.Tpo" -c -o verificationpacket.o `test -f 'lib/par2/verificationpacket.cpp' || echo '$(srcdir)/'`lib/par2/verificationpacket.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/verificationpacket.Tpo" "$(DEPDIR)/verificationpacket.Po"; else rm -f "$(DEPDIR)/verificationpacket.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/verificationpacket.cpp' object='verificationpacket.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o verificationpacket.o `test -f 'lib/par2/verificationpacket.cpp' || echo '$(srcdir)/'`lib/par2/verificationpacket.cpp verificationpacket.obj: lib/par2/verificationpacket.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT verificationpacket.obj -MD -MP -MF "$(DEPDIR)/verificationpacket.Tpo" -c -o verificationpacket.obj `if test -f 'lib/par2/verificationpacket.cpp'; then $(CYGPATH_W) 'lib/par2/verificationpacket.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/verificationpacket.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/verificationpacket.Tpo" "$(DEPDIR)/verificationpacket.Po"; else rm -f "$(DEPDIR)/verificationpacket.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='lib/par2/verificationpacket.cpp' object='verificationpacket.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o verificationpacket.obj `if test -f 'lib/par2/verificationpacket.cpp'; then $(CYGPATH_W) 'lib/par2/verificationpacket.cpp'; else $(CYGPATH_W) '$(srcdir)/lib/par2/verificationpacket.cpp'; fi` TestMain.o: tests/suite/TestMain.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT TestMain.o -MD -MP -MF "$(DEPDIR)/TestMain.Tpo" -c -o TestMain.o `test -f 'tests/suite/TestMain.cpp' || echo '$(srcdir)/'`tests/suite/TestMain.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/TestMain.Tpo" "$(DEPDIR)/TestMain.Po"; else rm -f "$(DEPDIR)/TestMain.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/suite/TestMain.cpp' object='TestMain.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o TestMain.o `test -f 'tests/suite/TestMain.cpp' || echo '$(srcdir)/'`tests/suite/TestMain.cpp TestMain.obj: tests/suite/TestMain.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT TestMain.obj -MD -MP -MF "$(DEPDIR)/TestMain.Tpo" -c -o TestMain.obj `if test -f 'tests/suite/TestMain.cpp'; then $(CYGPATH_W) 'tests/suite/TestMain.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/suite/TestMain.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/TestMain.Tpo" "$(DEPDIR)/TestMain.Po"; else rm -f "$(DEPDIR)/TestMain.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/suite/TestMain.cpp' object='TestMain.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o TestMain.obj `if test -f 'tests/suite/TestMain.cpp'; then $(CYGPATH_W) 'tests/suite/TestMain.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/suite/TestMain.cpp'; fi` TestUtil.o: tests/suite/TestUtil.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT TestUtil.o -MD -MP -MF "$(DEPDIR)/TestUtil.Tpo" -c -o TestUtil.o `test -f 'tests/suite/TestUtil.cpp' || echo '$(srcdir)/'`tests/suite/TestUtil.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/TestUtil.Tpo" "$(DEPDIR)/TestUtil.Po"; else rm -f "$(DEPDIR)/TestUtil.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/suite/TestUtil.cpp' object='TestUtil.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o TestUtil.o `test -f 'tests/suite/TestUtil.cpp' || echo '$(srcdir)/'`tests/suite/TestUtil.cpp TestUtil.obj: tests/suite/TestUtil.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT TestUtil.obj -MD -MP -MF "$(DEPDIR)/TestUtil.Tpo" -c -o TestUtil.obj `if test -f 'tests/suite/TestUtil.cpp'; then $(CYGPATH_W) 'tests/suite/TestUtil.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/suite/TestUtil.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/TestUtil.Tpo" "$(DEPDIR)/TestUtil.Po"; else rm -f "$(DEPDIR)/TestUtil.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/suite/TestUtil.cpp' object='TestUtil.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o TestUtil.obj `if test -f 'tests/suite/TestUtil.cpp'; then $(CYGPATH_W) 'tests/suite/TestUtil.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/suite/TestUtil.cpp'; fi` CommandLineParserTest.o: tests/main/CommandLineParserTest.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT CommandLineParserTest.o -MD -MP -MF "$(DEPDIR)/CommandLineParserTest.Tpo" -c -o CommandLineParserTest.o `test -f 'tests/main/CommandLineParserTest.cpp' || echo '$(srcdir)/'`tests/main/CommandLineParserTest.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/CommandLineParserTest.Tpo" "$(DEPDIR)/CommandLineParserTest.Po"; else rm -f "$(DEPDIR)/CommandLineParserTest.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/main/CommandLineParserTest.cpp' object='CommandLineParserTest.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o CommandLineParserTest.o `test -f 'tests/main/CommandLineParserTest.cpp' || echo '$(srcdir)/'`tests/main/CommandLineParserTest.cpp CommandLineParserTest.obj: tests/main/CommandLineParserTest.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT CommandLineParserTest.obj -MD -MP -MF "$(DEPDIR)/CommandLineParserTest.Tpo" -c -o CommandLineParserTest.obj `if test -f 'tests/main/CommandLineParserTest.cpp'; then $(CYGPATH_W) 'tests/main/CommandLineParserTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/main/CommandLineParserTest.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/CommandLineParserTest.Tpo" "$(DEPDIR)/CommandLineParserTest.Po"; else rm -f "$(DEPDIR)/CommandLineParserTest.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/main/CommandLineParserTest.cpp' object='CommandLineParserTest.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o CommandLineParserTest.obj `if test -f 'tests/main/CommandLineParserTest.cpp'; then $(CYGPATH_W) 'tests/main/CommandLineParserTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/main/CommandLineParserTest.cpp'; fi` OptionsTest.o: tests/main/OptionsTest.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT OptionsTest.o -MD -MP -MF "$(DEPDIR)/OptionsTest.Tpo" -c -o OptionsTest.o `test -f 'tests/main/OptionsTest.cpp' || echo '$(srcdir)/'`tests/main/OptionsTest.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/OptionsTest.Tpo" "$(DEPDIR)/OptionsTest.Po"; else rm -f "$(DEPDIR)/OptionsTest.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/main/OptionsTest.cpp' object='OptionsTest.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o OptionsTest.o `test -f 'tests/main/OptionsTest.cpp' || echo '$(srcdir)/'`tests/main/OptionsTest.cpp OptionsTest.obj: tests/main/OptionsTest.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT OptionsTest.obj -MD -MP -MF "$(DEPDIR)/OptionsTest.Tpo" -c -o OptionsTest.obj `if test -f 'tests/main/OptionsTest.cpp'; then $(CYGPATH_W) 'tests/main/OptionsTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/main/OptionsTest.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/OptionsTest.Tpo" "$(DEPDIR)/OptionsTest.Po"; else rm -f "$(DEPDIR)/OptionsTest.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/main/OptionsTest.cpp' object='OptionsTest.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o OptionsTest.obj `if test -f 'tests/main/OptionsTest.cpp'; then $(CYGPATH_W) 'tests/main/OptionsTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/main/OptionsTest.cpp'; fi` FeedFilterTest.o: tests/feed/FeedFilterTest.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT FeedFilterTest.o -MD -MP -MF "$(DEPDIR)/FeedFilterTest.Tpo" -c -o FeedFilterTest.o `test -f 'tests/feed/FeedFilterTest.cpp' || echo '$(srcdir)/'`tests/feed/FeedFilterTest.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/FeedFilterTest.Tpo" "$(DEPDIR)/FeedFilterTest.Po"; else rm -f "$(DEPDIR)/FeedFilterTest.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/feed/FeedFilterTest.cpp' object='FeedFilterTest.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o FeedFilterTest.o `test -f 'tests/feed/FeedFilterTest.cpp' || echo '$(srcdir)/'`tests/feed/FeedFilterTest.cpp FeedFilterTest.obj: tests/feed/FeedFilterTest.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT FeedFilterTest.obj -MD -MP -MF "$(DEPDIR)/FeedFilterTest.Tpo" -c -o FeedFilterTest.obj `if test -f 'tests/feed/FeedFilterTest.cpp'; then $(CYGPATH_W) 'tests/feed/FeedFilterTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/feed/FeedFilterTest.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/FeedFilterTest.Tpo" "$(DEPDIR)/FeedFilterTest.Po"; else rm -f "$(DEPDIR)/FeedFilterTest.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/feed/FeedFilterTest.cpp' object='FeedFilterTest.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o FeedFilterTest.obj `if test -f 'tests/feed/FeedFilterTest.cpp'; then $(CYGPATH_W) 'tests/feed/FeedFilterTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/feed/FeedFilterTest.cpp'; fi` ParCheckerTest.o: tests/postprocess/ParCheckerTest.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ParCheckerTest.o -MD -MP -MF "$(DEPDIR)/ParCheckerTest.Tpo" -c -o ParCheckerTest.o `test -f 'tests/postprocess/ParCheckerTest.cpp' || echo '$(srcdir)/'`tests/postprocess/ParCheckerTest.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ParCheckerTest.Tpo" "$(DEPDIR)/ParCheckerTest.Po"; else rm -f "$(DEPDIR)/ParCheckerTest.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/postprocess/ParCheckerTest.cpp' object='ParCheckerTest.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ParCheckerTest.o `test -f 'tests/postprocess/ParCheckerTest.cpp' || echo '$(srcdir)/'`tests/postprocess/ParCheckerTest.cpp ParCheckerTest.obj: tests/postprocess/ParCheckerTest.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ParCheckerTest.obj -MD -MP -MF "$(DEPDIR)/ParCheckerTest.Tpo" -c -o ParCheckerTest.obj `if test -f 'tests/postprocess/ParCheckerTest.cpp'; then $(CYGPATH_W) 'tests/postprocess/ParCheckerTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/postprocess/ParCheckerTest.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ParCheckerTest.Tpo" "$(DEPDIR)/ParCheckerTest.Po"; else rm -f "$(DEPDIR)/ParCheckerTest.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/postprocess/ParCheckerTest.cpp' object='ParCheckerTest.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ParCheckerTest.obj `if test -f 'tests/postprocess/ParCheckerTest.cpp'; then $(CYGPATH_W) 'tests/postprocess/ParCheckerTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/postprocess/ParCheckerTest.cpp'; fi` ParRenamerTest.o: tests/postprocess/ParRenamerTest.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ParRenamerTest.o -MD -MP -MF "$(DEPDIR)/ParRenamerTest.Tpo" -c -o ParRenamerTest.o `test -f 'tests/postprocess/ParRenamerTest.cpp' || echo '$(srcdir)/'`tests/postprocess/ParRenamerTest.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ParRenamerTest.Tpo" "$(DEPDIR)/ParRenamerTest.Po"; else rm -f "$(DEPDIR)/ParRenamerTest.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/postprocess/ParRenamerTest.cpp' object='ParRenamerTest.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ParRenamerTest.o `test -f 'tests/postprocess/ParRenamerTest.cpp' || echo '$(srcdir)/'`tests/postprocess/ParRenamerTest.cpp ParRenamerTest.obj: tests/postprocess/ParRenamerTest.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT ParRenamerTest.obj -MD -MP -MF "$(DEPDIR)/ParRenamerTest.Tpo" -c -o ParRenamerTest.obj `if test -f 'tests/postprocess/ParRenamerTest.cpp'; then $(CYGPATH_W) 'tests/postprocess/ParRenamerTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/postprocess/ParRenamerTest.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/ParRenamerTest.Tpo" "$(DEPDIR)/ParRenamerTest.Po"; else rm -f "$(DEPDIR)/ParRenamerTest.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/postprocess/ParRenamerTest.cpp' object='ParRenamerTest.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o ParRenamerTest.obj `if test -f 'tests/postprocess/ParRenamerTest.cpp'; then $(CYGPATH_W) 'tests/postprocess/ParRenamerTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/postprocess/ParRenamerTest.cpp'; fi` NZBFileTest.o: tests/queue/NZBFileTest.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NZBFileTest.o -MD -MP -MF "$(DEPDIR)/NZBFileTest.Tpo" -c -o NZBFileTest.o `test -f 'tests/queue/NZBFileTest.cpp' || echo '$(srcdir)/'`tests/queue/NZBFileTest.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NZBFileTest.Tpo" "$(DEPDIR)/NZBFileTest.Po"; else rm -f "$(DEPDIR)/NZBFileTest.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/queue/NZBFileTest.cpp' object='NZBFileTest.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NZBFileTest.o `test -f 'tests/queue/NZBFileTest.cpp' || echo '$(srcdir)/'`tests/queue/NZBFileTest.cpp NZBFileTest.obj: tests/queue/NZBFileTest.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT NZBFileTest.obj -MD -MP -MF "$(DEPDIR)/NZBFileTest.Tpo" -c -o NZBFileTest.obj `if test -f 'tests/queue/NZBFileTest.cpp'; then $(CYGPATH_W) 'tests/queue/NZBFileTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/queue/NZBFileTest.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/NZBFileTest.Tpo" "$(DEPDIR)/NZBFileTest.Po"; else rm -f "$(DEPDIR)/NZBFileTest.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/queue/NZBFileTest.cpp' object='NZBFileTest.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o NZBFileTest.obj `if test -f 'tests/queue/NZBFileTest.cpp'; then $(CYGPATH_W) 'tests/queue/NZBFileTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/queue/NZBFileTest.cpp'; fi` UtilTest.o: tests/util/UtilTest.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT UtilTest.o -MD -MP -MF "$(DEPDIR)/UtilTest.Tpo" -c -o UtilTest.o `test -f 'tests/util/UtilTest.cpp' || echo '$(srcdir)/'`tests/util/UtilTest.cpp; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/UtilTest.Tpo" "$(DEPDIR)/UtilTest.Po"; else rm -f "$(DEPDIR)/UtilTest.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/util/UtilTest.cpp' object='UtilTest.o' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o UtilTest.o `test -f 'tests/util/UtilTest.cpp' || echo '$(srcdir)/'`tests/util/UtilTest.cpp UtilTest.obj: tests/util/UtilTest.cpp @am__fastdepCXX_TRUE@ if $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT UtilTest.obj -MD -MP -MF "$(DEPDIR)/UtilTest.Tpo" -c -o UtilTest.obj `if test -f 'tests/util/UtilTest.cpp'; then $(CYGPATH_W) 'tests/util/UtilTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/util/UtilTest.cpp'; fi`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/UtilTest.Tpo" "$(DEPDIR)/UtilTest.Po"; else rm -f "$(DEPDIR)/UtilTest.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='tests/util/UtilTest.cpp' object='UtilTest.obj' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o UtilTest.obj `if test -f 'tests/util/UtilTest.cpp'; then $(CYGPATH_W) 'tests/util/UtilTest.cpp'; else $(CYGPATH_W) '$(srcdir)/tests/util/UtilTest.cpp'; fi` uninstall-info-am: install-dist_docDATA: $(dist_doc_DATA) @$(NORMAL_INSTALL) test -z "$(docdir)" || $(mkdir_p) "$(DESTDIR)$(docdir)" @list='$(dist_doc_DATA)'; for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ f=$(am__strip_dir) \ echo " $(dist_docDATA_INSTALL) '$$d$$p' '$(DESTDIR)$(docdir)/$$f'"; \ $(dist_docDATA_INSTALL) "$$d$$p" "$(DESTDIR)$(docdir)/$$f"; \ done uninstall-dist_docDATA: @$(NORMAL_UNINSTALL) @list='$(dist_doc_DATA)'; for p in $$list; do \ f=$(am__strip_dir) \ echo " rm -f '$(DESTDIR)$(docdir)/$$f'"; \ rm -f "$(DESTDIR)$(docdir)/$$f"; \ done install-dist_exampleconfDATA: $(dist_exampleconf_DATA) @$(NORMAL_INSTALL) test -z "$(exampleconfdir)" || $(mkdir_p) "$(DESTDIR)$(exampleconfdir)" @list='$(dist_exampleconf_DATA)'; for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ f=$(am__strip_dir) \ echo " $(dist_exampleconfDATA_INSTALL) '$$d$$p' '$(DESTDIR)$(exampleconfdir)/$$f'"; \ $(dist_exampleconfDATA_INSTALL) "$$d$$p" "$(DESTDIR)$(exampleconfdir)/$$f"; \ done uninstall-dist_exampleconfDATA: @$(NORMAL_UNINSTALL) @list='$(dist_exampleconf_DATA)'; for p in $$list; do \ f=$(am__strip_dir) \ echo " rm -f '$(DESTDIR)$(exampleconfdir)/$$f'"; \ rm -f "$(DESTDIR)$(exampleconfdir)/$$f"; \ done install-nobase_dist_webuiDATA: $(nobase_dist_webui_DATA) @$(NORMAL_INSTALL) test -z "$(webuidir)" || $(mkdir_p) "$(DESTDIR)$(webuidir)" @$(am__vpath_adj_setup) \ list='$(nobase_dist_webui_DATA)'; for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ $(am__vpath_adj) \ echo " $(nobase_dist_webuiDATA_INSTALL) '$$d$$p' '$(DESTDIR)$(webuidir)/$$f'"; \ $(nobase_dist_webuiDATA_INSTALL) "$$d$$p" "$(DESTDIR)$(webuidir)/$$f"; \ done uninstall-nobase_dist_webuiDATA: @$(NORMAL_UNINSTALL) @$(am__vpath_adj_setup) \ list='$(nobase_dist_webui_DATA)'; for p in $$list; do \ $(am__vpath_adj) \ echo " rm -f '$(DESTDIR)$(webuidir)/$$f'"; \ rm -f "$(DESTDIR)$(webuidir)/$$f"; \ done ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | \ $(AWK) ' { files[$$0] = 1; } \ END { for (i in files) print i; }'`; \ mkid -fID $$unique tags: TAGS TAGS: $(HEADERS) $(SOURCES) config.h.in $(TAGS_DEPENDENCIES) \ $(TAGS_FILES) $(LISP) tags=; \ here=`pwd`; \ list='$(SOURCES) $(HEADERS) config.h.in $(LISP) $(TAGS_FILES)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | \ $(AWK) ' { files[$$0] = 1; } \ END { for (i in files) print i; }'`; \ if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$tags $$unique; \ fi ctags: CTAGS CTAGS: $(HEADERS) $(SOURCES) config.h.in $(TAGS_DEPENDENCIES) \ $(TAGS_FILES) $(LISP) tags=; \ here=`pwd`; \ list='$(SOURCES) $(HEADERS) config.h.in $(LISP) $(TAGS_FILES)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | \ $(AWK) ' { files[$$0] = 1; } \ END { for (i in files) print i; }'`; \ test -z "$(CTAGS_ARGS)$$tags$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$tags $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && cd $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) $$here distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(DISTFILES) $(am__remove_distdir) mkdir $(distdir) $(mkdir_p) $(distdir)/daemon/windows $(distdir)/lib/par2 $(distdir)/linux $(distdir)/osx $(distdir)/osx/NZBGet.xcodeproj $(distdir)/osx/Resources $(distdir)/osx/Resources/Images $(distdir)/osx/Resources/licenses $(distdir)/posix $(distdir)/scripts $(distdir)/tests/testdata/dupematcher1 $(distdir)/tests/testdata/dupematcher2 $(distdir)/tests/testdata/nzbfile $(distdir)/tests/testdata/parchecker $(distdir)/webui $(distdir)/webui/img $(distdir)/webui/lib $(distdir)/windows $(distdir)/windows/resources $(distdir)/windows/setup @srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's|.|.|g'`; \ list='$(DISTFILES)'; for file in $$list; do \ case $$file in \ $(srcdir)/*) file=`echo "$$file" | sed "s|^$$srcdirstrip/||"`;; \ $(top_srcdir)/*) file=`echo "$$file" | sed "s|^$$topsrcdirstrip/|$(top_builddir)/|"`;; \ esac; \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ dir=`echo "$$file" | sed -e 's,/[^/]*$$,,'`; \ if test "$$dir" != "$$file" && test "$$dir" != "."; then \ dir="/$$dir"; \ $(mkdir_p) "$(distdir)$$dir"; \ else \ dir=''; \ fi; \ if test -d $$d/$$file; then \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \ fi; \ cp -pR $$d/$$file $(distdir)$$dir || exit 1; \ else \ test -f $(distdir)/$$file \ || cp -p $$d/$$file $(distdir)/$$file \ || exit 1; \ fi; \ done $(MAKE) $(AM_MAKEFLAGS) \ top_distdir="$(top_distdir)" distdir="$(distdir)" \ dist-hook -find $(distdir) -type d ! -perm -777 -exec chmod a+rwx {} \; -o \ ! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \ ! -type d ! -perm -400 -exec chmod a+r {} \; -o \ ! -type d ! -perm -444 -exec $(SHELL) $(install_sh) -c -m a+r {} {} \; \ || chmod -R a+r $(distdir) dist-gzip: distdir tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz $(am__remove_distdir) dist-bzip2: distdir tardir=$(distdir) && $(am__tar) | bzip2 -9 -c >$(distdir).tar.bz2 $(am__remove_distdir) dist-tarZ: distdir tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z $(am__remove_distdir) dist-shar: distdir shar $(distdir) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).shar.gz $(am__remove_distdir) dist-zip: distdir -rm -f $(distdir).zip zip -rq $(distdir).zip $(distdir) $(am__remove_distdir) dist dist-all: distdir tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz $(am__remove_distdir) # This target untars the dist file and tries a VPATH configuration. Then # it guarantees that the distribution is self-contained by making another # tarfile. distcheck: dist case '$(DIST_ARCHIVES)' in \ *.tar.gz*) \ GZIP=$(GZIP_ENV) gunzip -c $(distdir).tar.gz | $(am__untar) ;;\ *.tar.bz2*) \ bunzip2 -c $(distdir).tar.bz2 | $(am__untar) ;;\ *.tar.Z*) \ uncompress -c $(distdir).tar.Z | $(am__untar) ;;\ *.shar.gz*) \ GZIP=$(GZIP_ENV) gunzip -c $(distdir).shar.gz | unshar ;;\ *.zip*) \ unzip $(distdir).zip ;;\ esac chmod -R a-w $(distdir); chmod a+w $(distdir) mkdir $(distdir)/_build mkdir $(distdir)/_inst chmod a-w $(distdir) dc_install_base=`$(am__cd) $(distdir)/_inst && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \ && dc_destdir="$${TMPDIR-/tmp}/am-dc-$$$$/" \ && cd $(distdir)/_build \ && ../configure --srcdir=.. --prefix="$$dc_install_base" \ $(DISTCHECK_CONFIGURE_FLAGS) \ && $(MAKE) $(AM_MAKEFLAGS) \ && $(MAKE) $(AM_MAKEFLAGS) dvi \ && $(MAKE) $(AM_MAKEFLAGS) check \ && $(MAKE) $(AM_MAKEFLAGS) install \ && $(MAKE) $(AM_MAKEFLAGS) installcheck \ && $(MAKE) $(AM_MAKEFLAGS) uninstall \ && $(MAKE) $(AM_MAKEFLAGS) distuninstallcheck_dir="$$dc_install_base" \ distuninstallcheck \ && chmod -R a-w "$$dc_install_base" \ && ({ \ (cd ../.. && umask 077 && mkdir "$$dc_destdir") \ && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" install \ && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" uninstall \ && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" \ distuninstallcheck_dir="$$dc_destdir" distuninstallcheck; \ } || { rm -rf "$$dc_destdir"; exit 1; }) \ && rm -rf "$$dc_destdir" \ && $(MAKE) $(AM_MAKEFLAGS) dist \ && rm -rf $(DIST_ARCHIVES) \ && $(MAKE) $(AM_MAKEFLAGS) distcleancheck $(am__remove_distdir) @(echo "$(distdir) archives ready for distribution: "; \ list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \ sed -e '1{h;s/./=/g;p;x;}' -e '$${p;x;}' distuninstallcheck: @cd $(distuninstallcheck_dir) \ && test `$(distuninstallcheck_listfiles) | wc -l` -le 1 \ || { echo "ERROR: files left after uninstall:" ; \ if test -n "$(DESTDIR)"; then \ echo " (check DESTDIR support)"; \ fi ; \ $(distuninstallcheck_listfiles) ; \ exit 1; } >&2 distcleancheck: distclean @if test '$(srcdir)' = . ; then \ echo "ERROR: distcleancheck can only run from a VPATH build" ; \ exit 1 ; \ fi @test `$(distcleancheck_listfiles) | wc -l` -eq 0 \ || { echo "ERROR: files left in build directory after distclean:" ; \ $(distcleancheck_listfiles) ; \ exit 1; } >&2 check-am: all-am check: check-am all-am: Makefile $(PROGRAMS) $(SCRIPTS) $(DATA) config.h installdirs: for dir in "$(DESTDIR)$(bindir)" "$(DESTDIR)$(scriptsdir)" "$(DESTDIR)$(docdir)" "$(DESTDIR)$(exampleconfdir)" "$(DESTDIR)$(webuidir)"; do \ test -z "$$dir" || $(mkdir_p) "$$dir"; \ done install: install-am install-exec: install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ `test -z '$(STRIP)' || \ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install mostlyclean-generic: clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-am clean-am: clean-binPROGRAMS clean-generic mostlyclean-am distclean: distclean-am -rm -f $(am__CONFIG_DISTCLEAN_FILES) -rm -rf ./$(DEPDIR) -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-hdr distclean-tags dvi: dvi-am dvi-am: html: html-am info: info-am info-am: install-data-am: install-dist_docDATA install-dist_exampleconfDATA \ install-nobase_dist_scriptsSCRIPTS \ install-nobase_dist_webuiDATA @$(NORMAL_INSTALL) $(MAKE) $(AM_MAKEFLAGS) install-data-hook install-exec-am: install-binPROGRAMS install-info: install-info-am install-man: installcheck-am: maintainer-clean: maintainer-clean-am -rm -f $(am__CONFIG_DISTCLEAN_FILES) -rm -rf $(top_srcdir)/autom4te.cache -rm -rf ./$(DEPDIR) -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-binPROGRAMS uninstall-dist_docDATA \ uninstall-dist_exampleconfDATA uninstall-info-am \ uninstall-nobase_dist_scriptsSCRIPTS \ uninstall-nobase_dist_webuiDATA .PHONY: CTAGS GTAGS all all-am am--refresh check check-am clean \ clean-binPROGRAMS clean-generic ctags dist dist-all dist-bzip2 \ dist-gzip dist-hook dist-shar dist-tarZ dist-zip distcheck \ 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-data install-data-am \ install-data-hook install-dist_docDATA \ install-dist_exampleconfDATA install-exec install-exec-am \ install-info install-info-am install-man \ install-nobase_dist_scriptsSCRIPTS \ install-nobase_dist_webuiDATA 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_scriptsSCRIPTS \ uninstall-nobase_dist_webuiDATA # Note about "sed": # We need to make some changes in installed files. # On Linux "sed" has option "-i" for in-place-edit. Unfortunateley the BSD version of "sed" # has incompatible syntax. To solve the problem we perform in-place-edit in three steps: # 1) copy the original file to original.temp (delete existing original.temp, if any); # 2) sed < original.temp > original # 3) delete original.temp # These steps ensure that the output file has the same permissions as the original file. # 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:^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/scripts:installed to $(scriptsdir)/scripts:' < "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" > "$(DESTDIR)$(exampleconfdir)/nzbget.conf" rm "$(DESTDIR)$(exampleconfdir)/nzbget.conf.temp" # Install configuration files into /etc # (only if they do not exist there to prevent override by update) install-conf: if test ! -f "$(DESTDIR)$(sysconfdir)/nzbget.conf" ; then \ $(mkinstalldirs) "$(DESTDIR)$(sysconfdir)" ; \ cp "$(DESTDIR)$(exampleconfdir)/nzbget.conf" "$(DESTDIR)$(sysconfdir)/nzbget.conf" ; \ fi uninstall-conf: rm -f "$(DESTDIR)$(sysconfdir)/nzbget.conf" # Determining git revision: # 1) If directory ".git" exists we take revision from git log. # File is recreated only if revision number was changed. # 2) If directory ".git" doesn't exists we keep and reuse file "code_revision.cpp", # which was possibly created early. # 3) If neither directory ".git" nor file "code_revision.cpp" are available # we create new file "code_revision.c" with empty revision number. code_revision.cpp: FORCE @ if test -d ./.git ; then \ B="$(shell git branch | sed -n -e 's/^\* \(.*\)/\1/p')"; \ M="$(shell git status --porcelain)" ; \ if test "$$M" != "" ; then \ M="M" ; \ fi ; \ if test "$$B" = "master" ; then \ V="$$M" ; \ elif test "$$B" = "develop" ; then \ V="$(shell git rev-list HEAD | wc -l | xargs)" ; \ V="$${V}$$M" ; \ else \ V="$(shell git rev-list HEAD | wc -l | xargs)" ; \ V="$${V}$$M ($$B)" ; \ fi ; \ H="$(shell test -f ./code_revision.cpp && head -n 1 code_revision.cpp)"; \ if test "/* $$V */" != "$$H" ; then \ ( \ echo "/* $$V */" ;\ echo "/* This file is automatically regenerated on each build. Do not edit it. */" ;\ echo "const char* code_revision(void)" ;\ echo "{" ;\ echo " const char* revision = \"$$V\";" ;\ echo " return revision;" ;\ echo "}" ;\ ) > code_revision.cpp ; \ fi \ elif test -f ./code_revision.cpp ; then \ test "ok, reuse existing file"; \ else \ ( \ echo "/* */" ;\ echo "/* This file is automatically regenerated on each build. Do not edit it. */" ;\ echo "const char* code_revision(void)" ;\ echo "{" ;\ echo " const char* revision = \"\";" ;\ echo " return revision;" ;\ echo "}" ;\ ) > code_revision.cpp ; \ fi FORCE: clean-bak: rm *~ # Fix premissions dist-hook: find $(distdir)/daemon -type f -print -exec chmod -x {} \; find $(distdir)/webui -type f -print -exec chmod -x {} \; find $(distdir)/lib -type f -print -exec chmod -x {} \; find $(distdir)/tests -type f -print -exec chmod -x {} \; # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: nzbget-16.4/aclocal.m40000644000175000017500000010676612630544544014440 0ustar andreasandreas# generated automatically by aclocal 1.9.6 -*- Autoconf -*- # Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, # 2005 Free Software Foundation, Inc. # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. # pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*- # # Copyright © 2004 Scott James Remnant . # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # PKG_PROG_PKG_CONFIG([MIN-VERSION]) # ---------------------------------- AC_DEFUN([PKG_PROG_PKG_CONFIG], [m4_pattern_forbid([^_?PKG_[A-Z_]+$]) m4_pattern_allow([^PKG_CONFIG(_PATH)?$]) AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility])dnl if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then AC_PATH_TOOL([PKG_CONFIG], [pkg-config]) fi if test -n "$PKG_CONFIG"; then _pkg_min_version=m4_default([$1], [0.9.0]) AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version]) if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) PKG_CONFIG="" fi fi[]dnl ])# PKG_PROG_PKG_CONFIG # PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) # # Check to see whether a particular set of modules exists. Similar # to PKG_CHECK_MODULES(), but does not set variables or print errors. # # # Similar to PKG_CHECK_MODULES, make sure that the first instance of # this or PKG_CHECK_MODULES is called, or make sure to call # PKG_CHECK_EXISTS manually # -------------------------------------------------------------- AC_DEFUN([PKG_CHECK_EXISTS], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl if test -n "$PKG_CONFIG" && \ AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then m4_ifval([$2], [$2], [:]) m4_ifvaln([$3], [else $3])dnl fi]) # _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES]) # --------------------------------------------- m4_define([_PKG_CONFIG], [if test -n "$PKG_CONFIG"; then if test -n "$$1"; then pkg_cv_[]$1="$$1" else PKG_CHECK_EXISTS([$3], [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null`], [pkg_failed=yes]) fi else pkg_failed=untried fi[]dnl ])# _PKG_CONFIG # _PKG_SHORT_ERRORS_SUPPORTED # ----------------------------- AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED], [AC_REQUIRE([PKG_PROG_PKG_CONFIG]) if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then _pkg_short_errors_supported=yes else _pkg_short_errors_supported=no fi[]dnl ])# _PKG_SHORT_ERRORS_SUPPORTED # PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], # [ACTION-IF-NOT-FOUND]) # # # Note that if there is a possibility the first call to # PKG_CHECK_MODULES might not happen, you should be sure to include an # explicit call to PKG_PROG_PKG_CONFIG in your configure.ac # # # -------------------------------------------------------------- AC_DEFUN([PKG_CHECK_MODULES], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl pkg_failed=no AC_MSG_CHECKING([for $1]) _PKG_CONFIG([$1][_CFLAGS], [cflags], [$2]) _PKG_CONFIG([$1][_LIBS], [libs], [$2]) m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS and $1[]_LIBS to avoid the need to call pkg-config. See the pkg-config man page for more details.]) if test $pkg_failed = yes; then _PKG_SHORT_ERRORS_SUPPORTED if test $_pkg_short_errors_supported = yes; then $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --errors-to-stdout --print-errors "$2"` else $1[]_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "$2"` fi # Put the nasty error message in config.log where it belongs echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD ifelse([$4], , [AC_MSG_ERROR(dnl [Package requirements ($2) were not met: $$1_PKG_ERRORS Consider adjusting the PKG_CONFIG_PATH environment variable if you installed software in a non-standard prefix. _PKG_TEXT ])], [AC_MSG_RESULT([no]) $4]) elif test $pkg_failed = untried; then ifelse([$4], , [AC_MSG_FAILURE(dnl [The pkg-config script could not be found or is too old. Make sure it is in your PATH or set the PKG_CONFIG environment variable to the full path to pkg-config. _PKG_TEXT To get pkg-config, see .])], [$4]) else $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS $1[]_LIBS=$pkg_cv_[]$1[]_LIBS AC_MSG_RESULT([yes]) ifelse([$3], , :, [$3]) fi[]dnl ])# PKG_CHECK_MODULES # Copyright (C) 2002, 2003, 2005 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_AUTOMAKE_VERSION(VERSION) # ---------------------------- # Automake X.Y traces this macro to ensure aclocal.m4 has been # generated from the m4 files accompanying Automake X.Y. AC_DEFUN([AM_AUTOMAKE_VERSION], [am__api_version="1.9"]) # AM_SET_CURRENT_AUTOMAKE_VERSION # ------------------------------- # Call AM_AUTOMAKE_VERSION so it can be traced. # This function is AC_REQUIREd by AC_INIT_AUTOMAKE. AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION], [AM_AUTOMAKE_VERSION([1.9.6])]) # AM_AUX_DIR_EXPAND -*- Autoconf -*- # Copyright (C) 2001, 2003, 2005 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # For projects using AC_CONFIG_AUX_DIR([foo]), Autoconf sets # $ac_aux_dir to `$srcdir/foo'. In other projects, it is set to # `$srcdir', `$srcdir/..', or `$srcdir/../..'. # # Of course, Automake must honor this variable whenever it calls a # tool from the auxiliary directory. The problem is that $srcdir (and # therefore $ac_aux_dir as well) can be either absolute or relative, # depending on how configure is run. This is pretty annoying, since # it makes $ac_aux_dir quite unusable in subdirectories: in the top # source directory, any form will work fine, but in subdirectories a # relative path needs to be adjusted first. # # $ac_aux_dir/missing # fails when called from a subdirectory if $ac_aux_dir is relative # $top_srcdir/$ac_aux_dir/missing # fails if $ac_aux_dir is absolute, # fails when called from a subdirectory in a VPATH build with # a relative $ac_aux_dir # # The reason of the latter failure is that $top_srcdir and $ac_aux_dir # are both prefixed by $srcdir. In an in-source build this is usually # harmless because $srcdir is `.', but things will broke when you # start a VPATH build or use an absolute $srcdir. # # So we could use something similar to $top_srcdir/$ac_aux_dir/missing, # iff we strip the leading $srcdir from $ac_aux_dir. That would be: # am_aux_dir='\$(top_srcdir)/'`expr "$ac_aux_dir" : "$srcdir//*\(.*\)"` # and then we would define $MISSING as # MISSING="\${SHELL} $am_aux_dir/missing" # This will work as long as MISSING is not called from configure, because # unfortunately $(top_srcdir) has no meaning in configure. # However there are other variables, like CC, which are often used in # configure, and could therefore not use this "fixed" $ac_aux_dir. # # Another solution, used here, is to always expand $ac_aux_dir to an # absolute PATH. The drawback is that using absolute paths prevent a # configured tree to be moved without reconfiguration. AC_DEFUN([AM_AUX_DIR_EXPAND], [dnl Rely on autoconf to set up CDPATH properly. AC_PREREQ([2.50])dnl # expand $ac_aux_dir to an absolute path am_aux_dir=`cd $ac_aux_dir && pwd` ]) # AM_CONDITIONAL -*- Autoconf -*- # Copyright (C) 1997, 2000, 2001, 2003, 2004, 2005 # Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # serial 7 # AM_CONDITIONAL(NAME, SHELL-CONDITION) # ------------------------------------- # Define a conditional. AC_DEFUN([AM_CONDITIONAL], [AC_PREREQ(2.52)dnl ifelse([$1], [TRUE], [AC_FATAL([$0: invalid condition: $1])], [$1], [FALSE], [AC_FATAL([$0: invalid condition: $1])])dnl AC_SUBST([$1_TRUE]) AC_SUBST([$1_FALSE]) if $2; then $1_TRUE= $1_FALSE='#' else $1_TRUE='#' $1_FALSE= fi AC_CONFIG_COMMANDS_PRE( [if test -z "${$1_TRUE}" && test -z "${$1_FALSE}"; then AC_MSG_ERROR([[conditional "$1" was never defined. Usually this means the macro was only invoked conditionally.]]) fi])]) # Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 # Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # serial 8 # There are a few dirty hacks below to avoid letting `AC_PROG_CC' be # written in clear, in which case automake, when reading aclocal.m4, # will think it sees a *use*, and therefore will trigger all it's # C support machinery. Also note that it means that autoscan, seeing # CC etc. in the Makefile, will ask for an AC_PROG_CC use... # _AM_DEPENDENCIES(NAME) # ---------------------- # See how the compiler implements dependency checking. # NAME is "CC", "CXX", "GCJ", or "OBJC". # We try a few techniques and use that to set a single cache variable. # # We don't AC_REQUIRE the corresponding AC_PROG_CC since the latter was # modified to invoke _AM_DEPENDENCIES(CC); we would have a circular # dependency, and given that the user is not expected to run this macro, # just rely on AC_PROG_CC. AC_DEFUN([_AM_DEPENDENCIES], [AC_REQUIRE([AM_SET_DEPDIR])dnl AC_REQUIRE([AM_OUTPUT_DEPENDENCY_COMMANDS])dnl AC_REQUIRE([AM_MAKE_INCLUDE])dnl AC_REQUIRE([AM_DEP_TRACK])dnl ifelse([$1], CC, [depcc="$CC" am_compiler_list=], [$1], CXX, [depcc="$CXX" am_compiler_list=], [$1], OBJC, [depcc="$OBJC" am_compiler_list='gcc3 gcc'], [$1], GCJ, [depcc="$GCJ" am_compiler_list='gcc3 gcc'], [depcc="$$1" am_compiler_list=]) AC_CACHE_CHECK([dependency style of $depcc], [am_cv_$1_dependencies_compiler_type], [if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then # We make a subdir and do the tests there. Otherwise we can end up # making bogus files that we don't know about and never remove. For # instance it was reported that on HP-UX the gcc test will end up # making a dummy file named `D' -- because `-MD' means `put the output # in D'. mkdir conftest.dir # Copy depcomp to subdir because otherwise we won't find it if we're # using a relative directory. cp "$am_depcomp" conftest.dir cd conftest.dir # We will build objects and dependencies in a subdirectory because # it helps to detect inapplicable dependency modes. For instance # both Tru64's cc and ICC support -MD to output dependencies as a # side effect of compilation, but ICC will put the dependencies in # the current directory while Tru64 will put them in the object # directory. mkdir sub am_cv_$1_dependencies_compiler_type=none if test "$am_compiler_list" = ""; then am_compiler_list=`sed -n ['s/^#*\([a-zA-Z0-9]*\))$/\1/p'] < ./depcomp` fi for depmode in $am_compiler_list; do # Setup a source with many dependencies, because some compilers # like to wrap large dependency lists on column 80 (with \), and # we should not choose a depcomp mode which is confused by this. # # We need to recreate these files for each test, as the compiler may # overwrite some of them when testing with obscure command lines. # This happens at least with the AIX C compiler. : > sub/conftest.c for i in 1 2 3 4 5 6; do echo '#include "conftst'$i'.h"' >> sub/conftest.c # Using `: > sub/conftst$i.h' creates only sub/conftst1.h with # Solaris 8's {/usr,}/bin/sh. touch sub/conftst$i.h done echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf case $depmode in nosideeffect) # after this tag, mechanisms are not by side-effect, so they'll # only be used when explicitly requested if test "x$enable_dependency_tracking" = xyes; then continue else break fi ;; none) break ;; esac # We check with `-c' and `-o' for the sake of the "dashmstdout" # mode. It turns out that the SunPro C++ compiler does not properly # handle `-M -o', and we need to detect this. if depmode=$depmode \ source=sub/conftest.c object=sub/conftest.${OBJEXT-o} \ depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ $SHELL ./depcomp $depcc -c -o sub/conftest.${OBJEXT-o} sub/conftest.c \ >/dev/null 2>conftest.err && grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && grep sub/conftest.${OBJEXT-o} sub/conftest.Po > /dev/null 2>&1 && ${MAKE-make} -s -f confmf > /dev/null 2>&1; then # icc doesn't choke on unknown options, it will just issue warnings # or remarks (even with -Werror). So we grep stderr for any message # that says an option was ignored or not supported. # When given -MP, icc 7.0 and 7.1 complain thusly: # icc: Command line warning: ignoring option '-M'; no argument required # The diagnosis changed in icc 8.0: # icc: Command line remark: option '-MP' not supported if (grep 'ignoring option' conftest.err || grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else am_cv_$1_dependencies_compiler_type=$depmode break fi fi done cd .. rm -rf conftest.dir else am_cv_$1_dependencies_compiler_type=none fi ]) AC_SUBST([$1DEPMODE], [depmode=$am_cv_$1_dependencies_compiler_type]) AM_CONDITIONAL([am__fastdep$1], [ test "x$enable_dependency_tracking" != xno \ && test "$am_cv_$1_dependencies_compiler_type" = gcc3]) ]) # AM_SET_DEPDIR # ------------- # Choose a directory name for dependency files. # This macro is AC_REQUIREd in _AM_DEPENDENCIES AC_DEFUN([AM_SET_DEPDIR], [AC_REQUIRE([AM_SET_LEADING_DOT])dnl AC_SUBST([DEPDIR], ["${am__leading_dot}deps"])dnl ]) # AM_DEP_TRACK # ------------ AC_DEFUN([AM_DEP_TRACK], [AC_ARG_ENABLE(dependency-tracking, [ --disable-dependency-tracking speeds up one-time build --enable-dependency-tracking do not reject slow dependency extractors]) if test "x$enable_dependency_tracking" != xno; then am_depcomp="$ac_aux_dir/depcomp" AMDEPBACKSLASH='\' fi AM_CONDITIONAL([AMDEP], [test "x$enable_dependency_tracking" != xno]) AC_SUBST([AMDEPBACKSLASH]) ]) # Generate code to set up dependency tracking. -*- Autoconf -*- # Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 # Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. #serial 3 # _AM_OUTPUT_DEPENDENCY_COMMANDS # ------------------------------ AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS], [for mf in $CONFIG_FILES; do # Strip MF so we end up with the name of the file. mf=`echo "$mf" | sed -e 's/:.*$//'` # Check whether this is an Automake generated Makefile or not. # We used to match only the files named `Makefile.in', but # some people rename them; so instead we look at the file content. # Grep'ing the first line is not enough: some people post-process # each Makefile.in and add a new line on top of each file to say so. # So let's grep whole file. if grep '^#.*generated by automake' $mf > /dev/null 2>&1; then dirpart=`AS_DIRNAME("$mf")` else continue fi # Extract the definition of DEPDIR, am__include, and am__quote # from the Makefile without running `make'. DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"` test -z "$DEPDIR" && continue am__include=`sed -n 's/^am__include = //p' < "$mf"` test -z "am__include" && continue am__quote=`sed -n 's/^am__quote = //p' < "$mf"` # When using ansi2knr, U may be empty or an underscore; expand it U=`sed -n 's/^U = //p' < "$mf"` # Find all dependency output files, they are included files with # $(DEPDIR) in their names. We invoke sed twice because it is the # simplest approach to changing $(DEPDIR) to its actual value in the # expansion. for file in `sed -n " s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \ sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g' -e 's/\$U/'"$U"'/g'`; do # Make sure the directory exists. test -f "$dirpart/$file" && continue fdir=`AS_DIRNAME(["$file"])` AS_MKDIR_P([$dirpart/$fdir]) # echo "creating $dirpart/$file" echo '# dummy' > "$dirpart/$file" done done ])# _AM_OUTPUT_DEPENDENCY_COMMANDS # AM_OUTPUT_DEPENDENCY_COMMANDS # ----------------------------- # This macro should only be invoked once -- use via AC_REQUIRE. # # This code is only required when automatic dependency tracking # is enabled. FIXME. This creates each `.P' file that we will # need in order to bootstrap the dependency handling code. AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS], [AC_CONFIG_COMMANDS([depfiles], [test x"$AMDEP_TRUE" != x"" || _AM_OUTPUT_DEPENDENCY_COMMANDS], [AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir"]) ]) # Do all the work for Automake. -*- Autoconf -*- # Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005 # Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # serial 12 # This macro actually does too much. Some checks are only needed if # your package does certain things. But this isn't really a big deal. # AM_INIT_AUTOMAKE(PACKAGE, VERSION, [NO-DEFINE]) # AM_INIT_AUTOMAKE([OPTIONS]) # ----------------------------------------------- # The call with PACKAGE and VERSION arguments is the old style # call (pre autoconf-2.50), which is being phased out. PACKAGE # and VERSION should now be passed to AC_INIT and removed from # the call to AM_INIT_AUTOMAKE. # We support both call styles for the transition. After # the next Automake release, Autoconf can make the AC_INIT # arguments mandatory, and then we can depend on a new Autoconf # release and drop the old call support. AC_DEFUN([AM_INIT_AUTOMAKE], [AC_PREREQ([2.58])dnl dnl Autoconf wants to disallow AM_ names. We explicitly allow dnl the ones we care about. m4_pattern_allow([^AM_[A-Z]+FLAGS$])dnl AC_REQUIRE([AM_SET_CURRENT_AUTOMAKE_VERSION])dnl AC_REQUIRE([AC_PROG_INSTALL])dnl # test to see if srcdir already configured if test "`cd $srcdir && pwd`" != "`pwd`" && test -f $srcdir/config.status; then AC_MSG_ERROR([source directory already configured; run "make distclean" there first]) fi # test whether we have cygpath if test -z "$CYGPATH_W"; then if (cygpath --version) >/dev/null 2>/dev/null; then CYGPATH_W='cygpath -w' else CYGPATH_W=echo fi fi AC_SUBST([CYGPATH_W]) # Define the identity of the package. dnl Distinguish between old-style and new-style calls. m4_ifval([$2], [m4_ifval([$3], [_AM_SET_OPTION([no-define])])dnl AC_SUBST([PACKAGE], [$1])dnl AC_SUBST([VERSION], [$2])], [_AM_SET_OPTIONS([$1])dnl AC_SUBST([PACKAGE], ['AC_PACKAGE_TARNAME'])dnl AC_SUBST([VERSION], ['AC_PACKAGE_VERSION'])])dnl _AM_IF_OPTION([no-define],, [AC_DEFINE_UNQUOTED(PACKAGE, "$PACKAGE", [Name of package]) AC_DEFINE_UNQUOTED(VERSION, "$VERSION", [Version number of package])])dnl # Some tools Automake needs. AC_REQUIRE([AM_SANITY_CHECK])dnl AC_REQUIRE([AC_ARG_PROGRAM])dnl AM_MISSING_PROG(ACLOCAL, aclocal-${am__api_version}) AM_MISSING_PROG(AUTOCONF, autoconf) AM_MISSING_PROG(AUTOMAKE, automake-${am__api_version}) AM_MISSING_PROG(AUTOHEADER, autoheader) AM_MISSING_PROG(MAKEINFO, makeinfo) AM_PROG_INSTALL_SH AM_PROG_INSTALL_STRIP AC_REQUIRE([AM_PROG_MKDIR_P])dnl # We need awk for the "check" target. The system "awk" is bad on # some platforms. AC_REQUIRE([AC_PROG_AWK])dnl AC_REQUIRE([AC_PROG_MAKE_SET])dnl AC_REQUIRE([AM_SET_LEADING_DOT])dnl _AM_IF_OPTION([tar-ustar], [_AM_PROG_TAR([ustar])], [_AM_IF_OPTION([tar-pax], [_AM_PROG_TAR([pax])], [_AM_PROG_TAR([v7])])]) _AM_IF_OPTION([no-dependencies],, [AC_PROVIDE_IFELSE([AC_PROG_CC], [_AM_DEPENDENCIES(CC)], [define([AC_PROG_CC], defn([AC_PROG_CC])[_AM_DEPENDENCIES(CC)])])dnl AC_PROVIDE_IFELSE([AC_PROG_CXX], [_AM_DEPENDENCIES(CXX)], [define([AC_PROG_CXX], defn([AC_PROG_CXX])[_AM_DEPENDENCIES(CXX)])])dnl ]) ]) # When config.status generates a header, we must update the stamp-h file. # This file resides in the same directory as the config header # that is generated. The stamp files are numbered to have different names. # Autoconf calls _AC_AM_CONFIG_HEADER_HOOK (when defined) in the # loop where config.status creates the headers, so we can generate # our stamp files there. AC_DEFUN([_AC_AM_CONFIG_HEADER_HOOK], [# Compute $1's index in $config_headers. _am_stamp_count=1 for _am_header in $config_headers :; do case $_am_header in $1 | $1:* ) break ;; * ) _am_stamp_count=`expr $_am_stamp_count + 1` ;; esac done echo "timestamp for $1" >`AS_DIRNAME([$1])`/stamp-h[]$_am_stamp_count]) # Copyright (C) 2001, 2003, 2005 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_PROG_INSTALL_SH # ------------------ # Define $install_sh. AC_DEFUN([AM_PROG_INSTALL_SH], [AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl install_sh=${install_sh-"$am_aux_dir/install-sh"} AC_SUBST(install_sh)]) # Copyright (C) 2003, 2005 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # serial 2 # Check whether the underlying file-system supports filenames # with a leading dot. For instance MS-DOS doesn't. AC_DEFUN([AM_SET_LEADING_DOT], [rm -rf .tst 2>/dev/null mkdir .tst 2>/dev/null if test -d .tst; then am__leading_dot=. else am__leading_dot=_ fi rmdir .tst 2>/dev/null AC_SUBST([am__leading_dot])]) # Check to see how 'make' treats includes. -*- Autoconf -*- # Copyright (C) 2001, 2002, 2003, 2005 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # serial 3 # AM_MAKE_INCLUDE() # ----------------- # Check to see how make treats includes. AC_DEFUN([AM_MAKE_INCLUDE], [am_make=${MAKE-make} cat > confinc << 'END' am__doit: @echo done .PHONY: am__doit END # If we don't find an include directive, just comment out the code. AC_MSG_CHECKING([for style of include used by $am_make]) am__include="#" am__quote= _am_result=none # First try GNU make style include. echo "include confinc" > confmf # We grep out `Entering directory' and `Leaving directory' # messages which can occur if `w' ends up in MAKEFLAGS. # In particular we don't look at `^make:' because GNU make might # be invoked under some other name (usually "gmake"), in which # case it prints its new name instead of `make'. if test "`$am_make -s -f confmf 2> /dev/null | grep -v 'ing directory'`" = "done"; then am__include=include am__quote= _am_result=GNU fi # Now try BSD make style include. if test "$am__include" = "#"; then echo '.include "confinc"' > confmf if test "`$am_make -s -f confmf 2> /dev/null`" = "done"; then am__include=.include am__quote="\"" _am_result=BSD fi fi AC_SUBST([am__include]) AC_SUBST([am__quote]) AC_MSG_RESULT([$_am_result]) rm -f confinc confmf ]) # Fake the existence of programs that GNU maintainers use. -*- Autoconf -*- # Copyright (C) 1997, 1999, 2000, 2001, 2003, 2005 # Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # serial 4 # AM_MISSING_PROG(NAME, PROGRAM) # ------------------------------ AC_DEFUN([AM_MISSING_PROG], [AC_REQUIRE([AM_MISSING_HAS_RUN]) $1=${$1-"${am_missing_run}$2"} AC_SUBST($1)]) # AM_MISSING_HAS_RUN # ------------------ # Define MISSING if not defined so far and test if it supports --run. # If it does, set am_missing_run to use it, otherwise, to nothing. AC_DEFUN([AM_MISSING_HAS_RUN], [AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl test x"${MISSING+set}" = xset || MISSING="\${SHELL} $am_aux_dir/missing" # Use eval to expand $SHELL if eval "$MISSING --run true"; then am_missing_run="$MISSING --run " else am_missing_run= AC_MSG_WARN([`missing' script is too old or missing]) fi ]) # Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_PROG_MKDIR_P # --------------- # Check whether `mkdir -p' is supported, fallback to mkinstalldirs otherwise. # # Automake 1.8 used `mkdir -m 0755 -p --' to ensure that directories # created by `make install' are always world readable, even if the # installer happens to have an overly restrictive umask (e.g. 077). # This was a mistake. There are at least two reasons why we must not # use `-m 0755': # - it causes special bits like SGID to be ignored, # - it may be too restrictive (some setups expect 775 directories). # # Do not use -m 0755 and let people choose whatever they expect by # setting umask. # # We cannot accept any implementation of `mkdir' that recognizes `-p'. # Some implementations (such as Solaris 8's) are not thread-safe: if a # parallel make tries to run `mkdir -p a/b' and `mkdir -p a/c' # concurrently, both version can detect that a/ is missing, but only # one can create it and the other will error out. Consequently we # restrict ourselves to GNU make (using the --version option ensures # this.) AC_DEFUN([AM_PROG_MKDIR_P], [if mkdir -p --version . >/dev/null 2>&1 && test ! -d ./--version; then # We used to keeping the `.' as first argument, in order to # allow $(mkdir_p) to be used without argument. As in # $(mkdir_p) $(somedir) # where $(somedir) is conditionally defined. However this is wrong # for two reasons: # 1. if the package is installed by a user who cannot write `.' # make install will fail, # 2. the above comment should most certainly read # $(mkdir_p) $(DESTDIR)$(somedir) # so it does not work when $(somedir) is undefined and # $(DESTDIR) is not. # To support the latter case, we have to write # test -z "$(somedir)" || $(mkdir_p) $(DESTDIR)$(somedir), # so the `.' trick is pointless. mkdir_p='mkdir -p --' else # On NextStep and OpenStep, the `mkdir' command does not # recognize any option. It will interpret all options as # directories to create, and then abort because `.' already # exists. for d in ./-p ./--version; do test -d $d && rmdir $d done # $(mkinstalldirs) is defined by Automake if mkinstalldirs exists. if test -f "$ac_aux_dir/mkinstalldirs"; then mkdir_p='$(mkinstalldirs)' else mkdir_p='$(install_sh) -d' fi fi AC_SUBST([mkdir_p])]) # Helper functions for option handling. -*- Autoconf -*- # Copyright (C) 2001, 2002, 2003, 2005 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # serial 3 # _AM_MANGLE_OPTION(NAME) # ----------------------- AC_DEFUN([_AM_MANGLE_OPTION], [[_AM_OPTION_]m4_bpatsubst($1, [[^a-zA-Z0-9_]], [_])]) # _AM_SET_OPTION(NAME) # ------------------------------ # Set option NAME. Presently that only means defining a flag for this option. AC_DEFUN([_AM_SET_OPTION], [m4_define(_AM_MANGLE_OPTION([$1]), 1)]) # _AM_SET_OPTIONS(OPTIONS) # ---------------------------------- # OPTIONS is a space-separated list of Automake options. AC_DEFUN([_AM_SET_OPTIONS], [AC_FOREACH([_AM_Option], [$1], [_AM_SET_OPTION(_AM_Option)])]) # _AM_IF_OPTION(OPTION, IF-SET, [IF-NOT-SET]) # ------------------------------------------- # Execute IF-SET if OPTION is set, IF-NOT-SET otherwise. AC_DEFUN([_AM_IF_OPTION], [m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])]) # Check to make sure that the build environment is sane. -*- Autoconf -*- # Copyright (C) 1996, 1997, 2000, 2001, 2003, 2005 # Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # serial 4 # AM_SANITY_CHECK # --------------- AC_DEFUN([AM_SANITY_CHECK], [AC_MSG_CHECKING([whether build environment is sane]) # Just in case sleep 1 echo timestamp > conftest.file # Do `set' in a subshell so we don't clobber the current shell's # arguments. Must try -L first in case configure is actually a # symlink; some systems play weird games with the mod time of symlinks # (eg FreeBSD returns the mod time of the symlink's containing # directory). if ( set X `ls -Lt $srcdir/configure conftest.file 2> /dev/null` if test "$[*]" = "X"; then # -L didn't work. set X `ls -t $srcdir/configure conftest.file` fi rm -f conftest.file if test "$[*]" != "X $srcdir/configure conftest.file" \ && test "$[*]" != "X conftest.file $srcdir/configure"; then # If neither matched, then we have a broken ls. This can happen # if, for instance, CONFIG_SHELL is bash and it inherits a # broken ls alias from the environment. This has actually # happened. Such a system could not be considered "sane". AC_MSG_ERROR([ls -t appears to fail. Make sure there is not a broken alias in your environment]) fi test "$[2]" = conftest.file ) then # Ok. : else AC_MSG_ERROR([newly created file is older than distributed files! Check your system clock]) fi AC_MSG_RESULT(yes)]) # Copyright (C) 2001, 2003, 2005 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # AM_PROG_INSTALL_STRIP # --------------------- # One issue with vendor `install' (even GNU) is that you can't # specify the program used to strip binaries. This is especially # annoying in cross-compiling environments, where the build's strip # is unlikely to handle the host's binaries. # Fortunately install-sh will honor a STRIPPROG variable, so we # always use install-sh in `make install-strip', and initialize # STRIPPROG with the value of the STRIP variable (set by the user). AC_DEFUN([AM_PROG_INSTALL_STRIP], [AC_REQUIRE([AM_PROG_INSTALL_SH])dnl # Installed binaries are usually stripped using `strip' when the user # run `make install-strip'. However `strip' might not be the right # tool to use in cross-compilation environments, therefore Automake # will honor the `STRIP' environment variable to overrule this program. dnl Don't test for $cross_compiling = yes, because it might be `maybe'. if test "$cross_compiling" != no; then AC_CHECK_TOOL([STRIP], [strip], :) fi INSTALL_STRIP_PROGRAM="\${SHELL} \$(install_sh) -c -s" AC_SUBST([INSTALL_STRIP_PROGRAM])]) # Check how to create a tarball. -*- Autoconf -*- # Copyright (C) 2004, 2005 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # serial 2 # _AM_PROG_TAR(FORMAT) # -------------------- # Check how to create a tarball in format FORMAT. # FORMAT should be one of `v7', `ustar', or `pax'. # # Substitute a variable $(am__tar) that is a command # writing to stdout a FORMAT-tarball containing the directory # $tardir. # tardir=directory && $(am__tar) > result.tar # # Substitute a variable $(am__untar) that extract such # a tarball read from stdin. # $(am__untar) < result.tar AC_DEFUN([_AM_PROG_TAR], [# Always define AMTAR for backward compatibility. AM_MISSING_PROG([AMTAR], [tar]) m4_if([$1], [v7], [am__tar='${AMTAR} chof - "$$tardir"'; am__untar='${AMTAR} xf -'], [m4_case([$1], [ustar],, [pax],, [m4_fatal([Unknown tar format])]) AC_MSG_CHECKING([how to create a $1 tar archive]) # Loop over all known methods to create a tar archive until one works. _am_tools='gnutar m4_if([$1], [ustar], [plaintar]) pax cpio none' _am_tools=${am_cv_prog_tar_$1-$_am_tools} # Do not fold the above two line into one, because Tru64 sh and # Solaris sh will not grok spaces in the rhs of `-'. for _am_tool in $_am_tools do case $_am_tool in gnutar) for _am_tar in tar gnutar gtar; do AM_RUN_LOG([$_am_tar --version]) && break done am__tar="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$$tardir"' am__tar_="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$tardir"' am__untar="$_am_tar -xf -" ;; plaintar) # Must skip GNU tar: if it does not support --format= it doesn't create # ustar tarball either. (tar --version) >/dev/null 2>&1 && continue am__tar='tar chf - "$$tardir"' am__tar_='tar chf - "$tardir"' am__untar='tar xf -' ;; pax) am__tar='pax -L -x $1 -w "$$tardir"' am__tar_='pax -L -x $1 -w "$tardir"' am__untar='pax -r' ;; cpio) am__tar='find "$$tardir" -print | cpio -o -H $1 -L' am__tar_='find "$tardir" -print | cpio -o -H $1 -L' am__untar='cpio -i -H $1 -d' ;; none) am__tar=false am__tar_=false am__untar=false ;; esac # If the value was cached, stop now. We just wanted to have am__tar # and am__untar set. test -n "${am_cv_prog_tar_$1}" && break # tar/untar a dummy directory, and stop if the command works rm -rf conftest.dir mkdir conftest.dir echo GrepMe > conftest.dir/file AM_RUN_LOG([tardir=conftest.dir && eval $am__tar_ >conftest.tar]) rm -rf conftest.dir if test -s conftest.tar; then AM_RUN_LOG([$am__untar /dev/null 2>&1 && break fi done rm -rf conftest.dir AC_CACHE_VAL([am_cv_prog_tar_$1], [am_cv_prog_tar_$1=$_am_tool]) AC_MSG_RESULT([$am_cv_prog_tar_$1])]) AC_SUBST([am__tar]) AC_SUBST([am__untar]) ]) # _AM_PROG_TAR nzbget-16.4/.gitignore0000755000175000017500000000106212630544544014552 0ustar andreasandreas# Compiled Object files *.slo *.lo *.o *.obj # Precompiled Headers *.gch *.pch # Compiled Dynamic libraries *.so *.dylib *.dll # Compiled Static libraries *.lai *.la *.a *.lib # Executables *.exe *.out # GNU Autotools .deps/ config.h config.log config.status Makefile stamp-h1 autom4te.cache/ # Visual Studio User-specific files *.suo *.user *.userosscache *.sln.docstates .vs/ ipch/ *.aps *.ncb *.opensdf *.sdf *.cachefile *.ilk *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.vspscc *.vssscc *.pidb *.svclog *.scc *.sln # NZBGet specific nzbget code_revision.cpp nzbget-16.4/nzbget.conf0000644000175000017500000022635412630544544014734 0ustar andreasandreas# Sample configuration file for NZBGet # # On POSIX put this file to one of the following locations: # ~/.nzbget # /etc/nzbget.conf # /usr/etc/nzbget.conf # /usr/local/etc/nzbget.conf # /opt/etc/nzbget.conf # # On Windows put this file in program's directory. # # You can also put the file into any location, if you specify the path to it # using switch "-c", e.g: # nzbget -c /home/user/myconfig.txt # For quick start change the option MainDir and configure one news-server ############################################################################## ### PATHS ### # Root directory for all tasks. # # On POSIX you can use "~" as alias for home directory (e.g. "~/downloads"). # On Windows use absolute paths (e.g. "C:\Downloads"). MainDir=~/downloads # Destination directory for downloaded files. # # If you want to distinguish between partially downloaded files and # completed downloads, use also option . DestDir=${MainDir}/dst # Directory to store intermediate files. # # If this option is set (not empty) the files are downloaded into # this directory first. After successful download of nzb-file (possibly # after par-repair) the files are moved to destination directory # (option ). If download or unpack fail the files remain in # intermediate directory. # # Using of intermediate directory can significantly improve unpack # performance if you can put intermediate directory (option ) # and destination directory (option ) on separate physical # hard drives. # # NOTE: If the option is set to empty value the downloaded # files are put directly to destination directory (option ). InterDir=${MainDir}/inter # Directory for incoming nzb-files. # # If a new nzb-file is added to queue via web-interface or RPC-API, it # is saved into this directory and then processed by pre-processing # script (option ). # # This directory is also monitored for new nzb-files. If a new file # is found it is added to download queue. The directory can have # sub-directories. A nzb-file queued from a subdirectory is automatically # assigned to category with sub-directory-name. NzbDir=${MainDir}/nzb # Directory to store program state. # # This directory is used to save download queue, history, information # about fetched RSS feeds, statistics, etc. QueueDir=${MainDir}/queue # Directory to store temporary files. TempDir=${MainDir}/tmp # Directory with web-interface files. # # Example: /usr/local/share/nzbget/webui. # # NOTE: To disable web-interface set the option to an empty value. # This however doesn't disable the built-in web-server completely because # it is also used to serve JSON-/XML-RPC requests. WebDir= # Directory with post-processing and other scripts. # # NOTE: For information on writing scripts visit http://nzbget.net/Extension_scripts. ScriptDir=${MainDir}/scripts # Lock-file for daemon-mode, POSIX only. # # When started in daemon mode the program creates the lock file and # writes process-id (PID) into it. That info can be used in shell # scripts. If the lock file can not be created or the lock to the file # can not be acquired the daemon terminates, preventing unintentional # starting of multiple daemons. # # Set to empty value to disable the creating of the lock-file and the # check for another running instance (not recommended). LockFile=${MainDir}/nzbget.lock # Where to store log file, if it needs to be created. # # NOTE: See also option . LogFile=${DestDir}/nzbget.log # Configuration file template. # # Put the path to the example configuration file which comes with # NZBGet. Web-interface needs this file to read option descriptions. # # Do not put here your actual configuration file (typically stored # in your home directory or in /etc/nzbget.conf) but instead the unchanged # example configuration file (typically installed to # /usr/local/share/nzbget/nzbget.conf). # # Example: /usr/local/share/nzbget/nzbget.conf. ConfigTemplate= # Required directories. # # List of destination directories to be waited for on program start. Directories # must be separated with commas or semicolons. # # The list of directories is checked on program start. The program waits # until all directories become available before starting download or # post-processing. This is useful if the download destination is configured # on network or external drives, which may require some time to mount on boot. # # NOTE: Only directories used in option and option # (global or per-category) can be waited. Other directories, such as # option , option and option must be # available on program start. RequiredDir= ############################################################################## ### NEWS-SERVERS ### # This section defines which servers NZBGet should connect to. # # The servers should be numbered subsequently without holes. # For example if you configure three servers you should name them as Server1, # Server2 and Server3. If you need to delete Server2 later you should also # change the name of Server3 to Server2. Otherwise it will not be properly # read from the config file. Server number doesn't affect its priority (level). # Use this news server (yes, no). # # Set to "no" to disable the server on program start. Servers can be activated # later via scheduler tasks or manually via web-interface. # # NOTE: Download is not possible when all servers on level 0 are disabled. Servers # on higher levels are used only if at least one server on level 0 was tried. Server1.Active=yes # Name of news server. # # The name is used in UI and for logging. It can be any string, you # may even leave it empty. Server1.Name= # Level (priority) of news server (0-99). # # The servers are ordered by their level. NZBGet first tries to download # an article from one (any) of level-0-servers. If that server fails, # NZBGet tries all other level-0-servers. If all servers fail, it proceeds # with the level-1-servers, etc. # # Put your major download servers at level 0 and your fill servers at # levels 1, 2, etc.. # # Several servers with the same level may be defined, they have # the same priority. Server1.Level=0 # Group of news server (0-99). # # If you have multiple accounts with same conditions (retention, etc.) # on the same news server, set the same group (greater than 0) for all # of them. If download fails on one news server, NZBGet does not try # other servers from the same group. # # Value "0" means no group defined (default). Server1.Group=0 # Host name of news server. Server1.Host=my.newsserver.com # Port to connect to (1-65535). Server1.Port=119 # User name to use for authentication. Server1.Username=user # Password to use for authentication. Server1.Password=pass # Server requires "Join Group"-command (yes, no). Server1.JoinGroup=no # Encrypted server connection (TLS/SSL) (yes, no). # # NOTE: By changing this option you should also change the option # accordingly because unsecure and encrypted connections use different ports. Server1.Encryption=no # Cipher to use for encrypted server connection. # # By default (when the option is empty) the underlying encryption library # chooses the cipher automatically. To achieve the best performance # however you can manually select a faster cipher. # # See http://nzbget.net/Choosing_a_cipher for details. # # NOTE: One of the fastest cipher is RC4. To select it use the cipher string # "RC4-MD5" (if NZBGet was configured to use OpenSSL) or # "NONE:+VERS-TLS-ALL:+ARCFOUR-128:+RSA:+MD5:+COMP-ALL" # (if NZBGet was configured to use GnuTLS). Note that RC4 is considered insecure # by the IETF (http://tools.ietf.org/html/rfc7465), but may be sufficient for # the usage of NZBGet. # # NOTE: You may get a TLS handshake error if the news server does # not support the chosen cipher. You can also get an error "Could not # select cipher for TLS" if the cipher string is not valid. Server1.Cipher= # Maximum number of simultaneous connections to this server (0-999). Server1.Connections=4 # Server retention time (days). # # How long the articles are stored on the news server. The articles # whose age exceed the defined server retention time are not tried on # this news server, the articles are instead considered failed on this # news server. # # Value "0" disables retention check. Server1.Retention=0 # Second server, on level 0. #Server2.Level=0 #Server2.Host=my2.newsserver.com #Server2.Port=119 #Server2.Username=me #Server2.Password=mypass #Server2.JoinGroup=yes #Server2.Connections=4 # Third server, on level 1. #Server3.Level=1 #Server3.Host=fills.newsserver.com #Server3.Port=119 #Server3.Username=me2 #Server3.Password=mypass2 #Server3.JoinGroup=yes #Server3.Connections=1 ############################################################################## ### SECURITY ### # IP on which NZBGet server listen and which clients use to contact NZBGet. # # It could be a dns-hostname (e. g. "mypc") or an ip-address (e. g. "192.168.1.2" or # "127.0.0.1"). An IP-address is more effective because does not require dns-lookup. # # Your computer may have multiple network interfaces and therefore multiple IP # addresses. If you want NZBGet to listen to all interfaces and be available from # all IP-addresses use value "0.0.0.0". # # NOTE: When you start NZBGet as client (to send remote commands to NZBGet server) and # the option is set to "0.0.0.0" the client will use IP "127.0.0.1". # # NOTE: If you set the option to "127.0.0.1" you will be able to connect to NZBGet # only from the computer running NZBGet. This restriction applies to web-interface too. ControlIP=0.0.0.0 # Port which NZBGet server and remote client use (1-65535). # # NOTE: The communication via this port is not encrypted. For encrypted # communication see option . ControlPort=6789 # User name which NZBGet server and remote client use. # # Set to empty value to disable user name check (check only password). # # NOTE: This option was added in NZBGet 11. Older versions used predefined # not changeable user name "nzbget". Third-party tools or web-sites written # for older NZBGet versions may not have an option to define user name. In # this case you should set option to the default value # "nzbget" or use empty value. ControlUsername=nzbget # Password which NZBGet server and remote client use. # # Set to empty value to disable authorization request. ControlPassword=tegbzn6789 # User name for restricted access. # # Restricted user can control the program with few restrictions. He # has access to web-interface and can see most program settings. He # can not change program settings and can not view security related # options or options provided by extension scripts. # # Use this user to connect to NZBGet from other programs and web-sites. # # In terms of RPC-API the user: # - cannot use method "saveconfig"; # - methods "config" and "saveconfig" return string "***" for # options those content is protected from the user. # # Set to empty value to disable restricted user. # # NOTE: Don't forget to change default username/password of the control # user (options and ). RestrictedUsername= # Password for restricted access. # # Set to empty value to disable password check. RestrictedPassword= # User name to add downloads via RPC-API. # # Use the AddUsername/AddPassword to give other programs or web-services # access to NZBGet with only two permissions: # - add new downloads using RPC-method "append"; # - check program version using RPC-method "version". # # In a case the program/web-service needs more rights use the restricted # user instead (options and ). # # Set to empty value to disable add-user. # # NOTE: Don't forget to change default username/password of the control # user (options and ). AddUsername= # Password for user with add downloads access. # # Set to empty value to disable password check. AddPassword= # Secure control of NZBGet server (yes, no). # # Activate the option if you want to access NZBGet built-in web-server # via HTTPS (web-interface and RPC). You should also provide certificate # and key files, see option and option . SecureControl=no # Port which NZBGet server and remote client use for encrypted # communication (1-65535). SecurePort=6791 # Full path to certificate file for encrypted communication. SecureCert= # Full path to key file for encrypted communication. SecureKey= # IP-addresses allowed to connect without authorization. # # Comma separated list of privileged IPs for easy access to NZBGet # built-in web-server (web-interface and RPC). The connected clients # have full unrestricted access. # # Example: 127.0.0.1,192.168.178.2. # # NOTE: Do not use this option if the program works behind another # web-server because all requests will have the address of this server. AuthorizedIP= # User name for daemon-mode, POSIX only. # # Set the user that the daemon normally runs at (POSIX in daemon-mode only). # Set MainDir with an absolute path to be sure where it will write. # This allows NZBGet daemon to be launched in rc.local (at boot), and # download items as a specific user id. # # NOTE: This option has effect only if the program was started from # root-account, otherwise it is ignored and the daemon runs under # current user id. DaemonUsername=root # Specify default umask (affects file permissions) for newly created # files, POSIX only (000-1000). # # The value should be written in octal form (the same as for "umask" shell # command). # Empty value or value "1000" disable the setting of umask-mode; current # umask-mode (set via shell) is used in this case. UMask=1000 ############################################################################## ### CATEGORIES ### # This section defines categories available in web-interface. # Category name. # # Each nzb-file can be assigned to a category. # Category name is passed to post-processing script and can be used by it # to perform category specific processing. Category1.Name=Movies # Destination directory for this category. # # If this option is empty, then the default destination directory # (option ) is used. In this case if the option # is active, the program creates a subdirectory with category name within # destination directory. Category1.DestDir= # Unpack downloaded nzb-files (yes, no). # # For more information see global option . Category1.Unpack=yes # Default list of post-processing scripts. # # For more information see global option . Category1.PostScript= # List of aliases. # # When a nzb-file is added from URL, RSS or RPC the category name # is usually supplied by nzb-site or by application accessing # NZBGet. Using Aliases you can match their categories with your owns. # # Separate aliases with commas or semicolons. Use wildcard-characters # * and ? for pattern matching. # # Example: TV - HD, TV - SD, TV* Category1.Aliases= Category2.Name=Series Category3.Name=Music Category4.Name=Software ############################################################################## ### RSS FEEDS ### # Name of RSS Feed. # # The name is used in UI and for logging. It can be any string. #Feed1.Name=my feed # Address (URL) of RSS Feed. # # Example: https://myindexer.com/api?apikey=3544646bfd1c535a9654645609800901&t=search&q=game. #Feed1.URL= # Filter rules for items. # # Use filter to ignore unwanted items in the feed. In its simplest version # the filter is a space separated list of words which must be present in # the item title. # # Example: linux debian dvd. # # MORE INFO: # NOTE: This is a short documentation, for more information visit # http://nzbget.net/RSS. # # Feed filter consists of rules - one rule per line. Each rule defines # a search string and a command, which must be performed if the search # string matches. There are five kinds of rule-commands: Accept, # Reject, Require, Options, Comment. # # NOTE: Since options in the configuration file can not span multiple # lines, the lines (rules) must be separated with %-character (percent). # # Definition of a rule: # [A:|A(options):|R:|Q:|O(options):|#] search-string # # A - declares Accept-rule. Rules are accept-rules by default, the # "A:" can be imitted. If the feed item matches to the rule the # item is considered good and no further rules are checked. # R - declares Reject-rule. If the feed item matches to the rule the # item is considered bad and no further rules are checked. # Q - declares Require-rule. If the feed item DOES NOT match to the rule # the item is considered bad and no further rules are checked. # O - declares Options-rule. If the feed item matches to the rule the # options declared in the rule are set for the item. The item is # neither accepted nor rejected via this rule but can be accepted # later by one of Accept-rules. In this case the item will have its # options already set (unless the Accept-rule overrides them). # # - lines starting with # are considered comments and are ignored. You # can use comments to explain complex rules or to temporary disable # rules for debugging. # # Options allow to set properties on nzb-file. It's a comma-separated # list of property names with their values. # # Definition of an option: # name:value # # Options can be defined using long option names or short names: # category (cat, c) - set category name, value is a string; # pause (p) - add nzb in paused or unpaused state, possible # values are: yes (y), no (n); # priority (pr, r) - set priority, value is a signed integer number; # priority+ (pr+, r+) - increase priority, value is a signed integer number; # dupescore (ds, s) - set duplicate score, value is a signed integer number; # dupescore+ (ds+, s+) - increase duplicate score, value is a signed integer number; # dupekey (dk, k) - set duplicate key, value is a string; # dupekey+ (dk+, k+) - add to duplicate key, value is a string; # dupemode (dm, m) - set duplicate check mode, possible values # are: score (s), all (a), force (f); # rageid - generate duplicate key using this rageid # (integer number) and season/episode numbers; # series - generate duplicate key using series identifier # (any unique string) and season/episode numbers. # # Examples of option definitions: # Accept(category:my series, pause:yes, priority:100): my show 1080p; # Options(c:my series, p:y, r:100): 1080p; # Options(s:1000): 1080p; # Options(k+:1080p): 1080p; # Options(dupemode:force): BluRay. # # Rule-options override values set in feed-options. # # The search-string is similar to used in search engines. It consists of # search terms separated with spaces. Every term is checked for a feed # item and if they all succeed the rule is considered matching. # # Definition of a term: # [+|-][field:][command]param # # + - declares a positive term. Terms are positive by default, # the "+" can be omitted; # - - declares a negative term. If the term succeed the feed # item is ignored; # field - field to which apply the term. If not specified # the default field "title" is used; # command - a special character defining how to interpret the # parameter (followed after the command): # @ - search for string "param". This is default command, # the "@" can be omitted; # $ - "param" defines a regular expression (using POSIX Extended # Regular Expressions syntax); # = - equal; # < - less than; # <= - equal or less than; # > - greater than; # >= - equal or greater than; # param - parameter for command. # # Commands @ and $ are for use with text fields (title, filename, category, # link, description, dupekey). Commands =, <, <=, > and >= are for use # with numeric fields (size, age, imdbid, rageid, season, episode, priority, # dupescore). # # Only fields title, filename and age are always present. The availability of # other fields depend on rss feed provider. # # Any newznab attribute (encoded as "newznab:attr" in the RSS feed) can # be used as search field with prefix "attr-", for example "attr-genre". # # Text search (Command @) supports supports wildcard characters * (matches # any number of any characters), ? (matches any one character) # and # (matches one digit). # Text search is by default performed against words (word-search mode): the # field content is separated into words and then each word is checked # against pattern. If the search pattern starts and ends with * (star) # the search is performed against the whole field content # (substring-search mode). If the search pattern contains word separator # characters (except * and ?) the search is performed on the whole # field (the word-search would be obviously never successful in this # case). Word separators are: !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~. # # Field "size" can have suffixes "K" or "KB" for kilobytes, "M" or "MB" # for megabytes and "G" or "GB" for gigabytes. Field "age" can have # suffixes "m" for minutes, "h" for hours and "d" for days. If suffix # is not specified default is days. # # Examples (the trailing ; or . is not part of filter): # 1) A: s01* -category:anime; # 2) my show WEB-DL; # 3) *my?show* WEB-DL size:<1.8GB age:>2h; # 4) R: size:>9GB; # 5) Q: HDTV. # # NOTE: This is a short documentation, for more information visit # http://nzbget.net/RSS. #Feed1.Filter= # Treat all items on first fetch as backlog (yes, no). # # yes - when the feed is fetched for the very first time (or after # changing of URL or filter) all existing items are ignored (marked # as backlog). The items found on subsequentional fetches are processed; # no - all items are processed even on first fetch (or after # changing of URL or filter). #Feed1.Backlog=yes # How often to check for new items (minutes). # # Value "0" disables the automatic check of this feed. #Feed1.Interval=15 # Add nzb-files as paused (yes, no). #Feed1.PauseNzb=no # Category for added nzb-files. # # NOTE: Feed providers may include category name within response when nzb-file # is downloaded. If you want to use the providers category leave the option empty. #Feed1.Category= # Priority for added nzb-files (number). # # Priority can be any integer value. The web-interface however operates # with only six predefined priorities: -100 (very low priority), -50 # (low priority), 0 (normal priority, default), 50 (high priority), # 100 (very high priority) and 900 (force priority). Downloads with # priorities equal to or greater than 900 are downloaded and # post-processed even if the program is in paused state (force mode). #Feed1.Priority=0 # List of rss feed scripts to execute before rss feed content is processed. # # For more information see global option . #Feed1.FeedScript= ############################################################################## ### INCOMING NZBS ### # Create subdirectory with category-name in destination-directory (yes, no). AppendCategoryDir=yes # How often incoming-directory (option ) must be checked for new # nzb-files (seconds). # # Value "0" disables the check. # # NOTE: nzb-files are processed by scan and queue scripts. See # options and . NzbDirInterval=5 # How old nzb-file should at least be for it to be loaded to queue (seconds). # # NZBGet checks if nzb-file was not modified in last few seconds, defined by # this option. That safety interval prevents the loading of files, which # were not yet completely saved to disk, for example if they are still being # downloaded in web-browser. NzbDirFileAge=60 # Check for duplicate titles (yes, no). # # If this option is enabled the program checks by adding of a new nzb-file: # 1) if history contains the same title (see below) with success status # the nzb-file is not added to queue; # 2) if download queue already contains the same title the nzb-file is # added to queue for backup (if firt file fails); # 3) if nzb-file contains duplicate entries. This helps to find errors # in bad nzb-files. # # "Same title" means the nzb file name is same or the duplicate key is # same. Duplicate keys are set by fetching from RSS feeds using title # identifier fields provided by RSS provider (imdbid or rageid/season/episode). # # If duplicates were detected only one of them is downloaded. If download # fails another duplicate is tried. If download succeeds all remaining # duplicates are deleted from queue. # # NOTE: For automatic duplicate handling option must be # set to "Delete" or "None". If it is set to "Pause" you will need to # manually unpause another duplicate (if any exists in queue). # # NOTE: For more info on duplicates see http://nzbget.net/RSS. DupeCheck=yes ############################################################################## ### DOWNLOAD QUEUE ### # Save download queue to disk (yes, no). # # This allows to reload it on next start. SaveQueue=yes # Flush download queue to disk (yes, no). # # Immediately flush file buffers for queue state file. This improves # safety for the queue file but may decrease disk performance due to # disabling of disk caching for queue state file. # # You can disable this option if it negatively affects disk performance on your # system. You should create backups of queue-directory (option ) # in that case. Keep the option enabled if your system often crashes. FlushQueue=yes # Reload download queue on start, if it exists (yes, no). ReloadQueue=yes # Continue download of partially downloaded files (yes, no). # # If active the current state (the info about what articles were already # downloaded) is saved every second and is reloaded after restart. This is # about files included in download jobs (usually rar-files), not about # download-jobs (nzb-files) itself. Download-jobs are always # continued regardless of that option. # # Disabling this option may slighlty reduce disk access and is # therefore recommended on fast connections. ContinuePartial=yes # Propagation delay to your news servers (minutes). # # The option sets minimum post age for nzb-files. Very recent files # are not downloaded to avoid download failures. The files remain # on hold in the download queue until the propagation delay expires, # after that they are downloaded. PropagationDelay=0 # Decode articles (yes, no). # # yes - decode articles using internal decoder (supports yEnc and UU formats); # no - articles will not be decoded/joined. Useful to look at article's source text. # # NOTE: This option is primary for debugging purposes. You should not # disable it. Decode=yes # Memory limit for article cache (megabytes). # # Article cache helps to improve performance. First the amount of disk # operations can be significantly reduced. Second the created files are # less fragmented, which again speeds up the post-processing (unpacking). # # The article cache works best with option which can # effectively use even small cache (like 50 MB). # # If option is disabled the cache should be big enough to # hold all articles of one file (typically up to 200 MB, sometimes even # 500 MB). Otherwise the articles are written into temporary directory # when the cache is full, which degrades performance. # # Value "0" disables article cache. # # In 32 bit mode the maximum allowed value is 1900. # # NOTE: Also see option . ArticleCache=0 # Write decoded articles directly into destination output file (yes, no). # # Files are posted to Usenet in multiple pieces (articles). Each file # typically consists of hundreds of articles. # # When option is disabled and the article cache (option # ) is not active or is full the program saves downloaded # articles into temporary directory and later reads them all to write # again into the destination file. # # When option is enabled the program at first creates the # output destination file with required size (total size of all articles), # then writes the articles directly to this file without creating of any # temporary files. If article cache (option ) is active # the downloaded articles are saved into cache first and are written # into the destination file when the cache flushes. This happen when # all articles of the file are downloaded or when the cache becomes # full to 90%. # # The direct write relies on the ability of file system to create # empty files without allocating the space on the drive (sparse files), # which most modern file systems support including EXT3, EXT4 # and NTFS. The notable exception is HFS+ (default file system on OSX). # # The direct write usually improves performance by reducing the amount # of disk operations but may produce more fragmented files when used # without article cache. DirectWrite=yes # Memory limit for per article write buffer (kilobytes). # # When downloaded articles are written into disk the OS collects # data in the internal buffer before flushing it into disk. This option # controls the size of this buffer per connection/download thread. # # Larger buffers decrease the amount of disk operations and help # producing less fragmented files speeding up the post-processing # (unpack). # # To calculate the maximum memory required for all download threads multiply # WriteBuffer by number of connections configured in section # "NEWS-SERVERS". The option sets the limit, the actual buffer can be # smaller if the article size (typically about 500 KB) is below the limit. # # Write-buffer is managed by OS (system libraries) and therefore # the effect of the option is highly OS-dependent. # # Recommended value for computers with enough memory: 1024. # # Value "0" disables the setting of buffer size. In this case a buffer # of default size (OS and compiler specific) is used, which is usually # too small (1-4 KB) and therefore not optimal. # # NOTE: Also see option . WriteBuffer=0 # Check CRC of downloaded and decoded articles (yes, no). # # Normally this option should be enabled for better detecting of download # errors. However checking of CRC needs CPU time. On a fast connection and # slow CPU disabling of CRC-Check may improve performance. CrcCheck=yes # How many retries should be attempted if a download error occurs (0-99). # # If download fails because of incomplete or damaged article or due to # CRC-error the program tries to redownload the article from the same # news server as many times as defined in option . If all # attempts fail the program tries another news server. # # If download fails because of "article or group not found error" the # program tries another news server without retrying on the failed server. # # If download fails because of interrupted connection the program # tries another news server or the same server after the block interval # expires. Retries=3 # Wait interval between retries (seconds). # # If download of an article fails because of interrupted connection # the server is temporary blocked until the retry interval expires. RetryInterval=10 # Connection timeout for article downloading (seconds). ArticleTimeout=60 # Connection timeout for URL fetching (seconds). # # This includes fetching of nzb-files via URLs and fetching of RSS feeds. UrlTimeout=60 # Timeout until a download-thread should be killed (seconds). # # This can help on hanging downloads, but is dangerous. # Do not use small values! TerminateTimeout=600 # Set the maximum download rate on program start (kilobytes/sec). # # The download rate can be changed later via remote calls. # # Value "0" means no speed control. DownloadRate=0 # Accurate speed rate calculation (yes, no). # # During downloading using several connections the download threads may # interfere with each other when updating statistical data for speed # meter. This may cause small errors in current download speed reported # by the program. The speed meter recovers automatically from such errors # after max. 30 seconds (time window used for speed calculation). # # Enable the option to use thread synchronisation mechanisms in order to # provide absolutely accurate speed calculations. # # NOTE: Thread synchronisation increases CPU load and therefore can # decrease download speed. Do not activate this option on computers with # limited CPU power. Before activating the option it is recommended to # run tests to determine how the option affects the CPU usage and the # download speed on a particular system. AccurateRate=no # Pause if disk space gets below this value (megabytes). # # Disk space is checked for directories pointed by option and # option . # # Value "0" disables the check. DiskSpace=250 # Delete already downloaded files from disk when nzb-file is deleted # (yes, no). # # This option defines if downloaded files must be deleted when: # 1) download of nzb-file is cancelled (deleted from queue); # 2) history record with failure-status (par-failure or unpack-failure) # is deleted from history. DeleteCleanupDisk=yes # Delete source nzb-file when it is not needed anymore (yes, no). # # Enable this option for automatic deletion of source nzb-file from # incoming directory when the program doesn't require it anymore (the # nzb-file has been deleted from queue and history). NzbCleanupDisk=yes # Keep the history of downloaded nzb-files (days). # # After download and post-processing the items are added to history where # their status can be checked and they can be post-processed again if # neccessary. # # After expiring of defined period: # # If option is active the items become hidden and the amount # of data kept is significantly reduced (for better performance), only # fields necessary for duplicate check are kept. The item remain in the # hidden history (forever); # # If option is NOT active the items are removed from history. # # Value "0" disables history. Duplicate check will not work. KeepHistory=30 # Keep the history of outdated feed items (days). # # After fetching of an RSS feed the information about included items (nzb-files) # is saved to disk. This allows to detect new items on next fetch. Feed # providers update RSS feeds constantly. Since the feed length is limited # (usually 100 items or less) the old items get pushed away by new # ones. When an item is not present in the feed anymore it's not necessary # to keep the information about this item on the disk. # # If option is set to "0", the outdated items are deleted from history # immediately. # # Otherwise the items are held in the history for defined number of # days. Keeping of items for few days helps in situations when feed provider # has technical issues and may response with empty feeds (or with missing # items). When the technical issue is fixed the items may reappear in the # feed causing the program to redownload items if they were not found in # the feed history. FeedHistory=7 # Maximum number of simultaneous connections for nzb URL downloads (0-999). # # When NZB-files are added to queue via URL, the program downloads them # from the specified URL. The option limits the maximal number of connections # used for this purpose, when multiple URLs were added at the same time. UrlConnections=4 # Force URL-downloads even if download queue is paused (yes, no). # # If option is active the URL-downloads (such as appending of nzb-files # via URL or fetching of RSS feeds and nzb-files from feeds) are performed # even if download is in paused state. UrlForce=yes ############################################################################## ### LOGGING ### # How to use log file (none, append, reset, rotate). # # none - do not write into log file; # append - append to the existing log file or create it; # reset - delete existing log file on program start and create a new one; # rotate - create new log file for each day, delete old files, # see option . WriteLog=append # Log file rotation period (days). # # Defines how long to keep old log-files, when log rotation is active # (option is set to "rotate"). RotateLog=3 # How error messages must be printed (screen, log, both, none). ErrorTarget=both # How warning messages must be printed (screen, log, both, none). WarningTarget=both # How info messages must be printed (screen, log, both, none). InfoTarget=both # How detail messages must be printed (screen, log, both, none). DetailTarget=log # How debug messages must be printed (screen, log, both, none). # # Debug-messages can be printed only if the program was compiled in # debug-mode: "./configure --enable-debug". DebugTarget=log # Number of messages stored in buffer and available for remote # clients (messages). LogBufferSize=1000 # Create log for each downloaded nzb-file (yes, no). # # The messages are saved for each download separately and can be viewed # at any time in download details dialog or history details dialog. NzbLog=yes # Create a log of all broken files (yes, no). # # It is a text file placed near downloaded files, which contains # the names of broken files. BrokenLog=yes # Create memory dump (core-file) on abnormal termination, Linux only (yes, no). # # Core-files are very helpful for debugging. # # NOTE: Core-files may contain sensible data, like your login/password to # newsserver etc. DumpCore=no # Local time correction (hours or minutes). # # The option allows to adjust timestamps when converting system time to # local time and vice versa. The conversion is used when printing messages # to the log-file and by option "TaskX.Time" in the scheduler settings. # # The option is usually not needed if the time zone is set up correctly. # However, sometimes, especially when using a binary compiled on onother # platform (cross-compiling) the conversion between system and local time # may not work properly and requires adjustment. # # Values in the range -24..+24 are interpreted as hours, other values as minutes. # Example 1: set time correction to one hour: TimeCorrection=1; # Example 2: set time correction to one hour and a half: TimeCorrection=90. TimeCorrection=0 # See also option in section "PATHS" ############################################################################## ### DISPLAY (TERMINAL) ### # Set screen-outputmode (loggable, colored, curses). # # loggable - only messages will be printed to standard output; # colored - prints messages (with simple coloring for messages categories) # and download progress info; uses escape-sequences to move cursor; # curses - advanced interactive interface with the ability to edit # download queue and various output option. OutputMode=curses # Shows NZB-Filename in file list in curses-outputmode (yes, no). # # This option controls the initial state of curses-frontend, # it can be switched on/off in run-time with Z-key. CursesNzbName=yes # Show files in groups (NZB-files) in queue list in curses-outputmode (yes, no). # # This option controls the initial state of curses-frontend, # it can be switched on/off in run-time with G-key. CursesGroup=no # Show timestamps in message list in curses-outputmode (yes, no). # # This option controls the initial state of curses-frontend, # it can be switched on/off in run-time with T-key. CursesTime=no # Update interval for Frontend-output in console mode or remote client # mode (milliseconds). # # Min value 25. Bigger values reduce CPU usage (especially in curses-outputmode) # and network traffic in remote-client mode. UpdateInterval=200 ############################################################################## ### SCHEDULER ### # Time to execute the command (HH:MM). # # Multiple comma-separated values are accepted. # Asterix as hours-part means "every hour". # # Examples: "08:00", "00:00,06:00,12:00,18:00", "*:00", "*:00,*:30". # # NOTE: Also see option . #Task1.Time=08:00 # Week days to execute the command (1-7). # # Comma separated list of week days numbers. # 1 is Monday. # Character '-' may be used to define ranges. # # Examples: "1-7", "1-5", "5,6", "1-5, 7". #Task1.WeekDays=1-7 # Command to be executed (PauseDownload, UnpauseDownload, PausePostProcess, # UnpausePostProcess, PauseScan, UnpauseScan, DownloadRate, Script, Process, # ActivateServer, DeactivateServer, FetchFeed). # # Possible commands: # PauseDownload - pause download; # UnpauseDownload - resume download; # PausePostProcess - pause post-processing; # UnpausePostProcess - resume post-processing; # PauseScan - pause scan of incoming nzb-directory; # UnpauseScan - resume scan of incoming nzb-directory; # DownloadRate - set download rate limit; # Script - execute one or multiple scheduler scripts. The scripts # must be written specially for NZBGet; # Process - execute an external (any) program; # ActivateServer - activate news-server; # DeactivateServer - deactivate news-server; # FetchFeed - fetch RSS feed. # # On start the program checks all tasks and determines current state # for download-pause, scan-pause, download-rate and active servers. #Task1.Command=PauseDownload # Parameters for the command if needed. # # Some scheduler commands require additional parameters: # DownloadRate - download rate limit to be set (kilobytes/sec). # Example: 1000; # Script - list of scheduler scripts to execute. The scripts in # the list must be separated with commas or semicolons. Only # filenames without path must be used. All scripts must be # stored in directory pointed by option . For # more info see below; # Process - path to the program to execute and its parameters. # Example: /home/user/fetch.sh. # If filename or any parameter contains spaces it # must be surrounded with single quotation # marks. If filename/parameter contains single quotation marks, # each of them must be replaced with two single quotation # marks and the resulting filename/parameter must be # surrounded with single quotation marks. # Example: '/home/user/download/my scripts/task process.sh' 'world''s fun'. # In this example one parameter (world's fun) is passed # to the script (task process.sh). # ActivateServer - comma separated list of news server ids or server names. # Example: 1,3. # Example: my news server 1, my news server 2. # NOTE: server names should not have commas. # DeactivateServer - see ActivateServer. # FetchFeed - comma separated list of RSS feed ids or feed names. # Example: 1,3. # Example: bookmarks feed, another feed. # NOTE: feed names should not have commas. # NOTE: use feed id "0" to fetch all feeds. # # INFO FOR DEVELOPERS: # The rest of the description is for command "Script". # # NOTE: This is a short documentation, for more information visit # http://nzbget.net/Extension_scripts. # # NZBGet passes following arguments to scheduler script as environment # variables: # NZBSP_TASKID - id number of scheduler Task. # # In addition to these arguments NZBGet passes all nzbget.conf-options # as environment variables. These variables have prefix "NZBOP_" and # are written in UPPER CASE. For Example option "ParRepair" is passed as # environment variable "NZBOP_PARREPAIR". The dots in option names are # replaced with underscores, for example "SERVER1_HOST". For options # with predefined possible values (yes/no, etc.) the values are passed # always in lower case. # # NOTE: This is a short documentation, for more information visit # http://nzbget.net/Extension_scripts. #Task1.Param= #Task2.Time=20:00 #Task2.WeekDays=1-7 #Task2.Command=UnpauseDownload #Task2.Param= ############################################################################## ### PAR CHECK/REPAIR ### # Whether and how par-verification must be performed (auto, always, force, manual). # # Auto - par-check is performed when needed. One par2-file is always # downloaded. Additional par2-files are downloaded if needed # for repair. Repair is performed if the option # is enabled; # Always - check every download (even undamaged). One par2-file is # always downloaded. Additional par2-files are downloaded # if needed for repair. Repair is performed if the option # is enabled; # Force - force par-check for every download (even undamaged). All # par2-files are always downloaded. Repair is performed if # the option is enabled; # Manual - par-check is skipped. One par2-file is always # downloaded. If a damaged download is detected, all # par2-files are downloaded but neithet par-check nor par-repair # take place. The download can be then repaired manually, # eventually on another faster computer. ParCheck=auto # Check for renamed and missing files (yes, no). # # Par-rename restores original file names using information stored # in par2-files. It also detects missing files (files listed in # par2-files but not present on disk). When enabled the par-rename is # performed as the first step of post-processing for every nzb-file. # # Par-rename is very fast and is highly recommended, especially if # unpack is disabled. ParRename=yes # Automatic par-repair after par-verification (yes, no). # # If option is set to "Auto" or "Force" this option defines # if the download must be repaired when needed. The option can be # disabled if computer does not have enough CPU power, since repairing # may take too much resources and time on a slow computers. ParRepair=yes # What files should be scanned during par-verification (limited, extended, # full, dupe). # # Limited - scan only files belonging to par-set; # Extended - scan files belonging to par-set first, scan other files until # all missing files are found; # Full - scan all files in destination directory. Can be very time # consuming but may sometimes repair where Limited and Extended fail; # Dupe - scan files belonging to par-set first, scan other files until # repair is possible. Even files from other duplicate-downloads # are scanned. Can be very time consuming but brings best results. ParScan=extended # Quick file verification during par-check (yes, no). # # If the option is active the files are quickly verified using # checksums calculated during download; quick verification is very fast # because it doesn't require the reading of files from disk, NZBGet # knows checksums of downloaded files and quickly compares them with # checksums stored in the par-file. # # If the option is disabled the files are verified as usual. That's # slow. Use this if the quick verification doesn't work properly. ParQuick=yes # Memory limit for par-repair buffer (megabytes). # # Set the amount of RAM that the par-checker may use during repair. Having # the buffer as big as the total size of all damaged blocks allows for # the optimal repair speed. The option sets the maximum buffer size, the # allocated buffer can be smaller. # # If you have a lot of RAM set the option to few hundreds (MB) for the # best repair performance. ParBuffer=16 # Number of threads to use during par-repair (0-99). # # On multi-core CPUs for the best speed set the option to the number of # logical cores (physical cores + hyper-threading units). If you want # to utilize the CPU to 100% you may need to add one or two additional threads # to compensate for wait intervals used for thread synchronization. # # On single-core CPUs use only one thread. # # Set to '0' to automatically use all available CPU cores (may not # work on old or exotic platforms). ParThreads=0 # Files to ignore during par-check. # # List of file extensions, file names or file masks to ignore by # par-rename and par-check. The entries must be separated with # commas. # # The entries must be separated with commas. The entries can be file # extensions, file names or file masks containing wildcard # characters * and ?. # # If par-rename or par-check detect missing or damaged files they # will ignore files matching this option and will not initiate # repair. This avoids time costing repair for unimportant files. # # NOTE: Files matching the option are ignored as well. # # Example: .sfv, .nzb, .nfo ParIgnoreExt=.sfv, .nzb, .nfo # What to do if download health drops below critical health (delete, # pause, none). # # Delete - delete nzb-file from queue. If option # is active the already downloaded files will be deleted too; # Pause - pause nzb-file; # None - do nothing (continue download). # # NOTE: For automatic duplicate handling option must be set to "Delete" # or "None". If it is set to "Pause" you will need to manually return # another duplicate to queue (if any exists in history). See also # option . # # NOTE: When option is set to "Dupe" the delete-action is performed # only if article completion is below 10% (empirical threshold). This is to # improve efficiency of dupe par scan mode. HealthCheck=delete # Maximum allowed time for par-repair (minutes). # # If you use NZBGet on a very slow computer like NAS-device, it may be good to # limit the time allowed for par-repair. NZBGet calculates the estimated time # required for par-repair. If the estimated value exceeds the limit defined # here, NZBGet cancels the repair. # # To avoid a false cancellation NZBGet compares the estimated time with # after the first 5 minutes of repairing, when the calculated # estimated time is more or less accurate. But in a case if is # set to a value smaller than 5 minutes, the comparison is made after the first # whole minute. # # Value "0" means unlimited. # # NOTE: The option limits only the time required for repairing. It doesn't # affect the first stage of parcheck - verification of files. However the # verification speed is constant, it doesn't depend on files integrity and # therefore it is not necessary to limit the time needed for the first stage. ParTimeLimit=0 # Pause download queue during check/repair (yes, no). # # Enable the option to give CPU more time for par-check/repair. That helps # to speed up check/repair on slow CPUs with fast connection (e.g. NAS-devices). # # NOTE: If parchecker needs additional par-files it temporarily unpauses # the queue. # # NOTE: See also options and . ParPauseQueue=no # Cleanup download queue after successful check/repair (yes, no). # # Enable this option for automatic deletion of unneeded (paused) par-files # from download queue after successful check/repair. ParCleanupQueue=yes ############################################################################## ### UNPACK ### # Unpack downloaded nzb-files (yes, no). # # Each download (nzb-file) has a post-processing parameter "Unpack". The option # is the default value assigned to this pp-parameter of the download # when it is added to queue. # # When nzb-file is added to queue it can have a category assigned to it. In this # case the option overrides the global option . # # If the download is damaged and could not be repaired using par-files # the unpacking is not performed. # # If the option is set to "Auto" the program tries to unpack # downloaded files first. If the unpacking fails the par-check/repair # is performed and the unpack is executed again. Unpack=yes # Pause download queue during unpack (yes, no). # # Enable the option to give CPU more time for unpacking. That helps # to speed up unpacking on slow CPUs. # # NOTE: See also options and . UnpackPauseQueue=no # Delete archive files after successful unpacking (yes, no). UnpackCleanupDisk=yes # Full path to unrar executable. # # Example: /usr/bin/unrar. # # The option can also contain extra switches to pass to unrar. To the # here defined command line NZBGet adds the following switches: # x -y -p- -o+ *.rar ./_unpack/ # # Switch "x" is added only if neither "x" nor "e" were defined in # the option (this allows you to use switch "e" instead of "x"). switch # "-o+" is added only if neither "-o+" nor "-o-" were defined # in the command line. All other paramaters are always added. Parameter # "-p-" is replaced with "-ppassword" if a password is set for nzb-file. # # Examples: # 1) ignore file attributes (pemissions): # /usr/bin/unrar x -ai; # 2) decrease priority of unrar-process: # nice -n 19 unrar. # # For other useful switches refer to unrar documentation. # # If unrar is in your PATH you may leave the path part and set only # the executable name ("unrar" on POSIX or "unrar.exe" on Windows). UnrarCmd=unrar # Full path to 7-Zip executable. # # Example: /usr/bin/7z. # # Similar to option this option can also include extra switches. # # If 7-Zip binary is in your PATH you may leave the path part and set only # the executable name ("7z" or "7za" on POSIX or "7z.exe" on Windows). SevenZipCmd=7z # Files to delete after successful download. # # List of file extensions, file names or file masks to delete after # successful download. If either unpack or par-check fail the cleanup is # not performed. If neither unpack nor par-check were made (because they # were disabled or the download doesn't contain archives and/or par-files # the cleanup is performed if the health is 100%. # # The entries must be separated with commas. The entries can be file # extensions, file names or file masks containing wildcard # characters * and ?. # # Files listed here are also ignored by par-rename and par-check. # # NOTE: See also option . # # Example: .par2, .sfv ExtCleanupDisk=.par2, .sfv, _brokenlog.txt # Path to file containing unpack passwords. # # If the option is set the program will try all passwords from the file # when unpacking the archives. The file must be a text file containing # one password per line. # # If an nzb-file has a defined password (in the post-processing settings) # then the password-file is not used for that nzb-file. # # NOTE: Trying multiple passwords is a time consuming task. Whenever possible # passwords should be set per nzb-file in their post-processing settings. UnpackPassFile= ############################################################################## ### EXTENSION SCRIPTS ### # Default list of post-processing scripts to execute after the download # of nzb-file is completed and possibly par-checked/repaired and unpacked. # # The scripts in the list must be separated with commas or semicolons. Only # filenames without path must be used. All scripts must be stored in directory # pointed by option . # # Example: Cleanup.sh, Move.sh, EMail.py. # # Each download (nzb-file) has its own list of post-processing scripts. The option # is the default value assigned to download when it is added to # queue. The list of post-processing scripts for a particular download can be # changed in the edit dialog in web-interface or using remote command "--edit/-E". # # When nzb-file is added to queue it can have a category assigned to it. In this # case the option (if not empty) overrides the # global option . # # NOTE: The script execution order is controlled by option , not # by their order in option . # # NOTE: Changing options and doesn't affect # already queued downloads. # # NOTE: For the list of interesting post-processing scripts see # http://nzbget.net/Catalog_of_post-processing_scripts. # # INFO FOR DEVELOPERS: # NOTE: This is a short documentation, for more information visit # http://nzbget.net/Extension_scripts. # # NZBGet passes following arguments to post-processing script 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_FINALDIR - final destination path if set by one of previous pp-scripts; # NZBPP_CATEGORY - category assigned to nzb-file (can be empty string); # NZBPP_DUPEKEY - duplicate key of nzb-file; # NZBPP_DUPESCORE - duplicate score of nzb-file; # NZBPP_DUPEMODE - duplicate mode of nzb-file: SCORE, ALL, FORCE; # NZBPP_TOTALSTATUS - total status of nzb-file: # SUCCESS - everything OK; # WARNING - download is damaged but probably can # be repaired; user intervention is # required; # FAILURE - download has failed or a serious error # occurred during post-processing (unpack, par); # DELETED - download was deleted; post-processing # scripts are usually not called in this case; # however it's possible to force calling # scripts with command "post-process again"; # NZBPP_STATUS - complete status info for nzb-file: it consists # of total status and status detail separated with # slash, for example: "FAILURE/UNPACK"; for possible # status details see documentation on web site; # NZBPP_SCRIPTSTATUS - summary status of the scripts executed before the # current one: # NONE - no other scripts were executed yet or all # of them have ended with exit code "NONE"; # SUCCESS - all other scripts have ended with exit # code "SUCCESS" ; # FAILURE - at least one of the script has failed; # NZBPP_HEALTH - download health: an integer value in the range # from 0 (all articles failed) to 1000 (all articles # successfully downloaded); # NZBPP_CRITICALHEALTH - critical health for this nzb-file: an integer # value in the range 0-1000. The critical health # is calculated based on number and size of # par-files. If nzb-file doesn't have any par-files # the critical health is 1000 (100.0%). If a half # of nzb-file were par-files its critical health # would be 0. If NZBPP_HEALTH goes down below # NZBPP_CRITICALHEALTH the download becomes unrepairable; # NZBPP_TOTALARTICLES - number of articles in nzb-file; # NZBPP_SUCCESSARTICLES - number of successfully downloaded articles; # NZBPP_FAILEDARTICLES - number of failed articles; # NZBPP_SERVERX_SUCCESSARTICLES - number of successfully downloaded # articles from ServerX (X is replaced with server # number, for example NZBPP_SERVER1_SUCCESSARTICLES); # NZBPP_SERVERX_FAILEDARTICLES - number of failed articles from ServerX. # # If the script defines own options they are also passed as environment # variables. These variables have prefix "NZBPO_" in their names. For # example, option "myoption" will be passed as environment variable # "NZBPO_myoption" and in addition in uppercase as "NZBPO_MYOPTION". # # If the script defines own post-processing parameters, they are also passed as # environment variables. These variables have prefix "NZBPR_" in their # names. For example, pp-parameter "myparam" will be passed as environment # variable "NZBPR_myparam" and in addition in uppercase as "NZBPR_MYPARAM". # # In addition to arguments, pp-options and pp-parameters NZBGet passes all # nzbget.conf-options to pp-script as environment variables. These # variables have prefix "NZBOP_" and are written in UPPER CASE. For Example # option "ParRepair" is passed as environment variable "NZBOP_PARREPAIR". The # dots in option names are replaced with underscores, for example # "SERVER1_HOST". For options with predefined possible values (yes/no, etc.) # the values are passed always in lower case. # # If the script moves files it can inform the program about new location # by printing special message into standard output (which is processed # by NZBGet): # echo "[NZB] DIRECTORY=/path/to/moved/files"; # or: # echo "[NZB] FINALDIR=/path/to/moved/files"; # # Command "DIRECTORY" changes the destiantion path of the download and # affects the scripts executed after the current script as well as the # program code itself, for example the command "Post-process again" # will work on new location. Command "FINALDIR" just sets a separate # property of the download and should be used when the files are moved # into an existing directory containg other files to avoid the processing # of those files by other scripts. # # To assign post-processing parameters: # echo "[NZB] NZBPR_myvar=my value"; # # The prefix "NZBPR_" will be removed. In this example a post-processing # parameter with name "myvar" and value "my value" will be associated # with nzb-file. # # To inform NZBGet about bad download: # echo "[NZB] MARK=BAD"; # # Return value: NZBGet processes the exit code returned by the script: # 93 - post-process successful (status = SUCCESS); # 94 - post-process failed (status = FAILURE); # 95 - post-process skipped (status = NONE). Use this code when you script # terminates immediateley without doing any job and when this is not # a failure termination; # 92 - request NZBGet to do par-check/repair for current nzb-file. # # All other return codes are interpreted as failure (status = FAILURE). # # NOTE: This is a short documentation, for more information visit # http://nzbget.net/Extension_scripts. PostScript= # List of scan scripts to execute before a nzb-file is added to queue. # # The scripts in the list must be separated with commas or semicolons. Only # filenames without path must be used. All scripts must be stored in directory # pointed by option . # # The scripts are executed each time a new file is found in incoming # directory (option ) or a file is received via RPC (web-interface, # command "nzbget --append", etc.). # # Example: UnzipNzb.sh, ScanNotify.py. # # The scripts can unpack archives which were put in incoming directory, make # filename cleanup, change nzb-name, category, priority and post-processing # parameters of the nzb-file or do other things. # # INFO FOR DEVELOPERS: # NOTE: This is a short documentation, for more information visit # http://nzbget.net/Extension_scripts. # # NZBGet passes following arguments to the script as environment # variables: # NZBNP_DIRECTORY - path to directory, where file is located. It is a directory # specified by the option or a subdirectory; # NZBNP_FILENAME - name of file to be processed; # NZBNP_NZBNAME - nzb-name (without path but with extension); # NZBNP_CATEGORY - category of nzb-file; # NZBNP_PRIORITY - priority of nzb-file; # NZBNP_TOP - flag indicating that the file will be added to the top # of queue: 0 or 1; # NZBNP_PAUSED - flag indicating that the file will be added as # paused: 0 or 1; # NZBNP_DUPEKEY - duplicate key of nzb-file; # NZBNP_DUPESCORE - duplicate score of nzb-file; # NZBNP_DUPEMODE - duplicate mode of nzb-file: SCORE, ALL, FORCE. # # In addition to these arguments NZBGet passes all nzbget.conf-options # as environment variables. These variables have prefix "NZBOP_" and # are written in UPPER CASE. For Example option "ParRepair" is passed as # environment variable "NZBOP_PARREPAIR". The dots in option names are # replaced with underscores, for example "SERVER1_HOST". For options # with predefined possible values (yes/no, etc.) the values are passed # always in lower case. # # The script can change nzb-name, category, priority, # post-processing parameters and top-/paused-flags of the nzb-file # by printing special messages into standard output (which is processed # by NZBGet). # # To change nzb-name use following syntax: # echo "[NZB] NZBNAME=my download"; # # To change category: # echo "[NZB] CATEGORY=my category"; # # To change priority: # echo "[NZB] PRIORITY=signed_integer_value"; # # for example: to set priority higher than normal: # echo "[NZB] PRIORITY=50"; # # another example: use a negative value for "lower than normal" priority: # echo "[NZB] PRIORITY=-100"; # # Although priority can be any integer value, the web-interface operates # with six predefined priorities: # -100 - very low priority; # -50 - low priority; # 0 - normal priority (default); # 50 - high priority; # 100 - very high priority; # 900 - force priority. # # Downloads with priorities equal to or greater than 900 are downloaded and # post-processed even if the program is in paused state (force mode). # # To assign post-processing parameters: # echo "[NZB] NZBPR_myvar=my value"; # # The prefix "NZBPR_" will be removed. In this example a post-processing # parameter with name "myvar" and value "my value" will be associated # with nzb-file. # # To change top-flag (nzb-file will be added to the top of queue): # echo "[NZB] TOP=1"; # # To change paused-flag (nzb-file will be added in paused state): # echo "[NZB] PAUSED=1"; # # To change duplicate key: # echo "[NZB] DUPEKEY=tv show s01e02"; # # To change duplicate score: # echo "[NZB] DUPESCORE=integer_value"; # # To change duplicate mode: # echo "[NZB] DUPEMODE=(SCORE|ALL|FORCE)"; # # The script can delete processed file, rename it or move somewhere. # After the calling of the script the file will be either added to queue # (if it was an nzb-file) or renamed by adding the extension ".processed". # # NOTE: Files with extensions ".processed", ".queued" and ".error" are skipped # during the directory scanning. # # NOTE: Files with extension ".nzb_processed" are not passed to # scan-script before adding to queue. This feature allows scan-script # to prevent the scanning of nzb-files extracted from archives, if # they were already processed by the script. # # NOTE: Files added via RPC calls in particular from web-interface are # saved into incoming nzb-directory and then processed by the script. # # NOTE: This is a short documentation, for more information visit # http://nzbget.net/Extension_scripts. ScanScript= # List of queue scripts to execute on queue events. # # The scripts in the list must be separated with commas or semicolons. Only # filenames without path must be used. All scripts must be stored in directory # pointed by option . # # The scripts are executed on certain queue events such as adding # a new nzb-file to queue, etc. # # Example: DeleteQueueSamples.sh, NzbAddedNotify.py. # # The script can modify the files in download queue (for example # delete or pause all nfo, sfv, sample files) or do something else. # # INFO FOR DEVELOPERS: # NOTE: This is a short documentation, for more information visit # http://nzbget.net/Extension_scripts. # # NZBGet passes following arguments to the queue script as environment # variables: # NZBNA_NZBNAME - name of nzb-group. This name can be used in calls # to nzbget edit-command using subswitch "-GN name"; # NZBNA_FILENAME - filename of the nzb-file. If the file was added # from nzb-directory this is the fullname with path. # If the file was added via web-interface it contains # only filename without path; # NZBNA_EVENT - describes why the script was called: # NZB_ADDED - after adding of nzb-file to queue; # FILE_DOWNLOADED - after a file included in nzb is # downloaded; # NZB_DOWNLOADED - after all files in nzb are downloaded # (before post-processing); # NZB_DELETED - when nzb is deleted from queue (moved # to history). See NZBNA_DELETESTATUS for details; # URL_COMPLETED - after an URL download is completed # and the downloaded file was not added to queue # (not nzb-extension, download error, parse # error). See NZBNA_URLSTATUS for details; # In the future the list of supported events may be # extended. To avoid conflicts with future NZBGet # versions the script must exit if the parameter # has a value unknown to the script. # NZBNA_DELETESTATUS - delete status info, NZBNA_EVENT=NZB_DELETED: # MANUAL - deleted by user or via API call; # HEALTH - deleted by health check; # DUPE - moved to history by duplicate check, can be # reused later if necessary; # GOOD - moved to history by duplicate check because # there is already a duplicate marked as good; # BAD - marked as bad by user or by queue-script; # COPY - already in queue or in history; # SCAN - malformed nzb-file, cannot be parsed; # NZBNA_URLSTATUS - URL status info, when NZBNA_EVENT=URL_COMPLETED: # FAILURE - fetch error (could not be downloaded); # SCAN_SKIPPED - downloaded file doesn't have # nzb-extension and was skipped; # SCAN_FAILED - file format error; # NZBNA_CATEGORY - category of nzb-file (if assigned); # NZBNA_NZBID - id of the nzb-file. This ID can be used with # calls to nzbget edit-command; # NZBNA_PRIORITY - priority (default is 0); # NZBNA_DUPEKEY - duplicate key of nzb-file; # NZBNA_DUPESCORE - duplicate score of nzb-file; # NZBNA_DUPEMODE - duplicate mode of nzb-file: SCORE, ALL, FORCE. # # In addition to these arguments NZBGet passes all nzbget.conf-options # to the script as environment variables. These variables have prefix # "NZBOP_" and are written in UPPER CASE. For Example option "ParRepair" # is passed as environment variable "NZBOP_PARREPAIR". The dots in option # names are replaced with underscores, for example "SERVER1_HOST". For # options with predefined possible values (yes/no, etc.) the values are # passed always in lower case. # # The script can printing special messages into standard output (which # is processed by NZBGet). # # To assign post-processing parameters: # echo "[NZB] NZBPR_myvar=my value"; # # The prefix "NZBPR_" will be removed. In this example a post-processing # parameter with name "myvar" and value "my value" will be associated # with nzb-file. # # To inform NZBGet about bad download: # echo "[NZB] MARK=BAD"; # # Examples of what the script can do: # 1) pausing nzb-file using file-id: # "$NZBOP_APPBIN" -c "$NZBOP_CONFIGFILE" -E G P $NZBNA_NZBID; # 2) setting category using nzb-name: # "$NZBOP_APPBIN" -c "$NZBOP_CONFIGFILE" -E GN K "my cat" "$NZBNA_NZBNAME"; # 3) pausing files with extension "nzb": # "$NZBOP_APPBIN" -c "$NZBOP_CONFIGFILE" -E FR P "$NZBNA_NZBNAME/.*\.nzb"; # # NOTE: This is a short documentation, for more information visit # http://nzbget.net/Extension_scripts. QueueScript= # List of rss feed scripts to execute before a rss feed content is processed. # # The scripts in the list must be separated with commas or semicolons. Only # filenames without path must be used. All scripts must be stored in directory # pointed by option . # # If rss feed has option defined (if not empty) # the scripts defined there override the global option . # # The scripts are executed after rss feed is read from server and before it # is processed by the feed parser. Once the feed is fetched it is saved # to a temporary file and the feed scripts are executed. The scripts # can modify the content of the temporary feed file. Then the file is # read by the feed parser and processed. # # Example: Rss.sh, Filter.py. # # The feed content is usually filtered using option . If a # required filtering cannot be achieved via built-in filter commands the # more advanced processing of the feed can be made using feed scripts. # # INFO FOR DEVELOPERS: # NOTE: This is a short documentation, for more information visit # http://nzbget.net/Extension_scripts. # # NZBGet passes following arguments to the script as environment # variables: # NZBFP_FILENAME - name of feed file to be processed; # NZBFP_FEEDID - ID of the feed. # # In addition to these arguments NZBGet passes all nzbget.conf-options # as environment variables. These variables have prefix "NZBOP_" and # are written in UPPER CASE. For Example option "ParRepair" is passed as # environment variable "NZBOP_PARREPAIR". The dots in option names are # replaced with underscores, for example "SERVER1_HOST". For options # with predefined possible values (yes/no, etc.) the values are passed # always in lower case. # # NOTE: This is a short documentation, for more information visit # http://nzbget.net/Extension_scripts. FeedScript= # Execution order for scripts. # # If you assign multiple scripts to one nzb-file, they are executed in the # order defined by this option. Scripts not listed here are executed at # the end in their alphabetical order. # # The scripts in the list must be separated with commas or semicolons. Only # filenames without path must be used. All scripts must be stored in directory # pointed by option . # # Example: Cleanup.sh, Move.sh. ScriptOrder= # Pause download queue during executing of postprocess-script (yes, no). # # Enable the option to give CPU more time for postprocess-script. That helps # to speed up postprocess on slow CPUs with fast connection (e.g. NAS-devices). # # NOTE: See also options and . ScriptPauseQueue=no # Minimum interval between calls of queue-scripts (seconds). # # Queue-scripts are executed during download, after every file included in # nzb-file is downloaded. If the files are small they may be downloaded # very fast causing queue-scripts to be working all the time. Sometimes # this may lead to a performance decrease on systems with slow CPUs. # # This option allows to reduce the number of calls of queue-scripts by # skipping "file-downloaded"-events if the previous call of queue-scripts # for the same download (nzb-file) were performed a short time ago # (as defined by the option). # # Value "-1" disables executing of queue-scripts on # "file-downloaded"-events. Scripts are still executed on events # "nzb-added" and "nzb-downloaded". # # NOTE: This options affects only queue-scripts and only # "file-downloaded"-events. Queue-scripts can be activated using # option (for pure queue-scripts) or option # (for dual-mode scripts which act as queue- and post-processing-scripts # at the same time). EventInterval=0 nzbget-16.4/posix/0000755000175000017500000000000012630544544013722 5ustar andreasandreasnzbget-16.4/posix/install-sh0000755000175000017500000001425312630544544015733 0ustar andreasandreas#!/bin/sh # # install - install a program, script, or datafile # This comes from X11R5 (mit/util/scripts/install.sh). # # Copyright 1991 by the Massachusetts Institute of Technology # # Permission to use, copy, modify, distribute, and sell this software and its # documentation for any purpose is hereby granted without fee, provided that # the above copyright notice appear in all copies and that both that # copyright notice and this permission notice appear in supporting # documentation, and that the name of M.I.T. not be used in advertising or # publicity pertaining to distribution of the software without specific, # written prior permission. M.I.T. makes no representations about the # suitability of this software for any purpose. It is provided "as is" # without express or implied warranty. # # Calling this script install-sh is preferred over install.sh, to prevent # `make' implicit rules from creating a file called install from it # when there is no Makefile. # # This script is compatible with the BSD install script, but was written # from scratch. It can only install one file at a time, a restriction # shared with many OS's install programs. # set DOITPROG to echo to test this script # Don't use :- since 4.3BSD and earlier shells don't like it. doit="${DOITPROG-}" # put in absolute paths if you don't have them in your path; or use env. vars. mvprog="${MVPROG-mv}" cpprog="${CPPROG-cp}" chmodprog="${CHMODPROG-chmod}" chownprog="${CHOWNPROG-chown}" chgrpprog="${CHGRPPROG-chgrp}" stripprog="${STRIPPROG-strip}" rmprog="${RMPROG-rm}" mkdirprog="${MKDIRPROG-mkdir}" transformbasename="" transform_arg="" instcmd="$mvprog" chmodcmd="$chmodprog 0755" chowncmd="" chgrpcmd="" stripcmd="" rmcmd="$rmprog -f" mvcmd="$mvprog" src="" dst="" dir_arg="" while [ x"$1" != x ]; do case $1 in -c) instcmd=$cpprog shift continue;; -d) dir_arg=true shift continue;; -m) chmodcmd="$chmodprog $2" shift shift continue;; -o) chowncmd="$chownprog $2" shift shift continue;; -g) chgrpcmd="$chgrpprog $2" shift shift continue;; -s) stripcmd=$stripprog shift continue;; -t=*) transformarg=`echo $1 | sed 's/-t=//'` shift continue;; -b=*) transformbasename=`echo $1 | sed 's/-b=//'` shift continue;; *) if [ x"$src" = x ] then src=$1 else # this colon is to work around a 386BSD /bin/sh bug : dst=$1 fi shift continue;; esac done if [ x"$src" = x ] then echo "$0: no input file specified" >&2 exit 1 else : fi if [ x"$dir_arg" != x ]; then dst=$src src="" if [ -d "$dst" ]; then instcmd=: chmodcmd="" else instcmd=$mkdirprog fi else # Waiting for this to be detected by the "$instcmd $src $dsttmp" command # might cause directories to be created, which would be especially bad # if $src (and thus $dsttmp) contains '*'. if [ -f "$src" ] || [ -d "$src" ] then : else echo "$0: $src does not exist" >&2 exit 1 fi if [ x"$dst" = x ] then echo "$0: no destination specified" >&2 exit 1 else : fi # If destination is a directory, append the input filename; if your system # does not like double slashes in filenames, you may need to add some logic if [ -d "$dst" ] then dst=$dst/`basename "$src"` else : fi fi ## this sed command emulates the dirname command dstdir=`echo "$dst" | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` # Make sure that the destination directory exists. # this part is taken from Noah Friedman's mkinstalldirs script # Skip lots of stat calls in the usual case. if [ ! -d "$dstdir" ]; then defaultIFS=' ' IFS="${IFS-$defaultIFS}" oIFS=$IFS # Some sh's can't handle IFS=/ for some reason. IFS='%' set - `echo "$dstdir" | sed -e 's@/@%@g' -e 's@^%@/@'` IFS=$oIFS pathcomp='' while [ $# -ne 0 ] ; do pathcomp=$pathcomp$1 shift if [ ! -d "$pathcomp" ] ; then $mkdirprog "$pathcomp" else : fi pathcomp=$pathcomp/ done fi if [ x"$dir_arg" != x ] then $doit $instcmd "$dst" && if [ x"$chowncmd" != x ]; then $doit $chowncmd "$dst"; else : ; fi && if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd "$dst"; else : ; fi && if [ x"$stripcmd" != x ]; then $doit $stripcmd "$dst"; else : ; fi && if [ x"$chmodcmd" != x ]; then $doit $chmodcmd "$dst"; else : ; fi else # If we're going to rename the final executable, determine the name now. if [ x"$transformarg" = x ] then dstfile=`basename "$dst"` else dstfile=`basename "$dst" $transformbasename | sed $transformarg`$transformbasename fi # don't allow the sed command to completely eliminate the filename if [ x"$dstfile" = x ] then dstfile=`basename "$dst"` else : fi # Make a couple of temp file names in the proper directory. dsttmp=$dstdir/#inst.$$# rmtmp=$dstdir/#rm.$$# # Trap to clean up temp files at exit. trap 'status=$?; rm -f "$dsttmp" "$rmtmp" && exit $status' 0 trap '(exit $?); exit' 1 2 13 15 # Move or copy the file name to the temp name $doit $instcmd "$src" "$dsttmp" && # and set any options; do chmod last to preserve setuid bits # If any of these fail, we abort the whole thing. If we want to # ignore errors from any of these, just make sure not to ignore # errors from the above "$doit $instcmd $src $dsttmp" command. if [ x"$chowncmd" != x ]; then $doit $chowncmd "$dsttmp"; else :;fi && if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd "$dsttmp"; else :;fi && if [ x"$stripcmd" != x ]; then $doit $stripcmd "$dsttmp"; else :;fi && if [ x"$chmodcmd" != x ]; then $doit $chmodcmd "$dsttmp"; else :;fi && # Now remove or move aside any old file at destination location. We try this # two ways since rm can't unlink itself on some systems and the destination # file might be busy for other reasons. In this case, the final cleanup # might fail but the new file should still install successfully. { if [ -f "$dstdir/$dstfile" ] then $doit $rmcmd -f "$dstdir/$dstfile" 2>/dev/null || $doit $mvcmd -f "$dstdir/$dstfile" "$rmtmp" 2>/dev/null || { echo "$0: cannot unlink or rename $dstdir/$dstfile" >&2 (exit 1); exit } else : fi } && # Now rename the file to the real destination. $doit $mvcmd "$dsttmp" "$dstdir/$dstfile" fi && # The final little trick to "correctly" pass the exit status to the exit trap. { (exit 0); exit } nzbget-16.4/posix/missing0000755000175000017500000002403612630544544015326 0ustar andreasandreas#! /bin/sh # Common stub for a few missing GNU programs while installing. # Copyright (C) 1996, 1997, 1999, 2000, 2002 Free Software Foundation, Inc. # Originally by Fran,cois Pinard , 1996. # 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, 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., 59 Temple Place - Suite 330, Boston, MA # 02111-1307, USA. # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. if test $# -eq 0; then echo 1>&2 "Try \`$0 --help' for more information" exit 1 fi run=: # In the cases where this matters, `missing' is being run in the # srcdir already. if test -f configure.ac; then configure_ac=configure.ac else configure_ac=configure.in fi case "$1" in --run) # Try to run requested program, and just exit if it succeeds. run= shift "$@" && exit 0 ;; esac # If it does not exist, or fails to run (possibly an outdated version), # try to emulate it. case "$1" in -h|--h|--he|--hel|--help) echo "\ $0 [OPTION]... PROGRAM [ARGUMENT]... Handle \`PROGRAM [ARGUMENT]...' for when PROGRAM is missing, or return an error status if there is no known handling for PROGRAM. Options: -h, --help display this help and exit -v, --version output version information and exit --run try to run the given command, and emulate it if it fails Supported PROGRAM values: aclocal touch file \`aclocal.m4' autoconf touch file \`configure' autoheader touch file \`config.h.in' automake touch all \`Makefile.in' files bison create \`y.tab.[ch]', if possible, from existing .[ch] flex create \`lex.yy.c', if possible, from existing .c help2man touch the output file lex create \`lex.yy.c', if possible, from existing .c makeinfo touch the output file tar try tar, gnutar, gtar, then tar without non-portable flags yacc create \`y.tab.[ch]', if possible, from existing .[ch]" ;; -v|--v|--ve|--ver|--vers|--versi|--versio|--version) echo "missing 0.4 - GNU automake" ;; -*) echo 1>&2 "$0: Unknown \`$1' option" echo 1>&2 "Try \`$0 --help' for more information" exit 1 ;; aclocal*) if test -z "$run" && ($1 --version) > /dev/null 2>&1; then # We have it, but it failed. exit 1 fi echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified \`acinclude.m4' or \`${configure_ac}'. You might want to install the \`Automake' and \`Perl' packages. Grab them from any GNU archive site." touch aclocal.m4 ;; autoconf) if test -z "$run" && ($1 --version) > /dev/null 2>&1; then # We have it, but it failed. exit 1 fi echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified \`${configure_ac}'. You might want to install the \`Autoconf' and \`GNU m4' packages. Grab them from any GNU archive site." touch configure ;; autoheader) if test -z "$run" && ($1 --version) > /dev/null 2>&1; then # We have it, but it failed. exit 1 fi echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified \`acconfig.h' or \`${configure_ac}'. You might want to install the \`Autoconf' and \`GNU m4' packages. Grab them from any GNU archive site." files=`sed -n 's/^[ ]*A[CM]_CONFIG_HEADER(\([^)]*\)).*/\1/p' ${configure_ac}` test -z "$files" && files="config.h" touch_files= for f in $files; do case "$f" in *:*) touch_files="$touch_files "`echo "$f" | sed -e 's/^[^:]*://' -e 's/:.*//'`;; *) touch_files="$touch_files $f.in";; esac done touch $touch_files ;; automake*) if test -z "$run" && ($1 --version) > /dev/null 2>&1; then # We have it, but it failed. exit 1 fi echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified \`Makefile.am', \`acinclude.m4' or \`${configure_ac}'. You might want to install the \`Automake' and \`Perl' packages. Grab them from any GNU archive site." find . -type f -name Makefile.am -print | sed 's/\.am$/.in/' | while read f; do touch "$f"; done ;; autom4te) if test -z "$run" && ($1 --version) > /dev/null 2>&1; then # We have it, but it failed. exit 1 fi echo 1>&2 "\ WARNING: \`$1' is needed, and you do not seem to have it handy on your system. You might have modified some files without having the proper tools for further handling them. You can get \`$1Help2man' as part of \`Autoconf' from any GNU archive site." file=`echo "$*" | sed -n 's/.*--output[ =]*\([^ ]*\).*/\1/p'` test -z "$file" && file=`echo "$*" | sed -n 's/.*-o[ ]*\([^ ]*\).*/\1/p'` if test -f "$file"; then touch $file else test -z "$file" || exec >$file echo "#! /bin/sh" echo "# Created by GNU Automake missing as a replacement of" echo "# $ $@" echo "exit 0" chmod +x $file exit 1 fi ;; bison|yacc) echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified a \`.y' file. You may need the \`Bison' package in order for those modifications to take effect. You can get \`Bison' from any GNU archive site." rm -f y.tab.c y.tab.h if [ $# -ne 1 ]; then eval LASTARG="\${$#}" case "$LASTARG" in *.y) SRCFILE=`echo "$LASTARG" | sed 's/y$/c/'` if [ -f "$SRCFILE" ]; then cp "$SRCFILE" y.tab.c fi SRCFILE=`echo "$LASTARG" | sed 's/y$/h/'` if [ -f "$SRCFILE" ]; then cp "$SRCFILE" y.tab.h fi ;; esac fi if [ ! -f y.tab.h ]; then echo >y.tab.h fi if [ ! -f y.tab.c ]; then echo 'main() { return 0; }' >y.tab.c fi ;; lex|flex) echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified a \`.l' file. You may need the \`Flex' package in order for those modifications to take effect. You can get \`Flex' from any GNU archive site." rm -f lex.yy.c if [ $# -ne 1 ]; then eval LASTARG="\${$#}" case "$LASTARG" in *.l) SRCFILE=`echo "$LASTARG" | sed 's/l$/c/'` if [ -f "$SRCFILE" ]; then cp "$SRCFILE" lex.yy.c fi ;; esac fi if [ ! -f lex.yy.c ]; then echo 'main() { return 0; }' >lex.yy.c fi ;; help2man) if test -z "$run" && ($1 --version) > /dev/null 2>&1; then # We have it, but it failed. exit 1 fi echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified a dependency of a manual page. You may need the \`Help2man' package in order for those modifications to take effect. You can get \`Help2man' from any GNU archive site." file=`echo "$*" | sed -n 's/.*-o \([^ ]*\).*/\1/p'` if test -z "$file"; then file=`echo "$*" | sed -n 's/.*--output=\([^ ]*\).*/\1/p'` fi if [ -f "$file" ]; then touch $file else test -z "$file" || exec >$file echo ".ab help2man is required to generate this page" exit 1 fi ;; makeinfo) if test -z "$run" && (makeinfo --version) > /dev/null 2>&1; then # We have makeinfo, but it failed. exit 1 fi echo 1>&2 "\ WARNING: \`$1' is missing on your system. You should only need it if you modified a \`.texi' or \`.texinfo' file, or any other file indirectly affecting the aspect of the manual. The spurious call might also be the consequence of using a buggy \`make' (AIX, DU, IRIX). You might want to install the \`Texinfo' package or the \`GNU make' package. Grab either from any GNU archive site." file=`echo "$*" | sed -n 's/.*-o \([^ ]*\).*/\1/p'` if test -z "$file"; then file=`echo "$*" | sed 's/.* \([^ ]*\) *$/\1/'` file=`sed -n '/^@setfilename/ { s/.* \([^ ]*\) *$/\1/; p; q; }' $file` fi touch $file ;; tar) shift if test -n "$run"; then echo 1>&2 "ERROR: \`tar' requires --run" exit 1 fi # We have already tried tar in the generic part. # Look for gnutar/gtar before invocation to avoid ugly error # messages. if (gnutar --version > /dev/null 2>&1); then gnutar "$@" && exit 0 fi if (gtar --version > /dev/null 2>&1); then gtar "$@" && exit 0 fi firstarg="$1" if shift; then case "$firstarg" in *o*) firstarg=`echo "$firstarg" | sed s/o//` tar "$firstarg" "$@" && exit 0 ;; esac case "$firstarg" in *h*) firstarg=`echo "$firstarg" | sed s/h//` tar "$firstarg" "$@" && exit 0 ;; esac fi echo 1>&2 "\ WARNING: I can't seem to be able to run \`tar' with the given arguments. You may want to install GNU tar or Free paxutils, or check the command line arguments." exit 1 ;; *) echo 1>&2 "\ WARNING: \`$1' is needed, and you do not seem to have it handy on your system. You might have modified some files without having the proper tools for further handling them. Check the \`README' file, it often tells you about the needed prerequirements for installing this package. You may also peek at any GNU archive site, in case some other package would contain this missing \`$1' program." exit 1 ;; esac exit 0 nzbget-16.4/posix/depcomp0000755000175000017500000003117412630544544015305 0ustar andreasandreas#! /bin/sh # depcomp - compile a program generating dependencies as side-effects # Copyright 1999, 2000 Free Software Foundation, Inc. # 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, 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., 59 Temple Place - Suite 330, Boston, MA # 02111-1307, USA. # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a # configuration script generated by Autoconf, you may include it under # the same distribution terms that you use for the rest of that program. # Originally written by Alexandre Oliva . if test -z "$depmode" || test -z "$source" || test -z "$object"; then echo "depcomp: Variables source, object and depmode must be set" 1>&2 exit 1 fi # `libtool' can also be set to `yes' or `no'. depfile=${depfile-`echo "$object" | sed 's,\([^/]*\)$,.deps/\1,;s/\.\([^.]*\)$/.P\1/'`} tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`} rm -f "$tmpdepfile" # Some modes work just like other modes, but use different flags. We # parameterize here, but still list the modes in the big case below, # to make depend.m4 easier to write. Note that we *cannot* use a case # here, because this file can only contain one case statement. if test "$depmode" = hp; then # HP compiler uses -M and no extra arg. gccflag=-M depmode=gcc fi if test "$depmode" = dashXmstdout; then # This is just like dashmstdout with a different argument. dashmflag=-xM depmode=dashmstdout fi case "$depmode" in gcc3) ## gcc 3 implements dependency tracking that does exactly what ## we want. Yay! Note: for some reason libtool 1.4 doesn't like ## it if -MD -MP comes after the -MF stuff. Hmm. "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" stat=$? if test $stat -eq 0; then : else rm -f "$tmpdepfile" exit $stat fi mv "$tmpdepfile" "$depfile" ;; gcc) ## There are various ways to get dependency output from gcc. Here's ## why we pick this rather obscure method: ## - Don't want to use -MD because we'd like the dependencies to end ## up in a subdir. Having to rename by hand is ugly. ## (We might end up doing this anyway to support other compilers.) ## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like ## -MM, not -M (despite what the docs say). ## - Using -M directly means running the compiler twice (even worse ## than renaming). if test -z "$gccflag"; then gccflag=-MD, fi "$@" -Wp,"$gccflag$tmpdepfile" stat=$? if test $stat -eq 0; then : else rm -f "$tmpdepfile" exit $stat fi rm -f "$depfile" echo "$object : \\" > "$depfile" alpha=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz ## The second -e expression handles DOS-style file names with drive letters. sed -e 's/^[^:]*: / /' \ -e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile" ## This next piece of magic avoids the `deleted header file' problem. ## The problem is that when a header file which appears in a .P file ## is deleted, the dependency causes make to die (because there is ## typically no way to rebuild the header). We avoid this by adding ## dummy dependencies for each header file. Too bad gcc doesn't do ## this for us directly. tr ' ' ' ' < "$tmpdepfile" | ## Some versions of gcc put a space before the `:'. On the theory ## that the space means something, we add a space to the output as ## well. ## Some versions of the HPUX 10.20 sed can't process this invocation ## correctly. Breaking it into two sed invocations is a workaround. sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile" rm -f "$tmpdepfile" ;; hp) # This case exists only to let depend.m4 do its work. It works by # looking at the text of this script. This case will never be run, # since it is checked for above. exit 1 ;; sgi) if test "$libtool" = yes; then "$@" "-Wp,-MDupdate,$tmpdepfile" else "$@" -MDupdate "$tmpdepfile" fi stat=$? if test $stat -eq 0; then : else rm -f "$tmpdepfile" exit $stat fi rm -f "$depfile" if test -f "$tmpdepfile"; then # yes, the sourcefile depend on other files echo "$object : \\" > "$depfile" # Clip off the initial element (the dependent). Don't try to be # clever and replace this with sed code, as IRIX sed won't handle # lines with more than a fixed number of characters (4096 in # IRIX 6.2 sed, 8192 in IRIX 6.5). We also remove comment lines; # the IRIX cc adds comments like `#:fec' to the end of the # dependency line. tr ' ' ' ' < "$tmpdepfile" \ | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' | \ tr ' ' ' ' >> $depfile echo >> $depfile # The second pass generates a dummy entry for each header file. tr ' ' ' ' < "$tmpdepfile" \ | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \ >> $depfile else # The sourcefile does not contain any dependencies, so just # store a dummy comment line, to avoid errors with the Makefile # "include basename.Plo" scheme. echo "#dummy" > "$depfile" fi rm -f "$tmpdepfile" ;; aix) # The C for AIX Compiler uses -M and outputs the dependencies # in a .u file. This file always lives in the current directory. # Also, the AIX compiler puts `$object:' at the start of each line; # $object doesn't have directory information. stripped=`echo "$object" | sed -e 's,^.*/,,' -e 's/\(.*\)\..*$/\1/'` tmpdepfile="$stripped.u" outname="$stripped.o" if test "$libtool" = yes; then "$@" -Wc,-M else "$@" -M fi stat=$? if test $stat -eq 0; then : else rm -f "$tmpdepfile" exit $stat fi if test -f "$tmpdepfile"; then # Each line is of the form `foo.o: dependent.h'. # Do two passes, one to just change these to # `$object: dependent.h' and one to simply `dependent.h:'. sed -e "s,^$outname:,$object :," < "$tmpdepfile" > "$depfile" sed -e "s,^$outname: \(.*\)$,\1:," < "$tmpdepfile" >> "$depfile" else # The sourcefile does not contain any dependencies, so just # store a dummy comment line, to avoid errors with the Makefile # "include basename.Plo" scheme. echo "#dummy" > "$depfile" fi rm -f "$tmpdepfile" ;; icc) # Must come before tru64. # Intel's C compiler understands `-MD -MF file'. However # icc -MD -MF foo.d -c -o sub/foo.o sub/foo.c # will fill foo.d with something like # foo.o: sub/foo.c # foo.o: sub/foo.h # which is wrong. We want: # sub/foo.o: sub/foo.c # sub/foo.o: sub/foo.h # sub/foo.c: # sub/foo.h: "$@" -MD -MF "$tmpdepfile" stat=$? if test $stat -eq 0; then : else rm -f "$tmpdepfile" exit $stat fi rm -f "$depfile" # Each line is of the form `foo.o: dependent.h'. # Do two passes, one to just change these to # `$object: dependent.h' and one to simply `dependent.h:'. sed -e "s,^[^:]*:,$object :," < "$tmpdepfile" > "$depfile" sed -e "s,^[^:]*: \(.*\)$,\1:," < "$tmpdepfile" >> "$depfile" rm -f "$tmpdepfile" ;; tru64) # The Tru64 AIX compiler uses -MD to generate dependencies as a side # effect. `cc -MD -o foo.o ...' puts the dependencies into `foo.o.d'. # At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put # dependencies in `foo.d' instead, so we check for that too. # Subdirectories are respected. tmpdepfile1="$object.d" tmpdepfile2=`echo "$object" | sed -e 's/.o$/.d/'` if test "$libtool" = yes; then "$@" -Wc,-MD else "$@" -MD fi stat=$? if test $stat -eq 0; then : else rm -f "$tmpdepfile1" "$tmpdepfile2" exit $stat fi if test -f "$tmpdepfile1"; then tmpdepfile="$tmpdepfile1" else tmpdepfile="$tmpdepfile2" fi if test -f "$tmpdepfile"; then sed -e "s,^.*\.[a-z]*:,$object:," < "$tmpdepfile" > "$depfile" # That's a space and a tab in the []. sed -e 's,^.*\.[a-z]*:[ ]*,,' -e 's,$,:,' < "$tmpdepfile" >> "$depfile" else echo "#dummy" > "$depfile" fi rm -f "$tmpdepfile" ;; #nosideeffect) # This comment above is used by automake to tell side-effect # dependency tracking mechanisms from slower ones. dashmstdout) # Important note: in order to support this mode, a compiler *must* # always write the proprocessed file to stdout, regardless of -o, # because we must use -o when running libtool. test -z "$dashmflag" && dashmflag=-M ( IFS=" " case " $* " in *" --mode=compile "*) # this is libtool, let us make it quiet for arg do # cycle over the arguments case "$arg" in "--mode=compile") # insert --quiet before "--mode=compile" set fnord "$@" --quiet shift # fnord ;; esac set fnord "$@" "$arg" shift # fnord shift # "$arg" done ;; esac "$@" $dashmflag | sed 's:^[^:]*\:[ ]*:'"$object"'\: :' > "$tmpdepfile" ) & proc=$! "$@" stat=$? wait "$proc" if test "$stat" != 0; then exit $stat; fi rm -f "$depfile" cat < "$tmpdepfile" > "$depfile" tr ' ' ' ' < "$tmpdepfile" | \ ## Some versions of the HPUX 10.20 sed can't process this invocation ## correctly. Breaking it into two sed invocations is a workaround. sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile" rm -f "$tmpdepfile" ;; dashXmstdout) # This case only exists to satisfy depend.m4. It is never actually # run, as this mode is specially recognized in the preamble. exit 1 ;; makedepend) # X makedepend ( shift cleared=no for arg in "$@"; do case $cleared in no) set ""; shift cleared=yes esac case "$arg" in -D*|-I*) set fnord "$@" "$arg"; shift;; -*) ;; *) set fnord "$@" "$arg"; shift;; esac done obj_suffix="`echo $object | sed 's/^.*\././'`" touch "$tmpdepfile" ${MAKEDEPEND-makedepend} 2>/dev/null -o"$obj_suffix" -f"$tmpdepfile" "$@" ) & proc=$! "$@" stat=$? wait "$proc" if test "$stat" != 0; then exit $stat; fi rm -f "$depfile" cat < "$tmpdepfile" > "$depfile" tail +3 "$tmpdepfile" | tr ' ' ' ' | \ ## Some versions of the HPUX 10.20 sed can't process this invocation ## correctly. Breaking it into two sed invocations is a workaround. sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile" rm -f "$tmpdepfile" "$tmpdepfile".bak ;; cpp) # Important note: in order to support this mode, a compiler *must* # always write the proprocessed file to stdout, regardless of -o, # because we must use -o when running libtool. ( IFS=" " case " $* " in *" --mode=compile "*) for arg do # cycle over the arguments case $arg in "--mode=compile") # insert --quiet before "--mode=compile" set fnord "$@" --quiet shift # fnord ;; esac set fnord "$@" "$arg" shift # fnord shift # "$arg" done ;; esac "$@" -E | sed -n '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' | sed '$ s: \\$::' > "$tmpdepfile" ) & proc=$! "$@" stat=$? wait "$proc" if test "$stat" != 0; then exit $stat; fi rm -f "$depfile" echo "$object : \\" > "$depfile" cat < "$tmpdepfile" >> "$depfile" sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile" rm -f "$tmpdepfile" ;; msvisualcpp) # Important note: in order to support this mode, a compiler *must* # always write the proprocessed file to stdout, regardless of -o, # because we must use -o when running libtool. ( IFS=" " case " $* " in *" --mode=compile "*) for arg do # cycle over the arguments case $arg in "--mode=compile") # insert --quiet before "--mode=compile" set fnord "$@" --quiet shift # fnord ;; esac set fnord "$@" "$arg" shift # fnord shift # "$arg" done ;; esac "$@" -E | sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::echo "`cygpath -u \\"\1\\"`":p' | sort | uniq > "$tmpdepfile" ) & proc=$! "$@" stat=$? wait "$proc" if test "$stat" != 0; then exit $stat; fi rm -f "$depfile" echo "$object : \\" > "$depfile" . "$tmpdepfile" | sed 's% %\\ %g' | sed -n '/^\(.*\)$/ s:: \1 \\:p' >> "$depfile" echo " " >> "$depfile" . "$tmpdepfile" | sed 's% %\\ %g' | sed -n '/^\(.*\)$/ s::\1\::p' >> "$depfile" rm -f "$tmpdepfile" ;; none) exec "$@" ;; *) echo "Unknown depmode $depmode" 1>&2 exit 1 ;; esac exit 0 nzbget-16.4/nzbget.vcproj0000644000175000017500000005351512630544544015307 0ustar andreasandreas nzbget-16.4/lib/0000755000175000017500000000000012630544544013326 5ustar andreasandreasnzbget-16.4/lib/par2/0000755000175000017500000000000012630544544014172 5ustar andreasandreasnzbget-16.4/lib/par2/mainpacket.h0000644000175000017500000000567212630544544016471 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #ifndef __MAINPACKET_H__ #define __MAINPACKET_H__ // The main packet ties all other critical packets together. // It specifies the block size to use for both verification of // files and for the Reed Solomon computation. // It also specifies how many of the source files are repairable // and in what order they should be processed. class MainPacket : public CriticalPacket { public: // Construct the packet MainPacket(void) {}; ~MainPacket(void) {}; public: // Construct the main packet from the source file list and block size. // "sourcefiles" will be sorted base on their FileId value. bool Create(vector &sourcefiles, u64 _blocksize); // Load a main packet from a specified file bool Load(DiskFile *diskfile, u64 offset, PACKET_HEADER &header); public: // Get the set id. const MD5Hash& SetId(void) const; // Get the block size. u64 BlockSize(void) const; // Get the file counts. u32 RecoverableFileCount(void) const; u32 TotalFileCount(void) const; // Get the fileid of one file const MD5Hash& FileId(u32 filenumber) const; protected: u64 blocksize; u32 totalfilecount; u32 recoverablefilecount; }; // Get the data block size inline u64 MainPacket::BlockSize(void) const { assert(packetdata != 0); return blocksize; } // Get the number of recoverable files inline u32 MainPacket::RecoverableFileCount(void) const { assert(packetdata != 0); return recoverablefilecount; } // Get the total number of files inline u32 MainPacket::TotalFileCount(void) const { assert(packetdata != 0); return totalfilecount; } // Get the file id hash of one of the files inline const MD5Hash& MainPacket::FileId(u32 filenumber) const { assert(packetdata != 0); assert(filenumberfileid()[filenumber]; return ((const MAINPACKET*)packetdata)->fileid[filenumber]; } inline const MD5Hash& MainPacket::SetId(void) const { return ((const MAINPACKET*)packetdata)->header.setid; } #endif // __MAINPACKET_H__ nzbget-16.4/lib/par2/parheaders.cpp0000644000175000017500000000222212630544544017012 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #include "parheaders.h" ParHeaders::ParHeaders(void) { packets = -1; recovery_block = -1; recoverable_files = -1; other_files = -1; block_size = -1; data_blocks = -1; data_size = -1; setid = ""; chunk_size = -1; } nzbget-16.4/lib/par2/README0000644000175000017500000003050512630544544015055 0ustar andreasandreasBased on libpar2 v 0.4 (http://launchpad.net/libpar2), which is a Debian/Ubuntu fork of original libpar2 (http://parchive.sourceforge.net), which is a library for par2 based on par2cmdline 0.4. Changes made to libpar2-0.4: - removed files not required for NZBGet; - eliminated dependency from libsigc++ by changing of library API to use virtual functions instead of sigc++ signals. par2cmdline README follows : ------------------------------------------------------------- par2cmdline is a PAR 2.0 compatible file verification and repair tool. See http://parchive.sourceforge.net for details of PAR 2.0 specification and discussion of all things PAR. WHAT EXACTLY IS PAR2CMDLINE? par2cmdline is a program for creating and using PAR2 files to detect damage in data files and repair them if necessary. It can be used with any kind of file. WHY IS PAR 2.0 better than PAR 1.0? * It is not necessary to split a single large file into many equally size small files (although you can still do so if you wish). * There is no loss of efficiency when operating on multiple files of different sizes. * It is possible to repair damaged files (using exactly the amount of recovery data that corresponds to the amount of damage), rather than requiring the complete reconstruction of the damaged file. * Recovery files may be of different sizes making it possible to obtain exactly the amount of recovery data required to carry out a repair. * Because damaged data files are still useable during the recovery process, less recovery data is required to achieve a successfull repair. It is not therefore necessary to create as much recovery data in the first place to achieve the same level of protection. * You can protect up to 32768 files rather than the 256 that PAR 1.0 is limited to. * Damaged or incomplete recovery files can also be used during the recovery process in the same way that damaged data files can. * You require less recovery data to provide the same level of protection from damage compared with PAR 1.0. DOES PAR 2.0 HAVE ANY DISSADVANTAGES? Yes, there is one dissadvantage: * All PAR 2.0 program will take somewhat longer to create recovery files than a PAR 1.0 program does. This dissadvantage is considerably mitigated by the fact that you don't need to create as much recovery data in the first place to provide the same level of protection against loss and damage. COMPILING PAR2CMDLINE You should have received par2cmdline in the form of source code which you can compile on your computer. You may optionally have received a pre-compiled version of the program for your operating system. If you have only downloaded a precompiled executable, then the source code should be available from the same location that you downloaded the executable from. If you have MS Visual Studio .NET, then just open the par2cmdline.sln file and compile. You should then copy par2cmdline.exe to an appropriate location that is on your path. To compile on Linux and other unix variants use the following commands: ./configure make make check make install See INSTALL for full details of how to use the "configure" script. USING PAR2CMDLINE The command line parameters for par2cmdline are as follow: par2 c(reate) [options] [files] par2 v(erify) [options] [files] par2 r(epair) [options] [files] Also: par2create [options] [files] par2verify [options] [files] par2repair [options] [files] Options: -b : Set the Block-Count -s : Set the Block-Size (Don't use both -b and -s) -r : Level of Redundancy (%) -c : Recovery block count (don't use both -r and -c) -f : First Recovery-Block-Number -u : Uniform recovery file sizes -l : Limit size of recovery files (Don't use both -u and -l) -n : Number of recovery files (Don't use both -n and -l) -m : Memory (in MB) to use -v [-v]: Be more verbose -q [-q]: Be more quiet (-qq gives silence) -- : Treat all remaining CommandLine as filenames If you wish to create par2 files for a single source file, you may leave out the name of the par2 file from the command line. par2cmdline will then assume that you wish to base the filenames for the par2 files on the name of the source file. You may also leave off the .par2 file extension when verifying and repairing. CREATING PAR2 FILES With PAR 2.0 you can create PAR2 recovery files for as few as 1 or as many as 32768 files. If you wanted to create PAR1 recovery files for a single file you are forced to split the file into muliple parts and RAR is frequently used for this purpose. You do NOT need to split files with PAR 2.0. To create PAR 2 recovery files for a single data file (e.g. one called test.mpg), you can use the following command: par2 create test.mpg If test.mpg is an 800 MB file, then this will create a total of 8 PAR2 files with the following filenames (taking roughly 6 minutes on a PC with a 1500MHz CPU): test.mpg.par2 - This is an index file for verification only test.mpg.vol00+01.par2 - Recovery file with 1 recovery block test.mpg.vol01+02.par2 - Recovery file with 2 recovery blocks test.mpg.vol03+04.par2 - Recovery file with 4 recovery blocks test.mpg.vol07+08.par2 - Recovery file with 8 recovery blocks test.mpg.vol15+16.par2 - Recovery file with 16 recovery blocks test.mpg.vol31+32.par2 - Recovery file with 32 recovery blocks test.mpg.vol63+37.par2 - Recovery file with 37 recovery blocks The test.mpg.par2 file is 39 KB in size and the other files vary in size from 443 KB to 15 MB. These par2 files will enable the recovery of up to 100 errors totalling 40 MB of lost or damaged data from the original test.mpg file when it and the par2 files are posted on UseNet. When posting on UseNet it is recommended that you use the "-s" option to set a blocksize that is equal to the Article size that you will use to post the data file. If you wanted to post the test.mpg file using an article size of 300 KB then the command you would type is: par2 create -s307200 test.mpg This will create 9 PAR2 files instead of 8, and they will be capable of correcting up to 134 errors totalling 40 MB. It will take roughly 8 minutes to create the recovery files this time. In both of these two examples, the total quantity of recovery data created was 40 MB (which is 5% of 800 MB). If you wish to create a greater or lesser quantity of recovery data, you can use the "-r" option. To create 10% recovery data instead of the default of 5% and also to use a block size of 300 KB, you would use the following command: par2 create -s307200 -r10 test.mpg This would also create 9 PAR2 files, but they would be able to correct up to 269 errors totalling 80 MB. Since twice as much recovery data is created, it will take about 16 minutes to do so with a 1500MHz CPU. The "-u" and "-n" options can be used to control exactly how many recovery files are created and how the recovery blocks are distributed amoungst them. They do not affect the total quantity of recovery data created. The "-f" option is used when you create additional recovery data. e.g. If you have already created 10% and want another 5% then you migh use the following command: par2 create -s307200 -r5 -f300 test.mpg This specifies the same block size (which is a requirement for additional recovery files), 5% recovery data, and a first block number of 300. The "-m" option controls how much memory par2cmdline uses. It defaults to 16 MB unless you override it. CREATING PAR2 FILES FOR MULTIPLE DATA FILES When creating PAR2 recovery files form multiple data files, you must specify the base filename to use for the par2 files and the names of all of the data files. If test.mpg had been split into multiple RAR files, then you could use: par2 create test.mpg.rar.par2 test.mpg.part*.rar The files filename "test.mpg.rar.par2" says what you want the par2 files to be called and "test.mpg.part*.rar" should select all of the RAR files. VERIFYING AND REPAIRING When using par2 recovery files to verify or repair the data files from which they were created, you only need to specify the filename of one of the par2 files to par2cmdline. e.g.: par2 verify test.mpg.par2 This tells par2cmdline to use the information in test.mpg.par2 to verify the data files. par2cmdline will automatically search for the other par2 files that were created and use the information they contain to determine the filenames of the original data files and then to verify them. If all of the data files are ok, then par2cmdline will report that repair will not be required. If any of the data files are missing or damaged, par2cmdline will report the details of what it has found. If the recovery files contain enough recovery blocks to repair the damage, you will be told that repair is possible. Otherwise you will be told exactly how many recovery blocks will be required in order to repair. To carry out a repair use the following command: par2 repair test.mpg.par2 This tells par2cmdline to verify and if possible repair any damaged or missing files. If a repair is carried out, then each file which is repaired will be re-verified to confirm that the repair was successful. MISSNAMED AND INCOMPLETE DATA FILES If any of the recovery files or data files have the wrong filename, then par2cmdline will not automatically find and scan them. To have par2cmdline scan such files, you must include them on the command line when attempting to verify or repair. e.g.: par2 r test.mpg.par2 other.mpg This tells par2cmdline to scan the file called other.mpg to see if it contains any data belonging to the original data files. If one of the extra files specified in this way is an exact match for a data file, then the repair process will rename the file so that it has the correct filename. Because par2cmdline is designed to be able to find good data within a damaged file, it can do the same with incomplete files downloaded from UseNet. If some of the articles for a file are missing, you should still download the file and save it to disk for par2cmdline to scan. If you do this then you may find that you can carry out a repair in a situation where you would not otherwise have sufficient recovery data. You can have par2cmdline scan all files that are in the current directory using a command such as: par2 r test.mpg.par2 * WHAT TO DO WHEN YOU ARE TOLD YOU NEED MORE RECOVERY BLOCKS If par2cmdline determines that any of the data files are damaged or missing and finds that there is insufficient recovery data to effect a repair, you will be told that you need a certain number of recovery blocks. You can obtain these by downloading additional recovery files. In order to make things easy, par2 files have filenames that tell you exactly how many recovery blocks each one contains. Assuming that the following command was used to create recovery data: par2 c -b1000 -r5 test.mpg Then the recovery files that are created would be called: test.mpg.par2 test.mpg.vol00+01.par2 test.mpg.vol01+02.par2 test.mpg.vol03+04.par2 test.mpg.vol07+08.par2 test.mpg.vol15+16.par2 test.mpg.vol31+19.par2 The first file in this list does not contain any recovery data, it only contains information sufficient to verify the data files. Each of the other files contains a different number of recovery blocks. The number after the '+' sign is the number of recovery blocks and the number preceding the '+' sign is the block number of the first recovery block in that file. If par2cmdline told you that you needed 10 recovery blocks, then you would need "test.mpg.vol01+02.par2" and "test.mpg.vol07+08.par". You might of course choose to fetch "test.mpg.vol15+16.par2" instead (in which case you would have an extra 6 recovery blocks which would not be used for the repair). NOTES This version of par2cmdline does not support recording path information for files. Whilst you can create recovery files for files from multiple locations, it will expect all files to be in the current directory when verifying and repairing. This limitation will be corrected in an update. REED SOLOMON CODING PAR2 uses Reed Solomon Coding to perform its calculations. For details of this coding technique try the following link: ``A Tutorial on Reed-Solomon Coding for Fault-Tolerance in RAID-like Systems'' nzbget-16.4/lib/par2/descriptionpacket.h0000644000175000017500000000652112630544544020062 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #ifndef __DESCRIPTIONPACKET_H__ #define __DESCRIPTIONPACKET_H__ // The description packet records details about a file (including its name, // size, and the Hash of both the whole file and the first 16k of the file). class DescriptionPacket : public CriticalPacket { public: // Construct the packet DescriptionPacket(void) {}; ~DescriptionPacket(void) {}; public: // Construct the packet and store the filename and size. bool Create(string _filename, u64 _filesize); // Store the computed Hash values in the packet. void Hash16k(const MD5Hash &hash); void HashFull(const MD5Hash &hash); // Compute and return the file id hash from information in the packet void ComputeFileId(void); const MD5Hash& FileId(void) const; // Return the size of the file u64 FileSize(void) const; public: // Load a description packet from a specified file bool Load(DiskFile *diskfile, u64 offset, PACKET_HEADER &header); // Return the name of the file string FileName(void) const; // Get the Hash values from the packet const MD5Hash& HashFull(void) const; const MD5Hash& Hash16k(void) const; }; // Get the file id from the packet inline const MD5Hash& DescriptionPacket::FileId(void) const { assert(packetdata != 0); return ((const FILEDESCRIPTIONPACKET*)packetdata)->fileid; } // Get the size of the file from the packet inline u64 DescriptionPacket::FileSize(void) const { assert(packetdata != 0); return ((const FILEDESCRIPTIONPACKET*)packetdata)->length; } // Get the name of the file from the packet // NB whilst the file format does not guarantee that the name will have a NULL // termination character, par2cmdline always allocates a little extra data // and fills it with NULLs to allow the filename to be directly read out of // the packet. inline string DescriptionPacket::FileName(void) const { assert(packetdata != 0); // return (char*)((const FILEDESCRIPTIONPACKET*)packetdata)->name(); return (char*)((const FILEDESCRIPTIONPACKET*)packetdata)->name; } // Get the full file hash value from the packet inline const MD5Hash& DescriptionPacket::HashFull(void) const { assert(packetdata != 0); return ((const FILEDESCRIPTIONPACKET*)packetdata)->hashfull; } // The the hash of the first 16k of the file from the packet inline const MD5Hash& DescriptionPacket::Hash16k(void) const { assert(packetdata != 0); return ((const FILEDESCRIPTIONPACKET*)packetdata)->hash16k; } #endif // __DESCRIPTIONPACKET_H__ nzbget-16.4/lib/par2/AUTHORS0000644000175000017500000000031712630544544015243 0ustar andreasandreaspar2cmdline 0.4: Peter Brian Clements library API: Francois Lesueur changes for NZBGet: Andrey Prygunkov nzbget-16.4/lib/par2/par2creatorsourcefile.h0000644000175000017500000000615512630544544020657 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #ifndef __PAR2CREATORSOURCEFILE_H__ #define __PAR2CREATORSOURCEFILE_H__ class DescriptionPacket; class VerificationPacket; class DiskFile; // The Par2CreatorSourceFile contains the file verification and file description // packet for one source file. class Par2CreatorSourceFile { private: // Don't permit copying or assignment Par2CreatorSourceFile(const Par2CreatorSourceFile &other); Par2CreatorSourceFile& operator=(const Par2CreatorSourceFile &other); public: Par2CreatorSourceFile(void); ~Par2CreatorSourceFile(void); // Open the source file and compute the Hashes and CRCs. bool Open(CommandLine::NoiseLevel noiselevel, const CommandLine::ExtraFile &extrafile, u64 blocksize, bool deferhashcomputation); void Close(void); // Recover the file description and file verification packets // in the critical packet list. void RecordCriticalPackets(list &criticalpackets); // Get the file id const MD5Hash& FileId(void) const; // Sort source files based on the file id hash static bool CompareLess(const Par2CreatorSourceFile* const &left, const Par2CreatorSourceFile* const &right); // Allocate the appropriate number of source blocks to the source file void InitialiseSourceBlocks(vector::iterator &sourceblock, u64 blocksize); // Update the file hash and the block crc and hashes void UpdateHashes(u32 blocknumber, const void *buffer, size_t length); // Finish computation of the file hash void FinishHashes(void); // How many blocks does this source file use u32 BlockCount(void) const {return blockcount;} protected: DescriptionPacket *descriptionpacket; // The file description packet. VerificationPacket *verificationpacket; // The file verification packet. DiskFile *diskfile; // The source file u64 filesize; // The size of the source file. string diskfilename; // The filename of the source file on disk. string parfilename; // The filename that will be recorded in the file description packet. u32 blockcount; // How many blocks the file will be divided into. MD5Context *contextfull; // MD5 context used to calculate the hash of the whole file }; #endif // __PAR2CREATORSOURCEFILE_H__ nzbget-16.4/lib/par2/datablock.cpp0000644000175000017500000000632112630544544016624 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #include "par2cmdline.h" #ifdef _MSC_VER #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif #endif // Open the file associated with the data block if is not already open bool DataBlock::Open(void) { if (diskfile == 0) return false; if (diskfile->IsOpen()) return true; return diskfile->Open(); } // Read some data at a specified position within a data block // into a buffer in memory bool DataBlock::ReadData(u64 position, // Position within the block size_t size, // Size of the memory buffer void *buffer) // Pointer to memory buffer { assert(diskfile != 0); // Check to see if the position from which data is to be read // is within the bounds of the data block if (length > position) { // Compute the file offset and how much data to physically read from disk u64 fileoffset = offset + position; size_t want = (size_t)min((u64)size, length - position); // Read the data from the file into the buffer if (!diskfile->Read(fileoffset, buffer, want)) return false; // If the read extends beyond the end of the data block, // then the rest of the buffer is zeroed. if (want < size) { memset(&((u8*)buffer)[want], 0, size-want); } } else { // Zero the whole buffer memset(buffer, 0, size); } return true; } // Write some data at a specified position within a datablock // from memory to disk bool DataBlock::WriteData(u64 position, // Position within the block size_t size, // Size of the memory buffer const void *buffer, // Pointer to memory buffer size_t &wrote) // Amount actually written { assert(diskfile != 0); wrote = 0; // Check to see if the position from which data is to be written // is within the bounds of the data block if (length > position) { // Compute the file offset and how much data to physically write to disk u64 fileoffset = offset + position; size_t have = (size_t)min((u64)size, length - position); // Write the data from the buffer to disk if (!diskfile->Write(fileoffset, buffer, have)) return false; wrote = have; } return true; } nzbget-16.4/lib/par2/mainpacket.cpp0000644000175000017500000001010012630544544017002 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #include "par2cmdline.h" #ifdef _MSC_VER #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif #endif // Construct the main packet from the source files and the block size bool MainPacket::Create(vector &sourcefiles, u64 _blocksize) { recoverablefilecount = totalfilecount =(u32)sourcefiles.size(); blocksize = _blocksize; // Allocate memory for the main packet with enough fileid entries MAINPACKET *packet = (MAINPACKET *)AllocatePacket(sizeof(MAINPACKET) + totalfilecount * sizeof(MD5Hash)); // Record the details we already know in the packet packet->header.magic = packet_magic; packet->header.length = packetlength; //packet->header.hash; // Compute shortly //packet->header.setid; // Compute shortly packet->header.type = mainpacket_type; packet->blocksize = _blocksize; packet->recoverablefilecount = totalfilecount; //packet->fileid; // Compute shortly // Sort the source files according to their fileid values if (totalfilecount > 1) { sort(sourcefiles.begin(), sourcefiles.end(), Par2CreatorSourceFile::CompareLess); } // Store the fileid values in the main packet vector::const_iterator sourcefile; MD5Hash *hash; for ((sourcefile=sourcefiles.begin()),(hash=packet->fileid); sourcefile!=sourcefiles.end(); ++sourcefile, ++hash) { *hash = (*sourcefile)->FileId(); } // Compute the set_id_hash MD5Context setidcontext; setidcontext.Update(&packet->blocksize, packetlength - offsetof(MAINPACKET, blocksize)); setidcontext.Final(packet->header.setid); // Compute the packet_hash MD5Context packetcontext; packetcontext.Update(&packet->header.setid, packetlength - offsetof(MAINPACKET, header.setid)); packetcontext.Final(packet->header.hash); return true; } // Load a main packet from a specified file bool MainPacket::Load(DiskFile *diskfile, u64 offset, PACKET_HEADER &header) { // Is the packet large enough if (header.length < sizeof(MAINPACKET)) { return false; } // Is there a whole number of fileid values if (0 < (header.length - sizeof(MAINPACKET)) % sizeof(MD5Hash)) { return false; } // Is the packet too large if (header.length > sizeof(MAINPACKET) + 32768 * sizeof(MD5Hash)) { return false; } // Compute the total number of entries in the fileid array totalfilecount = (u32)(((size_t)header.length - sizeof(MAINPACKET)) / sizeof(MD5Hash)); MAINPACKET *packet = (MAINPACKET *)AllocatePacket((size_t)header.length); packet->header = header; // Read the rest of the packet from disk if (!diskfile->Read(offset + sizeof(PACKET_HEADER), &packet->blocksize, (size_t)packet->header.length - sizeof(PACKET_HEADER))) return false; // Does the packet have enough fileid values recoverablefilecount = packet->recoverablefilecount; if (recoverablefilecount > totalfilecount) { return false; } // Is the block size valid blocksize = packet->blocksize; if (blocksize == 0 || (blocksize & 3) != 0) { return false; } return true; } nzbget-16.4/lib/par2/creatorpacket.cpp0000644000175000017500000000556312630544544017536 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #include "par2cmdline.h" #ifdef _MSC_VER #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif #endif // Construct the creator packet. // The only external information required to complete construction is // the set_id_hash (which is normally computed from information in the // main packet). bool CreatorPacket::Create(const MD5Hash &setid) { string creator = "Created by " PACKAGE " version " VERSION "."; // Allocate a packet just large enough for creator name CREATORPACKET *packet = (CREATORPACKET *)AllocatePacket(sizeof(*packet) + (~3 & (3+(u32)creator.size()))); // Fill in the details the we know packet->header.magic = packet_magic; packet->header.length = packetlength; //packet->header.hash; // Compute shortly packet->header.setid = setid; packet->header.type = creatorpacket_type; // Copy the creator description into the packet memcpy(packet->client, creator.c_str(), creator.size()); // Compute the packet hash MD5Context packetcontext; packetcontext.Update(&packet->header.setid, packetlength - offsetof(PACKET_HEADER, setid)); packetcontext.Final(packet->header.hash); return true; } // Load the packet from disk. bool CreatorPacket::Load(DiskFile *diskfile, u64 offset, PACKET_HEADER &header) { // Is the packet long enough if (header.length <= sizeof(CREATORPACKET)) { return false; } // Is the packet too large (what is the longest reasonable creator description) if (header.length - sizeof(CREATORPACKET) > 100000) { return false; } // Allocate the packet (with a little extra so we will have NULLs after the description) CREATORPACKET *packet = (CREATORPACKET *)AllocatePacket((size_t)header.length, 4); packet->header = header; // Load the rest of the packet from disk return diskfile->Read(offset + sizeof(PACKET_HEADER), packet->client, (size_t)packet->header.length - sizeof(PACKET_HEADER)); } nzbget-16.4/lib/par2/verificationpacket.cpp0000644000175000017500000000660012630544544020552 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #include "par2cmdline.h" #ifdef _MSC_VER #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif #endif // Create a packet large enough for the specified number of blocks bool VerificationPacket::Create(u32 _blockcount) { blockcount = _blockcount; // Allocate a packet large enough to hold the required number of verification entries. FILEVERIFICATIONPACKET *packet = (FILEVERIFICATIONPACKET*)AllocatePacket(sizeof(FILEVERIFICATIONPACKET) + blockcount * sizeof(FILEVERIFICATIONENTRY)); // Record everything we know in the packet. packet->header.magic = packet_magic; packet->header.length = packetlength; //packet->header.hash; // Not known yet //packet->header.setid; // Not known yet packet->header.type = fileverificationpacket_type; //packet->fileid; // Not known yet //packet->entries; // Not known yet return true; } void VerificationPacket::FileId(const MD5Hash &fileid) { assert(packetdata != 0); // Store the fileid in the packet. ((FILEVERIFICATIONPACKET*)packetdata)->fileid = fileid; } void VerificationPacket::SetBlockHashAndCRC(u32 blocknumber, const MD5Hash &hash, u32 crc) { assert(packetdata != 0); assert(blocknumber < blockcount); // Store the block hash and block crc in the packet. FILEVERIFICATIONENTRY &entry = ((FILEVERIFICATIONPACKET*)packetdata)->entries[blocknumber]; entry.hash = hash; entry.crc = crc; } bool VerificationPacket::Load(DiskFile *diskfile, u64 offset, PACKET_HEADER &header) { // Is the packet large enough if (header.length <= sizeof(FILEVERIFICATIONPACKET)) { return false; } // Does the packet have a whole number of verification records if (0 < ((header.length - sizeof(FILEVERIFICATIONPACKET)) % sizeof(FILEVERIFICATIONENTRY))) { return false; } // Is the packet too large if (header.length > sizeof(FILEVERIFICATIONPACKET) + 32768 * sizeof(FILEVERIFICATIONENTRY)) { return false; } // Allocate the packet FILEVERIFICATIONPACKET *packet = (FILEVERIFICATIONPACKET*)AllocatePacket((size_t)header.length); packet->header = header; // How many blocks are there blockcount = (u32)((((FILEVERIFICATIONPACKET*)packetdata)->header.length - sizeof(FILEVERIFICATIONPACKET)) / sizeof(FILEVERIFICATIONENTRY)); // Read the rest of the packet return diskfile->Read(offset + sizeof(PACKET_HEADER), &packet->fileid, (size_t)packet->header.length - sizeof(PACKET_HEADER)); } nzbget-16.4/lib/par2/par2fileformat.cpp0000644000175000017500000000322412630544544017614 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #include "par2cmdline.h" MAGIC packet_magic = {{'P', 'A', 'R', '2', '\0','P', 'K', 'T'}}; PACKETTYPE fileverificationpacket_type = {{'P', 'A', 'R', ' ', '2', '.', '0', '\0', 'I', 'F', 'S', 'C', '\0','\0','\0','\0'}}; PACKETTYPE filedescriptionpacket_type = {{'P', 'A', 'R', ' ', '2', '.', '0', '\0', 'F', 'i', 'l', 'e', 'D', 'e', 's', 'c' }}; PACKETTYPE mainpacket_type = {{'P', 'A', 'R', ' ', '2', '.', '0', '\0', 'M', 'a', 'i', 'n', '\0','\0','\0','\0'}}; PACKETTYPE recoveryblockpacket_type = {{'P', 'A', 'R', ' ', '2', '.', '0', '\0', 'R', 'e', 'c', 'v', 'S', 'l', 'i', 'c' }}; PACKETTYPE creatorpacket_type = {{'P', 'A', 'R', ' ', '2', '.', '0', '\0', 'C', 'r', 'e', 'a', 't', 'o', 'r', '\0'}}; nzbget-16.4/lib/par2/recoverypacket.h0000644000175000017500000000677312630544544017406 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #ifndef __RECOVERYPACKET_H__ #define __RECOVERYPACKET_H__ // The RecoveryPacket object is used to access a specific recovery // packet within a recovery file (for when creating them, or using // them during a repair operation). // Because the actual recovery data for the packet may be large, the // RecoveryPacket object only contains a copy of packet header and // exponent value for the block and uses a DataBlock object for // all accesses to the actual recovery data in the packet. class RecoveryPacket { public: RecoveryPacket(void); ~RecoveryPacket(void); public: // Create a recovery packet for a specified file. void Create(DiskFile *diskfile, // Which file will the packet be stored in u64 offset, // At what offset will the packet be stored u64 blocksize, // How much recovery data will it contain u32 exponent, // What exponent value will be used const MD5Hash &setid); // What is the SetId // Write some data to the recovery data block and update the recovery packet. bool WriteData(u64 position, // Relative position within the data block size_t size, // Size of data to write to block const void *buffer); // Buffer containing the data to write // Finish computing the hash of the recovery packet and write the header to disk. bool WriteHeader(void); public: // Load a recovery packet from a specified file bool Load(DiskFile *diskfile, u64 offset, PACKET_HEADER &header); public: // Get the lenght of the packet. u64 PacketLength(void) const; // The the exponent of the packet. u32 Exponent(void) const; // The length of the recovery data u64 BlockSize(void) const; // The data block DataBlock* GetDataBlock(void); protected: DiskFile *diskfile; // The specific file that this packet is stored in u64 offset; // The offset at which the packet is stored RECOVERYBLOCKPACKET packet; // The packet (excluding the actual recovery data) MD5Context *packetcontext; // MD5 Context used to compute the packet hash DataBlock datablock; // The recovery data block. }; inline u64 RecoveryPacket::PacketLength(void) const { return packet.header.length; } inline u32 RecoveryPacket::Exponent(void) const { return packet.exponent; } inline u64 RecoveryPacket::BlockSize(void) const { return packet.header.length - sizeof(packet); } inline DataBlock* RecoveryPacket::GetDataBlock(void) { return &datablock; } #endif // __RECOVERYPACKET_H__ nzbget-16.4/lib/par2/creatorpacket.h0000644000175000017500000000316112630544544017173 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #ifndef __CREATORPACKET_H__ #define __CREATORPACKET_H__ // The creator packet records details as to which PAR2 client // created a particular recovery file. // The PAR 2.0 specification requires the presence of a // creator packet, but it is not actually needed for the // verification or recovery of damaged files. class CreatorPacket : public CriticalPacket { public: // Construct the packet CreatorPacket(void) {}; ~CreatorPacket(void) {}; // Create a creator packet for a specified set id hash value bool Create(const MD5Hash &set_id_hash); // Load a creator packet from a specified file bool Load(DiskFile *diskfile, u64 offset, PACKET_HEADER &header); }; #endif // __CREATORPACKET_H__ nzbget-16.4/lib/par2/par2repairersourcefile.cpp0000644000175000017500000001033012630544544021352 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #include "par2cmdline.h" #ifdef _MSC_VER #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif #endif Par2RepairerSourceFile::Par2RepairerSourceFile(DescriptionPacket *_descriptionpacket, VerificationPacket *_verificationpacket) { firstblocknumber = 0; descriptionpacket = _descriptionpacket; verificationpacket = _verificationpacket; // verificationhashtable = 0; targetexists = false; targetfile = 0; completefile = 0; } Par2RepairerSourceFile::~Par2RepairerSourceFile(void) { delete descriptionpacket; delete verificationpacket; // delete verificationhashtable; } void Par2RepairerSourceFile::SetDescriptionPacket(DescriptionPacket *_descriptionpacket) { descriptionpacket = _descriptionpacket; } void Par2RepairerSourceFile::SetVerificationPacket(VerificationPacket *_verificationpacket) { verificationpacket = _verificationpacket; } void Par2RepairerSourceFile::ComputeTargetFileName(string path) { // Get a version of the filename compatible with the OS string filename = DiskFile::TranslateFilename(descriptionpacket->FileName()); // Strip the path from the filename string::size_type where; if (string::npos != (where = filename.find_last_of('\\')) || string::npos != (where = filename.find_last_of('/')) #ifdef WIN32 || string::npos != (where = filename.find_last_of(':')) #endif ) { filename = filename.substr(where+1); } targetfilename = path + filename; } string Par2RepairerSourceFile::TargetFileName(void) const { return targetfilename; } void Par2RepairerSourceFile::SetTargetFile(DiskFile *diskfile) { targetfile = diskfile; } DiskFile* Par2RepairerSourceFile::GetTargetFile(void) const { return targetfile; } void Par2RepairerSourceFile::SetTargetExists(bool exists) { targetexists = exists; } bool Par2RepairerSourceFile::GetTargetExists(void) const { return targetexists; } void Par2RepairerSourceFile::SetCompleteFile(DiskFile *diskfile) { completefile = diskfile; } DiskFile* Par2RepairerSourceFile::GetCompleteFile(void) const { return completefile; } // Remember which source and target blocks will be used by this file // and set their lengths appropriately void Par2RepairerSourceFile::SetBlocks(u32 _blocknumber, u32 _blockcount, vector::iterator _sourceblocks, vector::iterator _targetblocks, u64 blocksize) { firstblocknumber = _blocknumber; blockcount = _blockcount; sourceblocks = _sourceblocks; targetblocks = _targetblocks; if (blockcount > 0) { u64 filesize = descriptionpacket->FileSize(); vector::iterator sb = sourceblocks; for (u32 blocknumber=0; blocknumberFileSize() + blocksize-1) / blocksize); } else { blockcount = 0; } } nzbget-16.4/lib/par2/criticalpacket.h0000644000175000017500000000757312630544544017341 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #ifndef __CRITICALPACKET_H__ #define __CRITICALPACKET_H__ // Base class for main packet, file verification packet, file description packet // and creator packet. // These packets are all small and are held in memory in their entirity class CriticalPacket { public: CriticalPacket(void); ~CriticalPacket(void); public: // Write a copy of the packet to the specified file at the specified offset bool WritePacket(DiskFile &diskfile, u64 fileoffset) const; // Obtain the lenght of the packet. size_t PacketLength(void) const; // Allocate some memory for the packet (plus some extra padding). void* AllocatePacket(size_t length, size_t extra = 0); // Finish a packet (by storing the set_id_hash and then computing the packet_hash). void FinishPacket(const MD5Hash &set_id_hash); protected: u8 *packetdata; size_t packetlength; }; inline CriticalPacket::CriticalPacket(void) { // There is no data initially packetdata = 0; packetlength = 0; } inline CriticalPacket::~CriticalPacket(void) { // Delete the data for the packet delete [] packetdata; } inline size_t CriticalPacket::PacketLength(void) const { return packetlength; } inline void* CriticalPacket::AllocatePacket(size_t length, size_t extra) { // Hey! We can't allocate the packet twice assert(packetlength == 0 && packetdata == 0); // Remember the requested packet length packetlength = length; // Allocate and clear the requested packet length plus the extra. packetdata = new u8[length+extra]; memset(packetdata, 0, length+extra); return packetdata; } // Class used to record the fact that a copy of a particular critical packet // will be written to a particular file at a specific offset. class CriticalPacketEntry { public: CriticalPacketEntry(DiskFile *_diskfile, u64 _offset, const CriticalPacket *_packet) : diskfile(_diskfile) , offset(_offset) , packet(_packet) {} CriticalPacketEntry(void) : diskfile(0) , offset(0) , packet(0) {} CriticalPacketEntry(const CriticalPacketEntry &other) : diskfile(other.diskfile) , offset(other.offset) , packet(other.packet) {} CriticalPacketEntry& operator=(const CriticalPacketEntry &other) { diskfile = other.diskfile; offset = other.offset; packet = other.packet; return *this; } public: // Write the packet to disk. bool WritePacket(void) const; // Obtain the length of the packet. u64 PacketLength(void) const; protected: DiskFile *diskfile; u64 offset; const CriticalPacket *packet; }; inline bool CriticalPacketEntry::WritePacket(void) const { assert(packet != 0 && diskfile != 0); // Tell the packet to write itself to disk return packet->WritePacket(*diskfile, offset); } inline u64 CriticalPacketEntry::PacketLength(void) const { assert(packet != 0); // Ask the packet how big it is. return packet->PacketLength(); } #endif // __CRITICALPACKET_H__ nzbget-16.4/lib/par2/commandline.h0000644000175000017500000001412012630544544016627 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #ifndef __COMMANDLINE_H__ #define __COMMANDLINE_H__ // The CommandLine object is responsible for understanding the format // of the command line parameters are parsing the command line to // extract details as to what the user wants to do. class CommandLine { public: CommandLine(void); // Parse the supplied command line arguments. bool Parse(int argc, char *argv[]); // Display details of the correct format for command line parameters. static void usage(void); // What operation will we be carrying out typedef enum { opNone = 0, opCreate, // Create new PAR2 recovery volumes opVerify, // Verify but don't repair damaged data files opRepair // Verify and if possible repair damaged data files } Operation; typedef enum { verUnknown = 0, verPar1, // Processing PAR 1.0 files verPar2 // Processing PAR 2.0 files } Version; typedef enum { scUnknown = 0, scVariable, // Each PAR2 file will have 2x as many blocks as previous scLimited, // Limit PAR2 file size scUniform // All PAR2 files the same size } Scheme; typedef enum { nlUnknown = 0, nlSilent, // Absolutely no output (other than errors) nlQuiet, // Bare minimum of output nlNormal, // Normal level of output nlNoisy, // Lots of output nlDebug // Extra debugging information } NoiseLevel; // Any extra files listed on the command line class ExtraFile { public: ExtraFile(void); ExtraFile(const ExtraFile&); ExtraFile& operator=(const ExtraFile&); ExtraFile(const string &name, u64 size); string FileName(void) const {return filename;} u64 FileSize(void) const {return filesize;} protected: string filename; u64 filesize; }; public: // Accessor functions for the command line parameters CommandLine::Operation GetOperation(void) const {return operation;} CommandLine::Version GetVersion(void) const {return version;} u64 GetBlockSize(void) const {return blocksize;} u32 GetBlockCount(void) const {return blockcount;} u32 GetRedundancy(void) const {return redundancy;} u32 GetFirstRecoveryBlock(void) const {return firstblock;} u32 GetRecoveryFileCount(void) const {return recoveryfilecount;} u32 GetRecoveryBlockCount(void) const {return recoveryblockcount;} CommandLine::Scheme GetRecoveryFileScheme(void) const {return recoveryfilescheme;} size_t GetMemoryLimit(void) const {return memorylimit;} u64 GetLargestSourceSize(void) const {return largestsourcesize;} u64 GetTotalSourceSize(void) const {return totalsourcesize;} CommandLine::NoiseLevel GetNoiseLevel(void) const {return noiselevel;} string GetParFilename(void) const {return parfilename;} const list& GetExtraFiles(void) const {return extrafiles;} protected: Operation operation; // The operation to be carried out. Version version; // What version files will be processed. NoiseLevel noiselevel; // How much display output should there be. u32 blockcount; // How many blocks the source files should // be virtually split into. u64 blocksize; // What virtual block size to use. u32 firstblock; // What the exponent value for the first // recovery block will be. Scheme recoveryfilescheme; // How the the size of the recovery files should // be calculated. u32 recoveryfilecount; // How many recovery files should be created. u32 recoveryblockcount; // How many recovery blocks should be created. bool recoveryblockcountset; // Set if the recoveryblockcount as been specified u32 redundancy; // What percentage of recovery data should // be created. bool redundancyset; // Set if the redundancy has been specified string parfilename; // The name of the PAR2 file to create, or // the name of the first PAR2 file to read // when verifying or repairing. list extrafiles; // The list of other files specified on the // command line. When creating, this will be // the source files, and when verifying or // repairing, this will be additional PAR2 // files or data files to be examined. u64 totalsourcesize; // Total size of the source files. u64 largestsourcesize; // Size of the largest source file. size_t memorylimit; // How much memory is permitted to be used // for the output buffer when creating // or repairing. }; typedef list::const_iterator ExtraFileIterator; #endif // __COMMANDLINE_H__ nzbget-16.4/lib/par2/letype.h0000644000175000017500000000654112630544544015653 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #ifndef __LETYPE_H__ #define __LETYPE_H__ #if __BYTE_ORDER == __LITTLE_ENDIAN typedef u16 leu16; typedef u32 leu32; typedef u64 leu64; #else struct leu16 { leu16& operator=(const u16 &other); operator u16(void) const; u16 value; }; inline leu16& leu16::operator=(const u16 &other) { ((unsigned char*)&value)[0] = (unsigned char)((other >> 0) & 0xff); ((unsigned char*)&value)[1] = (unsigned char)((other >> 8) & 0xff); return *this; } inline leu16::operator u16(void) const { return ((unsigned char*)&value)[0] << 0 | ((unsigned char*)&value)[1] << 8; } struct leu32 { leu32& operator=(const u32 &other); operator u32(void) const; u32 value; }; inline leu32& leu32::operator=(const u32 &other) { ((unsigned char*)&value)[0] = (unsigned char)((other >> 0) & 0xff); ((unsigned char*)&value)[1] = (unsigned char)((other >> 8) & 0xff); ((unsigned char*)&value)[2] = (unsigned char)((other >> 16) & 0xff); ((unsigned char*)&value)[3] = (unsigned char)((other >> 24) & 0xff); return *this; } inline leu32::operator u32(void) const { return ((unsigned char*)&value)[0] << 0 | ((unsigned char*)&value)[1] << 8 | ((unsigned char*)&value)[2] << 16 | ((unsigned char*)&value)[3] << 24; } struct leu64 { leu64& operator=(const u64 &other); operator u64(void) const; u64 value; }; inline leu64& leu64::operator=(const u64 &other) { ((unsigned char*)&value)[0] = (unsigned char)((other >> 0) & 0xff); ((unsigned char*)&value)[1] = (unsigned char)((other >> 8) & 0xff); ((unsigned char*)&value)[2] = (unsigned char)((other >> 16) & 0xff); ((unsigned char*)&value)[3] = (unsigned char)((other >> 24) & 0xff); ((unsigned char*)&value)[4] = (unsigned char)((other >> 32) & 0xff); ((unsigned char*)&value)[5] = (unsigned char)((other >> 40) & 0xff); ((unsigned char*)&value)[6] = (unsigned char)((other >> 48) & 0xff); ((unsigned char*)&value)[7] = (unsigned char)((other >> 56) & 0xff); return *this; } inline leu64::operator u64(void) const { return (u64)(((unsigned char*)&value)[0]) << 0 | (u64)(((unsigned char*)&value)[1]) << 8 | (u64)(((unsigned char*)&value)[2]) << 16 | (u64)(((unsigned char*)&value)[3]) << 24 | (u64)(((unsigned char*)&value)[4]) << 32 | (u64)(((unsigned char*)&value)[5]) << 40 | (u64)(((unsigned char*)&value)[6]) << 48 | (u64)(((unsigned char*)&value)[7]) << 56; } #endif #endif // __LETYPE_H__ nzbget-16.4/lib/par2/descriptionpacket.cpp0000644000175000017500000000716412630544544020421 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #include "par2cmdline.h" #ifdef _MSC_VER #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif #endif // Construct the packet and store the filename and size. bool DescriptionPacket::Create(string filename, u64 filesize) { // Allocate some extra bytes for the packet in memory so that strlen() can // be used on the filename. The extra bytes do not get written to disk. FILEDESCRIPTIONPACKET *packet = (FILEDESCRIPTIONPACKET *)AllocatePacket(sizeof(*packet) + (~3 & (3 + (u32)filename.size())), 4); // Store everything that is currently known in the packet. packet->header.magic = packet_magic; packet->header.length = packetlength; //packet->header.hash; // Not known yet //packet->header.setid; // Not known yet packet->header.type = filedescriptionpacket_type; //packet->fileid; // Not known yet //packet->hashfull; // Not known yet //packet->hash16k; // Not known yet packet->length = filesize; memcpy(packet->name, filename.c_str(), filename.size()); return true; } void DescriptionPacket::Hash16k(const MD5Hash &hash) { ((FILEDESCRIPTIONPACKET *)packetdata)->hash16k = hash; } void DescriptionPacket::HashFull(const MD5Hash &hash) { ((FILEDESCRIPTIONPACKET *)packetdata)->hashfull = hash; } void DescriptionPacket::ComputeFileId(void) { FILEDESCRIPTIONPACKET *packet = ((FILEDESCRIPTIONPACKET *)packetdata); // Compute the fileid from the hash, length, and name fields in the packet. MD5Context context; context.Update(&packet->hash16k, sizeof(FILEDESCRIPTIONPACKET)-offsetof(FILEDESCRIPTIONPACKET,hash16k) +strlen((const char*)packet->name)); context.Final(packet->fileid); } // Load a description packet from a specified file bool DescriptionPacket::Load(DiskFile *diskfile, u64 offset, PACKET_HEADER &header) { // Is the packet big enough if (header.length <= sizeof(FILEDESCRIPTIONPACKET)) { return false; } // Is the packet too large (what is the longest permissible filename) if (header.length - sizeof(FILEDESCRIPTIONPACKET) > 100000) { return false; } // Allocate the packet (with a little extra so we will have NULLs after the filename) FILEDESCRIPTIONPACKET *packet = (FILEDESCRIPTIONPACKET *)AllocatePacket((size_t)header.length, 4); packet->header = header; // Read the rest of the packet from disk if (!diskfile->Read(offset + sizeof(PACKET_HEADER), &packet->fileid, (size_t)packet->header.length - sizeof(PACKET_HEADER))) return false; // Are the file and 16k hashes consistent if (packet->length <= 16384 && packet->hash16k != packet->hashfull) { return false; } return true; } nzbget-16.4/lib/par2/galois.cpp0000644000175000017500000000207112630544544016154 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #include "par2cmdline.h" #ifdef _MSC_VER #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif #endif nzbget-16.4/lib/par2/filechecksummer.cpp0000644000175000017500000001352312630544544020050 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #include "par2cmdline.h" #ifdef _MSC_VER #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif #endif // Construct the checksummer and allocate buffers FileCheckSummer::FileCheckSummer(DiskFile *_diskfile, u64 _blocksize, const u32 (&_windowtable)[256], u32 _windowmask) : diskfile(_diskfile) , blocksize(_blocksize) , windowtable(_windowtable) , windowmask(_windowmask) { buffer = new char[(size_t)blocksize*2]; filesize = diskfile->FileSize(); currentoffset = 0; } FileCheckSummer::~FileCheckSummer(void) { delete [] buffer; } // Start reading the file at the beginning bool FileCheckSummer::Start(void) { currentoffset = readoffset = 0; tailpointer = outpointer = buffer; inpointer = &buffer[blocksize]; // Fill the buffer with new data if (!Fill()) return false; // Compute the checksum for the block checksum = ~0 ^ CRCUpdateBlock(~0, (size_t)blocksize, buffer); return true; } // Jump ahead bool FileCheckSummer::Jump(u64 distance) { // Are we already at the end of the file if (currentoffset >= filesize) return false; // Special distances if (distance == 0) return false; if (distance == 1) return Step(); // Not allowed to jump more than one block assert(distance <= blocksize); if (distance > blocksize) distance = blocksize; // Advance the current offset and check if we have reached the end of the file currentoffset += distance; if (currentoffset >= filesize) { currentoffset = filesize; tailpointer = outpointer = buffer; memset(buffer, 0, (size_t)blocksize); checksum = 0; return true; } // Move past the data being discarded outpointer += distance; assert(outpointer <= tailpointer); // Is there any data left in the buffer that we are keeping size_t keep = tailpointer - outpointer; if (keep > 0) { // Move it back to the start of the buffer memmove(buffer, outpointer, keep); tailpointer = &buffer[keep]; } else { tailpointer = buffer; } outpointer = buffer; inpointer = &buffer[blocksize]; if (!Fill()) return false; // Compute the checksum for the block checksum = ~0 ^ CRCUpdateBlock(~0, (size_t)blocksize, buffer); return true; } // Fill the buffer from disk bool FileCheckSummer::Fill(void) { // Have we already reached the end of the file if (readoffset >= filesize) return true; // How much data can we read into the buffer size_t want = (size_t)min(filesize-readoffset, (u64)(&buffer[2*blocksize]-tailpointer)); if (want > 0) { // Read data if (!diskfile->Read(readoffset, tailpointer, want)) return false; UpdateHashes(readoffset, tailpointer, want); readoffset += want; tailpointer += want; } // Did we fill the buffer want = &buffer[2*blocksize] - tailpointer; if (want > 0) { // Blank the rest of the buffer memset(tailpointer, 0, want); } return true; } // Update the full file hash and the 16k hash using the new data void FileCheckSummer::UpdateHashes(u64 offset, const void *buffer, size_t length) { // Are we already beyond the first 16k if (offset >= 16384) { contextfull.Update(buffer, length); } // Would we reach the 16k mark else if (offset+length >= 16384) { // Finish the 16k hash size_t first = (size_t)(16384-offset); context16k.Update(buffer, first); // Continue with the full hash contextfull = context16k; // Do we go beyond the 16k mark if (offset+length > 16384) { contextfull.Update(&((const char*)buffer)[first], length-first); } } else { context16k.Update(buffer, length); } } // Return the full file hash and the 16k file hash void FileCheckSummer::GetFileHashes(MD5Hash &hashfull, MD5Hash &hash16k) const { // Compute the hash of the first 16k MD5Context context = context16k; context.Final(hash16k); // Is the file smaller than 16k if (filesize < 16384) { // The hashes are the same hashfull = hash16k; } else { // Compute the hash of the full file context = contextfull; context.Final(hashfull); } } // Compute and return the current hash MD5Hash FileCheckSummer::Hash(void) { MD5Context context; context.Update(outpointer, (size_t)blocksize); MD5Hash hash; context.Final(hash); return hash; } u32 FileCheckSummer::ShortChecksum(u64 blocklength) { u32 crc = CRCUpdateBlock(~0, (size_t)blocklength, outpointer); if (blocksize > blocklength) { crc = CRCUpdateBlock(crc, (size_t)(blocksize-blocklength)); } crc ^= ~0; return crc; } MD5Hash FileCheckSummer::ShortHash(u64 blocklength) { MD5Context context; context.Update(outpointer, (size_t)blocklength); if (blocksize > blocklength) { context.Update((size_t)(blocksize-blocklength)); } // Get the hash value MD5Hash hash; context.Final(hash); return hash; } nzbget-16.4/lib/par2/reedsolomon.h0000644000175000017500000003710412630544544016676 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #ifndef __REEDSOLOMON_H__ #define __REEDSOLOMON_H__ // The ReedSolomon object is used to calculate and store the matrix // used during recovery block creation or data block reconstruction. // // During initialisation, one RSOutputRow object is created for each // recovery block that either needs to be created or is available for // use. class RSOutputRow { public: RSOutputRow(void) {}; RSOutputRow(bool _present, u16 _exponent) : present(_present), exponent(_exponent) {} public: bool present; u16 exponent; }; template class ReedSolomon { public: typedef g G; ReedSolomon(void); ~ReedSolomon(void); // Set which input blocks are present or missing bool SetInput(const vector &present); // Some input blocks are present bool SetInput(u32 count); // All input blocks are present // Set which output block are available or need to be computed bool SetOutput(bool present, u16 exponent); bool SetOutput(bool present, u16 lowexponent, u16 highexponent); // Compute the RS Matrix bool Compute(CommandLine::NoiseLevel noiselevel); // Process a block of data bool Process(size_t size, // The size of the block of data u32 inputindex, // The column in the RS matrix const void *inputbuffer, // Buffer containing input data u32 outputindex, // The row in the RS matrix void *outputbuffer); // Buffer containing output data protected: // Perform Gaussian Elimination bool GaussElim(CommandLine::NoiseLevel noiselevel, unsigned int rows, unsigned int leftcols, G *leftmatrix, G *rightmatrix, unsigned int datamissing); protected: u32 inputcount; // Total number of input blocks u32 datapresent; // Number of input blocks that are present u32 datamissing; // Number of input blocks that are missing u32 *datapresentindex; // The index numbers of the data blocks that are present u32 *datamissingindex; // The index numbers of the data blocks that are missing typename G::ValueType *database;// The "base" value to use for each input block u32 outputcount; // Total number of output blocks u32 parpresent; // Number of output blocks that are present u32 parmissing; // Number of output blocks that are missing u32 *parpresentindex; // The index numbers of the output blocks that are present u32 *parmissingindex; // The index numbers of the output blocks that are missing vector outputrows; // Details of the output blocks G *leftmatrix; // The main matrix // When the matrices are initialised: values of the form base ^ exponent are // stored (where the base values are obtained from database[] and the exponent // values are obtained from outputrows[]). #ifdef LONGMULTIPLY GaloisLongMultiplyTable *glmt; // A multiplication table used by Process() #endif }; template inline ReedSolomon::ReedSolomon(void) { inputcount = 0; datapresent = 0; datamissing = 0; datapresentindex = 0; datamissingindex = 0; database = 0; outputrows.empty(); outputcount = 0; parpresent = 0; parmissing = 0; parpresentindex = 0; parmissingindex = 0; leftmatrix = 0; #ifdef LONGMULTIPLY glmt = new GaloisLongMultiplyTable; #endif } template inline ReedSolomon::~ReedSolomon(void) { delete [] datapresentindex; delete [] datamissingindex; delete [] database; delete [] parpresentindex; delete [] parmissingindex; delete [] leftmatrix; #ifdef LONGMULTIPLY delete glmt; #endif } u32 gcd(u32 a, u32 b); // Record whether the recovery block with the specified // exponent values is present or missing. template inline bool ReedSolomon::SetOutput(bool present, u16 exponent) { // Store the exponent and whether or not the recovery block is present or missing outputrows.push_back(RSOutputRow(present, exponent)); outputcount++; // Update the counts. if (present) { parpresent++; } else { parmissing++; } return true; } // Record whether the recovery blocks with the specified // range of exponent values are present or missing. template inline bool ReedSolomon::SetOutput(bool present, u16 lowexponent, u16 highexponent) { for (unsigned int exponent=lowexponent; exponent<=highexponent; exponent++) { if (!SetOutput(present, exponent)) return false; } return true; } // Construct the Vandermonde matrix and solve it if necessary template inline bool ReedSolomon::Compute(CommandLine::NoiseLevel noiselevel) { u32 outcount = datamissing + parmissing; u32 incount = datapresent + datamissing; if (datamissing > parpresent) { cerr << "Not enough recovery blocks." << endl; return false; } else if (outcount == 0) { cerr << "No output blocks." << endl; return false; } if (noiselevel > CommandLine::nlQuiet) cout << "Computing Reed Solomon matrix." << endl; /* Layout of RS Matrix: parpresent datapresent datamissing datamissing parmissing / | \ / | \ parpresent | (ppi[row])| | | (ppi[row])| | datamissing | ^ | I | | ^ | 0 | |(dpi[col]) | | |(dmi[col]) | | +---------------------+-------------+ +---------------------+-----------+ | (pmi[row])| | | (pmi[row])| | parmissing | ^ | 0 | | ^ | I | |(dpi[col]) | | |(dmi[col]) | | \ | / \ | / */ // Allocate the left hand matrix leftmatrix = new G[outcount * incount]; memset(leftmatrix, 0, outcount * incount * sizeof(G)); // Allocate the right hand matrix only if we are recovering G *rightmatrix = 0; if (datamissing > 0) { rightmatrix = new G[outcount * outcount]; memset(rightmatrix, 0, outcount *outcount * sizeof(G)); } // Fill in the two matrices: vector::const_iterator outputrow = outputrows.begin(); // One row for each present recovery block that will be used for a missing data block for (unsigned int row=0; row CommandLine::nlQuiet) { int progress = row * 1000 / (datamissing+parmissing); cout << "Constructing: " << progress/10 << '.' << progress%10 << "%\r" << flush; } // Get the exponent of the next present recovery block while (!outputrow->present) { outputrow++; } u16 exponent = outputrow->exponent; // One column for each present data block for (unsigned int col=0; col 0) { // One column for each missing data block for (unsigned int col=0; col CommandLine::nlQuiet) { int progress = (row+datamissing) * 1000 / (datamissing+parmissing); cout << "Constructing: " << progress/10 << '.' << progress%10 << "%\r" << flush; } // Get the exponent of the next missing recovery block while (outputrow->present) { outputrow++; } u16 exponent = outputrow->exponent; // One column for each present data block for (unsigned int col=0; col 0) { // One column for each missing data block for (unsigned int col=0; col CommandLine::nlQuiet) cout << "Constructing: done." << endl; // Solve the matrices only if recovering data if (datamissing > 0) { // Perform Gaussian Elimination and then delete the right matrix (which // will no longer be required). bool success = GaussElim(noiselevel, outcount, incount, leftmatrix, rightmatrix, datamissing); delete [] rightmatrix; return success; } return true; } // Use Gaussian Elimination to solve the matrices template inline bool ReedSolomon::GaussElim(CommandLine::NoiseLevel noiselevel, unsigned int rows, unsigned int leftcols, G *leftmatrix, G *rightmatrix, unsigned int datamissing) { if (noiselevel == CommandLine::nlDebug) { for (unsigned int row=0; row8?4:2) << setfill('0') << (unsigned int)leftmatrix[row*leftcols+col]; } cout << ((row==0) ? " \\ /" : (row==rows-1) ? " / \\" : " | |"); for (unsigned int col=0; col8?4:2) << setfill('0') << (unsigned int)rightmatrix[row*rows+col]; } cout << ((row==0) ? " \\" : (row==rows-1) ? " /" : " | |"); cout << endl; cout << dec << setw(0) << setfill(' '); } } // Because the matrices being operated on are Vandermonde matrices // they are guaranteed not to be singular. // Additionally, because Galois arithmetic is being used, all calulations // involve exact values with no loss of precision. It is therefore // not necessary to carry out any row or column swapping. // Solve one row at a time int progress = 0; // For each row in the matrix for (unsigned int row=0; row CommandLine::nlQuiet) { int newprogress = (row*rows+row2) * 1000 / (datamissing*rows); if (progress != newprogress) { progress = newprogress; cout << "Solving: " << progress/10 << '.' << progress%10 << "%\r" << flush; } } if (row != row2) { // Get the scaling factor for this row. G scalevalue = rightmatrix[row2 * rows + row]; if (scalevalue == 1) { // If the scaling factor happens to be 1, just subtract rows for (unsigned int col=0; col CommandLine::nlQuiet) cout << "Solving: done." << endl; if (noiselevel == CommandLine::nlDebug) { for (unsigned int row=0; row8?4:2) << setfill('0') << (unsigned int)leftmatrix[row*leftcols+col]; } cout << ((row==0) ? " \\ /" : (row==rows-1) ? " / \\" : " | |"); for (unsigned int col=0; col8?4:2) << setfill('0') << (unsigned int)rightmatrix[row*rows+col]; } cout << ((row==0) ? " \\" : (row==rows-1) ? " /" : " | |"); cout << endl; cout << dec << setw(0) << setfill(' '); } } return true; } #endif // __REEDSOLOMON_H__ nzbget-16.4/lib/par2/verificationhashtable.cpp0000644000175000017500000000641512630544544021242 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #include "par2cmdline.h" #ifdef _MSC_VER #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif #endif VerificationHashTable::VerificationHashTable(void) { hashmask = 0; hashtable = 0; } VerificationHashTable::~VerificationHashTable(void) { // Destroy the hash table if (hashtable) { for (unsigned int entry=0; entry<=hashmask; entry++) { delete hashtable[entry]; } } delete [] hashtable; } // Allocate the hash table with a reasonable size void VerificationHashTable::SetLimit(u32 limit) { // Pick a good size for the hash table hashmask = 256; while (hashmask < limit && hashmask < 65536) { hashmask <<= 1; } // Allocate and clear the hash table hashtable = new VerificationHashEntry*[hashmask]; memset(hashtable, 0, hashmask * sizeof(hashtable[0])); hashmask--; } // Load data from a verification packet void VerificationHashTable::Load(Par2RepairerSourceFile *sourcefile, u64 blocksize) { VerificationHashEntry *preventry = 0; // Get information from the sourcefile VerificationPacket *verificationpacket = sourcefile->GetVerificationPacket(); u32 blockcount = verificationpacket->BlockCount(); // Iterate throught the data blocks for the source file and the verification // entries in the verification packet. vector::iterator sourceblocks = sourcefile->SourceBlocks(); const FILEVERIFICATIONENTRY *verificationentry = verificationpacket->VerificationEntry(0); u32 blocknumber = 0; while (blocknumberInsert(&hashtable[entry->Checksum() & hashmask]); // Make the previous entry point forwards to this one if (preventry) { preventry->Next(entry); } preventry = entry; ++blocknumber; ++sourceblocks; ++verificationentry; } } nzbget-16.4/lib/par2/md5.h0000644000175000017500000000707512630544544015041 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #ifndef __MD5_H__ #define __MD5_H__ #ifdef WIN32 #pragma pack(push, 1) #define PACKED #else #define PACKED __attribute__ ((packed)) #endif // This file defines the MD5Hash and MD5Context objects which are used // to compute and manipulate the MD5 Hash values for a block of data. // Usage: // // MD5Context context; // context.Update(buffer, length); // // MD5Hash hash; // context.Final(hash); // MD5 Hash value struct MD5Hash; ostream& operator<<(ostream &s, const MD5Hash &hash); struct MD5Hash { // Comparison operators bool operator==(const MD5Hash &other) const; bool operator!=(const MD5Hash &other) const; bool operator<(const MD5Hash &other) const; bool operator>=(const MD5Hash &other) const; bool operator>(const MD5Hash &other) const; bool operator<=(const MD5Hash &other) const; // Convert value to hex friend ostream& operator<<(ostream &s, const MD5Hash &hash); string print(void) const; u8 hash[16]; // 16 byte MD5 Hash value } PACKED; // Intermediate computation state class MD5State { public: MD5State(void); void Reset(void); public: void UpdateState(const u32 (&block)[16]); protected: u32 state[4]; // 16 byte MD5 computation state }; // MD5 computation context with 64 byte buffer class MD5Context : public MD5State { public: MD5Context(void); ~MD5Context(void) {}; void Reset(void); // Process data from a buffer void Update(const void *buffer, size_t length); // Process 0 bytes void Update(size_t length); // Compute the final hash value void Final(MD5Hash &output); // Get the Hash value and the total number of bytes processed. MD5Hash Hash(void) const; u64 Bytes(void) const {return bytes;} friend ostream& operator<<(ostream &s, const MD5Context &context); string print(void) const; protected: enum {buffersize = 64}; unsigned char block[buffersize]; size_t used; u64 bytes; }; // Compare hash values inline bool MD5Hash::operator==(const MD5Hash &other) const { return (0==memcmp(&hash, &other.hash, sizeof(hash))); } inline bool MD5Hash::operator!=(const MD5Hash &other) const { return !operator==(other); } inline bool MD5Hash::operator<(const MD5Hash &other) const { size_t index = 15; while (index > 0 && hash[index] == other.hash[index]) { index--; } return hash[index] < other.hash[index]; } inline bool MD5Hash::operator>=(const MD5Hash &other) const { return !operator<(other); } inline bool MD5Hash::operator>(const MD5Hash &other) const { return other.operator<(*this); } inline bool MD5Hash::operator<=(const MD5Hash &other) const { return !other.operator<(*this); } #ifdef WIN32 #pragma pack(pop) #endif #undef PACKED #endif // __MD5_H__ nzbget-16.4/lib/par2/diskfile.h0000644000175000017500000000643512630544544016145 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #ifndef __DISKFILE_H__ #define __DISKFILE_H__ // A disk file can be any type of file that par2cmdline needs // to read or write data from or to. class DiskFile { public: DiskFile(void); ~DiskFile(void); // Create a file and set its length bool Create(string filename, u64 filesize); // Write some data to the file bool Write(u64 offset, const void *buffer, size_t length); // Open the file bool Open(void); bool Open(string filename); bool Open(string filename, u64 filesize); // Check to see if the file is open #ifdef WIN32 bool IsOpen(void) const {return hFile != INVALID_HANDLE_VALUE;} #else bool IsOpen(void) const {return file != 0;} #endif // Read some data from the file bool Read(u64 offset, void *buffer, size_t length); // Close the file void Close(void); // Get the size of the file u64 FileSize(void) const {return filesize;} // Get the name of the file string FileName(void) const {return filename;} // Does the file exist bool Exists(void) const {return exists;} // Rename the file bool Rename(void); // Pick a filename automatically bool Rename(string filename); // Delete the file bool Delete(void); public: static string GetCanonicalPathname(string filename); static void SplitFilename(string filename, string &path, string &name); static string TranslateFilename(string filename); static bool FileExists(string filename); static u64 GetFileSize(string filename); // Search the specified path for files which match the specified wildcard // and return their names in a list. static list* FindFiles(string path, string wildcard); protected: string filename; u64 filesize; // OS file handle #ifdef WIN32 HANDLE hFile; #else FILE *file; #endif // Current offset within the file u64 offset; // Does the file exist bool exists; protected: #ifdef WIN32 static string ErrorMessage(DWORD error); #endif }; // This class keeps track of which DiskFile objects exist // and which file on disk they are associated with. // It is used to avoid a file being processed twice. class DiskFileMap { public: DiskFileMap(void); ~DiskFileMap(void); bool Insert(DiskFile *diskfile); void Remove(DiskFile *diskfile); DiskFile* Find(string filename) const; protected: map diskfilemap; // Map from filename to DiskFile }; #endif // __DISKFILE_H__ nzbget-16.4/lib/par2/diskfile.cpp0000644000175000017500000005006712630544544016500 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #include "par2cmdline.h" #ifdef _MSC_VER #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif #endif #ifdef WIN32 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// #define OffsetType __int64 #define MaxOffset 0x7fffffffffffffffI64 #define LengthType unsigned int #define MaxLength 0xffffffffUL DiskFile::DiskFile(void) { filename; filesize = 0; offset = 0; hFile = INVALID_HANDLE_VALUE; exists = false; } DiskFile::~DiskFile(void) { if (hFile != INVALID_HANDLE_VALUE) ::CloseHandle(hFile); } // Create new file on disk and make sure that there is enough // space on disk for it. bool DiskFile::Create(string _filename, u64 _filesize) { assert(hFile == INVALID_HANDLE_VALUE); filename = _filename; filesize = _filesize; // Create the file hFile = ::CreateFileA(_filename.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_NEW, 0, NULL); if (hFile == INVALID_HANDLE_VALUE) { DWORD error = ::GetLastError(); cerr << "Could not create \"" << _filename << "\": " << ErrorMessage(error) << endl; return false; } if (filesize > 0) { // Seek to the end of the file LONG lowoffset = ((LONG*)&filesize)[0]; LONG highoffset = ((LONG*)&filesize)[1]; if (INVALID_SET_FILE_POINTER == SetFilePointer(hFile, lowoffset, &highoffset, FILE_BEGIN)) { DWORD error = ::GetLastError(); cerr << "Could not set size of \"" << _filename << "\": " << ErrorMessage(error) << endl; ::CloseHandle(hFile); hFile = INVALID_HANDLE_VALUE; ::DeleteFile(_filename.c_str()); return false; } // Set the end of the file if (!::SetEndOfFile(hFile)) { DWORD error = ::GetLastError(); cerr << "Could not set size of \"" << _filename << "\": " << ErrorMessage(error) << endl; ::CloseHandle(hFile); hFile = INVALID_HANDLE_VALUE; ::DeleteFile(_filename.c_str()); return false; } } offset = filesize; exists = true; return true; } // Write some data to disk bool DiskFile::Write(u64 _offset, const void *buffer, size_t length) { assert(hFile != INVALID_HANDLE_VALUE); if (offset != _offset) { LONG lowoffset = ((LONG*)&_offset)[0]; LONG highoffset = ((LONG*)&_offset)[1]; // Seek to the required offset if (INVALID_SET_FILE_POINTER == SetFilePointer(hFile, lowoffset, &highoffset, FILE_BEGIN)) { DWORD error = ::GetLastError(); cerr << "Could not write " << (u64)length << " bytes to \"" << filename << "\" at offset " << _offset << ": " << ErrorMessage(error) << endl; return false; } offset = _offset; } if (length > MaxLength) { cerr << "Could not write " << (u64)length << " bytes to \"" << filename << "\" at offset " << _offset << ": " << "Write too long" << endl; return false; } DWORD write = (LengthType)length; DWORD wrote; // Write the data if (!::WriteFile(hFile, buffer, write, &wrote, NULL)) { DWORD error = ::GetLastError(); cerr << "Could not write " << (u64)length << " bytes to \"" << filename << "\" at offset " << _offset << ": " << ErrorMessage(error) << endl; return false; } offset += length; if (filesize < offset) { filesize = offset; } return true; } // Open the file bool DiskFile::Open(string _filename, u64 _filesize) { assert(hFile == INVALID_HANDLE_VALUE); filename = _filename; filesize = _filesize; hFile = ::CreateFileA(_filename.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (hFile == INVALID_HANDLE_VALUE) { DWORD error = ::GetLastError(); switch (error) { case ERROR_FILE_NOT_FOUND: case ERROR_PATH_NOT_FOUND: break; default: cerr << "Could not open \"" << _filename << "\": " << ErrorMessage(error) << endl; } return false; } offset = 0; exists = true; return true; } // Read some data from disk bool DiskFile::Read(u64 _offset, void *buffer, size_t length) { assert(hFile != INVALID_HANDLE_VALUE); if (offset != _offset) { LONG lowoffset = ((LONG*)&_offset)[0]; LONG highoffset = ((LONG*)&_offset)[1]; // Seek to the required offset if (INVALID_SET_FILE_POINTER == SetFilePointer(hFile, lowoffset, &highoffset, FILE_BEGIN)) { DWORD error = ::GetLastError(); cerr << "Could not read " << (u64)length << " bytes from \"" << filename << "\" at offset " << _offset << ": " << ErrorMessage(error) << endl; return false; } offset = _offset; } if (length > MaxLength) { cerr << "Could not read " << (u64)length << " bytes from \"" << filename << "\" at offset " << _offset << ": " << "Read too long" << endl; return false; } DWORD want = (LengthType)length; DWORD got; // Read the data if (!::ReadFile(hFile, buffer, want, &got, NULL)) { DWORD error = ::GetLastError(); cerr << "Could not read " << (u64)length << " bytes from \"" << filename << "\" at offset " << _offset << ": " << ErrorMessage(error) << endl; return false; } offset += length; return true; } void DiskFile::Close(void) { if (hFile != INVALID_HANDLE_VALUE) { ::CloseHandle(hFile); hFile = INVALID_HANDLE_VALUE; } } string DiskFile::GetCanonicalPathname(string filename) { char fullname[MAX_PATH]; char *filepart; // Resolve a relative path to a full path int length = ::GetFullPathName(filename.c_str(), sizeof(fullname), fullname, &filepart); if (length <= 0 || sizeof(fullname) < length) return filename; // Make sure the drive letter is upper case. fullname[0] = toupper(fullname[0]); // Translate all /'s to \'s char *current = strchr(fullname, '/'); while (current) { *current++ = '\\'; current = strchr(current, '/'); } // Copy the root directory to the output string string longname(fullname, 3); // Start processing at the first path component current = &fullname[3]; char *limit = &fullname[length]; // Process until we reach the end of the full name while (current < limit) { char *tail; // Find the next \, or the end of the string (tail = strchr(current, '\\')) || (tail = limit); *tail = 0; // Create a wildcard to search for the path string wild = longname + current; WIN32_FIND_DATA finddata; HANDLE hFind = ::FindFirstFile(wild.c_str(), &finddata); if (hFind != INVALID_HANDLE_VALUE) // Copy the component found to the output longname += finddata.cFileName; else // If the component was not found then just copy the component to the // output buffer verbatim. longname += current; ::FindClose(hFind); current = tail + 1; // If we have not reached the end of the name, add a "\" if (current < limit) longname += '\\'; } return longname; } list* DiskFile::FindFiles(string path, string wildcard) { list *matches = new list; wildcard = path + wildcard; WIN32_FIND_DATA fd; HANDLE h = ::FindFirstFile(wildcard.c_str(), &fd); if (h != INVALID_HANDLE_VALUE) { do { if (0 == (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { matches->push_back(path + fd.cFileName); } } while (::FindNextFile(h, &fd)); ::FindClose(h); } return matches; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// #else // !WIN32 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// #ifdef HAVE_FSEEKO # define OffsetType off_t # define MaxOffset ((off_t)0x7fffffffffffffffULL) # define fseek fseeko #else # if _FILE_OFFSET_BITS == 64 # define OffsetType unsigned long long # define MaxOffset 0x7fffffffffffffffULL # else # define OffsetType long # define MaxOffset 0x7fffffffUL # endif #endif #define LengthType unsigned int #define MaxLength 0xffffffffUL DiskFile::DiskFile(void) { //filename; filesize = 0; offset = 0; file = 0; exists = false; } DiskFile::~DiskFile(void) { if (file != 0) fclose(file); } // Create new file on disk and make sure that there is enough // space on disk for it. bool DiskFile::Create(string _filename, u64 _filesize) { assert(file == 0); filename = _filename; filesize = _filesize; file = fopen(_filename.c_str(), "wb"); if (file == 0) { cerr << "Could not create: " << _filename << endl; return false; } if (_filesize > (u64)MaxOffset) { cerr << "Requested file size for " << _filename << " is too large." << endl; return false; } if (_filesize > 0) { if (fseek(file, (OffsetType)_filesize-1, SEEK_SET)) { fclose(file); file = 0; ::remove(filename.c_str()); cerr << "Could not set end of file: " << _filename << endl; return false; } if (1 != fwrite(&_filesize, 1, 1, file)) { fclose(file); file = 0; ::remove(filename.c_str()); cerr << "Could not set end of file: " << _filename << endl; return false; } } offset = filesize; exists = true; return true; } // Write some data to disk bool DiskFile::Write(u64 _offset, const void *buffer, size_t length) { assert(file != 0); if (offset != _offset) { if (_offset > (u64)MaxOffset) { cerr << "Could not write " << (u64)length << " bytes to " << filename << " at offset " << _offset << endl; return false; } if (fseek(file, (OffsetType)_offset, SEEK_SET)) { cerr << "Could not write " << (u64)length << " bytes to " << filename << " at offset " << _offset << endl; return false; } offset = _offset; } if (length > MaxLength) { cerr << "Could not write " << (u64)length << " bytes to " << filename << " at offset " << _offset << endl; return false; } if (1 != fwrite(buffer, (LengthType)length, 1, file)) { cerr << "Could not write " << (u64)length << " bytes to " << filename << " at offset " << _offset << endl; return false; } offset += length; if (filesize < offset) { filesize = offset; } return true; } // Open the file bool DiskFile::Open(string _filename, u64 _filesize) { assert(file == 0); filename = _filename; filesize = _filesize; if (_filesize > (u64)MaxOffset) { cerr << "File size for " << _filename << " is too large." << endl; return false; } file = fopen(filename.c_str(), "rb"); if (file == 0) { return false; } offset = 0; exists = true; return true; } // Read some data from disk bool DiskFile::Read(u64 _offset, void *buffer, size_t length) { assert(file != 0); if (offset != _offset) { if (_offset > (u64)MaxOffset) { cerr << "Could not read " << (u64)length << " bytes from " << filename << " at offset " << _offset << endl; return false; } if (fseek(file, (OffsetType)_offset, SEEK_SET)) { cerr << "Could not read " << (u64)length << " bytes from " << filename << " at offset " << _offset << endl; return false; } offset = _offset; } if (length > MaxLength) { cerr << "Could not read " << (u64)length << " bytes from " << filename << " at offset " << _offset << endl; return false; } if (1 != fread(buffer, (LengthType)length, 1, file)) { cerr << "Could not read " << (u64)length << " bytes from " << filename << " at offset " << _offset << endl; return false; } offset += length; return true; } void DiskFile::Close(void) { if (file != 0) { fclose(file); file = 0; } } // Attempt to get the full pathname of the file string DiskFile::GetCanonicalPathname(string filename) { // Is the supplied path already an absolute one if (filename.size() == 0 || filename[0] == '/') return filename; // Get the current directory char curdir[1000]; if (0 == getcwd(curdir, sizeof(curdir))) { return filename; } // Allocate a work buffer and copy the resulting full path into it. char *work = new char[strlen(curdir) + filename.size() + 2]; strcpy(work, curdir); if (work[strlen(work)-1] != '/') strcat(work, "/"); strcat(work, filename.c_str()); char *in = work; char *out = work; while (*in) { if (*in == '/') { if (in[1] == '.' && in[2] == '/') { // skip the input past /./ in += 2; } else if (in[1] == '.' && in[2] == '.' && in[3] == '/') { // backtrack the output if /../ was found on the input in += 3; if (out > work) { do { out--; } while (out > work && *out != '/'); } } else { *out++ = *in++; } } else { *out++ = *in++; } } *out = 0; string result = work; delete [] work; return result; } list* DiskFile::FindFiles(string path, string wildcard) { list *matches = new list; string::size_type where; if ((where = wildcard.find_first_of('*')) != string::npos || (where = wildcard.find_first_of('?')) != string::npos) { string front = wildcard.substr(0, where); bool multiple = wildcard[where] == '*'; string back = wildcard.substr(where+1); DIR *dirp = opendir(path.c_str()); if (dirp != 0) { struct dirent *d; while ((d = readdir(dirp)) != 0) { string name = d->d_name; if (name == "." || name == "..") continue; if (multiple) { if (name.size() >= wildcard.size() && name.substr(0, where) == front && name.substr(name.size()-back.size()) == back) { matches->push_back(path + name); } } else { if (name.size() == wildcard.size()) { string::const_iterator pw = wildcard.begin(); string::const_iterator pn = name.begin(); while (pw != wildcard.end()) { if (*pw != '?' && *pw != *pn) break; ++pw; ++pn; } if (pw == wildcard.end()) { matches->push_back(path + name); } } } } closedir(dirp); } } else { struct stat st; string fn = path + wildcard; if (stat(fn.c_str(), &st) == 0) { matches->push_back(path + wildcard); } } return matches; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// #endif bool DiskFile::Open(void) { string _filename = filename; return Open(_filename); } bool DiskFile::Open(string _filename) { return Open(_filename, GetFileSize(_filename)); } // Delete the file bool DiskFile::Delete(void) { #ifdef WIN32 assert(hFile == INVALID_HANDLE_VALUE); #else assert(file == 0); #endif if (filename.size() > 0 && 0 == unlink(filename.c_str())) { return true; } else { cerr << "Cannot delete " << filename << endl; return false; } } //string DiskFile::GetPathFromFilename(string filename) //{ // string::size_type where; // // if (string::npos != (where = filename.find_last_of('/')) || // string::npos != (where = filename.find_last_of('\\'))) // { // return filename.substr(0, where+1); // } // else // { // return "." PATHSEP; // } //} void DiskFile::SplitFilename(string filename, string &path, string &name) { string::size_type where; if (string::npos != (where = filename.find_last_of('/')) || string::npos != (where = filename.find_last_of('\\'))) { path = filename.substr(0, where+1); name = filename.substr(where+1); } else { path = "." PATHSEP; name = filename; } } bool DiskFile::FileExists(string filename) { struct stat st; return ((0 == stat(filename.c_str(), &st)) && (0 != (st.st_mode & S_IFREG))); } u64 DiskFile::GetFileSize(string filename) { struct stat st; if ((0 == stat(filename.c_str(), &st)) && (0 != (st.st_mode & S_IFREG))) { return st.st_size; } else { return 0; } } // Take a filename from a PAR2 file and replace any characters // which would be illegal for a file on disk string DiskFile::TranslateFilename(string filename) { string result; string::iterator p = filename.begin(); while (p != filename.end()) { unsigned char ch = *p; bool ok = true; #ifdef WIN32 if (ch < 32) { ok = false; } else { switch (ch) { case '"': case '*': case '/': case ':': case '<': case '>': case '?': case '\\': case '|': ok = false; } } #else if (ch < 32) { ok = false; } else { switch (ch) { case '/': ok = false; } } #endif if (ok) { result += ch; } else { // convert problem characters to hex result += ((ch >> 4) < 10) ? (ch >> 4) + '0' : (ch >> 4) + 'A'-10; result += ((ch & 0xf) < 10) ? (ch & 0xf) + '0' : (ch & 0xf) + 'A'-10; } ++p; } return result; } bool DiskFile::Rename(void) { char newname[_MAX_PATH+1]; u32 index = 0; struct stat st; do { int length = snprintf(newname, _MAX_PATH, "%s.%d", filename.c_str(), ++index); if (length < 0 || length >= _MAX_PATH) { cerr << filename << " cannot be renamed." << endl; return false; } newname[length] = 0; } while (stat(newname, &st) == 0); return Rename(newname); } bool DiskFile::Rename(string _filename) { #ifdef WIN32 assert(hFile == INVALID_HANDLE_VALUE); #else assert(file == 0); #endif if (::rename(filename.c_str(), _filename.c_str()) == 0) { filename = _filename; return true; } else { cerr << filename << " cannot be renamed to " << _filename << endl; return false; } } #ifdef WIN32 string DiskFile::ErrorMessage(DWORD error) { string result; LPVOID lpMsgBuf; if (::FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&lpMsgBuf, 0, NULL)) { result = (char*)lpMsgBuf; LocalFree(lpMsgBuf); } else { char message[40]; _snprintf(message, sizeof(message), "Unknown error code (%d)", error); result = message; } return result; } #endif DiskFileMap::DiskFileMap(void) { } DiskFileMap::~DiskFileMap(void) { map::iterator fi = diskfilemap.begin(); while (fi != diskfilemap.end()) { delete (*fi).second; ++fi; } } bool DiskFileMap::Insert(DiskFile *diskfile) { string filename = diskfile->FileName(); assert(filename.length() != 0); pair::const_iterator,bool> location = diskfilemap.insert(pair(filename, diskfile)); return location.second; } void DiskFileMap::Remove(DiskFile *diskfile) { string filename = diskfile->FileName(); assert(filename.length() != 0); diskfilemap.erase(filename); } DiskFile* DiskFileMap::Find(string filename) const { assert(filename.length() != 0); map::const_iterator f = diskfilemap.find(filename); return (f != diskfilemap.end()) ? f->second : 0; } nzbget-16.4/lib/par2/galois.h0000644000175000017500000002140612630544544015624 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #ifndef __GALOIS_H__ #define __GALOIS_H__ template class GaloisTable; template class Galois; template class GaloisLongMultiplyTable; // This source file defines the Galois object for carrying out // arithmetic in GF(2^16) using the generator 0x1100B. // Also defined are the GaloisTable object (which contains log and // anti log tables for use in multiplication and division), and // the GaloisLongMultiplyTable object (which contains tables for // carrying out multiplation of 16-bit galois numbers 8 bits at a time). template class GaloisTable { public: typedef valuetype ValueType; GaloisTable(void); enum { Bits = bits, Count = 1< class Galois { public: typedef valuetype ValueType; // Basic constructors Galois(void) {}; Galois(ValueType v); // Copy and assignment Galois(const Galois &right) {value = right.value;} Galois& operator = (const Galois &right) { value = right.value; return *this;} // Addition Galois operator + (const Galois &right) const { return (value ^ right.value); } Galois& operator += (const Galois &right) { value ^= right.value; return *this;} // Subtraction Galois operator - (const Galois &right) const { return (value ^ right.value); } Galois& operator -= (const Galois &right) { value ^= right.value; return *this;} // Multiplication Galois operator * (const Galois &right) const; Galois& operator *= (const Galois &right); // Division Galois operator / (const Galois &right) const; Galois& operator /= (const Galois &right); // Power Galois pow(unsigned int right) const; Galois operator ^ (unsigned int right) const; Galois& operator ^= (unsigned int right); // Cast to value and value access operator ValueType(void) const {return value;} ValueType Value(void) const {return value;} // Direct log and antilog ValueType Log(void) const; ValueType ALog(void) const; enum { Bits = GaloisTable::Bits, Count = GaloisTable::Count, Limit = GaloisTable::Limit, }; protected: ValueType value; static GaloisTable table; }; #ifdef LONGMULTIPLY template class GaloisLongMultiplyTable { public: GaloisLongMultiplyTable(void); typedef g G; enum { Bytes = ((G::Bits + 7) >> 3), Count = ((Bytes * (Bytes+1)) / 2), }; G tables[Count * 256 * 256]; }; #endif // Construct the log and antilog tables from the generator template inline GaloisTable::GaloisTable(void) { u32 b = 1; for (u32 l=0; l GaloisTable Galois::table; template inline Galois::Galois(typename Galois::ValueType v) { value = v; } template inline Galois Galois::operator * (const Galois &right) const { if (value == 0 || right.value == 0) return 0; unsigned int sum = table.log[value] + table.log[right.value]; if (sum >= Limit) { return table.antilog[sum-Limit]; } else { return table.antilog[sum]; } } template inline Galois& Galois::operator *= (const Galois &right) { if (value == 0 || right.value == 0) { value = 0; } else { unsigned int sum = table.log[value] + table.log[right.value]; if (sum >= Limit) { value = table.antilog[sum-Limit]; } else { value = table.antilog[sum]; } } return *this; } template inline Galois Galois::operator / (const Galois &right) const { if (value == 0) return 0; assert(right.value != 0); if (right.value == 0) {return 0;} // Division by 0! int sum = table.log[value] - table.log[right.value]; if (sum < 0) { return table.antilog[sum+Limit]; } else { return table.antilog[sum]; } } template inline Galois& Galois::operator /= (const Galois &right) { if (value == 0) return *this; assert(right.value != 0); if (right.value == 0) {return *this;} // Division by 0! int sum = table.log[value] - table.log[right.value]; if (sum < 0) { value = table.antilog[sum+Limit]; } else { value = table.antilog[sum]; } return *this; } template inline Galois Galois::pow(unsigned int right) const { if (right == 0) return 1; if (value == 0) return 0; unsigned int sum = table.log[value] * right; sum = (sum >> Bits) + (sum & Limit); if (sum >= Limit) { return table.antilog[sum-Limit]; } else { return table.antilog[sum]; } } template inline Galois Galois::operator ^ (unsigned int right) const { if (right == 0) return 1; if (value == 0) return 0; unsigned int sum = table.log[value] * right; sum = (sum >> Bits) + (sum & Limit); if (sum >= Limit) { return table.antilog[sum-Limit]; } else { return table.antilog[sum]; } } template inline Galois& Galois::operator ^= (unsigned int right) { if (right == 1) {value = 1; return *this;} if (value == 0) return *this; unsigned int sum = table.log[value] * right; sum = (sum >> Bits) + (sum & Limit); if (sum >= Limit) { value = table.antilog[sum-Limit]; } else { value = table.antilog[sum]; } return *this; } template inline valuetype Galois::Log(void) const { return table.log[value]; } template inline valuetype Galois::ALog(void) const { return table.antilog[value]; } #ifdef LONGMULTIPLY template inline GaloisLongMultiplyTable::GaloisLongMultiplyTable(void) { G *table = tables; for (unsigned int i=0; i Galois8; typedef Galois<16,0x1100B,u16> Galois16; #endif // __GALOIS_H__ nzbget-16.4/lib/par2/criticalpacket.cpp0000644000175000017500000000314312630544544017661 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #include "par2cmdline.h" #ifdef _MSC_VER #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif #endif bool CriticalPacket::WritePacket(DiskFile &diskfile, u64 fileoffset) const { assert(packetdata != 0 && packetlength != 0); return diskfile.Write(fileoffset, packetdata, packetlength); } void CriticalPacket::FinishPacket(const MD5Hash &setid) { assert(packetdata != 0 && packetlength >= sizeof(PACKET_HEADER)); PACKET_HEADER *header = (PACKET_HEADER*)packetdata; header->setid = setid; MD5Context packetcontext; packetcontext.Update(&header->setid, packetlength - offsetof(PACKET_HEADER, setid)); packetcontext.Final(header->hash); } nzbget-16.4/lib/par2/par2repairer.h0000644000175000017500000002265212630544544016750 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #ifndef __PAR2REPAIRER_H__ #define __PAR2REPAIRER_H__ #include "parheaders.h" class Par2Repairer { public: Par2Repairer(void); ~Par2Repairer(void); Result PreProcess(const CommandLine &commandline); Result Process(const CommandLine &commandline, bool dorepair); protected: // Steps in verifying and repairing files: // Load packets from the specified file bool LoadPacketsFromFile(string filename); // Finish loading a recovery packet bool LoadRecoveryPacket(DiskFile *diskfile, u64 offset, PACKET_HEADER &header); // Finish loading a file description packet bool LoadDescriptionPacket(DiskFile *diskfile, u64 offset, PACKET_HEADER &header); // Finish loading a file verification packet bool LoadVerificationPacket(DiskFile *diskfile, u64 offset, PACKET_HEADER &header); // Finish loading the main packet bool LoadMainPacket(DiskFile *diskfile, u64 offset, PACKET_HEADER &header); // Finish loading the creator packet bool LoadCreatorPacket(DiskFile *diskfile, u64 offset, PACKET_HEADER &header); // Load packets from other PAR2 files with names based on the original PAR2 file bool LoadPacketsFromOtherFiles(string filename); // Load packets from any other PAR2 files whose names are given on the command line bool LoadPacketsFromExtraFiles(const list &extrafiles); // Check that the packets are consistent and discard any that are not bool CheckPacketConsistency(void); // Use the information in the main packet to get the source files // into the correct order and determine their filenames bool CreateSourceFileList(void); // Determine the total number of DataBlocks for the recoverable source files // The allocate the DataBlocks and assign them to each source file bool AllocateSourceBlocks(void); // Create a verification hash table for all files for which we have not // found a complete version of the file and for which we have // a verification packet bool PrepareVerificationHashTable(void); // Compute the table for the sliding CRC computation bool ComputeWindowTable(void); // Attempt to verify all of the source files bool VerifySourceFiles(void); // Scan any extra files specified on the command line bool VerifyExtraFiles(const list &extrafiles); // Attempt to match the data in the DiskFile with the source file bool VerifyDataFile(DiskFile *diskfile, Par2RepairerSourceFile *sourcefile); // Perform a sliding window scan of the DiskFile looking for blocks of data that // might belong to any of the source files (for which a verification packet was // available). If a block of data might be from more than one source file, prefer // the one specified by the "sourcefile" parameter. If the first data block // found is for a different source file then "sourcefile" is changed accordingly. virtual bool ScanDataFile(DiskFile *diskfile, // [in] The file being scanned Par2RepairerSourceFile* &sourcefile, // [in/out] The source file matched MatchType &matchtype, // [out] The type of match MD5Hash &hashfull, // [out] The full hash of the file MD5Hash &hash16k, // [out] The hash of the first 16k u32 &count); // [out] The number of blocks found // Find out how much data we have found void UpdateVerificationResults(void); // Check the verification results and report the results bool CheckVerificationResults(void); // Rename any damaged or missnamed target files. bool RenameTargetFiles(void); // Work out which files are being repaired, create them, and allocate // target DataBlocks to them, and remember them for later verification. bool CreateTargetFiles(void); // Work out which data blocks are available, which need to be copied // directly to the output, and which need to be recreated, and compute // the appropriate Reed Solomon matrix. bool ComputeRSmatrix(void); // Allocate memory buffers for reading and writing data to disk. bool AllocateBuffers(size_t memorylimit); // Read source data, process it through the RS matrix and write it to disk. bool ProcessData(u64 blockoffset, size_t blocklength); // Verify that all of the reconstructed target files are now correct bool VerifyTargetFiles(void); // Delete all of the partly reconstructed files bool DeleteIncompleteTargetFiles(void); // Signals virtual void sig_filename(std::string filename) {} virtual void sig_progress(int progress) {} virtual void sig_headers(ParHeaders* headers) {} virtual void sig_done(std::string filename, int available, int total) {} // Repair started virtual void BeginRepair() {} // Repair ended virtual void EndRepair() {} // Repair chunk of data (returns "true" if repaired or "false" if default repair-routine should be used) virtual bool RepairData(u32 inputindex, size_t blocklength) { return false; } protected: ParHeaders* headers; // Headers bool alreadyloaded; // Already loaded ? CommandLine::NoiseLevel noiselevel; // OnScreen display string searchpath; // Where to find files on disk bool firstpacket; // Whether or not a valid packet has been found. MD5Hash setid; // The SetId extracted from the first packet. map recoverypacketmap; // One recovery packet for each exponent value. MainPacket *mainpacket; // One copy of the main packet. CreatorPacket *creatorpacket; // One copy of the creator packet. DiskFileMap diskFileMap; map sourcefilemap;// Map from FileId to SourceFile vector sourcefiles; // The source files vector verifylist; // Those source files that are being repaired u64 blocksize; // The block size. u64 chunksize; // How much of a block can be processed. u32 sourceblockcount; // The total number of blocks u32 availableblockcount; // How many undamaged blocks have been found u32 missingblockcount; // How many blocks are missing bool blocksallocated; // Whether or not the DataBlocks have been allocated vector sourceblocks; // The DataBlocks that will be read from disk vector targetblocks; // The DataBlocks that will be written to disk u32 windowtable[256]; // Table for sliding CRCs u32 windowmask; // Maks for sliding CRCs bool blockverifiable; // Whether and files can be verified at the block level VerificationHashTable verificationhashtable; // Hash table for block verification list unverifiablesourcefiles; // Files that are not block verifiable u32 completefilecount; // How many files are fully verified u32 renamedfilecount; // How many files are verified but have the wrong name u32 damagedfilecount; // How many files exist but are damaged u32 missingfilecount; // How many files are completely missing vector inputblocks; // Which DataBlocks will be read from disk vector copyblocks; // Which DataBlocks will copied back to disk vector outputblocks; // Which DataBlocks have to calculated using RS ReedSolomon rs; // The Reed Solomon matrix. void *inputbuffer; // Buffer for reading DataBlocks (chunksize) void *outputbuffer; // Buffer for writing DataBlocks (chunksize * missingblockcount) u64 progress; // How much data has been processed. u64 totaldata; // Total amount of data to be processed. u64 totalsize; // Total data size bool cancelled; // repair cancelled }; #endif // __PAR2REPAIRER_H__ nzbget-16.4/lib/par2/recoverypacket.cpp0000644000175000017500000001006612630544544017727 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #include "par2cmdline.h" #ifdef _MSC_VER #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif #endif RecoveryPacket::RecoveryPacket(void) { diskfile = NULL; offset = 0; packetcontext = NULL; } RecoveryPacket::~RecoveryPacket(void) { delete packetcontext; } // Create a recovery packet. // The packet header can be almost completely filled in using the supplied // information and computation of the hash of the packet started immediately. // The hash will be updated as new data is written to the packet. void RecoveryPacket::Create(DiskFile *_diskfile, u64 _offset, u64 _blocksize, u32 _exponent, const MD5Hash &_setid) { diskfile = _diskfile; offset = _offset; // Record everything we know in the packet. packet.header.magic = packet_magic; packet.header.length = sizeof(packet) + _blocksize; //packet.header.hash; // Not known yet. packet.header.setid = _setid; packet.header.type = recoveryblockpacket_type; packet.exponent = _exponent; // Start computation of the packet hash packetcontext = new MD5Context; packetcontext->Update(&packet.header.setid, sizeof(RECOVERYBLOCKPACKET)-offsetof(RECOVERYBLOCKPACKET, header.setid)); // Set the data block to immediatly follow the header on disk datablock.SetLocation(_diskfile, _offset + sizeof(packet)); datablock.SetLength(_blocksize); } // Write data from the buffer to the data block on disk bool RecoveryPacket::WriteData(u64 position, size_t size, const void *buffer) { // Update the packet hash packetcontext->Update(buffer, size); // Write the data to the data block size_t wrote; return datablock.WriteData(position, size, buffer, wrote); } // Write the header of the packet to disk bool RecoveryPacket::WriteHeader(void) { // Finish computing the packet hash packetcontext->Final(packet.header.hash); // Write the header to disk return diskfile->Write(offset, &packet, sizeof(packet)); } // Load the recovery packet from disk. // // The header of the packet will already have been read from disk. The only // thing that actually needs to be read is the exponent value. // The recovery data is not read from disk at this point. Its location // is recovered in the DataBlock object. bool RecoveryPacket::Load(DiskFile *_diskfile, u64 _offset, PACKET_HEADER &_header) { diskfile = _diskfile; offset = _offset; // Is the packet actually large enough if (_header.length <= sizeof(packet)) { return false; } // Save the fixed header packet.header = _header; // Set the data block to immediatly follow the header on disk datablock.SetLocation(diskfile, offset + sizeof(packet)); datablock.SetLength(packet.header.length - sizeof(packet)); // Read the rest of the packet header return diskfile->Read(offset + sizeof(packet.header), &packet.exponent, sizeof(packet)-sizeof(packet.header)); } nzbget-16.4/lib/par2/par2repairersourcefile.h0000644000175000017500000001044312630544544021024 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #ifndef __PAR2REPAIRERSOURCEFILE_H__ #define __PAR2REPAIRERSOURCEFILE_H__ enum MatchType { eNoMatch = 0, ePartialMatch, eFullMatch }; // The Par2RepairerSourceFile object is used during verification and repair // to record details about a particular source file and the data blocks // for that file. class Par2RepairerSourceFile { public: // Construct the object and set the description and verification packets Par2RepairerSourceFile(DescriptionPacket *descriptionpacket, VerificationPacket *verificationpacket); ~Par2RepairerSourceFile(void); // Get/Set the description packet DescriptionPacket* GetDescriptionPacket(void) const {return descriptionpacket;} void SetDescriptionPacket(DescriptionPacket *descriptionpacket); // Get/Set the verification packet VerificationPacket* GetVerificationPacket(void) const {return verificationpacket;} void SetVerificationPacket(VerificationPacket *verificationpacket); // Record the details as to which data blocks belong to this source // file and set the length of each allocated block correctly. void SetBlocks(u32 _blocknumber, u32 _blockcount, vector::iterator _sourceblocks, vector::iterator _targetblocks, u64 blocksize); // Determine the block count from the file size and block size. void SetBlockCount(u64 blocksize); // Set/Get which DiskFile will contain the final repaired version of the file void SetTargetFile(DiskFile *diskfile); DiskFile* GetTargetFile(void) const; // Set/Get whether or not the target file actually exists void SetTargetExists(bool exists); bool GetTargetExists(void) const; // Set/Get which DiskFile contains a full undamaged version of the source file void SetCompleteFile(DiskFile *diskfile); DiskFile* GetCompleteFile(void) const; // Compute/Get the filename for the final repaired version of the file void ComputeTargetFileName(string path); string TargetFileName(void) const; // Get the number of blocks that the file uses u32 BlockCount(void) const {return blockcount;} // Get the relative block number of the first block in the file u32 FirstBlockNumber(void) const {return firstblocknumber;} // Get the first source DataBlock for the file vector::iterator SourceBlocks(void) const {return sourceblocks;} // Get the first target DataBlock for the file vector::iterator TargetBlocks(void) const {return targetblocks;} protected: DescriptionPacket *descriptionpacket; // The file description packet VerificationPacket *verificationpacket; // The file verification packet u32 blockcount; // The number of DataBlocks in the file u32 firstblocknumber; // The block number of the first DataBlock vector::iterator sourceblocks; // The first source DataBlock vector::iterator targetblocks; // The first target DataBlock bool targetexists; // Whether the target file exists DiskFile *targetfile; // The final version of the file DiskFile *completefile; // A complete version of the file string targetfilename; // The filename of the target file }; #endif // __PAR2REPAIRERSOURCEFILE_H__ nzbget-16.4/lib/par2/par2cmdline.h0000644000175000017500000001520112630544544016542 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #ifndef __PARCMDLINE_H__ #define __PARCMDLINE_H__ #ifdef WIN32 // Windows includes #define WIN32_LEAN_AND_MEAN #include // System includes #include #include #include #include #include #include #include #include #include #define snprintf _snprintf #define stat _stat #define __LITTLE_ENDIAN 1234 #define __BIG_ENDIAN 4321 #define __PDP_ENDIAN 3412 #define __BYTE_ORDER __LITTLE_ENDIAN typedef unsigned char u8; typedef unsigned short u16; typedef unsigned long u32; typedef unsigned __int64 u64; #ifndef _SIZE_T_DEFINED # ifdef _WIN64 typedef unsigned __int64 size_t; # else typedef unsigned int size_t; # endif # define _SIZE_T_DEFINED #endif #else // WIN32 #ifdef HAVE_CONFIG_H #include "config.h" #ifdef HAVE_STDLIB_H # include #endif #ifdef HAVE_STDIO_H # include #endif #if HAVE_DIRENT_H # include # define NAMELEN(dirent) strlen((dirent)->d_name) #else # define dirent direct # define NAMELEN(dirent) (dirent)->d_namelen # if HAVE_SYS_NDIR_H # include # endif # if HAVE_SYS_DIR_H # include # endif # if HAVE_NDIR_H # include # endif #endif #if STDC_HEADERS # include #else # if !HAVE_STRCHR # define strchr index # define strrchr rindex # endif char *strchr(), *strrchr(); # if !HAVE_MEMCPY # define memcpy(d, s, n) bcopy((s), (d), (n)) # define memove(d, s, n) bcopy((s), (d), (n)) # endif #endif #if HAVE_MEMORY_H # include #endif #if !HAVE_STRICMP # if HAVE_STRCASECMP # define stricmp strcasecmp # endif #endif #if HAVE_INTTYPES_H # include #endif #if HAVE_STDINT_H # include typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; typedef uint64_t u64; #else typedef unsigned char u8; typedef unsigned short u16; typedef unsigned int u32; typedef unsigned long long u64; #endif #if HAVE_SYS_STAT_H # include #endif #if HAVE_SYS_TYPES_H # include #endif #if HAVE_UNISTD_H # include #endif #define _MAX_PATH 255 #if HAVE_ENDIAN_H # include # ifndef __LITTLE_ENDIAN # ifdef _LITTLE_ENDIAN # define __LITTLE_ENDIAN _LITTLE_ENDIAN # define __LITTLE_ENDIAN _LITTLE_ENDIAN # define __BIG_ENDIAN _BIG_ENDIAN # define __PDP_ENDIAN _PDP_ENDIAN # else # error does not define __LITTLE_ENDIAN etc. # endif # endif #else # define __LITTLE_ENDIAN 1234 # define __BIG_ENDIAN 4321 # define __PDP_ENDIAN 3412 # if WORDS_BIGENDIAN # define __BYTE_ORDER __BIG_ENDIAN # else # define __BYTE_ORDER __LITTLE_ENDIAN # endif #endif #else // HAVE_CONFIG_H #include #include #include #include #include #include #include #include #include #include #include #define _MAX_PATH 255 #define stricmp strcasecmp #define _stat stat typedef unsigned char u8; typedef unsigned short u16; typedef unsigned int u32; typedef unsigned long long u64; #endif #endif #ifdef WIN32 #define PATHSEP "\\" #define ALTPATHSEP "/" #else #define PATHSEP "/" #define ALTPATHSEP "\\" #endif // Return type of par2cmdline typedef enum Result { eSuccess = 0, eRepairPossible = 1, // Data files are damaged and there is // enough recovery data available to // repair them. eRepairNotPossible = 2, // Data files are damaged and there is // insufficient recovery data available // to be able to repair them. eInvalidCommandLineArguments = 3, // There was something wrong with the // command line arguments eInsufficientCriticalData = 4, // The PAR2 files did not contain sufficient // information about the data files to be able // to verify them. eRepairFailed = 5, // Repair completed but the data files // still appear to be damaged. eFileIOError = 6, // An error occured when accessing files eLogicError = 7, // In internal error occurred eMemoryError = 8, // Out of memory } Result; #define LONGMULTIPLY // STL includes #include #include #include #include #include #include #include #include #include using namespace std; #ifdef offsetof #undef offsetof #endif #define offsetof(TYPE, MEMBER) ((size_t) ((char*)(&((TYPE *)1)->MEMBER) - (char*)1)) #include "letype.h" // par2cmdline includes #include "galois.h" #include "crc.h" #include "md5.h" #include "par2fileformat.h" #include "commandline.h" #include "reedsolomon.h" #include "diskfile.h" #include "datablock.h" #include "criticalpacket.h" #include "par2creatorsourcefile.h" #include "mainpacket.h" #include "creatorpacket.h" #include "descriptionpacket.h" #include "verificationpacket.h" #include "recoverypacket.h" #include "par2repairersourcefile.h" #include "filechecksummer.h" #include "verificationhashtable.h" //#include "par2creator.h" #include "par2repairer.h" //#include "par1fileformat.h" //#include "par1repairersourcefile.h" //#include "par1repairer.h" // Heap checking #ifdef _MSC_VER #define _CRTDBG_MAP_ALLOC #include #define DEBUG_NEW new(_NORMAL_BLOCK, THIS_FILE, __LINE__) #endif #endif // __PARCMDLINE_H__ nzbget-16.4/lib/par2/md5.cpp0000644000175000017500000002353712630544544015375 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #include "par2cmdline.h" #ifdef _MSC_VER #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif #endif // Convert hash values to hex ostream& operator<<(ostream &result, const MD5Hash &h) { char buffer[33]; sprintf(buffer, "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", h.hash[15], h.hash[14], h.hash[13], h.hash[12], h.hash[11], h.hash[10], h.hash[9], h.hash[8], h.hash[7], h.hash[6], h.hash[5], h.hash[4], h.hash[3], h.hash[2], h.hash[1], h.hash[0]); return result << buffer; } string MD5Hash::print(void) const { char buffer[33]; sprintf(buffer, "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", hash[15], hash[14], hash[13], hash[12], hash[11], hash[10], hash[9], hash[8], hash[7], hash[6], hash[5], hash[4], hash[3], hash[2], hash[1], hash[0]); return buffer; } MD5State::MD5State(void) { Reset(); } // Initialise the 16 byte state void MD5State::Reset(void) { state[0] = 0x67452301; state[1] = 0xefcdab89; state[2] = 0x98badcfe; state[3] = 0x10325476; } // Update the state using 64 bytes of new data void MD5State::UpdateState(const u32 (&block)[16]) { // Primitive operations #define F1(x,y,z) ( ((x) & (y)) | ((~(x)) & (z)) ) #define F2(x,y,z) ( ((x) & (z)) | ((~(z)) & (y)) ) #define F3(x,y,z) ( (x) ^ (y) ^ (z) ) #define F4(x,y,z) ( (y) ^ ( (x) | ~(z) ) ) // The first version of ROL does not work on an Alpha CPU! //#define ROL(x,y) ( ((x) << (y)) | (((unsigned int)x) >> (32-y)) ) #define ROL(x,y) ( ((x) << (y)) | (((x) >> (32-y)) & ((1< 0) { size_t size = min(buffersize-used, length); Update(wordblock, size); length -= size; } // Update as many whole buffers as possible while (length >= buffersize) { Update(wordblock, buffersize); length -= buffersize; } // Update any remainder if (length > 0) { Update(wordblock, length); } } // Update using data from a buffer void MD5Context::Update(const void *buffer, size_t length) { const unsigned char *current = (const unsigned char *)buffer; // Update the total amount of data processed. bytes += length; // Process any whole blocks while (used + length >= buffersize) { size_t have = buffersize - used; memcpy(&block[used], current, have); current += have; length -= have; u32 wordblock[16]; for (int i=0; i<16; i++) { // Convert source data from little endian format to internal format if different wordblock[i] = ( ((u32)block[i*4+3]) << 24 ) | ( ((u32)block[i*4+2]) << 16 ) | ( ((u32)block[i*4+1]) << 8 ) | ( ((u32)block[i*4+0]) << 0 ); } MD5State::UpdateState(wordblock); used = 0; } // Store any remainder if (length > 0) { memcpy(&block[used], current, length); used += length; } } // Finalise the computation and extract the Hash value void MD5Context::Final(MD5Hash &output) { // Temporary work buffer u8 buffer[64]; // How many bits were processed u64 bits = bytes << 3; // Pad as much as needed so that there are exactly 8 bytes needed to fill the buffer size_t padding; if (used >= buffersize-8) { padding = buffersize-8 + buffersize - used; } else { padding = buffersize-8 - used; } memset(buffer, 0, padding); buffer[0] = 0x80; Update(buffer, padding); // Pad with an additional 8 bytes containing the bit count in little endian format buffer[7] = (unsigned char)((bits >> 56) & 0xFF); buffer[6] = (unsigned char)((bits >> 48) & 0xFF); buffer[5] = (unsigned char)((bits >> 40) & 0xFF); buffer[4] = (unsigned char)((bits >> 32) & 0xFF); buffer[3] = (unsigned char)((bits >> 24) & 0xFF); buffer[2] = (unsigned char)((bits >> 16) & 0xFF); buffer[1] = (unsigned char)((bits >> 8) & 0xFF); buffer[0] = (unsigned char)((bits >> 0) & 0xFF); Update(buffer, 8); for (int i = 0; i < 4; i++) { // Read out the state and convert it from internal format to little endian format output.hash[4*i+3] = (u8)((MD5State::state[i] >> 24) & 0xFF); output.hash[4*i+2] = (u8)((MD5State::state[i] >> 16) & 0xFF); output.hash[4*i+1] = (u8)((MD5State::state[i] >> 8) & 0xFF); output.hash[4*i+0] = (u8)((MD5State::state[i] >> 0) & 0xFF); } } // Return the Hash value MD5Hash MD5Context::Hash(void) const { MD5Hash output; for (unsigned int i = 0; i < 4; i++) { // Read out the state and convert it from internal format to little endian format output.hash[4*i+3] = (unsigned char)((MD5State::state[i] >> 24) & 0xFF); output.hash[4*i+2] = (unsigned char)((MD5State::state[i] >> 16) & 0xFF); output.hash[4*i+1] = (unsigned char)((MD5State::state[i] >> 8) & 0xFF); output.hash[4*i+0] = (unsigned char)((MD5State::state[i] >> 0) & 0xFF); } return output; } ostream& operator<<(ostream &result, const MD5Context &c) { char buffer[50]; sprintf(buffer, "%08X%08X%08X%08X:%08X%08X", c.state[3],c.state[2],c.state[1],c.state[0], (u32)((c.bytes >> 32) & 0xffffffff), (u32)(c.bytes & 0xffffffff)); return result << buffer; } string MD5Context::print(void) const { char buffer[50]; sprintf(buffer, "%08X%08X%08X%08X:%08X%08X", state[3],state[2],state[1],state[0], (u32)((bytes >> 32) & 0xffffffff), (u32)(bytes & 0xffffffff)); return buffer; } nzbget-16.4/lib/par2/crc.h0000644000175000017500000000655512630544544015125 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #ifndef __CRC_H__ #define __CRC_H__ // These global functions are used to compute the CCITT CRC32 checksum of // blocks of data. // The CRC for a block of data may be computed piecemeal be repeatedly // calling CRCUpdateChar, and CRCUpdateBlock. // Given the CRC for a block of data in a buffer, CRCSlideChar may be used // to quickly compute the CRC for the block of data in the buffer that is the // same size but offset one character later in the buffer. // Construct the CRC32 lookup table from the specified polynomial void GenerateCRC32Table(u32 polynomial, u32 (&table)[256]); // A CRC32 lookup table struct crc32table { crc32table(u32 polynomial) { GenerateCRC32Table(polynomial, table); } u32 table[256]; }; // The one and only CCITT CRC32 lookup table extern crc32table ccitttable; // Update the CRC using one character inline u32 CRCUpdateChar(u32 crc, u8 ch) { return ((crc >> 8) & 0x00ffffffL) ^ ccitttable.table[(u8)crc ^ ch]; } // Update the CRC using a block of characters in a buffer inline u32 CRCUpdateBlock(u32 crc, size_t length, const void *buffer) { const unsigned char *current = (const unsigned char *)buffer; while (length-- > 0) { crc = ((crc >> 8) & 0x00ffffffL) ^ ccitttable.table[(u8)crc ^ (*current++)]; } return crc; } // Update the CRC using a block of 0s. inline u32 CRCUpdateBlock(u32 crc, size_t length) { while (length-- > 0) { crc = ((crc >> 8) & 0x00ffffffL) ^ ccitttable.table[(u8)crc]; } return crc; } // Construct a CRC32 lookup table for windowing void GenerateWindowTable(u64 window, u32 (&windowtable)[256]); // Construct the mask value to apply to the CRC when windowing u32 ComputeWindowMask(u64 window); // Slide the CRC along a buffer by one character (removing the old and adding the new). // The new character is added using the main CCITT CRC32 table, and the old character // is removed using the windowtable. inline u32 CRCSlideChar(u32 crc, u8 chNew, u8 chOld, const u32 (&windowtable)[256]) { return ((crc >> 8) & 0x00ffffffL) ^ ccitttable.table[(u8)crc ^ chNew] ^ windowtable[chOld]; } /* char *buffer; u64 window; //... u32 windowtable[256]; GenerateWindowTable(window, windowtable); u32 windowmask = ComputeWindowMask(window); u32 crc = ~0 ^ CRCUpdateBlock(~0, window, buffer); crc = windowmask ^ CRCSlideChar(windowmask ^ crc, buffer[window], buffer[0], windowtable); assert(crc == ~0 ^ CRCUpdateBlock(~0, window, buffer+1)); */ #endif // __CRC_H__ nzbget-16.4/lib/par2/datablock.h0000644000175000017500000000652412630544544016276 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #ifndef __DATABLOCK_H__ #define __DATABLOCK_H__ class DiskFile; // A Data Block is a block of data of a specific length at a specific // offset in a specific file. // It may be either a block of data in a source file from which recovery // data is being computed, a block of recovery data in a recovery file, or // a block in a target file that is being reconstructed. class DataBlock { public: DataBlock(void); ~DataBlock(void); public: // Set the length of the block void SetLength(u64 length); // Set the location of the block void SetLocation(DiskFile *diskfile, u64 offset); void ClearLocation(void); public: // Check to see if the location of the block has been set bool IsSet(void) const; // Which disk file is this data block in DiskFile* GetDiskFile(void) const; // What offset is the block located at u64 GetOffset(void) const; // What is the length of this block u64 GetLength(void) const; public: // Open the disk file if it is not already open (so that it can be read) bool Open(void); // Read some of the data from disk into memory. bool ReadData(u64 position, size_t size, void *buffer); // Write some of the data from memory to disk bool WriteData(u64 position, size_t size, const void *buffer, size_t &wrote); protected: DiskFile *diskfile; // Which disk file is the block associated with u64 offset; // What is the file offset u64 length; // How large is the block }; // Construct the data block inline DataBlock::DataBlock(void) { diskfile = 0; offset = 0; length = 0; } // Destroy the data block inline DataBlock::~DataBlock(void) { } // Set the length of the block inline void DataBlock::SetLength(u64 _length) { length = _length; } // Set the location of the block inline void DataBlock::SetLocation(DiskFile *_diskfile, u64 _offset) { diskfile = _diskfile; offset = _offset; } // Clear the location of the block inline void DataBlock::ClearLocation(void) { diskfile = 0; offset = 0; } // Check to see of the location is known inline bool DataBlock::IsSet(void) const { return (diskfile != 0); } // Which disk file is this data block in inline DiskFile* DataBlock::GetDiskFile(void) const { return diskfile; } // What offset is the block located at inline u64 DataBlock::GetOffset(void) const { return offset; } // What is the length of this block inline u64 DataBlock::GetLength(void) const { return length; } #endif // __DATABLOCK_H__ nzbget-16.4/lib/par2/parheaders.h0000644000175000017500000000230612630544544016462 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #include #include class ParHeaders { public: std::string setid; int packets; int recovery_block; int recoverable_files; int other_files; long long block_size; int data_blocks; long long data_size; long long chunk_size; ParHeaders(void); }; nzbget-16.4/lib/par2/par2repairer.cpp0000644000175000017500000021051512630544544017300 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #include "par2cmdline.h" #ifdef _MSC_VER #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif #endif Par2Repairer::Par2Repairer(void) { firstpacket = true; mainpacket = 0; creatorpacket = 0; blocksize = 0; sourceblockcount = 0; blocksallocated = false; availableblockcount = 0; missingblockcount = 0; completefilecount = 0; renamedfilecount = 0; damagedfilecount = 0; missingfilecount = 0; inputbuffer = 0; outputbuffer = 0; noiselevel = CommandLine::nlNormal; headers = new ParHeaders; alreadyloaded = false; cancelled = false; } Par2Repairer::~Par2Repairer(void) { delete [] (u8*)inputbuffer; delete [] (u8*)outputbuffer; map::iterator rp = recoverypacketmap.begin(); while (rp != recoverypacketmap.end()) { delete (*rp).second; ++rp; } map::iterator sf = sourcefilemap.begin(); while (sf != sourcefilemap.end()) { Par2RepairerSourceFile *sourcefile = (*sf).second; delete sourcefile; ++sf; } delete mainpacket; delete creatorpacket; delete headers; } Result Par2Repairer::PreProcess(const CommandLine &commandline) { //sig_filename.emit("coucou cmoa"); // What noiselevel are we using noiselevel = commandline.GetNoiseLevel(); // Get filesnames from the command line string par2filename = commandline.GetParFilename(); const list &extrafiles = commandline.GetExtraFiles(); // Determine the searchpath from the location of the main PAR2 file string name; DiskFile::SplitFilename(par2filename, searchpath, name); // Load packets from the main PAR2 file if (!LoadPacketsFromFile(searchpath + name)) return eLogicError; // Load packets from other PAR2 files with names based on the original PAR2 file if (!LoadPacketsFromOtherFiles(par2filename)) return eLogicError; // Load packets from any other PAR2 files whose names are given on the command line if (!LoadPacketsFromExtraFiles(extrafiles)) return eLogicError; if (noiselevel > CommandLine::nlQuiet) cout << endl; // Check that the packets are consistent and discard any that are not if (!CheckPacketConsistency()) return eInsufficientCriticalData; // Use the information in the main packet to get the source files // into the correct order and determine their filenames if (!CreateSourceFileList()) return eLogicError; // Determine the total number of DataBlocks for the recoverable source files // The allocate the DataBlocks and assign them to each source file if (!AllocateSourceBlocks()) return eLogicError; headers->setid = setid.print(); headers->block_size = blocksize; headers->chunk_size = chunksize; headers->data_blocks = sourceblockcount; headers->data_size = totalsize; headers->recoverable_files = mainpacket->RecoverableFileCount(); headers->other_files = mainpacket->TotalFileCount() - mainpacket->RecoverableFileCount(); sig_headers(headers); /* cout << "Values:" << endl << "setid: " << setid << endl << "blocksize: " << blocksize << endl << "chunksize: " << chunksize << endl << "sourceblockcount: " << sourceblockcount << endl << "availableblockcount: " << availableblockcount << endl << "missingblockcount: " << missingblockcount << endl << "completefilecount: " << completefilecount << endl << "renamedfilecount: " << renamedfilecount << endl << "damagedfilecount: " << damagedfilecount << endl << "missingfilecount: " << missingfilecount << endl << "progress: " << progress << endl << "totaldata: " << totaldata << endl << "totalsize: " << totalsize << endl << "RecoverableFileCount: " << mainpacket->RecoverableFileCount() << endl << "TotalFileCount: " << mainpacket->TotalFileCount() << endl; */ return eSuccess; } Result Par2Repairer::Process(const CommandLine &commandline, bool dorepair) { //sig_filename.emit("coucou cmoa"); // What noiselevel are we using noiselevel = commandline.GetNoiseLevel(); // Get filesnames from the command line string par2filename = commandline.GetParFilename(); const list &extrafiles = commandline.GetExtraFiles(); // Determine the searchpath from the location of the main PAR2 file string name; DiskFile::SplitFilename(par2filename, searchpath, name); // Load packets from the main PAR2 file /* if (!LoadPacketsFromFile(searchpath + name)) return eLogicError; // Load packets from other PAR2 files with names based on the original PAR2 file if (!LoadPacketsFromOtherFiles(par2filename)) return eLogicError; // Load packets from any other PAR2 files whose names are given on the command line if (!LoadPacketsFromExtraFiles(extrafiles)) return eLogicError; if (noiselevel > CommandLine::nlQuiet) cout << endl; // Check that the packets are consistent and discard any that are not if (!CheckPacketConsistency()) return eInsufficientCriticalData; // Use the information in the main packet to get the source files // into the correct order and determine their filenames if (!CreateSourceFileList()) return eLogicError; // Determine the total number of DataBlocks for the recoverable source files // The allocate the DataBlocks and assign them to each source file if (!AllocateSourceBlocks()) return eLogicError; */ // ---------- /Header ------------- // Create a verification hash table for all files for which we have not // found a complete version of the file and for which we have // a verification packet if (!alreadyloaded) { if (!PrepareVerificationHashTable()) return eLogicError; // Compute the table for the sliding CRC computation if (!ComputeWindowTable()) return eLogicError; if (noiselevel > CommandLine::nlQuiet) cout << endl << "Verifying source files:" << endl << endl; // Attempt to verify all of the source files if (!VerifySourceFiles()) return eFileIOError; if (completefilecountRecoverableFileCount()) { if (noiselevel > CommandLine::nlQuiet) cout << endl << "Scanning extra files:" << endl << endl; // Scan any extra files specified on the command line if (!VerifyExtraFiles(extrafiles)) return eLogicError; } // Find out how much data we have found UpdateVerificationResults(); alreadyloaded = true; } if (noiselevel > CommandLine::nlSilent) cout << endl; // Check the verification results and report the results if (!CheckVerificationResults()) return eRepairNotPossible; // Are any of the files incomplete if (completefilecountRecoverableFileCount()) { // Do we want to carry out a repair if (dorepair) { if (noiselevel > CommandLine::nlSilent) cout << endl; // Rename any damaged or missnamed target files. if (!RenameTargetFiles()) return eFileIOError; // Are we still missing any files if (completefilecountRecoverableFileCount()) { // Work out which files are being repaired, create them, and allocate // target DataBlocks to them, and remember them for later verification. if (!CreateTargetFiles()) return eFileIOError; // Work out which data blocks are available, which need to be copied // directly to the output, and which need to be recreated, and compute // the appropriate Reed Solomon matrix. if (!ComputeRSmatrix()) { // Delete all of the partly reconstructed files DeleteIncompleteTargetFiles(); return eFileIOError; } if (noiselevel > CommandLine::nlSilent) cout << endl; // Allocate memory buffers for reading and writing data to disk. if (!AllocateBuffers(commandline.GetMemoryLimit())) { // Delete all of the partly reconstructed files DeleteIncompleteTargetFiles(); return eMemoryError; } // Set the total amount of data to be processed. progress = 0; totaldata = blocksize * sourceblockcount * (missingblockcount > 0 ? missingblockcount : 1); BeginRepair(); // Start at an offset of 0 within a block. u64 blockoffset = 0; while (blockoffset < blocksize) // Continue until the end of the block. { // Work out how much data to process this time. size_t blocklength = (size_t)min((u64)chunksize, blocksize-blockoffset); // Read source data, process it through the RS matrix and write it to disk. if (!ProcessData(blockoffset, blocklength)) { // Delete all of the partly reconstructed files DeleteIncompleteTargetFiles(); EndRepair(); return eFileIOError; } // Advance to the need offset within each block blockoffset += blocklength; } EndRepair(); if (noiselevel > CommandLine::nlSilent) cout << endl << "Verifying repaired files:" << endl << endl; // Verify that all of the reconstructed target files are now correct if (!VerifyTargetFiles()) { // Delete all of the partly reconstructed files DeleteIncompleteTargetFiles(); return eFileIOError; } } // Are all of the target files now complete? if (completefilecountRecoverableFileCount()) { cerr << "Repair Failed." << endl; return eRepairFailed; } else { if (noiselevel > CommandLine::nlSilent) cout << endl << "Repair complete." << endl; } } else { return eRepairPossible; } } return eSuccess; } // Load the packets from the specified file bool Par2Repairer::LoadPacketsFromFile(string filename) { // Skip the file if it has already been processed if (diskFileMap.Find(filename) != 0) { return true; } DiskFile *diskfile = new DiskFile; // Open the file if (!diskfile->Open(filename)) { // If we could not open the file, ignore the error and // proceed to the next file delete diskfile; return true; } if (noiselevel > CommandLine::nlSilent) { string path; string name; DiskFile::SplitFilename(filename, path, name); cout << "Loading \"" << name << "\"." << endl; sig_filename(name); } // How many useable packets have we found u32 packets = 0; // How many recovery packets were there u32 recoverypackets = 0; // How big is the file u64 filesize = diskfile->FileSize(); if (filesize > 0) { // Allocate a buffer to read data into // The buffer should be large enough to hold a whole // critical packet (i.e. file verification, file description, main, // and creator), but not necessarily a whole recovery packet. size_t buffersize = (size_t)min((u64)1048576, filesize); u8 *buffer = new u8[buffersize]; // Progress indicator u64 progress = 0; // Start at the beginning of the file u64 offset = 0; // Continue as long as there is at least enough for the packet header while (offset + sizeof(PACKET_HEADER) <= filesize) { if (noiselevel > CommandLine::nlQuiet) { // Update a progress indicator u32 oldfraction = (u32)(1000 * progress / filesize); u32 newfraction = (u32)(1000 * offset / filesize); if (oldfraction != newfraction) { cout << "Loading: " << newfraction/10 << '.' << newfraction%10 << "%\r" << flush; progress = offset; sig_progress(newfraction); if (cancelled) { break; } } } // Attempt to read the next packet header PACKET_HEADER header; if (!diskfile->Read(offset, &header, sizeof(header))) break; // Does this look like it might be a packet if (packet_magic != header.magic) { offset++; // Is there still enough for at least a whole packet header while (offset + sizeof(PACKET_HEADER) <= filesize) { // How much can we read into the buffer size_t want = (size_t)min((u64)buffersize, filesize-offset); // Fill the buffer if (!diskfile->Read(offset, buffer, want)) { offset = filesize; break; } // Scan the buffer for the magic value u8 *current = buffer; u8 *limit = &buffer[want-sizeof(PACKET_HEADER)]; while (current <= limit && packet_magic != ((PACKET_HEADER*)current)->magic) { current++; } // What file offset did we reach offset += current-buffer; // Did we find the magic if (current <= limit) { memcpy(&header, current, sizeof(header)); break; } } // Did we reach the end of the file if (offset + sizeof(PACKET_HEADER) > filesize) { break; } } // We have found the magic // Check the packet length if (sizeof(PACKET_HEADER) > header.length || // packet length is too small 0 != (header.length & 3) || // packet length is not a multiple of 4 filesize < offset + header.length) // packet would extend beyond the end of the file { offset++; continue; } // Compute the MD5 Hash of the packet MD5Context context; context.Update(&header.setid, sizeof(header)-offsetof(PACKET_HEADER, setid)); // How much more do I need to read to get the whole packet u64 current = offset+sizeof(PACKET_HEADER); u64 limit = offset+header.length; while (current < limit) { size_t want = (size_t)min((u64)buffersize, limit-current); if (!diskfile->Read(current, buffer, want)) break; context.Update(buffer, want); current += want; } // Did the whole packet get processed if (currentClose(); // Did we actually find any interesting packets if (packets > 0) { if (noiselevel > CommandLine::nlQuiet) { cout << "Loaded " << packets << " new packets"; if (recoverypackets > 0) cout << " including " << recoverypackets << " recovery blocks"; cout << endl; } // Remember that the file was processed bool success = diskFileMap.Insert(diskfile); assert(success); (void)success; } else { if (noiselevel > CommandLine::nlQuiet) cout << "No new packets found" << endl; delete diskfile; } if (cancelled) { return false; } return true; } // Finish loading a recovery packet bool Par2Repairer::LoadRecoveryPacket(DiskFile *diskfile, u64 offset, PACKET_HEADER &header) { RecoveryPacket *packet = new RecoveryPacket; // Load the packet from disk if (!packet->Load(diskfile, offset, header)) { delete packet; return false; } // What is the exponent value of this recovery packet u32 exponent = packet->Exponent(); // Try to insert the new packet into the recovery packet map pair::const_iterator, bool> location = recoverypacketmap.insert(pair(exponent, packet)); // Did the insert fail if (!location.second) { // The packet must be a duplicate of one we already have delete packet; return false; } return true; } // Finish loading a file description packet bool Par2Repairer::LoadDescriptionPacket(DiskFile *diskfile, u64 offset, PACKET_HEADER &header) { DescriptionPacket *packet = new DescriptionPacket; // Load the packet from disk if (!packet->Load(diskfile, offset, header)) { delete packet; return false; } // What is the fileid const MD5Hash &fileid = packet->FileId(); // Look up the fileid in the source file map for an existing source file entry map::iterator sfmi = sourcefilemap.find(fileid); Par2RepairerSourceFile *sourcefile = (sfmi == sourcefilemap.end()) ? 0 :sfmi->second; // Was there an existing source file if (sourcefile) { // Does the source file already have a description packet if (sourcefile->GetDescriptionPacket()) { // Yes. We don't need another copy delete packet; return false; } else { // No. Store the packet in the source file sourcefile->SetDescriptionPacket(packet); return true; } } else { // Create a new source file for the packet sourcefile = new Par2RepairerSourceFile(packet, NULL); // Record the source file in the source file map sourcefilemap.insert(pair(fileid, sourcefile)); return true; } } // Finish loading a file verification packet bool Par2Repairer::LoadVerificationPacket(DiskFile *diskfile, u64 offset, PACKET_HEADER &header) { VerificationPacket *packet = new VerificationPacket; // Load the packet from disk if (!packet->Load(diskfile, offset, header)) { delete packet; return false; } // What is the fileid const MD5Hash &fileid = packet->FileId(); // Look up the fileid in the source file map for an existing source file entry map::iterator sfmi = sourcefilemap.find(fileid); Par2RepairerSourceFile *sourcefile = (sfmi == sourcefilemap.end()) ? 0 :sfmi->second; // Was there an existing source file if (sourcefile) { // Does the source file already have a verification packet if (sourcefile->GetVerificationPacket()) { // Yes. We don't need another copy. delete packet; return false; } else { // No. Store the packet in the source file sourcefile->SetVerificationPacket(packet); return true; } } else { // Create a new source file for the packet sourcefile = new Par2RepairerSourceFile(NULL, packet); // Record the source file in the source file map sourcefilemap.insert(pair(fileid, sourcefile)); return true; } } // Finish loading the main packet bool Par2Repairer::LoadMainPacket(DiskFile *diskfile, u64 offset, PACKET_HEADER &header) { // Do we already have a main packet if (0 != mainpacket) return false; MainPacket *packet = new MainPacket; // Load the packet from disk; if (!packet->Load(diskfile, offset, header)) { delete packet; return false; } mainpacket = packet; return true; } // Finish loading the creator packet bool Par2Repairer::LoadCreatorPacket(DiskFile *diskfile, u64 offset, PACKET_HEADER &header) { // Do we already have a creator packet if (0 != creatorpacket) return false; CreatorPacket *packet = new CreatorPacket; // Load the packet from disk; if (!packet->Load(diskfile, offset, header)) { delete packet; return false; } creatorpacket = packet; return true; } // Load packets from other PAR2 files with names based on the original PAR2 file bool Par2Repairer::LoadPacketsFromOtherFiles(string filename) { // Split the original PAR2 filename into path and name parts string path; string name; DiskFile::SplitFilename(filename, path, name); string::size_type where; // Trim ".par2" off of the end original name // Look for the last "." in the filename while (string::npos != (where = name.find_last_of('.'))) { // Trim what follows the last . string tail = name.substr(where+1); name = name.substr(0,where); // Was what followed the last "." "par2" if (0 == stricmp(tail.c_str(), "par2")) break; } // If what is left ends in ".volNNN-NNN" or ".volNNN+NNN" strip that as well // Is there another "." if (string::npos != (where = name.find_last_of('.'))) { // What follows the "." string tail = name.substr(where+1); // Scan what follows the last "." to see of it matches vol123-456 or vol123+456 int n = 0; string::const_iterator p; for (p=tail.begin(); p!=tail.end(); ++p) { char ch = *p; if (0 == n) { if (tolower(ch) == 'v') { n++; } else { break; } } else if (1 == n) { if (tolower(ch) == 'o') { n++; } else { break; } } else if (2 == n) { if (tolower(ch) == 'l') { n++; } else { break; } } else if (3 == n) { if (isdigit(ch)) {} else if (ch == '-' || ch == '+') { n++; } else { break; } } else if (4 == n) { if (isdigit(ch)) {} else { break; } } } // If we matched then retain only what preceeds the "." if (p == tail.end()) { name = name.substr(0,where); } } // Find files called "*.par2" or "name.*.par2" { string wildcard = name.empty() ? "*.par2" : name + ".*.par2"; list *files = DiskFile::FindFiles(path, wildcard); // Load packets from each file that was found for (list::const_iterator s=files->begin(); s!=files->end(); ++s) { LoadPacketsFromFile(*s); if (cancelled) { break; } } delete files; if (cancelled) { return false; } } { string wildcard = name.empty() ? "*.PAR2" : name + ".*.PAR2"; list *files = DiskFile::FindFiles(path, wildcard); // Load packets from each file that was found for (list::const_iterator s=files->begin(); s!=files->end(); ++s) { LoadPacketsFromFile(*s); if (cancelled) { break; } } delete files; if (cancelled) { return false; } } return true; } // Load packets from any other PAR2 files whose names are given on the command line bool Par2Repairer::LoadPacketsFromExtraFiles(const list &extrafiles) { for (ExtraFileIterator i=extrafiles.begin(); i!=extrafiles.end(); i++) { string filename = i->FileName(); // If the filename contains ".par2" anywhere if (string::npos != filename.find(".par2") || string::npos != filename.find(".PAR2")) { LoadPacketsFromFile(filename); if (cancelled) { break; } } } if (cancelled) { return false; } return true; } // Check that the packets are consistent and discard any that are not bool Par2Repairer::CheckPacketConsistency(void) { // Do we have a main packet if (0 == mainpacket) { // If we don't have a main packet, then there is nothing more that we can do. // We cannot verify or repair any files. cerr << "Main packet not found." << endl; return false; } // Remember the block size from the main packet blocksize = mainpacket->BlockSize(); // Check that the recovery blocks have the correct amount of data // and discard any that don't { map::iterator rp = recoverypacketmap.begin(); while (rp != recoverypacketmap.end()) { if (rp->second->BlockSize() == blocksize) { ++rp; } else { cerr << "Incorrect sized recovery block for exponent " << rp->second->Exponent() << " discarded" << endl; delete rp->second; map::iterator x = rp++; recoverypacketmap.erase(x); } } } // Check for source files that have no description packet or where the // verification packet has the wrong number of entries and discard them. { map::iterator sf = sourcefilemap.begin(); while (sf != sourcefilemap.end()) { // Do we have a description packet DescriptionPacket *descriptionpacket = sf->second->GetDescriptionPacket(); if (descriptionpacket == 0) { // No description packet // Discard the source file delete sf->second; map::iterator x = sf++; sourcefilemap.erase(x); continue; } // Compute and store the block count from the filesize and blocksize sf->second->SetBlockCount(blocksize); // Do we have a verification packet VerificationPacket *verificationpacket = sf->second->GetVerificationPacket(); if (verificationpacket == 0) { // No verification packet // That is ok, but we won't be able to use block verification. // Proceed to the next file. ++sf; continue; } // Work out the block count for the file from the file size // and compare that with the verification packet u64 filesize = descriptionpacket->FileSize(); u32 blockcount = verificationpacket->BlockCount(); if ((filesize + blocksize-1) / blocksize != (u64)blockcount) { // The block counts are different! cerr << "Incorrectly sized verification packet for \"" << descriptionpacket->FileName() << "\" discarded" << endl; // Discard the source file delete sf->second; map::iterator x = sf++; sourcefilemap.erase(x); continue; } // Everything is ok. // Proceed to the next file ++sf; } } if (noiselevel > CommandLine::nlQuiet) { cout << "There are " << mainpacket->RecoverableFileCount() << " recoverable files and " << mainpacket->TotalFileCount() - mainpacket->RecoverableFileCount() << " other files." << endl; cout << "The block size used was " << blocksize << " bytes." << endl; } return true; } // Use the information in the main packet to get the source files // into the correct order and determine their filenames bool Par2Repairer::CreateSourceFileList(void) { // For each FileId entry in the main packet for (u32 filenumber=0; filenumberTotalFileCount(); filenumber++) { const MD5Hash &fileid = mainpacket->FileId(filenumber); // Look up the fileid in the source file map map::iterator sfmi = sourcefilemap.find(fileid); Par2RepairerSourceFile *sourcefile = (sfmi == sourcefilemap.end()) ? 0 :sfmi->second; if (sourcefile) { sourcefile->ComputeTargetFileName(searchpath); } sourcefiles.push_back(sourcefile); } return true; } // Determine the total number of DataBlocks for the recoverable source files // The allocate the DataBlocks and assign them to each source file bool Par2Repairer::AllocateSourceBlocks(void) { sourceblockcount = 0; u32 filenumber = 0; vector::iterator sf = sourcefiles.begin(); // For each recoverable source file while (filenumber < mainpacket->RecoverableFileCount() && sf != sourcefiles.end()) { // Do we have a source file Par2RepairerSourceFile *sourcefile = *sf; if (sourcefile) { sourceblockcount += sourcefile->BlockCount(); } else { // No details for this source file so we don't know what the // total number of source blocks is // sourceblockcount = 0; // break; } ++sf; ++filenumber; } // Did we determine the total number of source blocks if (sourceblockcount > 0) { // Yes. // Allocate all of the Source and Target DataBlocks (which will be used // to read and write data to disk). sourceblocks.resize(sourceblockcount); targetblocks.resize(sourceblockcount); // Which DataBlocks will be allocated first vector::iterator sourceblock = sourceblocks.begin(); vector::iterator targetblock = targetblocks.begin(); u32 blocknumber = 0; totalsize = 0; filenumber = 0; sf = sourcefiles.begin(); while (filenumber < mainpacket->RecoverableFileCount() && sf != sourcefiles.end()) { Par2RepairerSourceFile *sourcefile = *sf; if (sourcefile) { totalsize += sourcefile->GetDescriptionPacket()->FileSize(); u32 blockcount = sourcefile->BlockCount(); // Allocate the source and target DataBlocks to the sourcefile sourcefile->SetBlocks(blocknumber, blockcount, sourceblock, targetblock, blocksize); blocknumber++; sourceblock += blockcount; targetblock += blockcount; } ++sf; ++filenumber; } blocksallocated = true; if (noiselevel > CommandLine::nlQuiet) { cout << "There are a total of " << sourceblockcount << " data blocks." << endl; cout << "The total size of the data files is " << totalsize << " bytes." << endl; } } return true; } // Create a verification hash table for all files for which we have not // found a complete version of the file and for which we have // a verification packet bool Par2Repairer::PrepareVerificationHashTable(void) { // Choose a size for the hash table verificationhashtable.SetLimit(sourceblockcount); // Will any files be block verifiable blockverifiable = false; // For each source file vector::iterator sf = sourcefiles.begin(); while (sf != sourcefiles.end()) { // Get the source file Par2RepairerSourceFile *sourcefile = *sf; if (sourcefile) { // Do we have a verification packet if (0 != sourcefile->GetVerificationPacket()) { // Yes. Load the verification entries into the hash table verificationhashtable.Load(sourcefile, blocksize); blockverifiable = true; } else { // No. We can only check the whole file unverifiablesourcefiles.push_back(sourcefile); } } ++sf; } return true; } // Compute the table for the sliding CRC computation bool Par2Repairer::ComputeWindowTable(void) { if (blockverifiable) { GenerateWindowTable(blocksize, windowtable); windowmask = ComputeWindowMask(blocksize); } return true; } static bool SortSourceFilesByFileName(Par2RepairerSourceFile *low, Par2RepairerSourceFile *high) { return low->TargetFileName() < high->TargetFileName(); } // Attempt to verify all of the source files bool Par2Repairer::VerifySourceFiles(void) { bool finalresult = true; // Created a sorted list of the source files and verify them in that // order rather than the order they are in the main packet. vector sortedfiles; u32 filenumber = 0; vector::iterator sf = sourcefiles.begin(); while (sf != sourcefiles.end()) { // Do we have a source file Par2RepairerSourceFile *sourcefile = *sf; if (sourcefile) { sortedfiles.push_back(sourcefile); } else { // Was this one of the recoverable files if (filenumber < mainpacket->RecoverableFileCount()) { cerr << "No details available for recoverable file number " << filenumber+1 << "." << endl << "Recovery will not be possible." << endl; // Set error but let verification of other files continue finalresult = false; } else { cerr << "No details available for non-recoverable file number " << filenumber - mainpacket->RecoverableFileCount() + 1 << endl; } } ++sf; } sort(sortedfiles.begin(), sortedfiles.end(), SortSourceFilesByFileName); // Start verifying the files sf = sortedfiles.begin(); while (sf != sortedfiles.end()) { if (cancelled) { return false; } // Do we have a source file Par2RepairerSourceFile *sourcefile = *sf; // What filename does the file use string filename = sourcefile->TargetFileName(); // Check to see if we have already used this file if (diskFileMap.Find(filename) != 0) { // The file has already been used! cerr << "Source file " << filenumber+1 << " is a duplicate." << endl; return false; } DiskFile *diskfile = new DiskFile; // Does the target file exist if (diskfile->Open(filename)) { // Yes. Record that fact. sourcefile->SetTargetExists(true); // Remember that the DiskFile is the target file sourcefile->SetTargetFile(diskfile); // Remember that we have processed this file bool success = diskFileMap.Insert(diskfile); assert(success); (void)success; // Do the actual verification if (!VerifyDataFile(diskfile, sourcefile)) finalresult = false; // We have finished with the file for now diskfile->Close(); // Find out how much data we have found UpdateVerificationResults(); } else { // The file does not exist. delete diskfile; if (noiselevel > CommandLine::nlSilent) { string path; string name; DiskFile::SplitFilename(filename, path, name); cout << "Target: \"" << name << "\" - missing." << endl; sig_done(name, 0, sourcefile && sourcefile->GetVerificationPacket() ? sourcefile->GetVerificationPacket()->BlockCount() : 0); } } ++sf; } return finalresult; } // Scan any extra files specified on the command line bool Par2Repairer::VerifyExtraFiles(const list &extrafiles) { for (ExtraFileIterator i=extrafiles.begin(); i!=extrafiles.end() && completefilecountRecoverableFileCount(); ++i) { string filename = i->FileName(); // If the filename does not include ".par2" we are interested in it. if (string::npos == filename.find(".par2") && string::npos == filename.find(".PAR2")) { filename = DiskFile::GetCanonicalPathname(filename); // Has this file already been dealt with if (diskFileMap.Find(filename) == 0) { DiskFile *diskfile = new DiskFile; // Does the file exist if (!diskfile->Open(filename)) { delete diskfile; continue; } // Remember that we have processed this file bool success = diskFileMap.Insert(diskfile); assert(success); (void)success; // Do the actual verification VerifyDataFile(diskfile, 0); // Ignore errors // We have finished with the file for now diskfile->Close(); // Find out how much data we have found UpdateVerificationResults(); } } } return true; } // Attempt to match the data in the DiskFile with the source file bool Par2Repairer::VerifyDataFile(DiskFile *diskfile, Par2RepairerSourceFile *sourcefile) { MatchType matchtype; // What type of match was made MD5Hash hashfull; // The MD5 Hash of the whole file MD5Hash hash16k; // The MD5 Hash of the files 16k of the file // Are there any files that can be verified at the block level if (blockverifiable) { u32 count; // Scan the file at the block level. if (!ScanDataFile(diskfile, // [in] The file to scan sourcefile, // [in/out] Modified in the match is for another source file matchtype, // [out] hashfull, // [out] hash16k, // [out] count)) // [out] return false; switch (matchtype) { case eNoMatch: // No data was found at all. // Continue to next test. break; case ePartialMatch: { // We found some data. // Return them. return true; } break; case eFullMatch: { // We found a perfect match. sourcefile->SetCompleteFile(diskfile); // Return the match return true; } break; } } // We did not find a match for any blocks of data within the file, but if // there are any files for which we did not have a verification packet // we can try a simple match of the hash for the whole file. // Are there any files that cannot be verified at the block level if (unverifiablesourcefiles.size() > 0) { // Would we have already computed the file hashes if (!blockverifiable) { u64 filesize = diskfile->FileSize(); size_t buffersize = 1024*1024; if (buffersize > min(blocksize, filesize)) buffersize = (size_t)min(blocksize, filesize); char *buffer = new char[buffersize]; u64 offset = 0; MD5Context context; while (offset < filesize) { size_t want = (size_t)min((u64)buffersize, filesize-offset); if (!diskfile->Read(offset, buffer, want)) { delete [] buffer; return false; } // Will the newly read data reach the 16k boundary if (offset < 16384 && offset + want >= 16384) { context.Update(buffer, (size_t)(16384-offset)); // Compute the 16k hash MD5Context temp = context; temp.Final(hash16k); // Is there more data if (offset + want > 16384) { context.Update(&buffer[16384-offset], (size_t)(offset+want)-16384); } } else { context.Update(buffer, want); } offset += want; } // Compute the file hash MD5Hash hashfull; context.Final(hashfull); // If we did not have 16k of data, then the 16k hash // is the same as the full hash if (filesize < 16384) { hash16k = hashfull; } } list::iterator sf = unverifiablesourcefiles.begin(); // Compare the hash values of each source file for a match while (sf != unverifiablesourcefiles.end()) { sourcefile = *sf; // Does the file match if (sourcefile->GetCompleteFile() == 0 && diskfile->FileSize() == sourcefile->GetDescriptionPacket()->FileSize() && hash16k == sourcefile->GetDescriptionPacket()->Hash16k() && hashfull == sourcefile->GetDescriptionPacket()->HashFull()) { if (noiselevel > CommandLine::nlSilent) cout << diskfile->FileName() << " is a perfect match for " << sourcefile->GetDescriptionPacket()->FileName() << endl; // Record that we have a perfect match for this source file sourcefile->SetCompleteFile(diskfile); if (blocksallocated) { // Allocate all of the DataBlocks for the source file to the DiskFile u64 offset = 0; u64 filesize = sourcefile->GetDescriptionPacket()->FileSize(); vector::iterator sb = sourcefile->SourceBlocks(); while (offset < filesize) { DataBlock &datablock = *sb; datablock.SetLocation(diskfile, offset); datablock.SetLength(min(blocksize, filesize-offset)); offset += blocksize; ++sb; } } // Return the match return true; } ++sf; } } return true; } // Perform a sliding window scan of the DiskFile looking for blocks of data that // might belong to any of the source files (for which a verification packet was // available). If a block of data might be from more than one source file, prefer // the one specified by the "sourcefile" parameter. If the first data block // found is for a different source file then "sourcefile" is changed accordingly. bool Par2Repairer::ScanDataFile(DiskFile *diskfile, // [in] Par2RepairerSourceFile* &sourcefile, // [in/out] MatchType &matchtype, // [out] MD5Hash &hashfull, // [out] MD5Hash &hash16k, // [out] u32 &count) // [out] { // Remember which file we wanted to match Par2RepairerSourceFile *originalsourcefile = sourcefile; matchtype = eNoMatch; // Is the file empty if (diskfile->FileSize() == 0) { // If the file is empty, then just return return true; } string path; string name; DiskFile::SplitFilename(diskfile->FileName(), path, name); sig_filename(name); string shortname; if (name.size() > 56) { shortname = name.substr(0, 28) + "..." + name.substr(name.size()-28); } else { shortname = name; } // Create the checksummer for the file and start reading from it FileCheckSummer filechecksummer(diskfile, blocksize, windowtable, windowmask); if (!filechecksummer.Start()) return false; // Assume we will make a perfect match for the file matchtype = eFullMatch; // How many matches have we had count = 0; // How many blocks have already been found u32 duplicatecount = 0; // Have we found data blocks in this file that belong to more than one target file bool multipletargets = false; // Which block do we expect to find first const VerificationHashEntry *nextentry = 0; u64 progress = 0; // Whilst we have not reached the end of the file while (filechecksummer.Offset() < diskfile->FileSize()) { if (noiselevel > CommandLine::nlQuiet) { // Update a progress indicator u32 oldfraction = (u32)(1000 * progress / diskfile->FileSize()); u32 newfraction = (u32)(1000 * (progress = filechecksummer.Offset()) / diskfile->FileSize()); if (oldfraction != newfraction) { cout << "Scanning: \"" << shortname << "\": " << newfraction/10 << '.' << newfraction%10 << "%\r" << flush; sig_progress(newfraction); if (cancelled) { break; } } } // If we fail to find a match, it might be because it was a duplicate of a block // that we have already found. bool duplicate; // Look for a match const VerificationHashEntry *currententry = verificationhashtable.FindMatch(nextentry, sourcefile, filechecksummer, duplicate); // Did we find a match if (currententry != 0) { // Is this the first match if (count == 0) { // Which source file was it sourcefile = currententry->SourceFile(); // If the first match found was not actually the first block // for the source file, or it was not at the start of the // data file: then this is a partial match. if (!currententry->FirstBlock() || filechecksummer.Offset() != 0) { matchtype = ePartialMatch; } } else { // If the match found is not the one which was expected // then this is a partial match if (currententry != nextentry) { matchtype = ePartialMatch; } // Is the match from a different source file if (sourcefile != currententry->SourceFile()) { multipletargets = true; } } if (blocksallocated) { // Record the match currententry->SetBlock(diskfile, filechecksummer.Offset()); } // Update the number of matches found count++; // What entry do we expect next nextentry = currententry->Next(); // Advance to the next block if (!filechecksummer.Jump(currententry->GetDataBlock()->GetLength())) return false; } else { // This cannot be a perfect match matchtype = ePartialMatch; // Was this a duplicate match if (duplicate) { duplicatecount++; // What entry would we expect next nextentry = 0; // Advance one whole block if (!filechecksummer.Jump(blocksize)) return false; } else { // What entry do we expect next nextentry = 0; // Advance 1 byte if (!filechecksummer.Step()) return false; } } } if (cancelled) { return false; } // Get the Full and 16k hash values of the file filechecksummer.GetFileHashes(hashfull, hash16k); // Did we make any matches at all if (count > 0) { // If this still might be a perfect match, check the // hashes, file size, and number of blocks to confirm. if (matchtype != eFullMatch || count != sourcefile->GetVerificationPacket()->BlockCount() || diskfile->FileSize() != sourcefile->GetDescriptionPacket()->FileSize() || hashfull != sourcefile->GetDescriptionPacket()->HashFull() || hash16k != sourcefile->GetDescriptionPacket()->Hash16k()) { matchtype = ePartialMatch; if (noiselevel > CommandLine::nlSilent) { // Did we find data from multiple target files if (multipletargets) { // Were we scanning the target file or an extra file if (originalsourcefile != 0) { cout << "Target: \"" << name << "\" - damaged, found " << count << " data blocks from several target files." << endl; } else { cout << "File: \"" << name << "\" - found " << count << " data blocks from several target files." << endl; } } else { // Did we find data blocks that belong to the target file if (originalsourcefile == sourcefile) { cout << "Target: \"" << name << "\" - damaged. Found " << count << " of " << sourcefile->GetVerificationPacket()->BlockCount() << " data blocks." << endl; } // Were we scanning the target file or an extra file else if (originalsourcefile != 0) { string targetname; DiskFile::SplitFilename(sourcefile->TargetFileName(), path, targetname); cout << "Target: \"" << name << "\" - damaged. Found " << count << " of " << sourcefile->GetVerificationPacket()->BlockCount() << " data blocks from \"" << targetname << "\"." << endl; } else { string targetname; DiskFile::SplitFilename(sourcefile->TargetFileName(), path, targetname); cout << "File: \"" << name << "\" - found " << count << " of " << sourcefile->GetVerificationPacket()->BlockCount() << " data blocks from \"" << targetname << "\"." << endl; } } } } else { if (noiselevel > CommandLine::nlSilent) { // Did we match the target file if (originalsourcefile == sourcefile) { cout << "Target: \"" << name << "\" - found." << endl; } // Were we scanning the target file or an extra file else if (originalsourcefile != 0) { string targetname; DiskFile::SplitFilename(sourcefile->TargetFileName(), path, targetname); cout << "Target: \"" << name << "\" - is a match for \"" << targetname << "\"." << endl; } else { string targetname; DiskFile::SplitFilename(sourcefile->TargetFileName(), path, targetname); cout << "File: \"" << name << "\" - is a match for \"" << targetname << "\"." << endl; } } } } else { matchtype = eNoMatch; if (noiselevel > CommandLine::nlSilent) { // We found not data, but did the file actually contain blocks we // had already found in other files. if (duplicatecount > 0) { cout << "File: \"" << name << "\" - found " << duplicatecount << " duplicate data blocks." << endl; } else { cout << "File: \"" << name << "\" - no data found." << endl; } } } sig_done(name,count, sourcefile && sourcefile->GetVerificationPacket() ? sourcefile->GetVerificationPacket()->BlockCount() : 0); sig_progress(1000); return true; } // Find out how much data we have found void Par2Repairer::UpdateVerificationResults(void) { availableblockcount = 0; missingblockcount = 0; completefilecount = 0; renamedfilecount = 0; damagedfilecount = 0; missingfilecount = 0; u32 filenumber = 0; vector::iterator sf = sourcefiles.begin(); // Check the recoverable files while (sf != sourcefiles.end() && filenumber < mainpacket->TotalFileCount()) { Par2RepairerSourceFile *sourcefile = *sf; if (sourcefile) { // Was a perfect match for the file found if (sourcefile->GetCompleteFile() != 0) { // Is it the target file or a different one if (sourcefile->GetCompleteFile() == sourcefile->GetTargetFile()) { completefilecount++; } else { renamedfilecount++; } availableblockcount += sourcefile->BlockCount(); } else { // Count the number of blocks that have been found vector::iterator sb = sourcefile->SourceBlocks(); for (u32 blocknumber=0; blocknumberBlockCount(); ++blocknumber, ++sb) { DataBlock &datablock = *sb; if (datablock.IsSet()) availableblockcount++; } // Does the target file exist if (sourcefile->GetTargetExists()) { damagedfilecount++; } else { missingfilecount++; } } } else { missingfilecount++; } ++filenumber; ++sf; } missingblockcount = sourceblockcount - availableblockcount; } // Check the verification results and report the results bool Par2Repairer::CheckVerificationResults(void) { // Is repair needed if (completefilecount < mainpacket->RecoverableFileCount() || renamedfilecount > 0 || damagedfilecount > 0 || missingfilecount > 0) { if (noiselevel > CommandLine::nlSilent) cout << "Repair is required." << endl; if (noiselevel > CommandLine::nlQuiet) { if (renamedfilecount > 0) cout << renamedfilecount << " file(s) have the wrong name." << endl; if (missingfilecount > 0) cout << missingfilecount << " file(s) are missing." << endl; if (damagedfilecount > 0) cout << damagedfilecount << " file(s) exist but are damaged." << endl; if (completefilecount > 0) cout << completefilecount << " file(s) are ok." << endl; cout << "You have " << availableblockcount << " out of " << sourceblockcount << " data blocks available." << endl; if (recoverypacketmap.size() > 0) cout << "You have " << (u32)recoverypacketmap.size() << " recovery blocks available." << endl; } // Is repair possible if (recoverypacketmap.size() >= missingblockcount) { if (noiselevel > CommandLine::nlSilent) cout << "Repair is possible." << endl; if (noiselevel > CommandLine::nlQuiet) { if (recoverypacketmap.size() > missingblockcount) cout << "You have an excess of " << (u32)recoverypacketmap.size() - missingblockcount << " recovery blocks." << endl; if (missingblockcount > 0) cout << missingblockcount << " recovery blocks will be used to repair." << endl; else if (recoverypacketmap.size()) cout << "None of the recovery blocks will be used for the repair." << endl; } return true; } else { if (noiselevel > CommandLine::nlSilent) { cout << "Repair is not possible." << endl; cout << "You need " << missingblockcount - recoverypacketmap.size() << " more recovery blocks to be able to repair." << endl; } return false; } } else { if (noiselevel > CommandLine::nlSilent) cout << "All files are correct, repair is not required." << endl; return true; } return true; } // Rename any damaged or missnamed target files. bool Par2Repairer::RenameTargetFiles(void) { u32 filenumber = 0; vector::iterator sf = sourcefiles.begin(); // Rename any damaged target files while (sf != sourcefiles.end() && filenumber < mainpacket->TotalFileCount()) { Par2RepairerSourceFile *sourcefile = *sf; // If the target file exists but is not a complete version of the file if (sourcefile->GetTargetExists() && sourcefile->GetTargetFile() != sourcefile->GetCompleteFile()) { DiskFile *targetfile = sourcefile->GetTargetFile(); // Rename it diskFileMap.Remove(targetfile); if (!targetfile->Rename()) return false; bool success = diskFileMap.Insert(targetfile); assert(success); (void)success; // We no longer have a target file sourcefile->SetTargetExists(false); sourcefile->SetTargetFile(0); } ++sf; ++filenumber; } filenumber = 0; sf = sourcefiles.begin(); // Rename any missnamed but complete versions of the files while (sf != sourcefiles.end() && filenumber < mainpacket->TotalFileCount()) { Par2RepairerSourceFile *sourcefile = *sf; // If there is no targetfile and there is a complete version if (sourcefile->GetTargetFile() == 0 && sourcefile->GetCompleteFile() != 0) { DiskFile *targetfile = sourcefile->GetCompleteFile(); // Rename it diskFileMap.Remove(targetfile); if (!targetfile->Rename(sourcefile->TargetFileName())) return false; bool success = diskFileMap.Insert(targetfile); assert(success); (void)success; // This file is now the target file sourcefile->SetTargetExists(true); sourcefile->SetTargetFile(targetfile); // We have one more complete file completefilecount++; } ++sf; ++filenumber; } return true; } // Work out which files are being repaired, create them, and allocate // target DataBlocks to them, and remember them for later verification. bool Par2Repairer::CreateTargetFiles(void) { u32 filenumber = 0; vector::iterator sf = sourcefiles.begin(); // Create any missing target files while (sf != sourcefiles.end() && filenumber < mainpacket->TotalFileCount()) { Par2RepairerSourceFile *sourcefile = *sf; // If the file does not exist if (!sourcefile->GetTargetExists()) { DiskFile *targetfile = new DiskFile; string filename = sourcefile->TargetFileName(); u64 filesize = sourcefile->GetDescriptionPacket()->FileSize(); // Create the target file if (!targetfile->Create(filename, filesize)) { delete targetfile; return false; } // This file is now the target file sourcefile->SetTargetExists(true); sourcefile->SetTargetFile(targetfile); // Remember this file bool success = diskFileMap.Insert(targetfile); assert(success); (void)success; u64 offset = 0; vector::iterator tb = sourcefile->TargetBlocks(); // Allocate all of the target data blocks while (offset < filesize) { DataBlock &datablock = *tb; datablock.SetLocation(targetfile, offset); datablock.SetLength(min(blocksize, filesize-offset)); offset += blocksize; ++tb; } // Add the file to the list of those that will need to be verified // once the repair has completed. verifylist.push_back(sourcefile); } ++sf; ++filenumber; } return true; } // Work out which data blocks are available, which need to be copied // directly to the output, and which need to be recreated, and compute // the appropriate Reed Solomon matrix. bool Par2Repairer::ComputeRSmatrix(void) { inputblocks.resize(sourceblockcount); // The DataBlocks that will read from disk copyblocks.resize(availableblockcount); // Those DataBlocks which need to be copied outputblocks.resize(missingblockcount); // Those DataBlocks that will re recalculated vector::iterator inputblock = inputblocks.begin(); vector::iterator copyblock = copyblocks.begin(); vector::iterator outputblock = outputblocks.begin(); // Build an array listing which source data blocks are present and which are missing vector present; present.resize(sourceblockcount); vector::iterator sourceblock = sourceblocks.begin(); vector::iterator targetblock = targetblocks.begin(); vector::iterator pres = present.begin(); // Iterate through all source blocks for all files while (sourceblock != sourceblocks.end()) { // Was this block found if (sourceblock->IsSet()) { // // Open the file the block was found in. // if (!sourceblock->Open()) // return false; // Record that the block was found *pres = true; // Add the block to the list of those which will be read // as input (and which might also need to be copied). *inputblock = &*sourceblock; *copyblock = &*targetblock; ++inputblock; ++copyblock; } else { // Record that the block was missing *pres = false; // Add the block to the list of those to be written *outputblock = &*targetblock; ++outputblock; } ++sourceblock; ++targetblock; ++pres; } // Set the number of source blocks and which of them are present if (!rs.SetInput(present)) return false; // Start iterating through the available recovery packets map::iterator rp = recoverypacketmap.begin(); // Continue to fill the remaining list of data blocks to be read while (inputblock != inputblocks.end()) { // Get the next available recovery packet u32 exponent = rp->first; RecoveryPacket* recoverypacket = rp->second; // Get the DataBlock from the recovery packet DataBlock *recoveryblock = recoverypacket->GetDataBlock(); // // Make sure the file is open // if (!recoveryblock->Open()) // return false; // Add the recovery block to the list of blocks that will be read *inputblock = recoveryblock; // Record that the corresponding exponent value is the next one // to use in the RS matrix if (!rs.SetOutput(true, (u16)exponent)) return false; ++inputblock; ++rp; } // If we need to, compute and solve the RS matrix if (missingblockcount == 0) return true; bool success = rs.Compute(noiselevel); return success; } // Allocate memory buffers for reading and writing data to disk. bool Par2Repairer::AllocateBuffers(size_t memorylimit) { // Would single pass processing use too much memory if (blocksize * missingblockcount > memorylimit) { // Pick a size that is small enough chunksize = ~3 & (memorylimit / missingblockcount); } else { chunksize = (size_t)blocksize; } // Allocate the two buffers inputbuffer = new u8[(size_t)chunksize]; outputbuffer = new u8[(size_t)chunksize * missingblockcount]; if (inputbuffer == NULL || outputbuffer == NULL) { cerr << "Could not allocate buffer memory." << endl; return false; } return true; } // Read source data, process it through the RS matrix and write it to disk. bool Par2Repairer::ProcessData(u64 blockoffset, size_t blocklength) { u64 totalwritten = 0; // Clear the output buffer memset(outputbuffer, 0, (size_t)chunksize * missingblockcount); vector::iterator inputblock = inputblocks.begin(); vector::iterator copyblock = copyblocks.begin(); u32 inputindex = 0; DiskFile *lastopenfile = NULL; // Are there any blocks which need to be reconstructed if (missingblockcount > 0) { // For each input block while (inputblock != inputblocks.end()) { // Are we reading from a new file? if (lastopenfile != (*inputblock)->GetDiskFile()) { // Close the last file if (lastopenfile != NULL) { lastopenfile->Close(); } // Open the new file lastopenfile = (*inputblock)->GetDiskFile(); if (!lastopenfile->Open()) { return false; } } // Read data from the current input block if (!(*inputblock)->ReadData(blockoffset, blocklength, inputbuffer)) return false; // Have we reached the last source data block if (copyblock != copyblocks.end()) { // Does this block need to be copied to the target file if ((*copyblock)->IsSet()) { size_t wrote; // Write the block back to disk in the new target file if (!(*copyblock)->WriteData(blockoffset, blocklength, inputbuffer, wrote)) return false; totalwritten += wrote; } ++copyblock; } if (!RepairData(inputindex, blocklength)) { // For each output block for (u32 outputindex=0; outputindex CommandLine::nlQuiet) { // Update a progress indicator u32 oldfraction = (u32)(1000 * progress / totaldata); progress += blocklength; u32 newfraction = (u32)(1000 * progress / totaldata); if (oldfraction != newfraction) { cout << "Repairing: " << newfraction/10 << '.' << newfraction%10 << "%\r" << flush; sig_progress(newfraction); if (cancelled) { break; } } } } } if (cancelled) { break; } ++inputblock; ++inputindex; } } else { // Reconstruction is not required, we are just copying blocks between files // For each block that might need to be copied while (copyblock != copyblocks.end()) { // Does this block need to be copied if ((*copyblock)->IsSet()) { // Are we reading from a new file? if (lastopenfile != (*inputblock)->GetDiskFile()) { // Close the last file if (lastopenfile != NULL) { lastopenfile->Close(); } // Open the new file lastopenfile = (*inputblock)->GetDiskFile(); if (!lastopenfile->Open()) { return false; } } // Read data from the current input block if (!(*inputblock)->ReadData(blockoffset, blocklength, inputbuffer)) return false; size_t wrote; if (!(*copyblock)->WriteData(blockoffset, blocklength, inputbuffer, wrote)) return false; totalwritten += wrote; } if (noiselevel > CommandLine::nlQuiet) { // Update a progress indicator u32 oldfraction = (u32)(1000 * progress / totaldata); progress += blocklength; u32 newfraction = (u32)(1000 * progress / totaldata); if (oldfraction != newfraction) { cout << "Processing: " << newfraction/10 << '.' << newfraction%10 << "%\r" << flush; sig_progress(newfraction); if (cancelled) { break; } } } if (cancelled) { break; } ++copyblock; ++inputblock; } } // Close the last file if (lastopenfile != NULL) { lastopenfile->Close(); } if (cancelled) { return false; } if (noiselevel > CommandLine::nlQuiet) cout << "Writing recovered data\r"; // For each output block that has been recomputed vector::iterator outputblock = outputblocks.begin(); for (u32 outputindex=0; outputindexWriteData(blockoffset, blocklength, outbuf, wrote)) return false; totalwritten += wrote; ++outputblock; } if (noiselevel > CommandLine::nlQuiet) cout << "Wrote " << totalwritten << " bytes to disk" << endl; return true; } // Verify that all of the reconstructed target files are now correct bool Par2Repairer::VerifyTargetFiles(void) { bool finalresult = true; // Verify the target files in alphabetical order sort(verifylist.begin(), verifylist.end(), SortSourceFilesByFileName); // Iterate through each file in the verification list for (vector::iterator sf = verifylist.begin(); sf != verifylist.end(); ++sf) { Par2RepairerSourceFile *sourcefile = *sf; DiskFile *targetfile = sourcefile->GetTargetFile(); // Close the file if (targetfile->IsOpen()) targetfile->Close(); // Mark all data blocks for the file as unknown vector::iterator sb = sourcefile->SourceBlocks(); for (u32 blocknumber=0; blocknumberBlockCount(); blocknumber++) { sb->ClearLocation(); ++sb; } // Say we don't have a complete version of the file sourcefile->SetCompleteFile(0); // Re-open the target file if (!targetfile->Open()) { finalresult = false; continue; } // Verify the file again if (!VerifyDataFile(targetfile, sourcefile)) finalresult = false; // Close the file again targetfile->Close(); // Find out how much data we have found UpdateVerificationResults(); } return finalresult; } // Delete all of the partly reconstructed files bool Par2Repairer::DeleteIncompleteTargetFiles(void) { vector::iterator sf = verifylist.begin(); // Iterate through each file in the verification list while (sf != verifylist.end()) { Par2RepairerSourceFile *sourcefile = *sf; if (sourcefile->GetTargetExists()) { DiskFile *targetfile = sourcefile->GetTargetFile(); // Close and delete the file if (targetfile->IsOpen()) targetfile->Close(); targetfile->Delete(); // Forget the file diskFileMap.Remove(targetfile); delete targetfile; // There is no target file sourcefile->SetTargetExists(false); sourcefile->SetTargetFile(0); } ++sf; } return true; } nzbget-16.4/lib/par2/commandline.cpp0000644000175000017500000005674012630544544017200 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #include "par2cmdline.h" #ifdef _MSC_VER #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif #endif CommandLine::ExtraFile::ExtraFile(void) : filename() , filesize(0) { } CommandLine::ExtraFile::ExtraFile(const CommandLine::ExtraFile &other) : filename(other.filename) , filesize(other.filesize) { } CommandLine::ExtraFile& CommandLine::ExtraFile::operator=(const CommandLine::ExtraFile &other) { filename = other.filename; filesize = other.filesize; return *this; } CommandLine::ExtraFile::ExtraFile(const string &name, u64 size) : filename(name) , filesize(size) { } CommandLine::CommandLine(void) : operation(opNone) , version(verUnknown) , noiselevel(nlUnknown) , blockcount(0) , blocksize(0) , firstblock(0) , recoveryfilescheme(scUnknown) , recoveryfilecount(0) , recoveryblockcount(0) , recoveryblockcountset(false) , redundancy(0) , redundancyset(false) , parfilename() , extrafiles() , totalsourcesize(0) , largestsourcesize(0) , memorylimit(0) { } void CommandLine::usage(void) { cout << "\n" "Usage:\n" "\n" " par2 c(reate) [options] [files] : Create PAR2 files\n" " par2 v(erify) [options] [files] : Verify files using PAR2 file\n" " par2 r(epair) [options] [files] : Repair files using PAR2 files\n" "\n" "You may also leave out the \"c\", \"v\", and \"r\" commands by using \"parcreate\",\n" "\"par2verify\", or \"par2repair\" instead.\n" "\n" "Options:\n" "\n" " -b : Set the Block-Count\n" " -s : Set the Block-Size (Don't use both -b and -s)\n" " -r : Level of Redundancy (%%)\n" " -c : Recovery block count (Don't use both -r and -c)\n" " -f : First Recovery-Block-Number\n" " -u : Uniform recovery file sizes\n" " -l : Limit size of recovery files (Don't use both -u and -l)\n" " -n : Number of recovery files (Don't use both -n and -l)\n" " -m : Memory (in MB) to use\n" " -v [-v]: Be more verbose\n" " -q [-q]: Be more quiet (-q -q gives silence)\n" " -- : Treat all remaining CommandLine as filenames\n" "\n" "If you wish to create par2 files for a single source file, you may leave\n" "out the name of the par2 file from the command line.\n"; } bool CommandLine::Parse(int argc, char *argv[]) { if (argc<1) { return false; } // Split the program name into path and filename string path, name; DiskFile::SplitFilename(argv[0], path, name); argc--; argv++; // Strip ".exe" from the end if (name.size() > 4 && 0 == stricmp(".exe", name.substr(name.length()-4).c_str())) { name = name.substr(0, name.length()-4); } // Check the resulting program name if (0 == stricmp("par2create", name.c_str())) { operation = opCreate; } else if (0 == stricmp("par2verify", name.c_str())) { operation = opVerify; } else if (0 == stricmp("par2repair", name.c_str())) { operation = opRepair; } // Have we determined what operation we want? if (operation == opNone) { if (argc<2) { cerr << "Not enough command line arguments." << endl; return false; } switch (tolower(argv[0][0])) { case 'c': if (argv[0][1] == 0 || 0 == stricmp(argv[0], "create")) operation = opCreate; break; case 'v': if (argv[0][1] == 0 || 0 == stricmp(argv[0], "verify")) operation = opVerify; break; case 'r': if (argv[0][1] == 0 || 0 == stricmp(argv[0], "repair")) operation = opRepair; break; } if (operation == opNone) { cerr << "Invalid operation specified: " << argv[0] << endl; return false; } argc--; argv++; } bool options = true; while (argc>0) { if (argv[0][0]) { if (options && argv[0][0] != '-') options = false; if (options) { switch (tolower(argv[0][1])) { case 'b': // Set the block count { if (operation != opCreate) { cerr << "Cannot specify block count unless creating." << endl; return false; } if (blockcount > 0) { cerr << "Cannot specify block count twice." << endl; return false; } else if (blocksize > 0) { cerr << "Cannot specify both block count and block size." << endl; return false; } char *p = &argv[0][2]; while (blockcount <= 3276 && *p && isdigit(*p)) { blockcount = blockcount * 10 + (*p - '0'); p++; } if (0 == blockcount || blockcount > 32768 || *p) { cerr << "Invalid block count option: " << argv[0] << endl; return false; } } break; case 's': // Set the block size { if (operation != opCreate) { cerr << "Cannot specify block size unless creating." << endl; return false; } if (blocksize > 0) { cerr << "Cannot specify block size twice." << endl; return false; } else if (blockcount > 0) { cerr << "Cannot specify both block count and block size." << endl; return false; } char *p = &argv[0][2]; while (blocksize <= 429496729 && *p && isdigit(*p)) { blocksize = blocksize * 10 + (*p - '0'); p++; } if (*p || blocksize == 0) { cerr << "Invalid block size option: " << argv[0] << endl; return false; } if (blocksize & 3) { cerr << "Block size must be a multiple of 4." << endl; return false; } } break; case 'r': // Set the amount of redundancy required { if (operation != opCreate) { cerr << "Cannot specify redundancy unless creating." << endl; return false; } if (redundancyset) { cerr << "Cannot specify redundancy twice." << endl; return false; } else if (recoveryblockcountset) { cerr << "Cannot specify both redundancy and recovery block count." << endl; return false; } char *p = &argv[0][2]; while (redundancy <= 10 && *p && isdigit(*p)) { redundancy = redundancy * 10 + (*p - '0'); p++; } if (redundancy > 100 || *p) { cerr << "Invalid redundancy option: " << argv[0] << endl; return false; } if (redundancy == 0 && recoveryfilecount > 0) { cerr << "Cannot set redundancy to 0 and file count > 0" << endl; return false; } redundancyset = true; } break; case 'c': // Set the number of recovery blocks to create { if (operation != opCreate) { cerr << "Cannot specify recovery block count unless creating." << endl; return false; } if (recoveryblockcountset) { cerr << "Cannot specify recovery block count twice." << endl; return false; } else if (redundancyset) { cerr << "Cannot specify both recovery block count and redundancy." << endl; return false; } char *p = &argv[0][2]; while (recoveryblockcount <= 32768 && *p && isdigit(*p)) { recoveryblockcount = recoveryblockcount * 10 + (*p - '0'); p++; } if (recoveryblockcount > 32768 || *p) { cerr << "Invalid recoveryblockcount option: " << argv[0] << endl; return false; } if (recoveryblockcount == 0 && recoveryfilecount > 0) { cerr << "Cannot set recoveryblockcount to 0 and file count > 0" << endl; return false; } recoveryblockcountset = true; } break; case 'f': // Specify the First block recovery number { if (operation != opCreate) { cerr << "Cannot specify first block number unless creating." << endl; return false; } if (firstblock > 0) { cerr << "Cannot specify first block twice." << endl; return false; } char *p = &argv[0][2]; while (firstblock <= 3276 && *p && isdigit(*p)) { firstblock = firstblock * 10 + (*p - '0'); p++; } if (firstblock > 32768 || *p) { cerr << "Invalid first block option: " << argv[0] << endl; return false; } } break; case 'u': // Specify uniformly sized recovery files { if (operation != opCreate) { cerr << "Cannot specify uniform files unless creating." << endl; return false; } if (argv[0][2]) { cerr << "Invalid option: " << argv[0] << endl; return false; } if (recoveryfilescheme != scUnknown) { cerr << "Cannot specify two recovery file size schemes." << endl; return false; } recoveryfilescheme = scUniform; } break; case 'l': // Limit the size of the recovery files { if (operation != opCreate) { cerr << "Cannot specify limit files unless creating." << endl; return false; } if (argv[0][2]) { cerr << "Invalid option: " << argv[0] << endl; return false; } if (recoveryfilescheme != scUnknown) { cerr << "Cannot specify two recovery file size schemes." << endl; return false; } if (recoveryfilecount > 0) { cerr << "Cannot specify limited size and number of files at the same time." << endl; return false; } recoveryfilescheme = scLimited; } break; case 'n': // Specify the number of recovery files { if (operation != opCreate) { cerr << "Cannot specify recovery file count unless creating." << endl; return false; } if (recoveryfilecount > 0) { cerr << "Cannot specify recovery file count twice." << endl; return false; } if (redundancyset && redundancy == 0) { cerr << "Cannot set file count when redundancy is set to 0." << endl; return false; } if (recoveryblockcountset && recoveryblockcount == 0) { cerr << "Cannot set file count when recovery block count is set to 0." << endl; return false; } if (recoveryfilescheme == scLimited) { cerr << "Cannot specify limited size and number of files at the same time." << endl; return false; } char *p = &argv[0][2]; while (*p && isdigit(*p)) { recoveryfilecount = recoveryfilecount * 10 + (*p - '0'); p++; } if (recoveryfilecount == 0 || *p) { cerr << "Invalid recovery file count option: " << argv[0] << endl; return false; } } break; case 'm': // Specify how much memory to use for output buffers { if (memorylimit > 0) { cerr << "Cannot specify memory limit twice." << endl; return false; } char *p = &argv[0][2]; while (*p && isdigit(*p)) { memorylimit = memorylimit * 10 + (*p - '0'); p++; } if (memorylimit == 0 || *p) { cerr << "Invalid memory limit option: " << argv[0] << endl; return false; } } break; case 'v': { switch (noiselevel) { case nlUnknown: { if (argv[0][2] == 'v') noiselevel = nlDebug; else noiselevel = nlNoisy; } break; case nlNoisy: case nlDebug: noiselevel = nlDebug; break; default: cerr << "Cannot use both -v and -q." << endl; return false; break; } } break; case 'q': { switch (noiselevel) { case nlUnknown: { if (argv[0][2] == 'q') noiselevel = nlSilent; else noiselevel = nlQuiet; } break; case nlQuiet: case nlSilent: noiselevel = nlSilent; break; default: cerr << "Cannot use both -v and -q." << endl; return false; break; } } break; case '-': { argc--; argv++; options = false; continue; } break; default: { cerr << "Invalid option specified: " << argv[0] << endl; return false; } } } else { list *filenames; // If the argument includes wildcard characters, // search the disk for matching files if (strchr(argv[0], '*') || strchr(argv[0], '?')) { string path; string name; DiskFile::SplitFilename(argv[0], path, name); filenames = DiskFile::FindFiles(path, name); } else { //start of shell expanded * patch. -- Michael Evans //The shell might expaned * so, if we have our name and we're creating, then filter for files... if ((parfilename.length() != 0) && (operation == opCreate)) { #ifdef WIN32 if (GetFileAttributes(argv[0]) & FILE_ATTRIBUTE_DIRECTORY) // != 0, but no need... #else //Not WIN32, probably *nix struct stat st; if (!(stat(argv[0], &st) == 0 && S_ISREG(st.st_mode))) #endif { cerr << "Skipping non-regular file: " << argv[0] << endl; argc--; argv++; options = false; continue; } }//end of shell expanded * patch. -- Michael Evans filenames = new list; filenames->push_back(argv[0]); } list::iterator fn = filenames->begin(); while (fn != filenames->end()) { // Convert filename from command line into a full path + filename string filename = DiskFile::GetCanonicalPathname(*fn); // If this is the first file on the command line, then it // is the main PAR2 file. if (parfilename.length() == 0) { // If we are verifying or repairing, the PAR2 file must // already exist if (operation != opCreate) { // Find the last '.' in the filename string::size_type where = filename.find_last_of('.'); if (where != string::npos) { // Get what follows the last '.' string tail = filename.substr(where+1); if (0 == stricmp(tail.c_str(), "par2")) { parfilename = filename; version = verPar2; } else if (0 == stricmp(tail.c_str(), "par") || (tail.size() == 3 && tolower(tail[0]) == 'p' && isdigit(tail[1]) && isdigit(tail[2]))) { parfilename = filename; version = verPar1; } } // If we haven't figured out which version of PAR file we // are using from the file extension, then presumable the // files filename was actually the name of a data file. if (version == verUnknown) { // Check for the existence of a PAR2 of PAR file. if (DiskFile::FileExists(filename + ".par2")) { version = verPar2; parfilename = filename + ".par2"; } else if (DiskFile::FileExists(filename + ".PAR2")) { version = verPar2; parfilename = filename + ".PAR2"; } else if (DiskFile::FileExists(filename + ".par")) { version = verPar1; parfilename = filename + ".par"; } else if (DiskFile::FileExists(filename + ".PAR")) { version = verPar1; parfilename = filename + ".PAR"; } } else { // Does the specified PAR or PAR2 file exist if (!DiskFile::FileExists(filename)) { version = verUnknown; } } if (version == verUnknown) { cerr << "The recovery file does not exist: " << filename << endl; return false; } } else { // We are creating a new file version = verPar2; parfilename = filename; } } else { // All other files must exist if (!DiskFile::FileExists(filename)) { cerr << "The source file does not exist: " << filename << endl; return false; } u64 filesize = DiskFile::GetFileSize(filename); // Ignore all 0 byte files if (filesize > 0) { extrafiles.push_back(ExtraFile(filename, filesize)); // track the total size of the source files and how // big the largest one is. totalsourcesize += filesize; if (largestsourcesize < filesize) largestsourcesize = filesize; } else { cout << "Skipping 0 byte file: " << filename << endl; } } ++fn; } delete filenames; } } argc--; argv++; } if (parfilename.length() == 0) { cerr << "You must specify a Recovery file." << endl; return false; } // Default noise level if (noiselevel == nlUnknown) { noiselevel = nlNormal; } // If we a creating, check the other parameters if (operation == opCreate) { // If no recovery file size scheme is specified then use Variable if (recoveryfilescheme == scUnknown) { recoveryfilescheme = scVariable; } // If neither block count not block size is specified if (blockcount == 0 && blocksize == 0) { // Use a block count of 2000 blockcount = 2000; } // If we are creating, the source files must be given. if (extrafiles.size() == 0) { // Does the par filename include the ".par2" on the end? if (parfilename.length() > 5 && 0 == stricmp(parfilename.substr(parfilename.length()-5, 5).c_str(), ".par2")) { // Yes it does. cerr << "You must specify a list of files when creating." << endl; return false; } else { // No it does not. // In that case check to see if the file exists, and if it does // assume that you wish to create par2 files for it. u64 filesize = 0; if (DiskFile::FileExists(parfilename) && (filesize = DiskFile::GetFileSize(parfilename)) > 0) { extrafiles.push_back(ExtraFile(parfilename, filesize)); // track the total size of the source files and how // big the largest one is. totalsourcesize += filesize; if (largestsourcesize < filesize) largestsourcesize = filesize; } else { // The file does not exist or it is empty. cerr << "You must specify a list of files when creating." << endl; return false; } } } // Strip the ".par2" from the end of the filename of the main PAR2 file. if (parfilename.length() > 5 && 0 == stricmp(parfilename.substr(parfilename.length()-5, 5).c_str(), ".par2")) { parfilename = parfilename.substr(0, parfilename.length()-5); } // Assume a redundancy of 5% if neither redundancy or recoveryblockcount were set. if (!redundancyset && !recoveryblockcountset) { redundancy = 5; } } // Assume a memory limit of 16MB if not specified. if (memorylimit == 0) { #ifdef WIN32 u64 TotalPhysicalMemory = 0; HMODULE hLib = ::LoadLibraryA("kernel32.dll"); if (NULL != hLib) { BOOL (WINAPI *pfn)(LPMEMORYSTATUSEX) = (BOOL (WINAPI*)(LPMEMORYSTATUSEX))::GetProcAddress(hLib, "GlobalMemoryStatusEx"); if (NULL != pfn) { MEMORYSTATUSEX mse; mse.dwLength = sizeof(mse); if (pfn(&mse)) { TotalPhysicalMemory = mse.ullTotalPhys; } } ::FreeLibrary(hLib); } if (TotalPhysicalMemory == 0) { MEMORYSTATUS ms; ::ZeroMemory(&ms, sizeof(ms)); ::GlobalMemoryStatus(&ms); TotalPhysicalMemory = ms.dwTotalPhys; } if (TotalPhysicalMemory == 0) { // Assume 128MB TotalPhysicalMemory = 128 * 1048576; } // Half of total physical memory memorylimit = (size_t)(TotalPhysicalMemory / 1048576 / 2); #else memorylimit = 16; #endif } memorylimit *= 1048576; return true; } nzbget-16.4/lib/par2/filechecksummer.h0000644000175000017500000001213712630544544017515 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #ifndef __FILECHECKSUMMER_H__ #define __FILECHECKSUMMER_H__ // This source file defines the FileCheckSummer object which is used // when scanning a data file to find blocks of undamaged data. // // The object uses a "window" into the data file and slides that window // along the file computing the CRC of the data in that window as it // goes. If the computed CRC matches the value for a block of data // from a target data file, then the MD5 Hash value is also computed // and compared with the value for that block of data. When a match // has been confirmed, the object jumps forward to where the next // block of data is expected to start. Whilst the file is being scanned // the object also computes the MD5 Hash of the whole file and of // the first 16k of the file for later tests. class FileCheckSummer { public: FileCheckSummer(DiskFile *diskfile, u64 blocksize, const u32 (&windowtable)[256], u32 windowmask); ~FileCheckSummer(void); // Start reading the file at the beginning bool Start(void); // Jump ahead the specified distance bool Jump(u64 distance); // Step forward one byte bool Step(void); // Return the current checksum u32 Checksum(void) const; // Compute and return the current hash MD5Hash Hash(void); // Compute short values of checksum and hash u32 ShortChecksum(u64 blocklength); MD5Hash ShortHash(u64 blocklength); // Do we have less than a full block of data bool ShortBlock(void) const; u64 BlockLength(void) const; // Return the current file offset u64 Offset(void) const; // Return the full file hash and the 16k file hash void GetFileHashes(MD5Hash &hashfull, MD5Hash &hash16k) const; // Which disk file is this const DiskFile* GetDiskFile(void) const {return diskfile;} protected: DiskFile *diskfile; u64 blocksize; const u32 (&windowtable)[256]; u32 windowmask; u64 filesize; u64 currentoffset; // file offset for current window position char *buffer; // buffer for reading from the file char *outpointer; // position in buffer of scan window char *inpointer; // &outpointer[blocksize]; char *tailpointer; // after last valid data in buffer // File offset for next read u64 readoffset; // The current checksum u32 checksum; // MD5 hash of whole file and of first 16k MD5Context contextfull; MD5Context context16k; protected: //void ComputeCurrentCRC(void); void UpdateHashes(u64 offset, const void *buffer, size_t length); //// Fill the buffers with more data from disk bool Fill(void); }; // Return the current checksum inline u32 FileCheckSummer::Checksum(void) const { return checksum; } // Return the current block length inline u64 FileCheckSummer::BlockLength(void) const { return min(blocksize, filesize-currentoffset); } // Return whether or not the current block is a short one. inline bool FileCheckSummer::ShortBlock(void) const { return BlockLength() < blocksize; } // Return the current file offset inline u64 FileCheckSummer::Offset(void) const { return currentoffset; } // Step forward one byte inline bool FileCheckSummer::Step(void) { // Are we already at the end of the file if (currentoffset >= filesize) return false; // Advance the file offset and check to see if // we have reached the end of the file if (++currentoffset >= filesize) { currentoffset = filesize; tailpointer = outpointer = buffer; memset(buffer, 0, (size_t)blocksize); checksum = 0; return true; } // Get the incoming and outgoing characters char inch = *inpointer++; char outch = *outpointer++; // Update the checksum checksum = windowmask ^ CRCSlideChar(windowmask ^ checksum, inch, outch, windowtable); // Can the window slide further if (outpointer < &buffer[blocksize]) return true; assert(outpointer == &buffer[blocksize]); // Copy the data back to the beginning of the buffer memmove(buffer, outpointer, (size_t)blocksize); inpointer = outpointer; outpointer = buffer; tailpointer -= blocksize; // Fill the rest of the buffer return Fill(); } #endif // __FILECHECKSUMMER_H__ nzbget-16.4/lib/par2/verificationhashtable.h0000644000175000017500000003223312630544544020704 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #ifndef __VERIFICATIONHASHTABLE_H__ #define __VERIFICATIONHASHTABLE_H__ class Par2RepairerSourceFile; class VerificationHashTable; // The VerificationHashEntry objects form the nodes of a binary trees stored // in a VerificationHashTable object. // There is one VerificationHashEntry object for each data block in the original // source files. class VerificationHashEntry { public: VerificationHashEntry(Par2RepairerSourceFile *_sourcefile, DataBlock *_datablock, bool _firstblock, const FILEVERIFICATIONENTRY *_verificationentry) { sourcefile = _sourcefile; datablock = _datablock; firstblock = _firstblock; crc = _verificationentry->crc; hash = _verificationentry->hash; left = right = same = next = 0; } ~VerificationHashEntry(void) { delete left; delete right; delete same; } // Insert the current object is a child of the specified parent void Insert(VerificationHashEntry **parent); // Search (starting at the specified parent) for an object with a matching crc static const VerificationHashEntry* Search(const VerificationHashEntry *entry, u32 crc); // Search (starting at the specified parent) for an object with a matching hash static const VerificationHashEntry* Search(const VerificationHashEntry *entry, const MD5Hash &hash); // Comparison operators for searching bool operator <(const VerificationHashEntry &r) const { return (crc < r.crc) || ((crc == r.crc) && (hash < r.hash)); } bool operator >(const VerificationHashEntry &r) const { return (crc > r.crc) || ((crc == r.crc) && (hash > r.hash)); } bool operator ==(const VerificationHashEntry &r) const { return (crc == r.crc) && (hash == r.hash); } bool operator <=(const VerificationHashEntry &r) const {return !operator>(r);} bool operator >=(const VerificationHashEntry &r) const {return !operator<(r);} bool operator !=(const VerificationHashEntry &r) const {return !operator==(r);} // Data Par2RepairerSourceFile* SourceFile(void) const {return sourcefile;} const DataBlock* GetDataBlock(void) const {return datablock;} bool FirstBlock(void) const {return firstblock;} // Set/Check the associated datablock void SetBlock(DiskFile *diskfile, u64 offset) const; bool IsSet(void) const; u32 Checksum(void) const {return crc;} const MD5Hash& Hash(void) const {return hash;} VerificationHashEntry* Same(void) const {return same;} VerificationHashEntry* Next(void) const {return next;} void Next(VerificationHashEntry *_next) {next = _next;} protected: // Data Par2RepairerSourceFile *sourcefile; DataBlock *datablock; bool firstblock; u32 crc; MD5Hash hash; protected: // Binary tree VerificationHashEntry *left; VerificationHashEntry *right; // Linked list of entries with the same crc and hash VerificationHashEntry *same; // Linked list of entries in sequence for same file VerificationHashEntry *next; }; inline void VerificationHashEntry::SetBlock(DiskFile *diskfile, u64 offset) const { datablock->SetLocation(diskfile, offset); } inline bool VerificationHashEntry::IsSet(void) const { return datablock->IsSet(); } // Insert a new entry in the tree inline void VerificationHashEntry::Insert(VerificationHashEntry **parent) { while (*parent) { if (**parent < *this) { parent = &(*parent)->right; } else if (**parent > *this) { parent = &(*parent)->left; } else { break; } } while (*parent) { parent = &(*parent)->same; } *parent = this; } // Search the tree for an entry with the correct crc inline const VerificationHashEntry* VerificationHashEntry::Search(const VerificationHashEntry *entry, u32 crc) { while (entry) { if (entry->crc < crc) { entry = entry->right; } else if (entry->crc > crc) { entry = entry->left; } else { break; } } return entry; } // Search the tree for an entry with the correct hash inline const VerificationHashEntry* VerificationHashEntry::Search(const VerificationHashEntry *entry, const MD5Hash &hash) { u32 crc = entry->crc; while (entry) { if ((entry->crc < crc) || ((entry->crc == crc) && (entry->hash < hash))) { entry = entry->right; } else if ((entry->crc > crc) || ((entry->crc == crc) && (entry->hash > hash))) { entry = entry->left; } else { break; } } return entry; } // The VerificationHashTable object contains all of the VerificationHashEntry objects // and is used to find matches for blocks of data in a target file that is being // scanned. // It is initialised by loading data from all available verification packets for the // source files. class VerificationHashTable { public: VerificationHashTable(void); ~VerificationHashTable(void); void SetLimit(u32 limit); // Load the data from the verification packet void Load(Par2RepairerSourceFile *sourcefile, u64 blocksize); // Try to find a match. // nextentry - The entry which we expect to find next. This is used // when a sequence of matches are found. // sourcefile - Which source file we would prefer to find a match for // if there are more than one possible match (with the // same crc and hash). // checksummer - Provides the crc and hash values being tested. // duplicate - Set on return if the match would have been valid except // for the fact that the block has already been found. const VerificationHashEntry* FindMatch(const VerificationHashEntry *nextentry, const Par2RepairerSourceFile *sourcefile, FileCheckSummer &checksummer, bool &duplicate) const; // Look up based on the block crc const VerificationHashEntry* Lookup(u32 crc) const; // Continue lookup based on the block hash const VerificationHashEntry* Lookup(const VerificationHashEntry *entry, const MD5Hash &hash); protected: VerificationHashEntry **hashtable; unsigned int hashmask; }; // Search for an entry with the specified crc inline const VerificationHashEntry* VerificationHashTable::Lookup(u32 crc) const { if (hashmask) { return VerificationHashEntry::Search(hashtable[crc & hashmask], crc); } return 0; } // Search for an entry with the specified hash inline const VerificationHashEntry* VerificationHashTable::Lookup(const VerificationHashEntry *entry, const MD5Hash &hash) { return VerificationHashEntry::Search(entry, hash); } inline const VerificationHashEntry* VerificationHashTable::FindMatch(const VerificationHashEntry *suggestedentry, const Par2RepairerSourceFile *sourcefile, FileCheckSummer &checksummer, bool &duplicate) const { duplicate = false; // Get the current checksum from the checksummer u32 crc = checksummer.Checksum(); MD5Hash hash; bool havehash = false; // Do we know what the next entry should be if (0 != suggestedentry) { // Is the suggested match supposed to be the last one in the file if (suggestedentry->Next() == 0) { // How long should the last block be u64 length = suggestedentry->GetDataBlock()->GetLength(); // Get a short checksum from the checksummer u32 checksum = checksummer.ShortChecksum(length); // Is the checksum correct if (checksum == suggestedentry->Checksum()) { // Get a short hash from the checksummer hash = checksummer.ShortHash(length); // If the hash matches as well, then return it if (hash == suggestedentry->Hash()) { return suggestedentry; } } } // If the suggested entry has not already been found, compare the checksum else if (!suggestedentry->IsSet() && suggestedentry->Checksum() == crc) { // Get the hash value from the checksummer havehash = true; hash = checksummer.Hash(); // If the hash value matches, then return it. if (hash == suggestedentry->Hash()) { return suggestedentry; } } } // Look for other possible matches for the checksum const VerificationHashEntry *nextentry = VerificationHashEntry::Search(hashtable[crc & hashmask], crc); if (0 == nextentry) return 0; // If we don't have the hash yet, get it if (!havehash) { hash = checksummer.Hash(); } // Look for an entry with a matching hash nextentry = VerificationHashEntry::Search(nextentry, hash); if (0 == nextentry) return 0; // Is there one match with the same checksum and hash, or many if (nextentry->Same() == 0) { // If the match is for a block that is part of a target file // for which we already have a complete match, then don't // return it. if (nextentry->SourceFile()->GetCompleteFile() != 0) { duplicate = true; return 0; } // If we are close to the end of the file and the block // length is wrong, don't return it because it is an // invalid match if (checksummer.ShortBlock() && checksummer.BlockLength() != nextentry->GetDataBlock()->GetLength()) { return 0; } // If the match was at the start of the file and it is the first // block for a target file, then return it. if (nextentry->FirstBlock() && checksummer.Offset() == 0) { return nextentry; } // Was this match actually the one which had originally been suggested // but which has presumably already been found if (nextentry == suggestedentry) { // Was the existing match in the same file as the new match if (nextentry->IsSet() && nextentry->GetDataBlock()->GetDiskFile() == checksummer.GetDiskFile()) { // Yes. Don't return it duplicate = true; return 0; } else { // No, it was in a different file. Return it. // This ensures that we can find a perfect match for a target // file even if some of the blocks had already been found // in a different file. return nextentry; } } else { // return it only if it has not already been used if (nextentry->IsSet()) { duplicate = true; return 0; } return nextentry; } } // Do we prefer to match entries for a particular source file if (0 != sourcefile) { const VerificationHashEntry *currententry = nextentry; nextentry = 0; // We don't want entries for the wrong source file, ones that // have already been matched, or ones that are the wrong length while (currententry && (currententry->SourceFile() != sourcefile || currententry->IsSet() || (checksummer.ShortBlock() && checksummer.BlockLength() != (currententry->GetDataBlock()->GetLength())) ) ) { // If we found an unused entry (which was presumably for the wrong // source file) remember it (providing it is the correct length). if (0 == nextentry && !(currententry->IsSet() || (checksummer.ShortBlock() && checksummer.BlockLength() != (currententry->GetDataBlock()->GetLength())) ) ) { nextentry = currententry; } currententry = currententry->Same(); } // If we found an unused entry for the source file we want, return it if (0 != currententry) return currententry; } // Check for an unused entry which is the correct length while (nextentry && (nextentry->IsSet() || (checksummer.ShortBlock() && checksummer.BlockLength() != nextentry->GetDataBlock()->GetLength()) ) ) { nextentry = nextentry->Same(); } // Return what we have found if (nextentry == 0) { duplicate = true; } return nextentry; } #endif // __VERIFICATIONHASHTABLE_H__ nzbget-16.4/lib/par2/verificationpacket.h0000644000175000017500000000503112630544544020214 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #ifndef __VERIFICATIONPACKET_H__ #define __VERIFICATIONPACKET_H__ // The file verification packet stores details that allow individual blocks // of valid data within a damaged file to be identified. class VerificationPacket : public CriticalPacket { public: // Construct the packet VerificationPacket(void) {}; ~VerificationPacket(void) {}; // Create a packet large enough for the specified number of blocks bool Create(u32 blockcount); // Set the fileid (computed from the file description packet). void FileId(const MD5Hash &fileid); // Set the block hash and block crc for a specific data block. void SetBlockHashAndCRC(u32 blocknumber, const MD5Hash &hash, u32 crc); // Load a verification packet from a specified file bool Load(DiskFile *diskfile, u64 offset, PACKET_HEADER &header); // Get the FileId const MD5Hash& FileId(void) const; // Get the block count u32 BlockCount(void) const; // Get a specific verification entry const FILEVERIFICATIONENTRY* VerificationEntry(u32 blocknumber) const; protected: u32 blockcount; }; inline const MD5Hash& VerificationPacket::FileId(void) const { assert(packetdata != 0); return ((FILEVERIFICATIONPACKET*)packetdata)->fileid; } inline u32 VerificationPacket::BlockCount(void) const { assert(packetdata != 0); return blockcount; } inline const FILEVERIFICATIONENTRY* VerificationPacket::VerificationEntry(u32 blocknumber) const { assert(packetdata != 0); // return &((FILEVERIFICATIONPACKET*)packetdata)->entries()[blocknumber]; return &((FILEVERIFICATIONPACKET*)packetdata)->entries[blocknumber]; } #endif // __VERIFICATIONPACKET_H__ nzbget-16.4/lib/par2/par2creatorsourcefile.cpp0000644000175000017500000002363012630544544021207 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #include "par2cmdline.h" #ifdef _MSC_VER #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif #endif Par2CreatorSourceFile::Par2CreatorSourceFile(void) { descriptionpacket = 0; verificationpacket = 0; diskfile = 0; blockcount = 0; //diskfilename; //parfilename; contextfull = 0; } Par2CreatorSourceFile::~Par2CreatorSourceFile(void) { delete descriptionpacket; delete verificationpacket; delete diskfile; delete contextfull; } // Open the source file, compute the MD5 Hash of the whole file and the first // 16k of the file, and then compute the FileId and store the results // in a file description packet and a file verification packet. bool Par2CreatorSourceFile::Open(CommandLine::NoiseLevel noiselevel, const CommandLine::ExtraFile &extrafile, u64 blocksize, bool deferhashcomputation) { // Get the filename and filesize diskfilename = extrafile.FileName(); filesize = extrafile.FileSize(); // Work out how many blocks the file will be sliced into blockcount = (u32)((filesize + blocksize-1) / blocksize); // Determine what filename to record in the PAR2 files string::size_type where; if (string::npos != (where = diskfilename.find_last_of('\\')) || string::npos != (where = diskfilename.find_last_of('/'))) { parfilename = diskfilename.substr(where+1); } else { parfilename = diskfilename; } // Create the Description and Verification packets descriptionpacket = new DescriptionPacket; descriptionpacket->Create(parfilename, filesize); verificationpacket = new VerificationPacket; verificationpacket->Create(blockcount); // Create the diskfile object diskfile = new DiskFile; // Open the source file if (!diskfile->Open(diskfilename, filesize)) return false; // Do we want to defer the computation of the full file hash, and // the block crc and hashes. This is only permitted if there // is sufficient memory available to create all recovery blocks // in one pass of the source files (i.e. chunksize == blocksize) if (deferhashcomputation) { // Initialise a buffer to read the first 16k of the source file size_t buffersize = 16 * 1024; if (buffersize > filesize) buffersize = (size_t)filesize; char *buffer = new char[buffersize]; // Read the data from the file if (!diskfile->Read(0, buffer, buffersize)) { diskfile->Close(); delete [] buffer; return false; } // Compute the hash of the data read from the file MD5Context context; context.Update(buffer, buffersize); delete [] buffer; MD5Hash hash; context.Final(hash); // Store the hash in the descriptionpacket and compute the file id descriptionpacket->Hash16k(hash); // Compute the fileid and store it in the verification packet. descriptionpacket->ComputeFileId(); verificationpacket->FileId(descriptionpacket->FileId()); // Allocate an MD5 context for computing the file hash // during the recovery data generation phase contextfull = new MD5Context; } else { // Initialise a buffer to read the source file size_t buffersize = 1024*1024; if (buffersize > min(blocksize,filesize)) buffersize = (size_t)min(blocksize,filesize); char *buffer = new char[buffersize]; // Get ready to start reading source file to compute the hashes and crcs u64 offset = 0; u32 blocknumber = 0; u64 need = blocksize; MD5Context filecontext; MD5Context blockcontext; u32 blockcrc = 0; // Whilst we have not reached the end of the file while (offset < filesize) { // Work out how much we can read size_t want = (size_t)min(filesize-offset, (u64)buffersize); // Read some data from the file into the buffer if (!diskfile->Read(offset, buffer, want)) { diskfile->Close(); delete [] buffer; return false; } // If the new data passes the 16k boundary, compute the 16k hash for the file if (offset < 16384 && offset + want >= 16384) { filecontext.Update(buffer, (size_t)(16384-offset)); MD5Context temp = filecontext; MD5Hash hash; temp.Final(hash); // Store the 16k hash in the file description packet descriptionpacket->Hash16k(hash); if (offset + want > 16384) { filecontext.Update(&buffer[16384-offset], (size_t)(offset+want)-16384); } } else { filecontext.Update(buffer, want); } // Get ready to update block hashes and crcs u32 used = 0; // Whilst we have not used all of the data we just read while (used < want) { // How much of it can we use for the current block u32 use = (u32)min(need, (u64)(want-used)); blockcrc = ~0 ^ CRCUpdateBlock(~0 ^ blockcrc, use, &buffer[used]); blockcontext.Update(&buffer[used], use); used += use; need -= use; // Have we finished the current block if (need == 0) { MD5Hash blockhash; blockcontext.Final(blockhash); // Store the block hash and block crc in the file verification packet. verificationpacket->SetBlockHashAndCRC(blocknumber, blockhash, blockcrc); blocknumber++; // More blocks if (blocknumber < blockcount) { need = blocksize; blockcontext.Reset(); blockcrc = 0; } } } if (noiselevel > CommandLine::nlQuiet) { // Display progress u32 oldfraction = (u32)(1000 * offset / filesize); offset += want; u32 newfraction = (u32)(1000 * offset / filesize); if (oldfraction != newfraction) { cout << newfraction/10 << '.' << newfraction%10 << "%\r" << flush; } } } // Did we finish the last block if (need > 0) { blockcrc = ~0 ^ CRCUpdateBlock(~0 ^ blockcrc, (size_t)need); blockcontext.Update((size_t)need); MD5Hash blockhash; blockcontext.Final(blockhash); // Store the block hash and block crc in the file verification packet. verificationpacket->SetBlockHashAndCRC(blocknumber, blockhash, blockcrc); blocknumber++; need = 0; } // Finish computing the file hash. MD5Hash filehash; filecontext.Final(filehash); // Store the file hash in the file description packet. descriptionpacket->HashFull(filehash); // Did we compute the 16k hash. if (offset < 16384) { // Store the 16k hash in the file description packet. descriptionpacket->Hash16k(filehash); } delete [] buffer; // Compute the fileid and store it in the verification packet. descriptionpacket->ComputeFileId(); verificationpacket->FileId(descriptionpacket->FileId()); } return true; } void Par2CreatorSourceFile::Close(void) { diskfile->Close(); } void Par2CreatorSourceFile::RecordCriticalPackets(list &criticalpackets) { // Add the file description packet and file verification packet to // the critical packet list. criticalpackets.push_back(descriptionpacket); criticalpackets.push_back(verificationpacket); } bool Par2CreatorSourceFile::CompareLess(const Par2CreatorSourceFile* const &left, const Par2CreatorSourceFile* const &right) { // Sort source files based on fileid return left->descriptionpacket->FileId() < right->descriptionpacket->FileId(); } const MD5Hash& Par2CreatorSourceFile::FileId(void) const { // Get the file id hash return descriptionpacket->FileId(); } void Par2CreatorSourceFile::InitialiseSourceBlocks(vector::iterator &sourceblock, u64 blocksize) { for (u32 blocknum=0; blocknumSetLocation(diskfile, // file blocknum * blocksize); // offset sourceblock->SetLength(min(blocksize, filesize - (u64)blocknum * blocksize)); // length sourceblock++; } } void Par2CreatorSourceFile::UpdateHashes(u32 blocknumber, const void *buffer, size_t length) { // Compute the crc and hash of the data u32 blockcrc = ~0 ^ CRCUpdateBlock(~0, length, buffer); MD5Context blockcontext; blockcontext.Update(buffer, length); MD5Hash blockhash; blockcontext.Final(blockhash); // Store the results in the verification packet verificationpacket->SetBlockHashAndCRC(blocknumber, blockhash, blockcrc); // Update the full file hash, but don't go beyond the end of the file if (length > filesize - blocknumber * length) { length = (size_t)(filesize - blocknumber * (u64)length); } assert(contextfull != 0); contextfull->Update(buffer, length); } void Par2CreatorSourceFile::FinishHashes(void) { assert(contextfull != 0); // Finish computation of the full file hash MD5Hash hash; contextfull->Final(hash); // Store it in the description packet descriptionpacket->HashFull(hash); } nzbget-16.4/lib/par2/par2fileformat.h0000644000175000017500000001570112630544544017264 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #ifndef __PAR2FILEFORMAT_H__ #define __PAR2FILEFORMAT_H__ // This file defines the format of a PAR2 file. // PAR2 files consist of one or more "packets" that contain information // that is required to be able to verify and repair damaged data files. // All packets start with a short "header" which contains information // used to describe what sort of data is stored in the rest of the packet // and also to allow that data to be verified. // This file details the format for the following packet types described // in the PAR 2.0 specification: // Main Packet struct MAINPACKET // File Description Packet struct FILEDESCRIPTIONPACKET // Input File Slice Checksum Packet struct FILEVERIFICATIONPACKET // Recovery Slice Packet struct RECOVERYBLOCKPACKET // Creator Packet struct CREATORPACKET #ifdef WIN32 #pragma pack(push, 1) #define PACKED #else #define PACKED __attribute__ ((packed)) #endif #ifdef _MSC_VER #pragma warning(disable:4200) #endif // All numeric fields in the file format are in LITTLE ENDIAN format. // The types leu32 and leu64 are defined in letype.h // Two simple types used in the packet header. struct MAGIC {u8 magic[8];} PACKED; struct PACKETTYPE {u8 type[16];} PACKED; // Every packet starts with a packet header. struct PACKET_HEADER { // Header MAGIC magic; // = {'P', 'A', 'R', '2', '\0', 'P', 'K', 'T'} leu64 length; // Length of entire packet including header MD5Hash hash; // Hash of entire packet excepting the first 3 fields MD5Hash setid; // Normally computed as the Hash of body of "Main Packet" PACKETTYPE type; // Used to specify the meaning of the rest of the packet } PACKED; // The file verification packet is used to determine whether or not any // parts of a damaged file are useable. // It contains a FileId used to pair it with a corresponding file description // packet, followed by an array of hash and crc values. The number of entries in // the array can be determined from the packet_length. struct FILEVERIFICATIONENTRY { MD5Hash hash; leu32 crc; } PACKED; struct FILEVERIFICATIONPACKET { PACKET_HEADER header; // Body MD5Hash fileid; // MD5hash of file_hash_16k, file_length, file_name FILEVERIFICATIONENTRY entries[]; } PACKED; // The file description packet is used to record the name of the file, // its size, and the Hash of both the whole file and the first 16k of // the file. // If the name of the file is an exact multiple of 4 characters in length // then it may not have a NULL termination. If the name of the file is not // an exact multiple of 4, then it will be padded with 0 bytes at the // end to make it up to a multiple of 4. struct FILEDESCRIPTIONPACKET { PACKET_HEADER header; // Body MD5Hash fileid; // MD5hash of [hash16k, length, name] MD5Hash hashfull; // MD5 Hash of the whole file MD5Hash hash16k; // MD5 Hash of the first 16k of the file leu64 length; // Length of the file u8 name[]; // Name of the file, padded with 1 to 3 zero bytes to reach // a multiple of 4 bytes. // Actual length can be determined from overall packet // length and then working backwards to find the first non // zero character. //u8* name(void) {return (u8*)&this[1];} //const u8* name(void) const {return (const u8*)&this[1];} } PACKED; // The main packet is used to tie together the other packets in a recovery file. // It specifies the block size used to virtually slice the source files, a count // of the number of source files, and an array of Hash values used to specify // in what order the source files are processed. // Each entry in the fileid array corresponds with the fileid value // in a file description packet and a file verification packet. // The fileid array may contain more entries than the count of the number // of recoverable files. The extra entries correspond to files that were not // used during the creation of the recovery files and which may not therefore // be repaired if they are found to be damaged. struct MAINPACKET { PACKET_HEADER header; // Body leu64 blocksize; leu32 recoverablefilecount; MD5Hash fileid[0]; //MD5Hash* fileid(void) {return (MD5Hash*)&this[1];} //const MD5Hash* fileid(void) const {return (const MD5Hash*)&this[1];} } PACKED; // The creator packet is used to identify which program created a particular // recovery file. It is not required for verification or recovery of damaged // files. struct CREATORPACKET { PACKET_HEADER header; // Body u8 client[]; //u8* client(void) {return (u8*)&this[1];} } PACKED; // The recovery block packet contains a single block of recovery data along // with the exponent value used during the computation of that block. struct RECOVERYBLOCKPACKET { PACKET_HEADER header; // Body leu32 exponent; // unsigned long data[]; // unsigned long* data(void) {return (unsigned long*)&this[1];} } PACKED; #ifdef _MSC_VER #pragma warning(default:4200) #endif #ifdef WIN32 #pragma pack(pop) #endif #undef PACKED // Operators for comparing the MAGIC and PACKETTYPE values inline bool operator == (const MAGIC &left, const MAGIC &right) { return (0==memcmp(&left, &right, sizeof(left))); } inline bool operator != (const MAGIC &left, const MAGIC &right) { return !operator==(left, right); } inline bool operator == (const PACKETTYPE &left, const PACKETTYPE &right) { return (0==memcmp(&left, &right, sizeof(left))); } inline bool operator != (const PACKETTYPE &left, const PACKETTYPE &right) { return !operator==(left, right); } extern MAGIC packet_magic; extern PACKETTYPE fileverificationpacket_type; extern PACKETTYPE filedescriptionpacket_type; extern PACKETTYPE mainpacket_type; extern PACKETTYPE recoveryblockpacket_type; extern PACKETTYPE creatorpacket_type; #endif //__PAR2FILEFORMAT_H__ nzbget-16.4/lib/par2/crc.cpp0000644000175000017500000000376412630544544015457 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #include "par2cmdline.h" #ifdef _MSC_VER #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif #endif // The one and only CCITT CRC32 lookup table crc32table ccitttable(0xEDB88320L); // Construct the CRC32 lookup table from the specified polynomial void GenerateCRC32Table(u32 polynomial, u32 (&table)[256]) { for (u32 i = 0; i <= 255 ; i++) { u32 crc = i; for (u32 j = 0; j < 8; j++) { crc = (crc >> 1) ^ ((crc & 1) ? polynomial : 0); } table[i] = crc; } } // Construct a CRC32 lookup table for windowing void GenerateWindowTable(u64 window, u32 (&target)[256]) { for (u32 i=0; i<=255; i++) { u32 crc = ccitttable.table[i]; for (u64 j=0; j> 8) & 0x00ffffffL) ^ ccitttable.table[(u8)crc]; } target[i] = crc; } } // Construct the mask value to apply to the CRC when windowing u32 ComputeWindowMask(u64 window) { u32 result = ~0; while (window > 0) { result = CRCUpdateChar(result, (char)0); window--; } result ^= ~0; return result; } nzbget-16.4/lib/par2/reedsolomon.cpp0000644000175000017500000002203712630544544017230 0ustar andreasandreas// This file is part of par2cmdline (a PAR 2.0 compatible file verification and // repair tool). See http://parchive.sourceforge.net for details of PAR 2.0. // // Copyright (c) 2003 Peter Brian Clements // // par2cmdline 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. // // par2cmdline 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 #include "par2cmdline.h" #ifdef _MSC_VER #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif #endif u32 gcd(u32 a, u32 b) { if (a && b) { while (a && b) { if (a>b) { a = a%b; } else { b = b%a; } } return a+b; } else { return 0; } } template <> bool ReedSolomon::SetInput(const vector &present) { inputcount = (u32)present.size(); datapresentindex = new u32[inputcount]; datamissingindex = new u32[inputcount]; database = new G::ValueType[inputcount]; G::ValueType base = 1; for (unsigned int index=0; index bool ReedSolomon::SetInput(u32 count) { inputcount = count; datapresentindex = new u32[inputcount]; datamissingindex = new u32[inputcount]; database = new G::ValueType[inputcount]; G::ValueType base = 1; for (unsigned int index=0; index bool ReedSolomon::Process(size_t size, u32 inputindex, const void *inputbuffer, u32 outputindex, void *outputbuffer) { // Look up the appropriate element in the RS matrix Galois8 factor = leftmatrix[outputindex * (datapresent + datamissing) + inputindex]; // Do nothing if the factor happens to be 0 if (factor == 0) return eSuccess; #ifdef LONGMULTIPLY // The 8-bit long multiplication tables Galois8 *table = glmt->tables; // Split the factor into Low and High bytes unsigned int fl = (factor >> 0) & 0xff; // Get the four separate multiplication tables Galois8 *LL = &table[(0*256 + fl) * 256 + 0]; // factor.low * source.low // Combine the four multiplication tables into two unsigned int L[256]; unsigned int *pL = &L[0]; for (unsigned int i=0; i<256; i++) { *pL = *LL; pL++; LL++; } // Treat the buffers as arrays of 32-bit unsigned ints. u32 *src4 = (u32 *)inputbuffer; u32 *end4 = (u32 *)&((u8*)inputbuffer)[size & ~3]; u32 *dst4 = (u32 *)outputbuffer; // Process the data while (src4 < end4) { u32 s = *src4++; // Use the two lookup tables computed earlier *dst4++ ^= (L[(s >> 0) & 0xff] ) ^ (L[(s >> 8) & 0xff] << 8 ) ^ (L[(s >> 16)& 0xff] << 16) ^ (L[(s >> 24)& 0xff] << 24); } // Process any left over bytes at the end of the buffer if (size & 3) { u8 *src1 = &((u8*)inputbuffer)[size & ~3]; u8 *end1 = &((u8*)inputbuffer)[size]; u8 *dst1 = &((u8*)outputbuffer)[size & ~3]; // Process the data while (src1 < end1) { u8 s = *src1++; *dst1++ ^= L[s]; } } #else // Treat the buffers as arrays of 16-bit Galois values. Galois8 *src = (Galois8 *)inputbuffer; Galois8 *end = (Galois8 *)&((u8*)inputbuffer)[size]; Galois8 *dst = (Galois8 *)outputbuffer; // Process the data while (src < end) { *dst++ += *src++ * factor; } #endif return eSuccess; } //////////////////////////////////////////////////////////////////////////////////////////// // Set which of the source files are present and which are missing // and compute the base values to use for the vandermonde matrix. template <> bool ReedSolomon::SetInput(const vector &present) { inputcount = (u32)present.size(); datapresentindex = new u32[inputcount]; datamissingindex = new u32[inputcount]; database = new G::ValueType[inputcount]; unsigned int logbase = 0; for (unsigned int index=0; index= G::Limit) { cerr << "Too many input blocks for Reed Solomon matrix." << endl; return false; } G::ValueType base = G(logbase++).ALog(); database[index] = base; } return true; } // Record that the specified number of source files are all present // and compute the base values to use for the vandermonde matrix. template <> bool ReedSolomon::SetInput(u32 count) { inputcount = count; datapresentindex = new u32[inputcount]; datamissingindex = new u32[inputcount]; database = new G::ValueType[inputcount]; unsigned int logbase = 0; for (unsigned int index=0; index= G::Limit) { cerr << "Too many input blocks for Reed Solomon matrix." << endl; return false; } G::ValueType base = G(logbase++).ALog(); database[index] = base; } return true; } template <> bool ReedSolomon::Process(size_t size, u32 inputindex, const void *inputbuffer, u32 outputindex, void *outputbuffer) { // Look up the appropriate element in the RS matrix Galois16 factor = leftmatrix[outputindex * (datapresent + datamissing) + inputindex]; // Do nothing if the factor happens to be 0 if (factor == 0) return eSuccess; #ifdef LONGMULTIPLY // The 8-bit long multiplication tables Galois16 *table = glmt->tables; // Split the factor into Low and High bytes unsigned int fl = (factor >> 0) & 0xff; unsigned int fh = (factor >> 8) & 0xff; // Get the four separate multiplication tables Galois16 *LL = &table[(0*256 + fl) * 256 + 0]; // factor.low * source.low Galois16 *LH = &table[(1*256 + fl) * 256 + 0]; // factor.low * source.high Galois16 *HL = &table[(1*256 + 0) * 256 + fh]; // factor.high * source.low Galois16 *HH = &table[(2*256 + fh) * 256 + 0]; // factor.high * source.high // Combine the four multiplication tables into two unsigned int L[256]; unsigned int H[256]; #if __BYTE_ORDER == __LITTLE_ENDIAN unsigned int *pL = &L[0]; unsigned int *pH = &H[0]; #else unsigned int *pL = &H[0]; unsigned int *pH = &L[0]; #endif for (unsigned int i=0; i<256; i++) { #if __BYTE_ORDER == __LITTLE_ENDIAN *pL = *LL + *HL; #else unsigned int temp = *LL + *HL; *pL = (temp >> 8) & 0xff | (temp << 8) & 0xff00; #endif pL++; LL++; HL+=256; #if __BYTE_ORDER == __LITTLE_ENDIAN *pH = *LH + *HH; #else temp = *LH + *HH; *pH = (temp >> 8) & 0xff | (temp << 8) & 0xff00; #endif pH++; LH++; HH++; } // Treat the buffers as arrays of 32-bit unsigned ints. u32 *src = (u32 *)inputbuffer; u32 *end = (u32 *)&((u8*)inputbuffer)[size]; u32 *dst = (u32 *)outputbuffer; // Process the data while (src < end) { u32 s = *src++; // Use the two lookup tables computed earlier //#if __BYTE_ORDER == __LITTLE_ENDIAN u32 d = *dst ^ (L[(s >> 0) & 0xff] ) ^ (H[(s >> 8) & 0xff] ) ^ (L[(s >> 16)& 0xff] << 16) ^ (H[(s >> 24)& 0xff] << 16); *dst++ = d; //#else // *dst++ ^= (L[(s >> 8) & 0xff] ) // ^ (H[(s >> 0) & 0xff] ) // ^ (L[(s >> 24)& 0xff] << 16) // ^ (H[(s >> 16)& 0xff] << 16); //#endif } #else // Treat the buffers as arrays of 16-bit Galois values. // BUG: This only works for __LITTLE_ENDIAN Galois16 *src = (Galois16 *)inputbuffer; Galois16 *end = (Galois16 *)&((u8*)inputbuffer)[size]; Galois16 *dst = (Galois16 *)outputbuffer; // Process the data while (src < end) { *dst++ += *src++ * factor; } #endif return eSuccess; } nzbget-16.4/lib/catch/0000755000175000017500000000000012630544544014410 5ustar andreasandreasnzbget-16.4/lib/catch/catch.h0000644000175000017500000122015212630544544015646 0ustar andreasandreas/* * CATCH v1.1 build 14 (develop branch) * Generated: 2015-03-04 18:32:24.627737 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. * * Distributed under the Boost Software License, Version 1.0. (See accompanying * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ #ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED #define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED #define TWOBLUECUBES_CATCH_HPP_INCLUDED // #included from: internal/catch_suppress_warnings.h #define TWOBLUECUBES_CATCH_SUPPRESS_WARNINGS_H_INCLUDED #ifdef __clang__ # ifdef __ICC // icpc defines the __clang__ macro # pragma warning(push) # pragma warning(disable: 161 1682) # else // __ICC # pragma clang diagnostic ignored "-Wglobal-constructors" # pragma clang diagnostic ignored "-Wvariadic-macros" # pragma clang diagnostic ignored "-Wc99-extensions" # pragma clang diagnostic ignored "-Wunused-variable" # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wpadded" # pragma clang diagnostic ignored "-Wc++98-compat" # pragma clang diagnostic ignored "-Wc++98-compat-pedantic" # endif #elif defined __GNUC__ # pragma GCC diagnostic ignored "-Wvariadic-macros" # pragma GCC diagnostic ignored "-Wunused-variable" # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wpadded" #endif #if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) # define CATCH_IMPL #endif #ifdef CATCH_IMPL # ifndef CLARA_CONFIG_MAIN # define CLARA_CONFIG_MAIN_NOT_DEFINED # define CLARA_CONFIG_MAIN # endif #endif // #included from: internal/catch_notimplemented_exception.h #define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_H_INCLUDED // #included from: catch_common.h #define TWOBLUECUBES_CATCH_COMMON_H_INCLUDED #define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line #define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) #define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) #define INTERNAL_CATCH_STRINGIFY2( expr ) #expr #define INTERNAL_CATCH_STRINGIFY( expr ) INTERNAL_CATCH_STRINGIFY2( expr ) #include #include #include // #included from: catch_compiler_capabilities.h #define TWOBLUECUBES_CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED // Much of the following code is based on Boost (1.53) #ifdef __clang__ # if __has_feature(cxx_nullptr) # define CATCH_CONFIG_CPP11_NULLPTR # endif # if __has_feature(cxx_noexcept) # define CATCH_CONFIG_CPP11_NOEXCEPT # endif #endif // __clang__ //////////////////////////////////////////////////////////////////////////////// // Borland #ifdef __BORLANDC__ #if (__BORLANDC__ > 0x582 ) //#define CATCH_CONFIG_SFINAE // Not confirmed #endif #endif // __BORLANDC__ //////////////////////////////////////////////////////////////////////////////// // EDG #ifdef __EDG_VERSION__ #if (__EDG_VERSION__ > 238 ) //#define CATCH_CONFIG_SFINAE // Not confirmed #endif #endif // __EDG_VERSION__ //////////////////////////////////////////////////////////////////////////////// // Digital Mars #ifdef __DMC__ #if (__DMC__ > 0x840 ) //#define CATCH_CONFIG_SFINAE // Not confirmed #endif #endif // __DMC__ //////////////////////////////////////////////////////////////////////////////// // GCC #ifdef __GNUC__ #if __GNUC__ < 3 #if (__GNUC_MINOR__ >= 96 ) //#define CATCH_CONFIG_SFINAE #endif #elif __GNUC__ >= 3 // #define CATCH_CONFIG_SFINAE // Taking this out completely for now #endif // __GNUC__ < 3 #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) ) #define CATCH_CONFIG_CPP11_NULLPTR #endif #endif // __GNUC__ //////////////////////////////////////////////////////////////////////////////// // Visual C++ #ifdef _MSC_VER #if (_MSC_VER >= 1600) #define CATCH_CONFIG_CPP11_NULLPTR #endif #if (_MSC_VER >= 1310 ) // (VC++ 7.0+) //#define CATCH_CONFIG_SFINAE // Not confirmed #endif #endif // _MSC_VER // Use variadic macros if the compiler supports them #if ( defined _MSC_VER && _MSC_VER > 1400 && !defined __EDGE__) || \ ( defined __WAVE__ && __WAVE_HAS_VARIADICS ) || \ ( defined __GNUC__ && __GNUC__ >= 3 ) || \ ( !defined __cplusplus && __STDC_VERSION__ >= 199901L || __cplusplus >= 201103L ) #ifndef CATCH_CONFIG_NO_VARIADIC_MACROS #define CATCH_CONFIG_VARIADIC_MACROS #endif #endif //////////////////////////////////////////////////////////////////////////////// // C++ language feature support // detect language version: #if (__cplusplus == 201103L) # define CATCH_CPP11 # define CATCH_CPP11_OR_GREATER #elif (__cplusplus >= 201103L) # define CATCH_CPP11_OR_GREATER #endif // noexcept support: #if defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_NOEXCEPT) # define CATCH_NOEXCEPT noexcept # define CATCH_NOEXCEPT_IS(x) noexcept(x) #else # define CATCH_NOEXCEPT throw() # define CATCH_NOEXCEPT_IS(x) #endif namespace Catch { class NonCopyable { #ifdef CATCH_CPP11_OR_GREATER NonCopyable( NonCopyable const& ) = delete; NonCopyable( NonCopyable && ) = delete; NonCopyable& operator = ( NonCopyable const& ) = delete; NonCopyable& operator = ( NonCopyable && ) = delete; #else NonCopyable( NonCopyable const& info ); NonCopyable& operator = ( NonCopyable const& ); #endif protected: NonCopyable() {} virtual ~NonCopyable(); }; class SafeBool { public: typedef void (SafeBool::*type)() const; static type makeSafe( bool value ) { return value ? &SafeBool::trueValue : 0; } private: void trueValue() const {} }; template inline void deleteAll( ContainerT& container ) { typename ContainerT::const_iterator it = container.begin(); typename ContainerT::const_iterator itEnd = container.end(); for(; it != itEnd; ++it ) delete *it; } template inline void deleteAllValues( AssociativeContainerT& container ) { typename AssociativeContainerT::const_iterator it = container.begin(); typename AssociativeContainerT::const_iterator itEnd = container.end(); for(; it != itEnd; ++it ) delete it->second; } bool startsWith( std::string const& s, std::string const& prefix ); bool endsWith( std::string const& s, std::string const& suffix ); bool contains( std::string const& s, std::string const& infix ); void toLowerInPlace( std::string& s ); std::string toLower( std::string const& s ); std::string trim( std::string const& str ); bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); struct pluralise { pluralise( std::size_t count, std::string const& label ); friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); std::size_t m_count; std::string m_label; }; struct SourceLineInfo { SourceLineInfo(); SourceLineInfo( char const* _file, std::size_t _line ); SourceLineInfo( SourceLineInfo const& other ); # ifdef CATCH_CPP11_OR_GREATER SourceLineInfo( SourceLineInfo && ) = default; SourceLineInfo& operator = ( SourceLineInfo const& ) = default; SourceLineInfo& operator = ( SourceLineInfo && ) = default; # endif bool empty() const; bool operator == ( SourceLineInfo const& other ) const; bool operator < ( SourceLineInfo const& other ) const; std::string file; std::size_t line; }; std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); // This is just here to avoid compiler warnings with macro constants and boolean literals inline bool isTrue( bool value ){ return value; } inline bool alwaysTrue() { return true; } inline bool alwaysFalse() { return false; } void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ); // Use this in variadic streaming macros to allow // >> +StreamEndStop // as well as // >> stuff +StreamEndStop struct StreamEndStop { std::string operator+() { return std::string(); } }; template T const& operator + ( T const& value, StreamEndStop ) { return value; } } #define CATCH_INTERNAL_LINEINFO ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) #define CATCH_INTERNAL_ERROR( msg ) ::Catch::throwLogicError( msg, CATCH_INTERNAL_LINEINFO ); #include namespace Catch { class NotImplementedException : public std::exception { public: NotImplementedException( SourceLineInfo const& lineInfo ); NotImplementedException( NotImplementedException const& ) {} virtual ~NotImplementedException() CATCH_NOEXCEPT {} virtual const char* what() const CATCH_NOEXCEPT; private: std::string m_what; SourceLineInfo m_lineInfo; }; } // end namespace Catch /////////////////////////////////////////////////////////////////////////////// #define CATCH_NOT_IMPLEMENTED throw Catch::NotImplementedException( CATCH_INTERNAL_LINEINFO ) // #included from: internal/catch_context.h #define TWOBLUECUBES_CATCH_CONTEXT_H_INCLUDED // #included from: catch_interfaces_generators.h #define TWOBLUECUBES_CATCH_INTERFACES_GENERATORS_H_INCLUDED #include namespace Catch { struct IGeneratorInfo { virtual ~IGeneratorInfo(); virtual bool moveNext() = 0; virtual std::size_t getCurrentIndex() const = 0; }; struct IGeneratorsForTest { virtual ~IGeneratorsForTest(); virtual IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) = 0; virtual bool moveNext() = 0; }; IGeneratorsForTest* createGeneratorsForTest(); } // end namespace Catch // #included from: catch_ptr.hpp #define TWOBLUECUBES_CATCH_PTR_HPP_INCLUDED #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpadded" #endif namespace Catch { // An intrusive reference counting smart pointer. // T must implement addRef() and release() methods // typically implementing the IShared interface template class Ptr { public: Ptr() : m_p( NULL ){} Ptr( T* p ) : m_p( p ){ if( m_p ) m_p->addRef(); } Ptr( Ptr const& other ) : m_p( other.m_p ){ if( m_p ) m_p->addRef(); } ~Ptr(){ if( m_p ) m_p->release(); } void reset() { if( m_p ) m_p->release(); m_p = NULL; } Ptr& operator = ( T* p ){ Ptr temp( p ); swap( temp ); return *this; } Ptr& operator = ( Ptr const& other ){ Ptr temp( other ); swap( temp ); return *this; } void swap( Ptr& other ) { std::swap( m_p, other.m_p ); } T* get() { return m_p; } const T* get() const{ return m_p; } T& operator*() const { return *m_p; } T* operator->() const { return m_p; } bool operator !() const { return m_p == NULL; } operator SafeBool::type() const { return SafeBool::makeSafe( m_p != NULL ); } private: T* m_p; }; struct IShared : NonCopyable { virtual ~IShared(); virtual void addRef() const = 0; virtual void release() const = 0; }; template struct SharedImpl : T { SharedImpl() : m_rc( 0 ){} virtual void addRef() const { ++m_rc; } virtual void release() const { if( --m_rc == 0 ) delete this; } mutable unsigned int m_rc; }; } // end namespace Catch #ifdef __clang__ #pragma clang diagnostic pop #endif #include #include #include namespace Catch { class TestCase; class Stream; struct IResultCapture; struct IRunner; struct IGeneratorsForTest; struct IConfig; struct IContext { virtual ~IContext(); virtual IResultCapture* getResultCapture() = 0; virtual IRunner* getRunner() = 0; virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) = 0; virtual bool advanceGeneratorsForCurrentTest() = 0; virtual Ptr getConfig() const = 0; }; struct IMutableContext : IContext { virtual ~IMutableContext(); virtual void setResultCapture( IResultCapture* resultCapture ) = 0; virtual void setRunner( IRunner* runner ) = 0; virtual void setConfig( Ptr const& config ) = 0; }; IContext& getCurrentContext(); IMutableContext& getCurrentMutableContext(); void cleanUpContext(); Stream createStream( std::string const& streamName ); } // #included from: internal/catch_test_registry.hpp #define TWOBLUECUBES_CATCH_TEST_REGISTRY_HPP_INCLUDED // #included from: catch_interfaces_testcase.h #define TWOBLUECUBES_CATCH_INTERFACES_TESTCASE_H_INCLUDED #include namespace Catch { class TestSpec; struct ITestCase : IShared { virtual void invoke () const = 0; protected: virtual ~ITestCase(); }; class TestCase; struct IConfig; struct ITestCaseRegistry { virtual ~ITestCaseRegistry(); virtual std::vector const& getAllTests() const = 0; virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector& matchingTestCases, bool negated = false ) const = 0; }; } namespace Catch { template class MethodTestCase : public SharedImpl { public: MethodTestCase( void (C::*method)() ) : m_method( method ) {} virtual void invoke() const { C obj; (obj.*m_method)(); } private: virtual ~MethodTestCase() {} void (C::*m_method)(); }; typedef void(*TestFunction)(); struct NameAndDesc { NameAndDesc( const char* _name = "", const char* _description= "" ) : name( _name ), description( _description ) {} const char* name; const char* description; }; struct AutoReg { AutoReg( TestFunction function, SourceLineInfo const& lineInfo, NameAndDesc const& nameAndDesc ); template AutoReg( void (C::*method)(), char const* className, NameAndDesc const& nameAndDesc, SourceLineInfo const& lineInfo ) { registerTestCase( new MethodTestCase( method ), className, nameAndDesc, lineInfo ); } void registerTestCase( ITestCase* testCase, char const* className, NameAndDesc const& nameAndDesc, SourceLineInfo const& lineInfo ); ~AutoReg(); private: AutoReg( AutoReg const& ); void operator= ( AutoReg const& ); }; } // end namespace Catch #ifdef CATCH_CONFIG_VARIADIC_MACROS /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TESTCASE( ... ) \ static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )(); \ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); }\ static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )() /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); } /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... )\ namespace{ \ struct INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) : ClassName{ \ void test(); \ }; \ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); \ } \ void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test() #else /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TESTCASE( Name, Desc ) \ static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )(); \ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); }\ static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )() /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, Name, Desc ) \ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( Name, Desc ), CATCH_INTERNAL_LINEINFO ); } /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\ namespace{ \ struct INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) : ClassName{ \ void test(); \ }; \ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); \ } \ void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test() #endif // #included from: internal/catch_capture.hpp #define TWOBLUECUBES_CATCH_CAPTURE_HPP_INCLUDED // #included from: catch_result_builder.h #define TWOBLUECUBES_CATCH_RESULT_BUILDER_H_INCLUDED // #included from: catch_result_type.h #define TWOBLUECUBES_CATCH_RESULT_TYPE_H_INCLUDED namespace Catch { // ResultWas::OfType enum struct ResultWas { enum OfType { Unknown = -1, Ok = 0, Info = 1, Warning = 2, FailureBit = 0x10, ExpressionFailed = FailureBit | 1, ExplicitFailure = FailureBit | 2, Exception = 0x100 | FailureBit, ThrewException = Exception | 1, DidntThrowException = Exception | 2, FatalErrorCondition = 0x200 | FailureBit }; }; inline bool isOk( ResultWas::OfType resultType ) { return ( resultType & ResultWas::FailureBit ) == 0; } inline bool isJustInfo( int flags ) { return flags == ResultWas::Info; } // ResultDisposition::Flags enum struct ResultDisposition { enum Flags { Normal = 0x00, ContinueOnFailure = 0x01, // Failures fail test, but execution continues FalseTest = 0x02, // Prefix expression with ! SuppressFail = 0x04 // Failures are reported but do not fail the test }; }; inline ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { return static_cast( static_cast( lhs ) | static_cast( rhs ) ); } inline bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } inline bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } } // end namespace Catch // #included from: catch_assertionresult.h #define TWOBLUECUBES_CATCH_ASSERTIONRESULT_H_INCLUDED #include namespace Catch { struct AssertionInfo { AssertionInfo() {} AssertionInfo( std::string const& _macroName, SourceLineInfo const& _lineInfo, std::string const& _capturedExpression, ResultDisposition::Flags _resultDisposition ); std::string macroName; SourceLineInfo lineInfo; std::string capturedExpression; ResultDisposition::Flags resultDisposition; }; struct AssertionResultData { AssertionResultData() : resultType( ResultWas::Unknown ) {} std::string reconstructedExpression; std::string message; ResultWas::OfType resultType; }; class AssertionResult { public: AssertionResult(); AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); ~AssertionResult(); # ifdef CATCH_CPP11_OR_GREATER AssertionResult( AssertionResult const& ) = default; AssertionResult( AssertionResult && ) = default; AssertionResult& operator = ( AssertionResult const& ) = default; AssertionResult& operator = ( AssertionResult && ) = default; # endif bool isOk() const; bool succeeded() const; ResultWas::OfType getResultType() const; bool hasExpression() const; bool hasMessage() const; std::string getExpression() const; std::string getExpressionInMacro() const; bool hasExpandedExpression() const; std::string getExpandedExpression() const; std::string getMessage() const; SourceLineInfo getSourceInfo() const; std::string getTestMacroName() const; protected: AssertionInfo m_info; AssertionResultData m_resultData; }; } // end namespace Catch namespace Catch { struct TestFailureException{}; template class ExpressionLhs; struct STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison; struct CopyableStream { CopyableStream() {} CopyableStream( CopyableStream const& other ) { oss << other.oss.str(); } CopyableStream& operator=( CopyableStream const& other ) { oss.str(""); oss << other.oss.str(); return *this; } std::ostringstream oss; }; class ResultBuilder { public: ResultBuilder( char const* macroName, SourceLineInfo const& lineInfo, char const* capturedExpression, ResultDisposition::Flags resultDisposition ); template ExpressionLhs operator->* ( T const& operand ); ExpressionLhs operator->* ( bool value ); template ResultBuilder& operator << ( T const& value ) { m_stream.oss << value; return *this; } template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& ); ResultBuilder& setResultType( ResultWas::OfType result ); ResultBuilder& setResultType( bool result ); ResultBuilder& setLhs( std::string const& lhs ); ResultBuilder& setRhs( std::string const& rhs ); ResultBuilder& setOp( std::string const& op ); void endExpression(); std::string reconstructExpression() const; AssertionResult build() const; void useActiveException( ResultDisposition::Flags resultDisposition = ResultDisposition::Normal ); void captureResult( ResultWas::OfType resultType ); void captureExpression(); void react(); bool shouldDebugBreak() const; bool allowThrows() const; private: AssertionInfo m_assertionInfo; AssertionResultData m_data; struct ExprComponents { ExprComponents() : testFalse( false ) {} bool testFalse; std::string lhs, rhs, op; } m_exprComponents; CopyableStream m_stream; bool m_shouldDebugBreak; bool m_shouldThrow; }; } // namespace Catch // Include after due to circular dependency: // #included from: catch_expression_lhs.hpp #define TWOBLUECUBES_CATCH_EXPRESSION_LHS_HPP_INCLUDED // #included from: catch_evaluate.hpp #define TWOBLUECUBES_CATCH_EVALUATE_HPP_INCLUDED #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable:4389) // '==' : signed/unsigned mismatch #endif #include namespace Catch { namespace Internal { enum Operator { IsEqualTo, IsNotEqualTo, IsLessThan, IsGreaterThan, IsLessThanOrEqualTo, IsGreaterThanOrEqualTo }; template struct OperatorTraits { static const char* getName(){ return "*error*"; } }; template<> struct OperatorTraits { static const char* getName(){ return "=="; } }; template<> struct OperatorTraits { static const char* getName(){ return "!="; } }; template<> struct OperatorTraits { static const char* getName(){ return "<"; } }; template<> struct OperatorTraits { static const char* getName(){ return ">"; } }; template<> struct OperatorTraits { static const char* getName(){ return "<="; } }; template<> struct OperatorTraits{ static const char* getName(){ return ">="; } }; template inline T& opCast(T const& t) { return const_cast(t); } // nullptr_t support based on pull request #154 from Konstantin Baumann #ifdef CATCH_CONFIG_CPP11_NULLPTR inline std::nullptr_t opCast(std::nullptr_t) { return nullptr; } #endif // CATCH_CONFIG_CPP11_NULLPTR // So the compare overloads can be operator agnostic we convey the operator as a template // enum, which is used to specialise an Evaluator for doing the comparison. template class Evaluator{}; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs) { return opCast( lhs ) == opCast( rhs ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return opCast( lhs ) != opCast( rhs ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return opCast( lhs ) < opCast( rhs ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return opCast( lhs ) > opCast( rhs ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return opCast( lhs ) >= opCast( rhs ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return opCast( lhs ) <= opCast( rhs ); } }; template bool applyEvaluator( T1 const& lhs, T2 const& rhs ) { return Evaluator::evaluate( lhs, rhs ); } // This level of indirection allows us to specialise for integer types // to avoid signed/ unsigned warnings // "base" overload template bool compare( T1 const& lhs, T2 const& rhs ) { return Evaluator::evaluate( lhs, rhs ); } // unsigned X to int template bool compare( unsigned int lhs, int rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } template bool compare( unsigned long lhs, int rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } template bool compare( unsigned char lhs, int rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } // unsigned X to long template bool compare( unsigned int lhs, long rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } template bool compare( unsigned long lhs, long rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } template bool compare( unsigned char lhs, long rhs ) { return applyEvaluator( lhs, static_cast( rhs ) ); } // int to unsigned X template bool compare( int lhs, unsigned int rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( int lhs, unsigned long rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( int lhs, unsigned char rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } // long to unsigned X template bool compare( long lhs, unsigned int rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( long lhs, unsigned long rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } template bool compare( long lhs, unsigned char rhs ) { return applyEvaluator( static_cast( lhs ), rhs ); } // pointer to long (when comparing against NULL) template bool compare( long lhs, T* rhs ) { return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); } template bool compare( T* lhs, long rhs ) { return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); } // pointer to int (when comparing against NULL) template bool compare( int lhs, T* rhs ) { return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); } template bool compare( T* lhs, int rhs ) { return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); } #ifdef CATCH_CONFIG_CPP11_NULLPTR // pointer to nullptr_t (when comparing against nullptr) template bool compare( std::nullptr_t, T* rhs ) { return Evaluator::evaluate( NULL, rhs ); } template bool compare( T* lhs, std::nullptr_t ) { return Evaluator::evaluate( lhs, NULL ); } #endif // CATCH_CONFIG_CPP11_NULLPTR } // end of namespace Internal } // end of namespace Catch #ifdef _MSC_VER #pragma warning(pop) #endif // #included from: catch_tostring.h #define TWOBLUECUBES_CATCH_TOSTRING_H_INCLUDED // #included from: catch_sfinae.hpp #define TWOBLUECUBES_CATCH_SFINAE_HPP_INCLUDED // Try to detect if the current compiler supports SFINAE namespace Catch { struct TrueType { static const bool value = true; typedef void Enable; char sizer[1]; }; struct FalseType { static const bool value = false; typedef void Disable; char sizer[2]; }; #ifdef CATCH_CONFIG_SFINAE template struct NotABooleanExpression; template struct If : NotABooleanExpression {}; template<> struct If : TrueType {}; template<> struct If : FalseType {}; template struct SizedIf; template<> struct SizedIf : TrueType {}; template<> struct SizedIf : FalseType {}; #endif // CATCH_CONFIG_SFINAE } // end namespace Catch #include #include #include #include #include #ifdef __OBJC__ // #included from: catch_objc_arc.hpp #define TWOBLUECUBES_CATCH_OBJC_ARC_HPP_INCLUDED #import #ifdef __has_feature #define CATCH_ARC_ENABLED __has_feature(objc_arc) #else #define CATCH_ARC_ENABLED 0 #endif void arcSafeRelease( NSObject* obj ); id performOptionalSelector( id obj, SEL sel ); #if !CATCH_ARC_ENABLED inline void arcSafeRelease( NSObject* obj ) { [obj release]; } inline id performOptionalSelector( id obj, SEL sel ) { if( [obj respondsToSelector: sel] ) return [obj performSelector: sel]; return nil; } #define CATCH_UNSAFE_UNRETAINED #define CATCH_ARC_STRONG #else inline void arcSafeRelease( NSObject* ){} inline id performOptionalSelector( id obj, SEL sel ) { #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" #endif if( [obj respondsToSelector: sel] ) return [obj performSelector: sel]; #ifdef __clang__ #pragma clang diagnostic pop #endif return nil; } #define CATCH_UNSAFE_UNRETAINED __unsafe_unretained #define CATCH_ARC_STRONG __strong #endif #endif #ifdef CATCH_CPP11_OR_GREATER #include #include #endif namespace Catch { // Why we're here. template std::string toString( T const& value ); // Built in overloads std::string toString( std::string const& value ); std::string toString( std::wstring const& value ); std::string toString( const char* const value ); std::string toString( char* const value ); std::string toString( const wchar_t* const value ); std::string toString( wchar_t* const value ); std::string toString( int value ); std::string toString( unsigned long value ); std::string toString( unsigned int value ); std::string toString( const double value ); std::string toString( const float value ); std::string toString( bool value ); std::string toString( char value ); std::string toString( signed char value ); std::string toString( unsigned char value ); #ifdef CATCH_CONFIG_CPP11_NULLPTR std::string toString( std::nullptr_t ); #endif #ifdef __OBJC__ std::string toString( NSString const * const& nsstring ); std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ); std::string toString( NSObject* const& nsObject ); #endif namespace Detail { extern std::string unprintableString; // SFINAE is currently disabled by default for all compilers. // If the non SFINAE version of IsStreamInsertable is ambiguous for you // and your compiler supports SFINAE, try #defining CATCH_CONFIG_SFINAE #ifdef CATCH_CONFIG_SFINAE template class IsStreamInsertableHelper { template struct TrueIfSizeable : TrueType {}; template static TrueIfSizeable dummy(T2*); static FalseType dummy(...); public: typedef SizedIf type; }; template struct IsStreamInsertable : IsStreamInsertableHelper::type {}; #else struct BorgType { template BorgType( T const& ); }; TrueType& testStreamable( std::ostream& ); FalseType testStreamable( FalseType ); FalseType operator<<( std::ostream const&, BorgType const& ); template struct IsStreamInsertable { static std::ostream &s; static T const&t; enum { value = sizeof( testStreamable(s << t) ) == sizeof( TrueType ) }; }; #endif #if defined(CATCH_CPP11_OR_GREATER) template::value > struct EnumStringMaker { static std::string convert( T const& ) { return unprintableString; } }; template struct EnumStringMaker { static std::string convert( T const& v ) { return ::Catch::toString( static_cast::type>(v) ); } }; #endif template struct StringMakerBase { #if defined(CATCH_CPP11_OR_GREATER) template static std::string convert( T const& v ) { return EnumStringMaker::convert( v ); } #else template static std::string convert( T const& ) { return unprintableString; } #endif }; template<> struct StringMakerBase { template static std::string convert( T const& _value ) { std::ostringstream oss; oss << _value; return oss.str(); } }; std::string rawMemoryToString( const void *object, std::size_t size ); template inline std::string rawMemoryToString( const T& object ) { return rawMemoryToString( &object, sizeof(object) ); } } // end namespace Detail template struct StringMaker : Detail::StringMakerBase::value> {}; template struct StringMaker { template static std::string convert( U* p ) { if( !p ) return INTERNAL_CATCH_STRINGIFY( NULL ); else return Detail::rawMemoryToString( p ); } }; template struct StringMaker { static std::string convert( R C::* p ) { if( !p ) return INTERNAL_CATCH_STRINGIFY( NULL ); else return Detail::rawMemoryToString( p ); } }; namespace Detail { template std::string rangeToString( InputIterator first, InputIterator last ); } //template //struct StringMaker > { // static std::string convert( std::vector const& v ) { // return Detail::rangeToString( v.begin(), v.end() ); // } //}; template std::string toString( std::vector const& v ) { return Detail::rangeToString( v.begin(), v.end() ); } #ifdef CATCH_CPP11_OR_GREATER // toString for tuples namespace TupleDetail { template< typename Tuple, std::size_t N = 0, bool = (N < std::tuple_size::value) > struct ElementPrinter { static void print( const Tuple& tuple, std::ostream& os ) { os << ( N ? ", " : " " ) << Catch::toString(std::get(tuple)); ElementPrinter::print(tuple,os); } }; template< typename Tuple, std::size_t N > struct ElementPrinter { static void print( const Tuple&, std::ostream& ) {} }; } template struct StringMaker> { static std::string convert( const std::tuple& tuple ) { std::ostringstream os; os << '{'; TupleDetail::ElementPrinter>::print( tuple, os ); os << " }"; return os.str(); } }; #endif namespace Detail { template std::string makeString( T const& value ) { return StringMaker::convert( value ); } } // end namespace Detail /// \brief converts any type to a string /// /// The default template forwards on to ostringstream - except when an /// ostringstream overload does not exist - in which case it attempts to detect /// that and writes {?}. /// Overload (not specialise) this template for custom typs that you don't want /// to provide an ostream overload for. template std::string toString( T const& value ) { return StringMaker::convert( value ); } namespace Detail { template std::string rangeToString( InputIterator first, InputIterator last ) { std::ostringstream oss; oss << "{ "; if( first != last ) { oss << Catch::toString( *first ); for( ++first ; first != last ; ++first ) oss << ", " << Catch::toString( *first ); } oss << " }"; return oss.str(); } } } // end namespace Catch namespace Catch { // Wraps the LHS of an expression and captures the operator and RHS (if any) - // wrapping them all in a ResultBuilder object template class ExpressionLhs { ExpressionLhs& operator = ( ExpressionLhs const& ); # ifdef CATCH_CPP11_OR_GREATER ExpressionLhs& operator = ( ExpressionLhs && ) = delete; # endif public: ExpressionLhs( ResultBuilder& rb, T lhs ) : m_rb( rb ), m_lhs( lhs ) {} # ifdef CATCH_CPP11_OR_GREATER ExpressionLhs( ExpressionLhs const& ) = default; ExpressionLhs( ExpressionLhs && ) = default; # endif template ResultBuilder& operator == ( RhsT const& rhs ) { return captureExpression( rhs ); } template ResultBuilder& operator != ( RhsT const& rhs ) { return captureExpression( rhs ); } template ResultBuilder& operator < ( RhsT const& rhs ) { return captureExpression( rhs ); } template ResultBuilder& operator > ( RhsT const& rhs ) { return captureExpression( rhs ); } template ResultBuilder& operator <= ( RhsT const& rhs ) { return captureExpression( rhs ); } template ResultBuilder& operator >= ( RhsT const& rhs ) { return captureExpression( rhs ); } ResultBuilder& operator == ( bool rhs ) { return captureExpression( rhs ); } ResultBuilder& operator != ( bool rhs ) { return captureExpression( rhs ); } void endExpression() { bool value = m_lhs ? true : false; m_rb .setLhs( Catch::toString( value ) ) .setResultType( value ) .endExpression(); } // Only simple binary expressions are allowed on the LHS. // If more complex compositions are required then place the sub expression in parentheses template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator + ( RhsT const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator - ( RhsT const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator / ( RhsT const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator * ( RhsT const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& ); private: template ResultBuilder& captureExpression( RhsT const& rhs ) { return m_rb .setResultType( Internal::compare( m_lhs, rhs ) ) .setLhs( Catch::toString( m_lhs ) ) .setRhs( Catch::toString( rhs ) ) .setOp( Internal::OperatorTraits::getName() ); } private: ResultBuilder& m_rb; T m_lhs; }; } // end namespace Catch namespace Catch { template inline ExpressionLhs ResultBuilder::operator->* ( T const& operand ) { return ExpressionLhs( *this, operand ); } inline ExpressionLhs ResultBuilder::operator->* ( bool value ) { return ExpressionLhs( *this, value ); } } // namespace Catch // #included from: catch_message.h #define TWOBLUECUBES_CATCH_MESSAGE_H_INCLUDED #include namespace Catch { struct MessageInfo { MessageInfo( std::string const& _macroName, SourceLineInfo const& _lineInfo, ResultWas::OfType _type ); std::string macroName; SourceLineInfo lineInfo; ResultWas::OfType type; std::string message; unsigned int sequence; bool operator == ( MessageInfo const& other ) const { return sequence == other.sequence; } bool operator < ( MessageInfo const& other ) const { return sequence < other.sequence; } private: static unsigned int globalCount; }; struct MessageBuilder { MessageBuilder( std::string const& macroName, SourceLineInfo const& lineInfo, ResultWas::OfType type ) : m_info( macroName, lineInfo, type ) {} template MessageBuilder& operator << ( T const& value ) { m_stream << value; return *this; } MessageInfo m_info; std::ostringstream m_stream; }; class ScopedMessage { public: ScopedMessage( MessageBuilder const& builder ); ScopedMessage( ScopedMessage const& other ); ~ScopedMessage(); MessageInfo m_info; }; } // end namespace Catch // #included from: catch_interfaces_capture.h #define TWOBLUECUBES_CATCH_INTERFACES_CAPTURE_H_INCLUDED #include namespace Catch { class TestCase; class AssertionResult; struct AssertionInfo; struct SectionInfo; struct MessageInfo; class ScopedMessageBuilder; struct Counts; struct IResultCapture { virtual ~IResultCapture(); virtual void assertionEnded( AssertionResult const& result ) = 0; virtual bool sectionStarted( SectionInfo const& sectionInfo, Counts& assertions ) = 0; virtual void sectionEnded( SectionInfo const& name, Counts const& assertions, double _durationInSeconds ) = 0; virtual void pushScopedMessage( MessageInfo const& message ) = 0; virtual void popScopedMessage( MessageInfo const& message ) = 0; virtual std::string getCurrentTestName() const = 0; virtual const AssertionResult* getLastResult() const = 0; virtual void handleFatalErrorCondition( std::string const& message ) = 0; }; IResultCapture& getResultCapture(); } // #included from: catch_debugger.h #define TWOBLUECUBES_CATCH_DEBUGGER_H_INCLUDED // #included from: catch_platform.h #define TWOBLUECUBES_CATCH_PLATFORM_H_INCLUDED #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) #define CATCH_PLATFORM_MAC #elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) #define CATCH_PLATFORM_IPHONE #elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) #define CATCH_PLATFORM_WINDOWS #endif #include namespace Catch{ bool isDebuggerActive(); void writeToDebugConsole( std::string const& text ); } #ifdef CATCH_PLATFORM_MAC // The following code snippet based on: // http://cocoawithlove.com/2008/03/break-into-debugger.html #ifdef DEBUG #if defined(__ppc64__) || defined(__ppc__) #define CATCH_BREAK_INTO_DEBUGGER() \ if( Catch::isDebuggerActive() ) { \ __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \ : : : "memory","r0","r3","r4" ); \ } #else #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) {__asm__("int $3\n" : : );} #endif #endif #elif defined(_MSC_VER) #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { __debugbreak(); } #elif defined(__MINGW32__) extern "C" __declspec(dllimport) void __stdcall DebugBreak(); #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { DebugBreak(); } #endif #ifndef CATCH_BREAK_INTO_DEBUGGER #define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue(); #endif // #included from: catch_interfaces_runner.h #define TWOBLUECUBES_CATCH_INTERFACES_RUNNER_H_INCLUDED namespace Catch { class TestCase; struct IRunner { virtual ~IRunner(); virtual bool aborting() const = 0; }; } /////////////////////////////////////////////////////////////////////////////// // In the event of a failure works out if the debugger needs to be invoked // and/or an exception thrown and takes appropriate action. // This needs to be done as a macro so the debugger will stop in the user // source code rather than in Catch library code #define INTERNAL_CATCH_REACT( resultBuilder ) \ if( resultBuilder.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \ resultBuilder.react(); /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ try { \ ( __catchResult->*expr ).endExpression(); \ } \ catch( ... ) { \ __catchResult.useActiveException( Catch::ResultDisposition::Normal ); \ } \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::isTrue( false && (expr) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_IF( expr, resultDisposition, macroName ) \ INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \ if( Catch::getResultCapture().getLastResult()->succeeded() ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_ELSE( expr, resultDisposition, macroName ) \ INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \ if( !Catch::getResultCapture().getLastResult()->succeeded() ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_NO_THROW( expr, resultDisposition, macroName ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ try { \ expr; \ __catchResult.captureResult( Catch::ResultWas::Ok ); \ } \ catch( ... ) { \ __catchResult.useActiveException( resultDisposition ); \ } \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_THROWS( expr, resultDisposition, macroName ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ if( __catchResult.allowThrows() ) \ try { \ expr; \ __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ } \ catch( ... ) { \ __catchResult.captureResult( Catch::ResultWas::Ok ); \ } \ else \ __catchResult.captureResult( Catch::ResultWas::Ok ); \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_THROWS_AS( expr, exceptionType, resultDisposition, macroName ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ if( __catchResult.allowThrows() ) \ try { \ expr; \ __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ } \ catch( exceptionType ) { \ __catchResult.captureResult( Catch::ResultWas::Ok ); \ } \ catch( ... ) { \ __catchResult.useActiveException( resultDisposition ); \ } \ else \ __catchResult.captureResult( Catch::ResultWas::Ok ); \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) /////////////////////////////////////////////////////////////////////////////// #ifdef CATCH_CONFIG_VARIADIC_MACROS #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, ... ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ __catchResult << __VA_ARGS__ + ::Catch::StreamEndStop(); \ __catchResult.captureResult( messageType ); \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) #else #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, log ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ __catchResult << log + ::Catch::StreamEndStop(); \ __catchResult.captureResult( messageType ); \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) #endif /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_INFO( log, macroName ) \ Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage ) = Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log; /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CHECK_THAT( arg, matcher, resultDisposition, macroName ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg " " #matcher, resultDisposition ); \ try { \ std::string matcherAsString = ::Catch::Matchers::matcher.toString(); \ __catchResult \ .setLhs( Catch::toString( arg ) ) \ .setRhs( matcherAsString == Catch::Detail::unprintableString ? #matcher : matcherAsString ) \ .setOp( "matches" ) \ .setResultType( ::Catch::Matchers::matcher.match( arg ) ); \ __catchResult.captureExpression(); \ } catch( ... ) { \ __catchResult.useActiveException( resultDisposition | Catch::ResultDisposition::ContinueOnFailure ); \ } \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) // #included from: internal/catch_section.h #define TWOBLUECUBES_CATCH_SECTION_H_INCLUDED // #included from: catch_section_info.h #define TWOBLUECUBES_CATCH_SECTION_INFO_H_INCLUDED namespace Catch { struct SectionInfo { SectionInfo ( SourceLineInfo const& _lineInfo, std::string const& _name, std::string const& _description = std::string() ); std::string name; std::string description; SourceLineInfo lineInfo; }; } // end namespace Catch // #included from: catch_totals.hpp #define TWOBLUECUBES_CATCH_TOTALS_HPP_INCLUDED #include namespace Catch { struct Counts { Counts() : passed( 0 ), failed( 0 ), failedButOk( 0 ) {} Counts operator - ( Counts const& other ) const { Counts diff; diff.passed = passed - other.passed; diff.failed = failed - other.failed; diff.failedButOk = failedButOk - other.failedButOk; return diff; } Counts& operator += ( Counts const& other ) { passed += other.passed; failed += other.failed; failedButOk += other.failedButOk; return *this; } std::size_t total() const { return passed + failed + failedButOk; } bool allPassed() const { return failed == 0 && failedButOk == 0; } bool allOk() const { return failed == 0; } std::size_t passed; std::size_t failed; std::size_t failedButOk; }; struct Totals { Totals operator - ( Totals const& other ) const { Totals diff; diff.assertions = assertions - other.assertions; diff.testCases = testCases - other.testCases; return diff; } Totals delta( Totals const& prevTotals ) const { Totals diff = *this - prevTotals; if( diff.assertions.failed > 0 ) ++diff.testCases.failed; else if( diff.assertions.failedButOk > 0 ) ++diff.testCases.failedButOk; else ++diff.testCases.passed; return diff; } Totals& operator += ( Totals const& other ) { assertions += other.assertions; testCases += other.testCases; return *this; } Counts assertions; Counts testCases; }; } // #included from: catch_timer.h #define TWOBLUECUBES_CATCH_TIMER_H_INCLUDED #ifdef CATCH_PLATFORM_WINDOWS typedef unsigned long long uint64_t; #else #include #endif namespace Catch { class Timer { public: Timer() : m_ticks( 0 ) {} void start(); unsigned int getElapsedMicroseconds() const; unsigned int getElapsedMilliseconds() const; double getElapsedSeconds() const; private: uint64_t m_ticks; }; } // namespace Catch #include namespace Catch { class Section : NonCopyable { public: Section( SectionInfo const& info ); ~Section(); // This indicates whether the section should be executed or not operator bool() const; private: SectionInfo m_info; std::string m_name; Counts m_assertions; bool m_sectionIncluded; Timer m_timer; }; } // end namespace Catch #ifdef CATCH_CONFIG_VARIADIC_MACROS #define INTERNAL_CATCH_SECTION( ... ) \ if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) #else #define INTERNAL_CATCH_SECTION( name, desc ) \ if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, name, desc ) ) #endif // #included from: internal/catch_generators.hpp #define TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED #include #include #include #include namespace Catch { template struct IGenerator { virtual ~IGenerator() {} virtual T getValue( std::size_t index ) const = 0; virtual std::size_t size () const = 0; }; template class BetweenGenerator : public IGenerator { public: BetweenGenerator( T from, T to ) : m_from( from ), m_to( to ){} virtual T getValue( std::size_t index ) const { return m_from+static_cast( index ); } virtual std::size_t size() const { return static_cast( 1+m_to-m_from ); } private: T m_from; T m_to; }; template class ValuesGenerator : public IGenerator { public: ValuesGenerator(){} void add( T value ) { m_values.push_back( value ); } virtual T getValue( std::size_t index ) const { return m_values[index]; } virtual std::size_t size() const { return m_values.size(); } private: std::vector m_values; }; template class CompositeGenerator { public: CompositeGenerator() : m_totalSize( 0 ) {} // *** Move semantics, similar to auto_ptr *** CompositeGenerator( CompositeGenerator& other ) : m_fileInfo( other.m_fileInfo ), m_totalSize( 0 ) { move( other ); } CompositeGenerator& setFileInfo( const char* fileInfo ) { m_fileInfo = fileInfo; return *this; } ~CompositeGenerator() { deleteAll( m_composed ); } operator T () const { size_t overallIndex = getCurrentContext().getGeneratorIndex( m_fileInfo, m_totalSize ); typename std::vector*>::const_iterator it = m_composed.begin(); typename std::vector*>::const_iterator itEnd = m_composed.end(); for( size_t index = 0; it != itEnd; ++it ) { const IGenerator* generator = *it; if( overallIndex >= index && overallIndex < index + generator->size() ) { return generator->getValue( overallIndex-index ); } index += generator->size(); } CATCH_INTERNAL_ERROR( "Indexed past end of generated range" ); return T(); // Suppress spurious "not all control paths return a value" warning in Visual Studio - if you know how to fix this please do so } void add( const IGenerator* generator ) { m_totalSize += generator->size(); m_composed.push_back( generator ); } CompositeGenerator& then( CompositeGenerator& other ) { move( other ); return *this; } CompositeGenerator& then( T value ) { ValuesGenerator* valuesGen = new ValuesGenerator(); valuesGen->add( value ); add( valuesGen ); return *this; } private: void move( CompositeGenerator& other ) { std::copy( other.m_composed.begin(), other.m_composed.end(), std::back_inserter( m_composed ) ); m_totalSize += other.m_totalSize; other.m_composed.clear(); } std::vector*> m_composed; std::string m_fileInfo; size_t m_totalSize; }; namespace Generators { template CompositeGenerator between( T from, T to ) { CompositeGenerator generators; generators.add( new BetweenGenerator( from, to ) ); return generators; } template CompositeGenerator values( T val1, T val2 ) { CompositeGenerator generators; ValuesGenerator* valuesGen = new ValuesGenerator(); valuesGen->add( val1 ); valuesGen->add( val2 ); generators.add( valuesGen ); return generators; } template CompositeGenerator values( T val1, T val2, T val3 ){ CompositeGenerator generators; ValuesGenerator* valuesGen = new ValuesGenerator(); valuesGen->add( val1 ); valuesGen->add( val2 ); valuesGen->add( val3 ); generators.add( valuesGen ); return generators; } template CompositeGenerator values( T val1, T val2, T val3, T val4 ) { CompositeGenerator generators; ValuesGenerator* valuesGen = new ValuesGenerator(); valuesGen->add( val1 ); valuesGen->add( val2 ); valuesGen->add( val3 ); valuesGen->add( val4 ); generators.add( valuesGen ); return generators; } } // end namespace Generators using namespace Generators; } // end namespace Catch #define INTERNAL_CATCH_LINESTR2( line ) #line #define INTERNAL_CATCH_LINESTR( line ) INTERNAL_CATCH_LINESTR2( line ) #define INTERNAL_CATCH_GENERATE( expr ) expr.setFileInfo( __FILE__ "(" INTERNAL_CATCH_LINESTR( __LINE__ ) ")" ) // #included from: internal/catch_interfaces_exception.h #define TWOBLUECUBES_CATCH_INTERFACES_EXCEPTION_H_INCLUDED #include // #included from: catch_interfaces_registry_hub.h #define TWOBLUECUBES_CATCH_INTERFACES_REGISTRY_HUB_H_INCLUDED #include namespace Catch { class TestCase; struct ITestCaseRegistry; struct IExceptionTranslatorRegistry; struct IExceptionTranslator; struct IReporterRegistry; struct IReporterFactory; struct IRegistryHub { virtual ~IRegistryHub(); virtual IReporterRegistry const& getReporterRegistry() const = 0; virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0; }; struct IMutableRegistryHub { virtual ~IMutableRegistryHub(); virtual void registerReporter( std::string const& name, IReporterFactory* factory ) = 0; virtual void registerTest( TestCase const& testInfo ) = 0; virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; }; IRegistryHub& getRegistryHub(); IMutableRegistryHub& getMutableRegistryHub(); void cleanUp(); std::string translateActiveException(); } namespace Catch { typedef std::string(*exceptionTranslateFunction)(); struct IExceptionTranslator { virtual ~IExceptionTranslator(); virtual std::string translate() const = 0; }; struct IExceptionTranslatorRegistry { virtual ~IExceptionTranslatorRegistry(); virtual std::string translateActiveException() const = 0; }; class ExceptionTranslatorRegistrar { template class ExceptionTranslator : public IExceptionTranslator { public: ExceptionTranslator( std::string(*translateFunction)( T& ) ) : m_translateFunction( translateFunction ) {} virtual std::string translate() const { try { throw; } catch( T& ex ) { return m_translateFunction( ex ); } } protected: std::string(*m_translateFunction)( T& ); }; public: template ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) { getMutableRegistryHub().registerTranslator ( new ExceptionTranslator( translateFunction ) ); } }; } /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) \ static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature ); \ namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ) ); }\ static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature ) // #included from: internal/catch_approx.hpp #define TWOBLUECUBES_CATCH_APPROX_HPP_INCLUDED #include #include namespace Catch { namespace Detail { class Approx { public: explicit Approx ( double value ) : m_epsilon( std::numeric_limits::epsilon()*100 ), m_scale( 1.0 ), m_value( value ) {} Approx( Approx const& other ) : m_epsilon( other.m_epsilon ), m_scale( other.m_scale ), m_value( other.m_value ) {} static Approx custom() { return Approx( 0 ); } Approx operator()( double value ) { Approx approx( value ); approx.epsilon( m_epsilon ); approx.scale( m_scale ); return approx; } friend bool operator == ( double lhs, Approx const& rhs ) { // Thanks to Richard Harris for his help refining this formula return fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( fabs(lhs), fabs(rhs.m_value) ) ); } friend bool operator == ( Approx const& lhs, double rhs ) { return operator==( rhs, lhs ); } friend bool operator != ( double lhs, Approx const& rhs ) { return !operator==( lhs, rhs ); } friend bool operator != ( Approx const& lhs, double rhs ) { return !operator==( rhs, lhs ); } Approx& epsilon( double newEpsilon ) { m_epsilon = newEpsilon; return *this; } Approx& scale( double newScale ) { m_scale = newScale; return *this; } std::string toString() const { std::ostringstream oss; oss << "Approx( " << Catch::toString( m_value ) << " )"; return oss.str(); } private: double m_epsilon; double m_scale; double m_value; }; } template<> inline std::string toString( Detail::Approx const& value ) { return value.toString(); } } // end namespace Catch // #included from: internal/catch_matchers.hpp #define TWOBLUECUBES_CATCH_MATCHERS_HPP_INCLUDED namespace Catch { namespace Matchers { namespace Impl { template struct Matcher : SharedImpl { typedef ExpressionT ExpressionType; virtual ~Matcher() {} virtual Ptr clone() const = 0; virtual bool match( ExpressionT const& expr ) const = 0; virtual std::string toString() const = 0; }; template struct MatcherImpl : Matcher { virtual Ptr > clone() const { return Ptr >( new DerivedT( static_cast( *this ) ) ); } }; namespace Generic { template class AllOf : public MatcherImpl, ExpressionT> { public: AllOf() {} AllOf( AllOf const& other ) : m_matchers( other.m_matchers ) {} AllOf& add( Matcher const& matcher ) { m_matchers.push_back( matcher.clone() ); return *this; } virtual bool match( ExpressionT const& expr ) const { for( std::size_t i = 0; i < m_matchers.size(); ++i ) if( !m_matchers[i]->match( expr ) ) return false; return true; } virtual std::string toString() const { std::ostringstream oss; oss << "( "; for( std::size_t i = 0; i < m_matchers.size(); ++i ) { if( i != 0 ) oss << " and "; oss << m_matchers[i]->toString(); } oss << " )"; return oss.str(); } private: std::vector > > m_matchers; }; template class AnyOf : public MatcherImpl, ExpressionT> { public: AnyOf() {} AnyOf( AnyOf const& other ) : m_matchers( other.m_matchers ) {} AnyOf& add( Matcher const& matcher ) { m_matchers.push_back( matcher.clone() ); return *this; } virtual bool match( ExpressionT const& expr ) const { for( std::size_t i = 0; i < m_matchers.size(); ++i ) if( m_matchers[i]->match( expr ) ) return true; return false; } virtual std::string toString() const { std::ostringstream oss; oss << "( "; for( std::size_t i = 0; i < m_matchers.size(); ++i ) { if( i != 0 ) oss << " or "; oss << m_matchers[i]->toString(); } oss << " )"; return oss.str(); } private: std::vector > > m_matchers; }; } namespace StdString { inline std::string makeString( std::string const& str ) { return str; } inline std::string makeString( const char* str ) { return str ? std::string( str ) : std::string(); } struct Equals : MatcherImpl { Equals( std::string const& str ) : m_str( str ){} Equals( Equals const& other ) : m_str( other.m_str ){} virtual ~Equals(); virtual bool match( std::string const& expr ) const { return m_str == expr; } virtual std::string toString() const { return "equals: \"" + m_str + "\""; } std::string m_str; }; struct Contains : MatcherImpl { Contains( std::string const& substr ) : m_substr( substr ){} Contains( Contains const& other ) : m_substr( other.m_substr ){} virtual ~Contains(); virtual bool match( std::string const& expr ) const { return expr.find( m_substr ) != std::string::npos; } virtual std::string toString() const { return "contains: \"" + m_substr + "\""; } std::string m_substr; }; struct StartsWith : MatcherImpl { StartsWith( std::string const& substr ) : m_substr( substr ){} StartsWith( StartsWith const& other ) : m_substr( other.m_substr ){} virtual ~StartsWith(); virtual bool match( std::string const& expr ) const { return expr.find( m_substr ) == 0; } virtual std::string toString() const { return "starts with: \"" + m_substr + "\""; } std::string m_substr; }; struct EndsWith : MatcherImpl { EndsWith( std::string const& substr ) : m_substr( substr ){} EndsWith( EndsWith const& other ) : m_substr( other.m_substr ){} virtual ~EndsWith(); virtual bool match( std::string const& expr ) const { return expr.find( m_substr ) == expr.size() - m_substr.size(); } virtual std::string toString() const { return "ends with: \"" + m_substr + "\""; } std::string m_substr; }; } // namespace StdString } // namespace Impl // The following functions create the actual matcher objects. // This allows the types to be inferred template inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, Impl::Matcher const& m2 ) { return Impl::Generic::AllOf().add( m1 ).add( m2 ); } template inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, Impl::Matcher const& m2, Impl::Matcher const& m3 ) { return Impl::Generic::AllOf().add( m1 ).add( m2 ).add( m3 ); } template inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, Impl::Matcher const& m2 ) { return Impl::Generic::AnyOf().add( m1 ).add( m2 ); } template inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, Impl::Matcher const& m2, Impl::Matcher const& m3 ) { return Impl::Generic::AnyOf().add( m1 ).add( m2 ).add( m3 ); } inline Impl::StdString::Equals Equals( std::string const& str ) { return Impl::StdString::Equals( str ); } inline Impl::StdString::Equals Equals( const char* str ) { return Impl::StdString::Equals( Impl::StdString::makeString( str ) ); } inline Impl::StdString::Contains Contains( std::string const& substr ) { return Impl::StdString::Contains( substr ); } inline Impl::StdString::Contains Contains( const char* substr ) { return Impl::StdString::Contains( Impl::StdString::makeString( substr ) ); } inline Impl::StdString::StartsWith StartsWith( std::string const& substr ) { return Impl::StdString::StartsWith( substr ); } inline Impl::StdString::StartsWith StartsWith( const char* substr ) { return Impl::StdString::StartsWith( Impl::StdString::makeString( substr ) ); } inline Impl::StdString::EndsWith EndsWith( std::string const& substr ) { return Impl::StdString::EndsWith( substr ); } inline Impl::StdString::EndsWith EndsWith( const char* substr ) { return Impl::StdString::EndsWith( Impl::StdString::makeString( substr ) ); } } // namespace Matchers using namespace Matchers; } // namespace Catch // #included from: internal/catch_interfaces_tag_alias_registry.h #define TWOBLUECUBES_CATCH_INTERFACES_TAG_ALIAS_REGISTRY_H_INCLUDED // #included from: catch_tag_alias.h #define TWOBLUECUBES_CATCH_TAG_ALIAS_H_INCLUDED #include namespace Catch { struct TagAlias { TagAlias( std::string _tag, SourceLineInfo _lineInfo ) : tag( _tag ), lineInfo( _lineInfo ) {} std::string tag; SourceLineInfo lineInfo; }; struct RegistrarForTagAliases { RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); }; } // end namespace Catch #define CATCH_REGISTER_TAG_ALIAS( alias, spec ) namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } // #included from: catch_option.hpp #define TWOBLUECUBES_CATCH_OPTION_HPP_INCLUDED namespace Catch { // An optional type template class Option { public: Option() : nullableValue( NULL ) {} Option( T const& _value ) : nullableValue( new( storage ) T( _value ) ) {} Option( Option const& _other ) : nullableValue( _other ? new( storage ) T( *_other ) : NULL ) {} ~Option() { reset(); } Option& operator= ( Option const& _other ) { if( &_other != this ) { reset(); if( _other ) nullableValue = new( storage ) T( *_other ); } return *this; } Option& operator = ( T const& _value ) { reset(); nullableValue = new( storage ) T( _value ); return *this; } void reset() { if( nullableValue ) nullableValue->~T(); nullableValue = NULL; } T& operator*() { return *nullableValue; } T const& operator*() const { return *nullableValue; } T* operator->() { return nullableValue; } const T* operator->() const { return nullableValue; } T valueOr( T const& defaultValue ) const { return nullableValue ? *nullableValue : defaultValue; } bool some() const { return nullableValue != NULL; } bool none() const { return nullableValue == NULL; } bool operator !() const { return nullableValue == NULL; } operator SafeBool::type() const { return SafeBool::makeSafe( some() ); } private: T* nullableValue; char storage[sizeof(T)]; }; } // end namespace Catch namespace Catch { struct ITagAliasRegistry { virtual ~ITagAliasRegistry(); virtual Option find( std::string const& alias ) const = 0; virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; static ITagAliasRegistry const& get(); }; } // end namespace Catch // These files are included here so the single_include script doesn't put them // in the conditionally compiled sections // #included from: internal/catch_test_case_info.h #define TWOBLUECUBES_CATCH_TEST_CASE_INFO_H_INCLUDED #include #include #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpadded" #endif namespace Catch { struct ITestCase; struct TestCaseInfo { enum SpecialProperties{ None = 0, IsHidden = 1 << 1, ShouldFail = 1 << 2, MayFail = 1 << 3, Throws = 1 << 4 }; TestCaseInfo( std::string const& _name, std::string const& _className, std::string const& _description, std::set const& _tags, SourceLineInfo const& _lineInfo ); TestCaseInfo( TestCaseInfo const& other ); bool isHidden() const; bool throws() const; bool okToFail() const; bool expectedToFail() const; std::string name; std::string className; std::string description; std::set tags; std::set lcaseTags; std::string tagsAsString; SourceLineInfo lineInfo; SpecialProperties properties; }; class TestCase : public TestCaseInfo { public: TestCase( ITestCase* testCase, TestCaseInfo const& info ); TestCase( TestCase const& other ); TestCase withName( std::string const& _newName ) const; void invoke() const; TestCaseInfo const& getTestCaseInfo() const; void swap( TestCase& other ); bool operator == ( TestCase const& other ) const; bool operator < ( TestCase const& other ) const; TestCase& operator = ( TestCase const& other ); private: Ptr test; }; TestCase makeTestCase( ITestCase* testCase, std::string const& className, std::string const& name, std::string const& description, SourceLineInfo const& lineInfo ); } #ifdef __clang__ #pragma clang diagnostic pop #endif #ifdef __OBJC__ // #included from: internal/catch_objc.hpp #define TWOBLUECUBES_CATCH_OBJC_HPP_INCLUDED #import #include // NB. Any general catch headers included here must be included // in catch.hpp first to make sure they are included by the single // header for non obj-usage /////////////////////////////////////////////////////////////////////////////// // This protocol is really only here for (self) documenting purposes, since // all its methods are optional. @protocol OcFixture @optional -(void) setUp; -(void) tearDown; @end namespace Catch { class OcMethod : public SharedImpl { public: OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {} virtual void invoke() const { id obj = [[m_cls alloc] init]; performOptionalSelector( obj, @selector(setUp) ); performOptionalSelector( obj, m_sel ); performOptionalSelector( obj, @selector(tearDown) ); arcSafeRelease( obj ); } private: virtual ~OcMethod() {} Class m_cls; SEL m_sel; }; namespace Detail{ inline std::string getAnnotation( Class cls, std::string const& annotationName, std::string const& testCaseName ) { NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()]; SEL sel = NSSelectorFromString( selStr ); arcSafeRelease( selStr ); id value = performOptionalSelector( cls, sel ); if( value ) return [(NSString*)value UTF8String]; return ""; } } inline size_t registerTestMethods() { size_t noTestMethods = 0; int noClasses = objc_getClassList( NULL, 0 ); Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); objc_getClassList( classes, noClasses ); for( int c = 0; c < noClasses; c++ ) { Class cls = classes[c]; { u_int count; Method* methods = class_copyMethodList( cls, &count ); for( u_int m = 0; m < count ; m++ ) { SEL selector = method_getName(methods[m]); std::string methodName = sel_getName(selector); if( startsWith( methodName, "Catch_TestCase_" ) ) { std::string testCaseName = methodName.substr( 15 ); std::string name = Detail::getAnnotation( cls, "Name", testCaseName ); std::string desc = Detail::getAnnotation( cls, "Description", testCaseName ); const char* className = class_getName( cls ); getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, name.c_str(), desc.c_str(), SourceLineInfo() ) ); noTestMethods++; } } free(methods); } } return noTestMethods; } namespace Matchers { namespace Impl { namespace NSStringMatchers { template struct StringHolder : MatcherImpl{ StringHolder( NSString* substr ) : m_substr( [substr copy] ){} StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){} StringHolder() { arcSafeRelease( m_substr ); } NSString* m_substr; }; struct Equals : StringHolder { Equals( NSString* substr ) : StringHolder( substr ){} virtual bool match( ExpressionType const& str ) const { return (str != nil || m_substr == nil ) && [str isEqualToString:m_substr]; } virtual std::string toString() const { return "equals string: " + Catch::toString( m_substr ); } }; struct Contains : StringHolder { Contains( NSString* substr ) : StringHolder( substr ){} virtual bool match( ExpressionType const& str ) const { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location != NSNotFound; } virtual std::string toString() const { return "contains string: " + Catch::toString( m_substr ); } }; struct StartsWith : StringHolder { StartsWith( NSString* substr ) : StringHolder( substr ){} virtual bool match( ExpressionType const& str ) const { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location == 0; } virtual std::string toString() const { return "starts with: " + Catch::toString( m_substr ); } }; struct EndsWith : StringHolder { EndsWith( NSString* substr ) : StringHolder( substr ){} virtual bool match( ExpressionType const& str ) const { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location == [str length] - [m_substr length]; } virtual std::string toString() const { return "ends with: " + Catch::toString( m_substr ); } }; } // namespace NSStringMatchers } // namespace Impl inline Impl::NSStringMatchers::Equals Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); } inline Impl::NSStringMatchers::Contains Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); } inline Impl::NSStringMatchers::StartsWith StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); } inline Impl::NSStringMatchers::EndsWith EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); } } // namespace Matchers using namespace Matchers; } // namespace Catch /////////////////////////////////////////////////////////////////////////////// #define OC_TEST_CASE( name, desc )\ +(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Name_test ) \ {\ return @ name; \ }\ +(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Description_test ) \ { \ return @ desc; \ } \ -(void) INTERNAL_CATCH_UNIQUE_NAME( Catch_TestCase_test ) #endif #ifdef CATCH_IMPL // #included from: internal/catch_impl.hpp #define TWOBLUECUBES_CATCH_IMPL_HPP_INCLUDED // Collect all the implementation files together here // These are the equivalent of what would usually be cpp files #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wweak-vtables" #endif // #included from: ../catch_runner.hpp #define TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED // #included from: internal/catch_commandline.hpp #define TWOBLUECUBES_CATCH_COMMANDLINE_HPP_INCLUDED // #included from: catch_config.hpp #define TWOBLUECUBES_CATCH_CONFIG_HPP_INCLUDED // #included from: catch_test_spec_parser.hpp #define TWOBLUECUBES_CATCH_TEST_SPEC_PARSER_HPP_INCLUDED #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpadded" #endif // #included from: catch_test_spec.hpp #define TWOBLUECUBES_CATCH_TEST_SPEC_HPP_INCLUDED #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpadded" #endif #include #include namespace Catch { class TestSpec { struct Pattern : SharedImpl<> { virtual ~Pattern(); virtual bool matches( TestCaseInfo const& testCase ) const = 0; }; class NamePattern : public Pattern { enum WildcardPosition { NoWildcard = 0, WildcardAtStart = 1, WildcardAtEnd = 2, WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd }; public: NamePattern( std::string const& name ) : m_name( toLower( name ) ), m_wildcard( NoWildcard ) { if( startsWith( m_name, "*" ) ) { m_name = m_name.substr( 1 ); m_wildcard = WildcardAtStart; } if( endsWith( m_name, "*" ) ) { m_name = m_name.substr( 0, m_name.size()-1 ); m_wildcard = static_cast( m_wildcard | WildcardAtEnd ); } } virtual ~NamePattern(); virtual bool matches( TestCaseInfo const& testCase ) const { switch( m_wildcard ) { case NoWildcard: return m_name == toLower( testCase.name ); case WildcardAtStart: return endsWith( toLower( testCase.name ), m_name ); case WildcardAtEnd: return startsWith( toLower( testCase.name ), m_name ); case WildcardAtBothEnds: return contains( toLower( testCase.name ), m_name ); } #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunreachable-code" #endif throw std::logic_error( "Unknown enum" ); #ifdef __clang__ #pragma clang diagnostic pop #endif } private: std::string m_name; WildcardPosition m_wildcard; }; class TagPattern : public Pattern { public: TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} virtual ~TagPattern(); virtual bool matches( TestCaseInfo const& testCase ) const { return testCase.lcaseTags.find( m_tag ) != testCase.lcaseTags.end(); } private: std::string m_tag; }; class ExcludedPattern : public Pattern { public: ExcludedPattern( Ptr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} virtual ~ExcludedPattern(); virtual bool matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } private: Ptr m_underlyingPattern; }; struct Filter { std::vector > m_patterns; bool matches( TestCaseInfo const& testCase ) const { // All patterns in a filter must match for the filter to be a match for( std::vector >::const_iterator it = m_patterns.begin(), itEnd = m_patterns.end(); it != itEnd; ++it ) if( !(*it)->matches( testCase ) ) return false; return true; } }; public: bool hasFilters() const { return !m_filters.empty(); } bool matches( TestCaseInfo const& testCase ) const { // A TestSpec matches if any filter matches for( std::vector::const_iterator it = m_filters.begin(), itEnd = m_filters.end(); it != itEnd; ++it ) if( it->matches( testCase ) ) return true; return false; } private: std::vector m_filters; friend class TestSpecParser; }; } #ifdef __clang__ #pragma clang diagnostic pop #endif namespace Catch { class TestSpecParser { enum Mode{ None, Name, QuotedName, Tag }; Mode m_mode; bool m_exclusion; std::size_t m_start, m_pos; std::string m_arg; TestSpec::Filter m_currentFilter; TestSpec m_testSpec; ITagAliasRegistry const* m_tagAliases; public: TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} TestSpecParser& parse( std::string const& arg ) { m_mode = None; m_exclusion = false; m_start = std::string::npos; m_arg = m_tagAliases->expandAliases( arg ); for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) visitChar( m_arg[m_pos] ); if( m_mode == Name ) addPattern(); return *this; } TestSpec testSpec() { addFilter(); return m_testSpec; } private: void visitChar( char c ) { if( m_mode == None ) { switch( c ) { case ' ': return; case '~': m_exclusion = true; return; case '[': return startNewMode( Tag, ++m_pos ); case '"': return startNewMode( QuotedName, ++m_pos ); default: startNewMode( Name, m_pos ); break; } } if( m_mode == Name ) { if( c == ',' ) { addPattern(); addFilter(); } else if( c == '[' ) { if( subString() == "exclude:" ) m_exclusion = true; else addPattern(); startNewMode( Tag, ++m_pos ); } } else if( m_mode == QuotedName && c == '"' ) addPattern(); else if( m_mode == Tag && c == ']' ) addPattern(); } void startNewMode( Mode mode, std::size_t start ) { m_mode = mode; m_start = start; } std::string subString() const { return m_arg.substr( m_start, m_pos - m_start ); } template void addPattern() { std::string token = subString(); if( startsWith( token, "exclude:" ) ) { m_exclusion = true; token = token.substr( 8 ); } if( !token.empty() ) { Ptr pattern = new T( token ); if( m_exclusion ) pattern = new TestSpec::ExcludedPattern( pattern ); m_currentFilter.m_patterns.push_back( pattern ); } m_exclusion = false; m_mode = None; } void addFilter() { if( !m_currentFilter.m_patterns.empty() ) { m_testSpec.m_filters.push_back( m_currentFilter ); m_currentFilter = TestSpec::Filter(); } } }; inline TestSpec parseTestSpec( std::string const& arg ) { return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); } } // namespace Catch #ifdef __clang__ #pragma clang diagnostic pop #endif // #included from: catch_interfaces_config.h #define TWOBLUECUBES_CATCH_INTERFACES_CONFIG_H_INCLUDED #include #include #include namespace Catch { struct Verbosity { enum Level { NoOutput = 0, Quiet, Normal }; }; struct WarnAbout { enum What { Nothing = 0x00, NoAssertions = 0x01 }; }; struct ShowDurations { enum OrNot { DefaultForReporter, Always, Never }; }; struct RunTests { enum InWhatOrder { InDeclarationOrder, InLexicographicalOrder, InRandomOrder }; }; class TestSpec; struct IConfig : IShared { virtual ~IConfig(); virtual bool allowThrows() const = 0; virtual std::ostream& stream() const = 0; virtual std::string name() const = 0; virtual bool includeSuccessfulResults() const = 0; virtual bool shouldDebugBreak() const = 0; virtual bool warnAboutMissingAssertions() const = 0; virtual int abortAfter() const = 0; virtual bool showInvisibles() const = 0; virtual ShowDurations::OrNot showDurations() const = 0; virtual TestSpec const& testSpec() const = 0; virtual RunTests::InWhatOrder runOrder() const = 0; virtual unsigned int rngSeed() const = 0; virtual bool forceColour() const = 0; }; } // #included from: catch_stream.h #define TWOBLUECUBES_CATCH_STREAM_H_INCLUDED #include #ifdef __clang__ #pragma clang diagnostic ignored "-Wpadded" #endif namespace Catch { class Stream { public: Stream(); Stream( std::streambuf* _streamBuf, bool _isOwned ); void release(); std::streambuf* streamBuf; private: bool isOwned; }; std::ostream& cout(); std::ostream& cerr(); } #include #include #include #include #include #ifndef CATCH_CONFIG_CONSOLE_WIDTH #define CATCH_CONFIG_CONSOLE_WIDTH 80 #endif namespace Catch { struct ConfigData { ConfigData() : listTests( false ), listTags( false ), listReporters( false ), listTestNamesOnly( false ), showSuccessfulTests( false ), shouldDebugBreak( false ), noThrow( false ), showHelp( false ), showInvisibles( false ), forceColour( false ), abortAfter( -1 ), rngSeed( 0 ), verbosity( Verbosity::Normal ), warnings( WarnAbout::Nothing ), showDurations( ShowDurations::DefaultForReporter ), runOrder( RunTests::InDeclarationOrder ) {} bool listTests; bool listTags; bool listReporters; bool listTestNamesOnly; bool showSuccessfulTests; bool shouldDebugBreak; bool noThrow; bool showHelp; bool showInvisibles; bool forceColour; int abortAfter; unsigned int rngSeed; Verbosity::Level verbosity; WarnAbout::What warnings; ShowDurations::OrNot showDurations; RunTests::InWhatOrder runOrder; std::string reporterName; std::string outputFilename; std::string name; std::string processName; std::vector testsOrTags; }; class Config : public SharedImpl { private: Config( Config const& other ); Config& operator = ( Config const& other ); virtual void dummy(); public: Config() : m_os( Catch::cout().rdbuf() ) {} Config( ConfigData const& data ) : m_data( data ), m_os( Catch::cout().rdbuf() ) { if( !data.testsOrTags.empty() ) { TestSpecParser parser( ITagAliasRegistry::get() ); for( std::size_t i = 0; i < data.testsOrTags.size(); ++i ) parser.parse( data.testsOrTags[i] ); m_testSpec = parser.testSpec(); } } virtual ~Config() { m_os.rdbuf( Catch::cout().rdbuf() ); m_stream.release(); } void setFilename( std::string const& filename ) { m_data.outputFilename = filename; } std::string const& getFilename() const { return m_data.outputFilename ; } bool listTests() const { return m_data.listTests; } bool listTestNamesOnly() const { return m_data.listTestNamesOnly; } bool listTags() const { return m_data.listTags; } bool listReporters() const { return m_data.listReporters; } std::string getProcessName() const { return m_data.processName; } bool shouldDebugBreak() const { return m_data.shouldDebugBreak; } void setStreamBuf( std::streambuf* buf ) { m_os.rdbuf( buf ? buf : Catch::cout().rdbuf() ); } void useStream( std::string const& streamName ) { Stream stream = createStream( streamName ); setStreamBuf( stream.streamBuf ); m_stream.release(); m_stream = stream; } std::string getReporterName() const { return m_data.reporterName; } int abortAfter() const { return m_data.abortAfter; } TestSpec const& testSpec() const { return m_testSpec; } bool showHelp() const { return m_data.showHelp; } bool showInvisibles() const { return m_data.showInvisibles; } // IConfig interface virtual bool allowThrows() const { return !m_data.noThrow; } virtual std::ostream& stream() const { return m_os; } virtual std::string name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } virtual bool includeSuccessfulResults() const { return m_data.showSuccessfulTests; } virtual bool warnAboutMissingAssertions() const { return m_data.warnings & WarnAbout::NoAssertions; } virtual ShowDurations::OrNot showDurations() const { return m_data.showDurations; } virtual RunTests::InWhatOrder runOrder() const { return m_data.runOrder; } virtual unsigned int rngSeed() const { return m_data.rngSeed; } virtual bool forceColour() const { return m_data.forceColour; } private: ConfigData m_data; Stream m_stream; mutable std::ostream m_os; TestSpec m_testSpec; }; } // end namespace Catch // #included from: catch_clara.h #define TWOBLUECUBES_CATCH_CLARA_H_INCLUDED // Use Catch's value for console width (store Clara's off to the side, if present) #ifdef CLARA_CONFIG_CONSOLE_WIDTH #define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH #undef CLARA_CONFIG_CONSOLE_WIDTH #endif #define CLARA_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH // Declare Clara inside the Catch namespace #define STITCH_CLARA_OPEN_NAMESPACE namespace Catch { // #included from: ../external/clara.h // Only use header guard if we are not using an outer namespace #if !defined(TWOBLUECUBES_CLARA_H_INCLUDED) || defined(STITCH_CLARA_OPEN_NAMESPACE) #ifndef STITCH_CLARA_OPEN_NAMESPACE #define TWOBLUECUBES_CLARA_H_INCLUDED #define STITCH_CLARA_OPEN_NAMESPACE #define STITCH_CLARA_CLOSE_NAMESPACE #else #define STITCH_CLARA_CLOSE_NAMESPACE } #endif #define STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE // ----------- #included from tbc_text_format.h ----------- // Only use header guard if we are not using an outer namespace #if !defined(TBC_TEXT_FORMAT_H_INCLUDED) || defined(STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE) #ifndef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE #define TBC_TEXT_FORMAT_H_INCLUDED #endif #include #include #include // Use optional outer namespace #ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE namespace STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE { #endif namespace Tbc { #ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; #else const unsigned int consoleWidth = 80; #endif struct TextAttributes { TextAttributes() : initialIndent( std::string::npos ), indent( 0 ), width( consoleWidth-1 ), tabChar( '\t' ) {} TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } std::size_t initialIndent; // indent of first line, or npos std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos std::size_t width; // maximum width of text, including indent. Longer text will wrap char tabChar; // If this char is seen the indent is changed to current pos }; class Text { public: Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) : attr( _attr ) { std::string wrappableChars = " [({.,/|\\-"; std::size_t indent = _attr.initialIndent != std::string::npos ? _attr.initialIndent : _attr.indent; std::string remainder = _str; while( !remainder.empty() ) { if( lines.size() >= 1000 ) { lines.push_back( "... message truncated due to excessive size" ); return; } std::size_t tabPos = std::string::npos; std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); std::size_t pos = remainder.find_first_of( '\n' ); if( pos <= width ) { width = pos; } pos = remainder.find_last_of( _attr.tabChar, width ); if( pos != std::string::npos ) { tabPos = pos; if( remainder[width] == '\n' ) width--; remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); } if( width == remainder.size() ) { spliceLine( indent, remainder, width ); } else if( remainder[width] == '\n' ) { spliceLine( indent, remainder, width ); if( width <= 1 || remainder.size() != 1 ) remainder = remainder.substr( 1 ); indent = _attr.indent; } else { pos = remainder.find_last_of( wrappableChars, width ); if( pos != std::string::npos && pos > 0 ) { spliceLine( indent, remainder, pos ); if( remainder[0] == ' ' ) remainder = remainder.substr( 1 ); } else { spliceLine( indent, remainder, width-1 ); lines.back() += "-"; } if( lines.size() == 1 ) indent = _attr.indent; if( tabPos != std::string::npos ) indent += tabPos; } } } void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); _remainder = _remainder.substr( _pos ); } typedef std::vector::const_iterator const_iterator; const_iterator begin() const { return lines.begin(); } const_iterator end() const { return lines.end(); } std::string const& last() const { return lines.back(); } std::size_t size() const { return lines.size(); } std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } std::string toString() const { std::ostringstream oss; oss << *this; return oss.str(); } inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); it != itEnd; ++it ) { if( it != _text.begin() ) _stream << "\n"; _stream << *it; } return _stream; } private: std::string str; TextAttributes attr; std::vector lines; }; } // end namespace Tbc #ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE } // end outer namespace #endif #endif // TBC_TEXT_FORMAT_H_INCLUDED // ----------- end of #include from tbc_text_format.h ----------- // ........... back in /Users/philnash/Dev/OSS/Clara/srcs/clara.h #undef STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE #include #include #include #include // Use optional outer namespace #ifdef STITCH_CLARA_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE #endif namespace Clara { struct UnpositionalTag {}; extern UnpositionalTag _; #ifdef CLARA_CONFIG_MAIN UnpositionalTag _; #endif namespace Detail { #ifdef CLARA_CONSOLE_WIDTH const unsigned int consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; #else const unsigned int consoleWidth = 80; #endif using namespace Tbc; inline bool startsWith( std::string const& str, std::string const& prefix ) { return str.size() >= prefix.size() && str.substr( 0, prefix.size() ) == prefix; } template struct RemoveConstRef{ typedef T type; }; template struct RemoveConstRef{ typedef T type; }; template struct RemoveConstRef{ typedef T type; }; template struct RemoveConstRef{ typedef T type; }; template struct IsBool { static const bool value = false; }; template<> struct IsBool { static const bool value = true; }; template void convertInto( std::string const& _source, T& _dest ) { std::stringstream ss; ss << _source; ss >> _dest; if( ss.fail() ) throw std::runtime_error( "Unable to convert " + _source + " to destination type" ); } inline void convertInto( std::string const& _source, std::string& _dest ) { _dest = _source; } inline void convertInto( std::string const& _source, bool& _dest ) { std::string sourceLC = _source; std::transform( sourceLC.begin(), sourceLC.end(), sourceLC.begin(), ::tolower ); if( sourceLC == "y" || sourceLC == "1" || sourceLC == "true" || sourceLC == "yes" || sourceLC == "on" ) _dest = true; else if( sourceLC == "n" || sourceLC == "0" || sourceLC == "false" || sourceLC == "no" || sourceLC == "off" ) _dest = false; else throw std::runtime_error( "Expected a boolean value but did not recognise:\n '" + _source + "'" ); } inline void convertInto( bool _source, bool& _dest ) { _dest = _source; } template inline void convertInto( bool, T& ) { throw std::runtime_error( "Invalid conversion" ); } template struct IArgFunction { virtual ~IArgFunction() {} # ifdef CATCH_CPP11_OR_GREATER IArgFunction() = default; IArgFunction( IArgFunction const& ) = default; # endif virtual void set( ConfigT& config, std::string const& value ) const = 0; virtual void setFlag( ConfigT& config ) const = 0; virtual bool takesArg() const = 0; virtual IArgFunction* clone() const = 0; }; template class BoundArgFunction { public: BoundArgFunction() : functionObj( NULL ) {} BoundArgFunction( IArgFunction* _functionObj ) : functionObj( _functionObj ) {} BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : NULL ) {} BoundArgFunction& operator = ( BoundArgFunction const& other ) { IArgFunction* newFunctionObj = other.functionObj ? other.functionObj->clone() : NULL; delete functionObj; functionObj = newFunctionObj; return *this; } ~BoundArgFunction() { delete functionObj; } void set( ConfigT& config, std::string const& value ) const { functionObj->set( config, value ); } void setFlag( ConfigT& config ) const { functionObj->setFlag( config ); } bool takesArg() const { return functionObj->takesArg(); } bool isSet() const { return functionObj != NULL; } private: IArgFunction* functionObj; }; template struct NullBinder : IArgFunction{ virtual void set( C&, std::string const& ) const {} virtual void setFlag( C& ) const {} virtual bool takesArg() const { return true; } virtual IArgFunction* clone() const { return new NullBinder( *this ); } }; template struct BoundDataMember : IArgFunction{ BoundDataMember( M C::* _member ) : member( _member ) {} virtual void set( C& p, std::string const& stringValue ) const { convertInto( stringValue, p.*member ); } virtual void setFlag( C& p ) const { convertInto( true, p.*member ); } virtual bool takesArg() const { return !IsBool::value; } virtual IArgFunction* clone() const { return new BoundDataMember( *this ); } M C::* member; }; template struct BoundUnaryMethod : IArgFunction{ BoundUnaryMethod( void (C::*_member)( M ) ) : member( _member ) {} virtual void set( C& p, std::string const& stringValue ) const { typename RemoveConstRef::type value; convertInto( stringValue, value ); (p.*member)( value ); } virtual void setFlag( C& p ) const { typename RemoveConstRef::type value; convertInto( true, value ); (p.*member)( value ); } virtual bool takesArg() const { return !IsBool::value; } virtual IArgFunction* clone() const { return new BoundUnaryMethod( *this ); } void (C::*member)( M ); }; template struct BoundNullaryMethod : IArgFunction{ BoundNullaryMethod( void (C::*_member)() ) : member( _member ) {} virtual void set( C& p, std::string const& stringValue ) const { bool value; convertInto( stringValue, value ); if( value ) (p.*member)(); } virtual void setFlag( C& p ) const { (p.*member)(); } virtual bool takesArg() const { return false; } virtual IArgFunction* clone() const { return new BoundNullaryMethod( *this ); } void (C::*member)(); }; template struct BoundUnaryFunction : IArgFunction{ BoundUnaryFunction( void (*_function)( C& ) ) : function( _function ) {} virtual void set( C& obj, std::string const& stringValue ) const { bool value; convertInto( stringValue, value ); if( value ) function( obj ); } virtual void setFlag( C& p ) const { function( p ); } virtual bool takesArg() const { return false; } virtual IArgFunction* clone() const { return new BoundUnaryFunction( *this ); } void (*function)( C& ); }; template struct BoundBinaryFunction : IArgFunction{ BoundBinaryFunction( void (*_function)( C&, T ) ) : function( _function ) {} virtual void set( C& obj, std::string const& stringValue ) const { typename RemoveConstRef::type value; convertInto( stringValue, value ); function( obj, value ); } virtual void setFlag( C& obj ) const { typename RemoveConstRef::type value; convertInto( true, value ); function( obj, value ); } virtual bool takesArg() const { return !IsBool::value; } virtual IArgFunction* clone() const { return new BoundBinaryFunction( *this ); } void (*function)( C&, T ); }; } // namespace Detail struct Parser { Parser() : separators( " \t=:" ) {} struct Token { enum Type { Positional, ShortOpt, LongOpt }; Token( Type _type, std::string const& _data ) : type( _type ), data( _data ) {} Type type; std::string data; }; void parseIntoTokens( int argc, char const * const * argv, std::vector& tokens ) const { const std::string doubleDash = "--"; for( int i = 1; i < argc && argv[i] != doubleDash; ++i ) parseIntoTokens( argv[i] , tokens); } void parseIntoTokens( std::string arg, std::vector& tokens ) const { while( !arg.empty() ) { Parser::Token token( Parser::Token::Positional, arg ); arg = ""; if( token.data[0] == '-' ) { if( token.data.size() > 1 && token.data[1] == '-' ) { token = Parser::Token( Parser::Token::LongOpt, token.data.substr( 2 ) ); } else { token = Parser::Token( Parser::Token::ShortOpt, token.data.substr( 1 ) ); if( token.data.size() > 1 && separators.find( token.data[1] ) == std::string::npos ) { arg = "-" + token.data.substr( 1 ); token.data = token.data.substr( 0, 1 ); } } } if( token.type != Parser::Token::Positional ) { std::size_t pos = token.data.find_first_of( separators ); if( pos != std::string::npos ) { arg = token.data.substr( pos+1 ); token.data = token.data.substr( 0, pos ); } } tokens.push_back( token ); } } std::string separators; }; template struct CommonArgProperties { CommonArgProperties() {} CommonArgProperties( Detail::BoundArgFunction const& _boundField ) : boundField( _boundField ) {} Detail::BoundArgFunction boundField; std::string description; std::string detail; std::string placeholder; // Only value if boundField takes an arg bool takesArg() const { return !placeholder.empty(); } void validate() const { if( !boundField.isSet() ) throw std::logic_error( "option not bound" ); } }; struct OptionArgProperties { std::vector shortNames; std::string longName; bool hasShortName( std::string const& shortName ) const { return std::find( shortNames.begin(), shortNames.end(), shortName ) != shortNames.end(); } bool hasLongName( std::string const& _longName ) const { return _longName == longName; } }; struct PositionalArgProperties { PositionalArgProperties() : position( -1 ) {} int position; // -1 means non-positional (floating) bool isFixedPositional() const { return position != -1; } }; template class CommandLine { struct Arg : CommonArgProperties, OptionArgProperties, PositionalArgProperties { Arg() {} Arg( Detail::BoundArgFunction const& _boundField ) : CommonArgProperties( _boundField ) {} using CommonArgProperties::placeholder; // !TBD std::string dbgName() const { if( !longName.empty() ) return "--" + longName; if( !shortNames.empty() ) return "-" + shortNames[0]; return "positional args"; } std::string commands() const { std::ostringstream oss; bool first = true; std::vector::const_iterator it = shortNames.begin(), itEnd = shortNames.end(); for(; it != itEnd; ++it ) { if( first ) first = false; else oss << ", "; oss << "-" << *it; } if( !longName.empty() ) { if( !first ) oss << ", "; oss << "--" << longName; } if( !placeholder.empty() ) oss << " <" << placeholder << ">"; return oss.str(); } }; // NOTE: std::auto_ptr is deprecated in c++11/c++0x #if defined(__cplusplus) && __cplusplus > 199711L typedef std::unique_ptr ArgAutoPtr; #else typedef std::auto_ptr ArgAutoPtr; #endif friend void addOptName( Arg& arg, std::string const& optName ) { if( optName.empty() ) return; if( Detail::startsWith( optName, "--" ) ) { if( !arg.longName.empty() ) throw std::logic_error( "Only one long opt may be specified. '" + arg.longName + "' already specified, now attempting to add '" + optName + "'" ); arg.longName = optName.substr( 2 ); } else if( Detail::startsWith( optName, "-" ) ) arg.shortNames.push_back( optName.substr( 1 ) ); else throw std::logic_error( "option must begin with - or --. Option was: '" + optName + "'" ); } friend void setPositionalArg( Arg& arg, int position ) { arg.position = position; } class ArgBuilder { public: ArgBuilder( Arg* arg ) : m_arg( arg ) {} // Bind a non-boolean data member (requires placeholder string) template void bind( M C::* field, std::string const& placeholder ) { m_arg->boundField = new Detail::BoundDataMember( field ); m_arg->placeholder = placeholder; } // Bind a boolean data member (no placeholder required) template void bind( bool C::* field ) { m_arg->boundField = new Detail::BoundDataMember( field ); } // Bind a method taking a single, non-boolean argument (requires a placeholder string) template void bind( void (C::* unaryMethod)( M ), std::string const& placeholder ) { m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); m_arg->placeholder = placeholder; } // Bind a method taking a single, boolean argument (no placeholder string required) template void bind( void (C::* unaryMethod)( bool ) ) { m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); } // Bind a method that takes no arguments (will be called if opt is present) template void bind( void (C::* nullaryMethod)() ) { m_arg->boundField = new Detail::BoundNullaryMethod( nullaryMethod ); } // Bind a free function taking a single argument - the object to operate on (no placeholder string required) template void bind( void (* unaryFunction)( C& ) ) { m_arg->boundField = new Detail::BoundUnaryFunction( unaryFunction ); } // Bind a free function taking a single argument - the object to operate on (requires a placeholder string) template void bind( void (* binaryFunction)( C&, T ), std::string const& placeholder ) { m_arg->boundField = new Detail::BoundBinaryFunction( binaryFunction ); m_arg->placeholder = placeholder; } ArgBuilder& describe( std::string const& description ) { m_arg->description = description; return *this; } ArgBuilder& detail( std::string const& detail ) { m_arg->detail = detail; return *this; } protected: Arg* m_arg; }; class OptBuilder : public ArgBuilder { public: OptBuilder( Arg* arg ) : ArgBuilder( arg ) {} OptBuilder( OptBuilder& other ) : ArgBuilder( other ) {} OptBuilder& operator[]( std::string const& optName ) { addOptName( *ArgBuilder::m_arg, optName ); return *this; } }; public: CommandLine() : m_boundProcessName( new Detail::NullBinder() ), m_highestSpecifiedArgPosition( 0 ), m_throwOnUnrecognisedTokens( false ) {} CommandLine( CommandLine const& other ) : m_boundProcessName( other.m_boundProcessName ), m_options ( other.m_options ), m_positionalArgs( other.m_positionalArgs ), m_highestSpecifiedArgPosition( other.m_highestSpecifiedArgPosition ), m_throwOnUnrecognisedTokens( other.m_throwOnUnrecognisedTokens ) { if( other.m_floatingArg.get() ) m_floatingArg.reset( new Arg( *other.m_floatingArg ) ); } CommandLine& setThrowOnUnrecognisedTokens( bool shouldThrow = true ) { m_throwOnUnrecognisedTokens = shouldThrow; return *this; } OptBuilder operator[]( std::string const& optName ) { m_options.push_back( Arg() ); addOptName( m_options.back(), optName ); OptBuilder builder( &m_options.back() ); return builder; } ArgBuilder operator[]( int position ) { m_positionalArgs.insert( std::make_pair( position, Arg() ) ); if( position > m_highestSpecifiedArgPosition ) m_highestSpecifiedArgPosition = position; setPositionalArg( m_positionalArgs[position], position ); ArgBuilder builder( &m_positionalArgs[position] ); return builder; } // Invoke this with the _ instance ArgBuilder operator[]( UnpositionalTag ) { if( m_floatingArg.get() ) throw std::logic_error( "Only one unpositional argument can be added" ); m_floatingArg.reset( new Arg() ); ArgBuilder builder( m_floatingArg.get() ); return builder; } template void bindProcessName( M C::* field ) { m_boundProcessName = new Detail::BoundDataMember( field ); } template void bindProcessName( void (C::*_unaryMethod)( M ) ) { m_boundProcessName = new Detail::BoundUnaryMethod( _unaryMethod ); } void optUsage( std::ostream& os, std::size_t indent = 0, std::size_t width = Detail::consoleWidth ) const { typename std::vector::const_iterator itBegin = m_options.begin(), itEnd = m_options.end(), it; std::size_t maxWidth = 0; for( it = itBegin; it != itEnd; ++it ) maxWidth = (std::max)( maxWidth, it->commands().size() ); for( it = itBegin; it != itEnd; ++it ) { Detail::Text usage( it->commands(), Detail::TextAttributes() .setWidth( maxWidth+indent ) .setIndent( indent ) ); Detail::Text desc( it->description, Detail::TextAttributes() .setWidth( width - maxWidth - 3 ) ); for( std::size_t i = 0; i < (std::max)( usage.size(), desc.size() ); ++i ) { std::string usageCol = i < usage.size() ? usage[i] : ""; os << usageCol; if( i < desc.size() && !desc[i].empty() ) os << std::string( indent + 2 + maxWidth - usageCol.size(), ' ' ) << desc[i]; os << "\n"; } } } std::string optUsage() const { std::ostringstream oss; optUsage( oss ); return oss.str(); } void argSynopsis( std::ostream& os ) const { for( int i = 1; i <= m_highestSpecifiedArgPosition; ++i ) { if( i > 1 ) os << " "; typename std::map::const_iterator it = m_positionalArgs.find( i ); if( it != m_positionalArgs.end() ) os << "<" << it->second.placeholder << ">"; else if( m_floatingArg.get() ) os << "<" << m_floatingArg->placeholder << ">"; else throw std::logic_error( "non consecutive positional arguments with no floating args" ); } // !TBD No indication of mandatory args if( m_floatingArg.get() ) { if( m_highestSpecifiedArgPosition > 1 ) os << " "; os << "[<" << m_floatingArg->placeholder << "> ...]"; } } std::string argSynopsis() const { std::ostringstream oss; argSynopsis( oss ); return oss.str(); } void usage( std::ostream& os, std::string const& procName ) const { validate(); os << "usage:\n " << procName << " "; argSynopsis( os ); if( !m_options.empty() ) { os << " [options]\n\nwhere options are: \n"; optUsage( os, 2 ); } os << "\n"; } std::string usage( std::string const& procName ) const { std::ostringstream oss; usage( oss, procName ); return oss.str(); } ConfigT parse( int argc, char const * const * argv ) const { ConfigT config; parseInto( argc, argv, config ); return config; } std::vector parseInto( int argc, char const * const * argv, ConfigT& config ) const { std::string processName = argv[0]; std::size_t lastSlash = processName.find_last_of( "/\\" ); if( lastSlash != std::string::npos ) processName = processName.substr( lastSlash+1 ); m_boundProcessName.set( config, processName ); std::vector tokens; Parser parser; parser.parseIntoTokens( argc, argv, tokens ); return populate( tokens, config ); } std::vector populate( std::vector const& tokens, ConfigT& config ) const { validate(); std::vector unusedTokens = populateOptions( tokens, config ); unusedTokens = populateFixedArgs( unusedTokens, config ); unusedTokens = populateFloatingArgs( unusedTokens, config ); return unusedTokens; } std::vector populateOptions( std::vector const& tokens, ConfigT& config ) const { std::vector unusedTokens; std::vector errors; for( std::size_t i = 0; i < tokens.size(); ++i ) { Parser::Token const& token = tokens[i]; typename std::vector::const_iterator it = m_options.begin(), itEnd = m_options.end(); for(; it != itEnd; ++it ) { Arg const& arg = *it; try { if( ( token.type == Parser::Token::ShortOpt && arg.hasShortName( token.data ) ) || ( token.type == Parser::Token::LongOpt && arg.hasLongName( token.data ) ) ) { if( arg.takesArg() ) { if( i == tokens.size()-1 || tokens[i+1].type != Parser::Token::Positional ) errors.push_back( "Expected argument to option: " + token.data ); else arg.boundField.set( config, tokens[++i].data ); } else { arg.boundField.setFlag( config ); } break; } } catch( std::exception& ex ) { errors.push_back( std::string( ex.what() ) + "\n- while parsing: (" + arg.commands() + ")" ); } } if( it == itEnd ) { if( token.type == Parser::Token::Positional || !m_throwOnUnrecognisedTokens ) unusedTokens.push_back( token ); else if( errors.empty() && m_throwOnUnrecognisedTokens ) errors.push_back( "unrecognised option: " + token.data ); } } if( !errors.empty() ) { std::ostringstream oss; for( std::vector::const_iterator it = errors.begin(), itEnd = errors.end(); it != itEnd; ++it ) { if( it != errors.begin() ) oss << "\n"; oss << *it; } throw std::runtime_error( oss.str() ); } return unusedTokens; } std::vector populateFixedArgs( std::vector const& tokens, ConfigT& config ) const { std::vector unusedTokens; int position = 1; for( std::size_t i = 0; i < tokens.size(); ++i ) { Parser::Token const& token = tokens[i]; typename std::map::const_iterator it = m_positionalArgs.find( position ); if( it != m_positionalArgs.end() ) it->second.boundField.set( config, token.data ); else unusedTokens.push_back( token ); if( token.type == Parser::Token::Positional ) position++; } return unusedTokens; } std::vector populateFloatingArgs( std::vector const& tokens, ConfigT& config ) const { if( !m_floatingArg.get() ) return tokens; std::vector unusedTokens; for( std::size_t i = 0; i < tokens.size(); ++i ) { Parser::Token const& token = tokens[i]; if( token.type == Parser::Token::Positional ) m_floatingArg->boundField.set( config, token.data ); else unusedTokens.push_back( token ); } return unusedTokens; } void validate() const { if( m_options.empty() && m_positionalArgs.empty() && !m_floatingArg.get() ) throw std::logic_error( "No options or arguments specified" ); for( typename std::vector::const_iterator it = m_options.begin(), itEnd = m_options.end(); it != itEnd; ++it ) it->validate(); } private: Detail::BoundArgFunction m_boundProcessName; std::vector m_options; std::map m_positionalArgs; ArgAutoPtr m_floatingArg; int m_highestSpecifiedArgPosition; bool m_throwOnUnrecognisedTokens; }; } // end namespace Clara STITCH_CLARA_CLOSE_NAMESPACE #undef STITCH_CLARA_OPEN_NAMESPACE #undef STITCH_CLARA_CLOSE_NAMESPACE #endif // TWOBLUECUBES_CLARA_H_INCLUDED #undef STITCH_CLARA_OPEN_NAMESPACE // Restore Clara's value for console width, if present #ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH #define CLARA_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH #undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH #endif #include namespace Catch { inline void abortAfterFirst( ConfigData& config ) { config.abortAfter = 1; } inline void abortAfterX( ConfigData& config, int x ) { if( x < 1 ) throw std::runtime_error( "Value after -x or --abortAfter must be greater than zero" ); config.abortAfter = x; } inline void addTestOrTags( ConfigData& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); } inline void addWarning( ConfigData& config, std::string const& _warning ) { if( _warning == "NoAssertions" ) config.warnings = static_cast( config.warnings | WarnAbout::NoAssertions ); else throw std::runtime_error( "Unrecognised warning: '" + _warning + "'" ); } inline void setOrder( ConfigData& config, std::string const& order ) { if( startsWith( "declared", order ) ) config.runOrder = RunTests::InDeclarationOrder; else if( startsWith( "lexical", order ) ) config.runOrder = RunTests::InLexicographicalOrder; else if( startsWith( "random", order ) ) config.runOrder = RunTests::InRandomOrder; else throw std::runtime_error( "Unrecognised ordering: '" + order + "'" ); } inline void setRngSeed( ConfigData& config, std::string const& seed ) { if( seed == "time" ) { config.rngSeed = static_cast( std::time(0) ); } else { std::stringstream ss; ss << seed; ss >> config.rngSeed; if( ss.fail() ) throw std::runtime_error( "Argment to --rng-seed should be the word 'time' or a number" ); } } inline void setVerbosity( ConfigData& config, int level ) { // !TBD: accept strings? config.verbosity = static_cast( level ); } inline void setShowDurations( ConfigData& config, bool _showDurations ) { config.showDurations = _showDurations ? ShowDurations::Always : ShowDurations::Never; } inline void loadTestNamesFromFile( ConfigData& config, std::string const& _filename ) { std::ifstream f( _filename.c_str() ); if( !f.is_open() ) throw std::domain_error( "Unable to load input file: " + _filename ); std::string line; while( std::getline( f, line ) ) { line = trim(line); if( !line.empty() && !startsWith( line, "#" ) ) addTestOrTags( config, "\"" + line + "\"," ); } } inline Clara::CommandLine makeCommandLineParser() { using namespace Clara; CommandLine cli; cli.bindProcessName( &ConfigData::processName ); cli["-?"]["-h"]["--help"] .describe( "display usage information" ) .bind( &ConfigData::showHelp ); cli["-l"]["--list-tests"] .describe( "list all/matching test cases" ) .bind( &ConfigData::listTests ); cli["-t"]["--list-tags"] .describe( "list all/matching tags" ) .bind( &ConfigData::listTags ); cli["-s"]["--success"] .describe( "include successful tests in output" ) .bind( &ConfigData::showSuccessfulTests ); cli["-b"]["--break"] .describe( "break into debugger on failure" ) .bind( &ConfigData::shouldDebugBreak ); cli["-e"]["--nothrow"] .describe( "skip exception tests" ) .bind( &ConfigData::noThrow ); cli["-i"]["--invisibles"] .describe( "show invisibles (tabs, newlines)" ) .bind( &ConfigData::showInvisibles ); cli["-o"]["--out"] .describe( "output filename" ) .bind( &ConfigData::outputFilename, "filename" ); cli["-r"]["--reporter"] // .placeholder( "name[:filename]" ) .describe( "reporter to use (defaults to console)" ) .bind( &ConfigData::reporterName, "name" ); cli["-n"]["--name"] .describe( "suite name" ) .bind( &ConfigData::name, "name" ); cli["-a"]["--abort"] .describe( "abort at first failure" ) .bind( &abortAfterFirst ); cli["-x"]["--abortx"] .describe( "abort after x failures" ) .bind( &abortAfterX, "no. failures" ); cli["-w"]["--warn"] .describe( "enable warnings" ) .bind( &addWarning, "warning name" ); // - needs updating if reinstated // cli.into( &setVerbosity ) // .describe( "level of verbosity (0=no output)" ) // .shortOpt( "v") // .longOpt( "verbosity" ) // .placeholder( "level" ); cli[_] .describe( "which test or tests to use" ) .bind( &addTestOrTags, "test name, pattern or tags" ); cli["-d"]["--durations"] .describe( "show test durations" ) .bind( &setShowDurations, "yes/no" ); cli["-f"]["--input-file"] .describe( "load test names to run from a file" ) .bind( &loadTestNamesFromFile, "filename" ); // Less common commands which don't have a short form cli["--list-test-names-only"] .describe( "list all/matching test cases names only" ) .bind( &ConfigData::listTestNamesOnly ); cli["--list-reporters"] .describe( "list all reporters" ) .bind( &ConfigData::listReporters ); cli["--order"] .describe( "test case order (defaults to decl)" ) .bind( &setOrder, "decl|lex|rand" ); cli["--rng-seed"] .describe( "set a specific seed for random numbers" ) .bind( &setRngSeed, "'time'|number" ); cli["--force-colour"] .describe( "force colourised output" ) .bind( &ConfigData::forceColour ); return cli; } } // end namespace Catch // #included from: internal/catch_list.hpp #define TWOBLUECUBES_CATCH_LIST_HPP_INCLUDED // #included from: catch_text.h #define TWOBLUECUBES_CATCH_TEXT_H_INCLUDED #define TBC_TEXT_FORMAT_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH #define CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE Catch // #included from: ../external/tbc_text_format.h // Only use header guard if we are not using an outer namespace #ifndef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE # ifdef TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED # ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED # define TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED # endif # else # define TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED # endif #endif #ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED #include #include #include // Use optional outer namespace #ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE namespace CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE { #endif namespace Tbc { #ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; #else const unsigned int consoleWidth = 80; #endif struct TextAttributes { TextAttributes() : initialIndent( std::string::npos ), indent( 0 ), width( consoleWidth-1 ), tabChar( '\t' ) {} TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } std::size_t initialIndent; // indent of first line, or npos std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos std::size_t width; // maximum width of text, including indent. Longer text will wrap char tabChar; // If this char is seen the indent is changed to current pos }; class Text { public: Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) : attr( _attr ) { std::string wrappableChars = " [({.,/|\\-"; std::size_t indent = _attr.initialIndent != std::string::npos ? _attr.initialIndent : _attr.indent; std::string remainder = _str; while( !remainder.empty() ) { if( lines.size() >= 1000 ) { lines.push_back( "... message truncated due to excessive size" ); return; } std::size_t tabPos = std::string::npos; std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); std::size_t pos = remainder.find_first_of( '\n' ); if( pos <= width ) { width = pos; } pos = remainder.find_last_of( _attr.tabChar, width ); if( pos != std::string::npos ) { tabPos = pos; if( remainder[width] == '\n' ) width--; remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); } if( width == remainder.size() ) { spliceLine( indent, remainder, width ); } else if( remainder[width] == '\n' ) { spliceLine( indent, remainder, width ); if( width <= 1 || remainder.size() != 1 ) remainder = remainder.substr( 1 ); indent = _attr.indent; } else { pos = remainder.find_last_of( wrappableChars, width ); if( pos != std::string::npos && pos > 0 ) { spliceLine( indent, remainder, pos ); if( remainder[0] == ' ' ) remainder = remainder.substr( 1 ); } else { spliceLine( indent, remainder, width-1 ); lines.back() += "-"; } if( lines.size() == 1 ) indent = _attr.indent; if( tabPos != std::string::npos ) indent += tabPos; } } } void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); _remainder = _remainder.substr( _pos ); } typedef std::vector::const_iterator const_iterator; const_iterator begin() const { return lines.begin(); } const_iterator end() const { return lines.end(); } std::string const& last() const { return lines.back(); } std::size_t size() const { return lines.size(); } std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } std::string toString() const { std::ostringstream oss; oss << *this; return oss.str(); } inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); it != itEnd; ++it ) { if( it != _text.begin() ) _stream << "\n"; _stream << *it; } return _stream; } private: std::string str; TextAttributes attr; std::vector lines; }; } // end namespace Tbc #ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE } // end outer namespace #endif #endif // TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED #undef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE namespace Catch { using Tbc::Text; using Tbc::TextAttributes; } // #included from: catch_console_colour.hpp #define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_HPP_INCLUDED namespace Catch { struct Colour { enum Code { None = 0, White, Red, Green, Blue, Cyan, Yellow, Grey, Bright = 0x10, BrightRed = Bright | Red, BrightGreen = Bright | Green, LightGrey = Bright | Grey, BrightWhite = Bright | White, // By intention FileName = LightGrey, Warning = Yellow, ResultError = BrightRed, ResultSuccess = BrightGreen, ResultExpectedFailure = Warning, Error = BrightRed, Success = Green, OriginalExpression = Cyan, ReconstructedExpression = Yellow, SecondaryText = LightGrey, Headers = White }; // Use constructed object for RAII guard Colour( Code _colourCode ); Colour( Colour const& other ); ~Colour(); // Use static method for one-shot changes static void use( Code _colourCode ); private: bool m_moved; }; inline std::ostream& operator << ( std::ostream& os, Colour const& ) { return os; } } // end namespace Catch // #included from: catch_interfaces_reporter.h #define TWOBLUECUBES_CATCH_INTERFACES_REPORTER_H_INCLUDED #include #include #include #include namespace Catch { struct ReporterConfig { explicit ReporterConfig( Ptr const& _fullConfig ) : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} ReporterConfig( Ptr const& _fullConfig, std::ostream& _stream ) : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} std::ostream& stream() const { return *m_stream; } Ptr fullConfig() const { return m_fullConfig; } private: std::ostream* m_stream; Ptr m_fullConfig; }; struct ReporterPreferences { ReporterPreferences() : shouldRedirectStdOut( false ) {} bool shouldRedirectStdOut; }; template struct LazyStat : Option { LazyStat() : used( false ) {} LazyStat& operator=( T const& _value ) { Option::operator=( _value ); used = false; return *this; } void reset() { Option::reset(); used = false; } bool used; }; struct TestRunInfo { TestRunInfo( std::string const& _name ) : name( _name ) {} std::string name; }; struct GroupInfo { GroupInfo( std::string const& _name, std::size_t _groupIndex, std::size_t _groupsCount ) : name( _name ), groupIndex( _groupIndex ), groupsCounts( _groupsCount ) {} std::string name; std::size_t groupIndex; std::size_t groupsCounts; }; struct AssertionStats { AssertionStats( AssertionResult const& _assertionResult, std::vector const& _infoMessages, Totals const& _totals ) : assertionResult( _assertionResult ), infoMessages( _infoMessages ), totals( _totals ) { if( assertionResult.hasMessage() ) { // Copy message into messages list. // !TBD This should have been done earlier, somewhere MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); builder << assertionResult.getMessage(); builder.m_info.message = builder.m_stream.str(); infoMessages.push_back( builder.m_info ); } } virtual ~AssertionStats(); # ifdef CATCH_CPP11_OR_GREATER AssertionStats( AssertionStats const& ) = default; AssertionStats( AssertionStats && ) = default; AssertionStats& operator = ( AssertionStats const& ) = default; AssertionStats& operator = ( AssertionStats && ) = default; # endif AssertionResult assertionResult; std::vector infoMessages; Totals totals; }; struct SectionStats { SectionStats( SectionInfo const& _sectionInfo, Counts const& _assertions, double _durationInSeconds, bool _missingAssertions ) : sectionInfo( _sectionInfo ), assertions( _assertions ), durationInSeconds( _durationInSeconds ), missingAssertions( _missingAssertions ) {} virtual ~SectionStats(); # ifdef CATCH_CPP11_OR_GREATER SectionStats( SectionStats const& ) = default; SectionStats( SectionStats && ) = default; SectionStats& operator = ( SectionStats const& ) = default; SectionStats& operator = ( SectionStats && ) = default; # endif SectionInfo sectionInfo; Counts assertions; double durationInSeconds; bool missingAssertions; }; struct TestCaseStats { TestCaseStats( TestCaseInfo const& _testInfo, Totals const& _totals, std::string const& _stdOut, std::string const& _stdErr, bool _aborting ) : testInfo( _testInfo ), totals( _totals ), stdOut( _stdOut ), stdErr( _stdErr ), aborting( _aborting ) {} virtual ~TestCaseStats(); # ifdef CATCH_CPP11_OR_GREATER TestCaseStats( TestCaseStats const& ) = default; TestCaseStats( TestCaseStats && ) = default; TestCaseStats& operator = ( TestCaseStats const& ) = default; TestCaseStats& operator = ( TestCaseStats && ) = default; # endif TestCaseInfo testInfo; Totals totals; std::string stdOut; std::string stdErr; bool aborting; }; struct TestGroupStats { TestGroupStats( GroupInfo const& _groupInfo, Totals const& _totals, bool _aborting ) : groupInfo( _groupInfo ), totals( _totals ), aborting( _aborting ) {} TestGroupStats( GroupInfo const& _groupInfo ) : groupInfo( _groupInfo ), aborting( false ) {} virtual ~TestGroupStats(); # ifdef CATCH_CPP11_OR_GREATER TestGroupStats( TestGroupStats const& ) = default; TestGroupStats( TestGroupStats && ) = default; TestGroupStats& operator = ( TestGroupStats const& ) = default; TestGroupStats& operator = ( TestGroupStats && ) = default; # endif GroupInfo groupInfo; Totals totals; bool aborting; }; struct TestRunStats { TestRunStats( TestRunInfo const& _runInfo, Totals const& _totals, bool _aborting ) : runInfo( _runInfo ), totals( _totals ), aborting( _aborting ) {} virtual ~TestRunStats(); # ifndef CATCH_CPP11_OR_GREATER TestRunStats( TestRunStats const& _other ) : runInfo( _other.runInfo ), totals( _other.totals ), aborting( _other.aborting ) {} # else TestRunStats( TestRunStats const& ) = default; TestRunStats( TestRunStats && ) = default; TestRunStats& operator = ( TestRunStats const& ) = default; TestRunStats& operator = ( TestRunStats && ) = default; # endif TestRunInfo runInfo; Totals totals; bool aborting; }; struct IStreamingReporter : IShared { virtual ~IStreamingReporter(); // Implementing class must also provide the following static method: // static std::string getDescription(); virtual ReporterPreferences getPreferences() const = 0; virtual void noMatchingTestCases( std::string const& spec ) = 0; virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; // The return value indicates if the messages buffer should be cleared: virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; virtual void sectionEnded( SectionStats const& sectionStats ) = 0; virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; virtual void skipTest( TestCaseInfo const& testInfo ) = 0; }; struct IReporterFactory { virtual ~IReporterFactory(); virtual IStreamingReporter* create( ReporterConfig const& config ) const = 0; virtual std::string getDescription() const = 0; }; struct IReporterRegistry { typedef std::map FactoryMap; virtual ~IReporterRegistry(); virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const = 0; virtual FactoryMap const& getFactories() const = 0; }; } #include #include namespace Catch { inline std::size_t listTests( Config const& config ) { TestSpec testSpec = config.testSpec(); if( config.testSpec().hasFilters() ) Catch::cout() << "Matching test cases:\n"; else { Catch::cout() << "All available test cases:\n"; testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); } std::size_t matchedTests = 0; TextAttributes nameAttr, tagsAttr; nameAttr.setInitialIndent( 2 ).setIndent( 4 ); tagsAttr.setIndent( 6 ); std::vector matchedTestCases; getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); it != itEnd; ++it ) { matchedTests++; TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); Colour::Code colour = testCaseInfo.isHidden() ? Colour::SecondaryText : Colour::None; Colour colourGuard( colour ); Catch::cout() << Text( testCaseInfo.name, nameAttr ) << std::endl; if( !testCaseInfo.tags.empty() ) Catch::cout() << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl; } if( !config.testSpec().hasFilters() ) Catch::cout() << pluralise( matchedTests, "test case" ) << "\n" << std::endl; else Catch::cout() << pluralise( matchedTests, "matching test case" ) << "\n" << std::endl; return matchedTests; } inline std::size_t listTestsNamesOnly( Config const& config ) { TestSpec testSpec = config.testSpec(); if( !config.testSpec().hasFilters() ) testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); std::size_t matchedTests = 0; std::vector matchedTestCases; getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); it != itEnd; ++it ) { matchedTests++; TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); Catch::cout() << testCaseInfo.name << std::endl; } return matchedTests; } struct TagInfo { TagInfo() : count ( 0 ) {} void add( std::string const& spelling ) { ++count; spellings.insert( spelling ); } std::string all() const { std::string out; for( std::set::const_iterator it = spellings.begin(), itEnd = spellings.end(); it != itEnd; ++it ) out += "[" + *it + "]"; return out; } std::set spellings; std::size_t count; }; inline std::size_t listTags( Config const& config ) { TestSpec testSpec = config.testSpec(); if( config.testSpec().hasFilters() ) Catch::cout() << "Tags for matching test cases:\n"; else { Catch::cout() << "All available tags:\n"; testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); } std::map tagCounts; std::vector matchedTestCases; getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); it != itEnd; ++it ) { for( std::set::const_iterator tagIt = it->getTestCaseInfo().tags.begin(), tagItEnd = it->getTestCaseInfo().tags.end(); tagIt != tagItEnd; ++tagIt ) { std::string tagName = *tagIt; std::string lcaseTagName = toLower( tagName ); std::map::iterator countIt = tagCounts.find( lcaseTagName ); if( countIt == tagCounts.end() ) countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first; countIt->second.add( tagName ); } } for( std::map::const_iterator countIt = tagCounts.begin(), countItEnd = tagCounts.end(); countIt != countItEnd; ++countIt ) { std::ostringstream oss; oss << " " << std::setw(2) << countIt->second.count << " "; Text wrapper( countIt->second.all(), TextAttributes() .setInitialIndent( 0 ) .setIndent( oss.str().size() ) .setWidth( CATCH_CONFIG_CONSOLE_WIDTH-10 ) ); Catch::cout() << oss.str() << wrapper << "\n"; } Catch::cout() << pluralise( tagCounts.size(), "tag" ) << "\n" << std::endl; return tagCounts.size(); } inline std::size_t listReporters( Config const& /*config*/ ) { Catch::cout() << "Available reporters:\n"; IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); IReporterRegistry::FactoryMap::const_iterator itBegin = factories.begin(), itEnd = factories.end(), it; std::size_t maxNameLen = 0; for(it = itBegin; it != itEnd; ++it ) maxNameLen = (std::max)( maxNameLen, it->first.size() ); for(it = itBegin; it != itEnd; ++it ) { Text wrapper( it->second->getDescription(), TextAttributes() .setInitialIndent( 0 ) .setIndent( 7+maxNameLen ) .setWidth( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) ); Catch::cout() << " " << it->first << ":" << std::string( maxNameLen - it->first.size() + 2, ' ' ) << wrapper << "\n"; } Catch::cout() << std::endl; return factories.size(); } inline Option list( Config const& config ) { Option listedCount; if( config.listTests() ) listedCount = listedCount.valueOr(0) + listTests( config ); if( config.listTestNamesOnly() ) listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config ); if( config.listTags() ) listedCount = listedCount.valueOr(0) + listTags( config ); if( config.listReporters() ) listedCount = listedCount.valueOr(0) + listReporters( config ); return listedCount; } } // end namespace Catch // #included from: internal/catch_runner_impl.hpp #define TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED // #included from: catch_test_case_tracker.hpp #define TWOBLUECUBES_CATCH_TEST_CASE_TRACKER_HPP_INCLUDED #include #include #include namespace Catch { namespace SectionTracking { class TrackedSection { typedef std::map TrackedSections; public: enum RunState { NotStarted, Executing, ExecutingChildren, Completed }; TrackedSection( std::string const& name, TrackedSection* parent ) : m_name( name ), m_runState( NotStarted ), m_parent( parent ) {} RunState runState() const { return m_runState; } TrackedSection* findChild( std::string const& childName ) { TrackedSections::iterator it = m_children.find( childName ); return it != m_children.end() ? &it->second : NULL; } TrackedSection* acquireChild( std::string const& childName ) { if( TrackedSection* child = findChild( childName ) ) return child; m_children.insert( std::make_pair( childName, TrackedSection( childName, this ) ) ); return findChild( childName ); } void enter() { if( m_runState == NotStarted ) m_runState = Executing; } void leave() { for( TrackedSections::const_iterator it = m_children.begin(), itEnd = m_children.end(); it != itEnd; ++it ) if( it->second.runState() != Completed ) { m_runState = ExecutingChildren; return; } m_runState = Completed; } TrackedSection* getParent() { return m_parent; } bool hasChildren() const { return !m_children.empty(); } private: std::string m_name; RunState m_runState; TrackedSections m_children; TrackedSection* m_parent; }; class TestCaseTracker { public: TestCaseTracker( std::string const& testCaseName ) : m_testCase( testCaseName, NULL ), m_currentSection( &m_testCase ), m_completedASectionThisRun( false ) {} bool enterSection( std::string const& name ) { TrackedSection* child = m_currentSection->acquireChild( name ); if( m_completedASectionThisRun || child->runState() == TrackedSection::Completed ) return false; m_currentSection = child; m_currentSection->enter(); return true; } void leaveSection() { m_currentSection->leave(); m_currentSection = m_currentSection->getParent(); assert( m_currentSection != NULL ); m_completedASectionThisRun = true; } bool currentSectionHasChildren() const { return m_currentSection->hasChildren(); } bool isCompleted() const { return m_testCase.runState() == TrackedSection::Completed; } class Guard { public: Guard( TestCaseTracker& tracker ) : m_tracker( tracker ) { m_tracker.enterTestCase(); } ~Guard() { m_tracker.leaveTestCase(); } private: Guard( Guard const& ); void operator = ( Guard const& ); TestCaseTracker& m_tracker; }; private: void enterTestCase() { m_currentSection = &m_testCase; m_completedASectionThisRun = false; m_testCase.enter(); } void leaveTestCase() { m_testCase.leave(); } TrackedSection m_testCase; TrackedSection* m_currentSection; bool m_completedASectionThisRun; }; } // namespace SectionTracking using SectionTracking::TestCaseTracker; } // namespace Catch // #included from: catch_fatal_condition.hpp #define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED namespace Catch { // Report the error condition then exit the process inline void fatal( std::string const& message, int exitCode ) { IContext& context = Catch::getCurrentContext(); IResultCapture* resultCapture = context.getResultCapture(); resultCapture->handleFatalErrorCondition( message ); if( Catch::alwaysTrue() ) // avoids "no return" warnings exit( exitCode ); } } // namespace Catch #if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// namespace Catch { struct FatalConditionHandler { void reset() {} }; } // namespace Catch #else // Not Windows - assumed to be POSIX compatible ////////////////////////// #include namespace Catch { struct SignalDefs { int id; const char* name; }; extern SignalDefs signalDefs[]; SignalDefs signalDefs[] = { { SIGINT, "SIGINT - Terminal interrupt signal" }, { SIGILL, "SIGILL - Illegal instruction signal" }, { SIGFPE, "SIGFPE - Floating point error signal" }, { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, { SIGTERM, "SIGTERM - Termination request signal" }, { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } }; struct FatalConditionHandler { static void handleSignal( int sig ) { for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) if( sig == signalDefs[i].id ) fatal( signalDefs[i].name, -sig ); fatal( "", -sig ); } FatalConditionHandler() : m_isSet( true ) { for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) signal( signalDefs[i].id, handleSignal ); } ~FatalConditionHandler() { reset(); } void reset() { if( m_isSet ) { for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) signal( signalDefs[i].id, SIG_DFL ); m_isSet = false; } } bool m_isSet; }; } // namespace Catch #endif // not Windows #include #include namespace Catch { class StreamRedirect { public: StreamRedirect( std::ostream& stream, std::string& targetString ) : m_stream( stream ), m_prevBuf( stream.rdbuf() ), m_targetString( targetString ) { stream.rdbuf( m_oss.rdbuf() ); } ~StreamRedirect() { m_targetString += m_oss.str(); m_stream.rdbuf( m_prevBuf ); } private: std::ostream& m_stream; std::streambuf* m_prevBuf; std::ostringstream m_oss; std::string& m_targetString; }; /////////////////////////////////////////////////////////////////////////// class RunContext : public IResultCapture, public IRunner { RunContext( RunContext const& ); void operator =( RunContext const& ); public: explicit RunContext( Ptr const& config, Ptr const& reporter ) : m_runInfo( config->name() ), m_context( getCurrentMutableContext() ), m_activeTestCase( NULL ), m_config( config ), m_reporter( reporter ), m_prevRunner( m_context.getRunner() ), m_prevResultCapture( m_context.getResultCapture() ), m_prevConfig( m_context.getConfig() ) { m_context.setRunner( this ); m_context.setConfig( m_config ); m_context.setResultCapture( this ); m_reporter->testRunStarting( m_runInfo ); } virtual ~RunContext() { m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, aborting() ) ); m_context.setRunner( m_prevRunner ); m_context.setConfig( NULL ); m_context.setResultCapture( m_prevResultCapture ); m_context.setConfig( m_prevConfig ); } void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ) { m_reporter->testGroupStarting( GroupInfo( testSpec, groupIndex, groupsCount ) ); } void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ) { m_reporter->testGroupEnded( TestGroupStats( GroupInfo( testSpec, groupIndex, groupsCount ), totals, aborting() ) ); } Totals runTest( TestCase const& testCase ) { Totals prevTotals = m_totals; std::string redirectedCout; std::string redirectedCerr; TestCaseInfo testInfo = testCase.getTestCaseInfo(); m_reporter->testCaseStarting( testInfo ); m_activeTestCase = &testCase; m_testCaseTracker = TestCaseTracker( testInfo.name ); do { do { runCurrentTest( redirectedCout, redirectedCerr ); } while( !m_testCaseTracker->isCompleted() && !aborting() ); } while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() ); Totals deltaTotals = m_totals.delta( prevTotals ); m_totals.testCases += deltaTotals.testCases; m_reporter->testCaseEnded( TestCaseStats( testInfo, deltaTotals, redirectedCout, redirectedCerr, aborting() ) ); m_activeTestCase = NULL; m_testCaseTracker.reset(); return deltaTotals; } Ptr config() const { return m_config; } private: // IResultCapture virtual void assertionEnded( AssertionResult const& result ) { if( result.getResultType() == ResultWas::Ok ) { m_totals.assertions.passed++; } else if( !result.isOk() ) { m_totals.assertions.failed++; } if( m_reporter->assertionEnded( AssertionStats( result, m_messages, m_totals ) ) ) m_messages.clear(); // Reset working state m_lastAssertionInfo = AssertionInfo( "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition ); m_lastResult = result; } virtual bool sectionStarted ( SectionInfo const& sectionInfo, Counts& assertions ) { std::ostringstream oss; oss << sectionInfo.name << "@" << sectionInfo.lineInfo; if( !m_testCaseTracker->enterSection( oss.str() ) ) return false; m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; m_reporter->sectionStarting( sectionInfo ); assertions = m_totals.assertions; return true; } bool testForMissingAssertions( Counts& assertions ) { if( assertions.total() != 0 || !m_config->warnAboutMissingAssertions() || m_testCaseTracker->currentSectionHasChildren() ) return false; m_totals.assertions.failed++; assertions.failed++; return true; } virtual void sectionEnded( SectionInfo const& info, Counts const& prevAssertions, double _durationInSeconds ) { if( std::uncaught_exception() ) { m_unfinishedSections.push_back( UnfinishedSections( info, prevAssertions, _durationInSeconds ) ); return; } Counts assertions = m_totals.assertions - prevAssertions; bool missingAssertions = testForMissingAssertions( assertions ); m_testCaseTracker->leaveSection(); m_reporter->sectionEnded( SectionStats( info, assertions, _durationInSeconds, missingAssertions ) ); m_messages.clear(); } virtual void pushScopedMessage( MessageInfo const& message ) { m_messages.push_back( message ); } virtual void popScopedMessage( MessageInfo const& message ) { m_messages.erase( std::remove( m_messages.begin(), m_messages.end(), message ), m_messages.end() ); } virtual std::string getCurrentTestName() const { return m_activeTestCase ? m_activeTestCase->getTestCaseInfo().name : ""; } virtual const AssertionResult* getLastResult() const { return &m_lastResult; } virtual void handleFatalErrorCondition( std::string const& message ) { ResultBuilder resultBuilder = makeUnexpectedResultBuilder(); resultBuilder.setResultType( ResultWas::FatalErrorCondition ); resultBuilder << message; resultBuilder.captureExpression(); handleUnfinishedSections(); // Recreate section for test case (as we will lose the one that was in scope) TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); Counts assertions; assertions.failed = 1; SectionStats testCaseSectionStats( testCaseSection, assertions, 0, false ); m_reporter->sectionEnded( testCaseSectionStats ); TestCaseInfo testInfo = m_activeTestCase->getTestCaseInfo(); Totals deltaTotals; deltaTotals.testCases.failed = 1; m_reporter->testCaseEnded( TestCaseStats( testInfo, deltaTotals, "", "", false ) ); m_totals.testCases.failed++; testGroupEnded( "", m_totals, 1, 1 ); m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, false ) ); } public: // !TBD We need to do this another way! bool aborting() const { return m_totals.assertions.failed == static_cast( m_config->abortAfter() ); } private: void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ) { TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); m_reporter->sectionStarting( testCaseSection ); Counts prevAssertions = m_totals.assertions; double duration = 0; try { m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal ); TestCaseTracker::Guard guard( *m_testCaseTracker ); Timer timer; timer.start(); if( m_reporter->getPreferences().shouldRedirectStdOut ) { StreamRedirect coutRedir( Catch::cout(), redirectedCout ); StreamRedirect cerrRedir( Catch::cerr(), redirectedCerr ); invokeActiveTestCase(); } else { invokeActiveTestCase(); } duration = timer.getElapsedSeconds(); } catch( TestFailureException& ) { // This just means the test was aborted due to failure } catch(...) { makeUnexpectedResultBuilder().useActiveException(); } handleUnfinishedSections(); m_messages.clear(); Counts assertions = m_totals.assertions - prevAssertions; bool missingAssertions = testForMissingAssertions( assertions ); if( testCaseInfo.okToFail() ) { std::swap( assertions.failedButOk, assertions.failed ); m_totals.assertions.failed -= assertions.failedButOk; m_totals.assertions.failedButOk += assertions.failedButOk; } SectionStats testCaseSectionStats( testCaseSection, assertions, duration, missingAssertions ); m_reporter->sectionEnded( testCaseSectionStats ); } void invokeActiveTestCase() { FatalConditionHandler fatalConditionHandler; // Handle signals m_activeTestCase->invoke(); fatalConditionHandler.reset(); } private: ResultBuilder makeUnexpectedResultBuilder() const { return ResultBuilder( m_lastAssertionInfo.macroName.c_str(), m_lastAssertionInfo.lineInfo, m_lastAssertionInfo.capturedExpression.c_str(), m_lastAssertionInfo.resultDisposition ); } void handleUnfinishedSections() { // If sections ended prematurely due to an exception we stored their // infos here so we can tear them down outside the unwind process. for( std::vector::const_reverse_iterator it = m_unfinishedSections.rbegin(), itEnd = m_unfinishedSections.rend(); it != itEnd; ++it ) sectionEnded( it->info, it->prevAssertions, it->durationInSeconds ); m_unfinishedSections.clear(); } struct UnfinishedSections { UnfinishedSections( SectionInfo const& _info, Counts const& _prevAssertions, double _durationInSeconds ) : info( _info ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) {} SectionInfo info; Counts prevAssertions; double durationInSeconds; }; TestRunInfo m_runInfo; IMutableContext& m_context; TestCase const* m_activeTestCase; Option m_testCaseTracker; AssertionResult m_lastResult; Ptr m_config; Totals m_totals; Ptr m_reporter; std::vector m_messages; IRunner* m_prevRunner; IResultCapture* m_prevResultCapture; Ptr m_prevConfig; AssertionInfo m_lastAssertionInfo; std::vector m_unfinishedSections; }; IResultCapture& getResultCapture() { if( IResultCapture* capture = getCurrentContext().getResultCapture() ) return *capture; else throw std::logic_error( "No result capture instance" ); } } // end namespace Catch // #included from: internal/catch_version.h #define TWOBLUECUBES_CATCH_VERSION_H_INCLUDED namespace Catch { // Versioning information struct Version { Version( unsigned int _majorVersion, unsigned int _minorVersion, unsigned int _buildNumber, char const* const _branchName ) : majorVersion( _majorVersion ), minorVersion( _minorVersion ), buildNumber( _buildNumber ), branchName( _branchName ) {} unsigned int const majorVersion; unsigned int const minorVersion; unsigned int const buildNumber; char const* const branchName; private: void operator=( Version const& ); }; extern Version libraryVersion; } #include #include #include namespace Catch { class Runner { public: Runner( Ptr const& config ) : m_config( config ) { openStream(); makeReporter(); } Totals runTests() { RunContext context( m_config.get(), m_reporter ); Totals totals; context.testGroupStarting( "all tests", 1, 1 ); // deprecated? TestSpec testSpec = m_config->testSpec(); if( !testSpec.hasFilters() ) testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "~[.]" ).testSpec(); // All not hidden tests std::vector testCases; getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, *m_config, testCases ); int testsRunForGroup = 0; for( std::vector::const_iterator it = testCases.begin(), itEnd = testCases.end(); it != itEnd; ++it ) { testsRunForGroup++; if( m_testsAlreadyRun.find( *it ) == m_testsAlreadyRun.end() ) { if( context.aborting() ) break; totals += context.runTest( *it ); m_testsAlreadyRun.insert( *it ); } } std::vector skippedTestCases; getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, *m_config, skippedTestCases, true ); for( std::vector::const_iterator it = skippedTestCases.begin(), itEnd = skippedTestCases.end(); it != itEnd; ++it ) m_reporter->skipTest( *it ); context.testGroupEnded( "all tests", totals, 1, 1 ); return totals; } private: void openStream() { // Open output file, if specified if( !m_config->getFilename().empty() ) { m_ofs.open( m_config->getFilename().c_str() ); if( m_ofs.fail() ) { std::ostringstream oss; oss << "Unable to open file: '" << m_config->getFilename() << "'"; throw std::domain_error( oss.str() ); } m_config->setStreamBuf( m_ofs.rdbuf() ); } } void makeReporter() { std::string reporterName = m_config->getReporterName().empty() ? "console" : m_config->getReporterName(); m_reporter = getRegistryHub().getReporterRegistry().create( reporterName, m_config.get() ); if( !m_reporter ) { std::ostringstream oss; oss << "No reporter registered with name: '" << reporterName << "'"; throw std::domain_error( oss.str() ); } } private: Ptr m_config; std::ofstream m_ofs; Ptr m_reporter; std::set m_testsAlreadyRun; }; class Session : NonCopyable { static bool alreadyInstantiated; public: struct OnUnusedOptions { enum DoWhat { Ignore, Fail }; }; Session() : m_cli( makeCommandLineParser() ) { if( alreadyInstantiated ) { std::string msg = "Only one instance of Catch::Session can ever be used"; Catch::cerr() << msg << std::endl; throw std::logic_error( msg ); } alreadyInstantiated = true; } ~Session() { Catch::cleanUp(); } void showHelp( std::string const& processName ) { Catch::cout() << "\nCatch v" << libraryVersion.majorVersion << "." << libraryVersion.minorVersion << " build " << libraryVersion.buildNumber; if( libraryVersion.branchName != std::string( "master" ) ) Catch::cout() << " (" << libraryVersion.branchName << " branch)"; Catch::cout() << "\n"; m_cli.usage( Catch::cout(), processName ); Catch::cout() << "For more detail usage please see the project docs\n" << std::endl; } int applyCommandLine( int argc, char* const argv[], OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) { try { m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail ); m_unusedTokens = m_cli.parseInto( argc, argv, m_configData ); if( m_configData.showHelp ) showHelp( m_configData.processName ); m_config.reset(); } catch( std::exception& ex ) { { Colour colourGuard( Colour::Red ); Catch::cerr() << "\nError(s) in input:\n" << Text( ex.what(), TextAttributes().setIndent(2) ) << "\n\n"; } m_cli.usage( Catch::cout(), m_configData.processName ); return (std::numeric_limits::max)(); } return 0; } void useConfigData( ConfigData const& _configData ) { m_configData = _configData; m_config.reset(); } int run( int argc, char* const argv[] ) { int returnCode = applyCommandLine( argc, argv ); if( returnCode == 0 ) returnCode = run(); return returnCode; } int run() { if( m_configData.showHelp ) return 0; try { config(); // Force config to be constructed std::srand( m_configData.rngSeed ); Runner runner( m_config ); // Handle list request if( Option listed = list( config() ) ) return static_cast( *listed ); return static_cast( runner.runTests().assertions.failed ); } catch( std::exception& ex ) { Catch::cerr() << ex.what() << std::endl; return (std::numeric_limits::max)(); } } Clara::CommandLine const& cli() const { return m_cli; } std::vector const& unusedTokens() const { return m_unusedTokens; } ConfigData& configData() { return m_configData; } Config& config() { if( !m_config ) m_config = new Config( m_configData ); return *m_config; } private: Clara::CommandLine m_cli; std::vector m_unusedTokens; ConfigData m_configData; Ptr m_config; }; bool Session::alreadyInstantiated = false; } // end namespace Catch // #included from: catch_registry_hub.hpp #define TWOBLUECUBES_CATCH_REGISTRY_HUB_HPP_INCLUDED // #included from: catch_test_case_registry_impl.hpp #define TWOBLUECUBES_CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED #include #include #include #include #include namespace Catch { class TestRegistry : public ITestCaseRegistry { struct LexSort { bool operator() (TestCase i,TestCase j) const { return (i const& getAllTests() const { return m_functionsInOrder; } virtual std::vector const& getAllNonHiddenTests() const { return m_nonHiddenFunctions; } virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector& matchingTestCases, bool negated = false ) const { for( std::vector::const_iterator it = m_functionsInOrder.begin(), itEnd = m_functionsInOrder.end(); it != itEnd; ++it ) { bool includeTest = testSpec.matches( *it ) && ( config.allowThrows() || !it->throws() ); if( includeTest != negated ) matchingTestCases.push_back( *it ); } sortTests( config, matchingTestCases ); } private: static void sortTests( IConfig const& config, std::vector& matchingTestCases ) { switch( config.runOrder() ) { case RunTests::InLexicographicalOrder: std::sort( matchingTestCases.begin(), matchingTestCases.end(), LexSort() ); break; case RunTests::InRandomOrder: { RandomNumberGenerator rng; std::random_shuffle( matchingTestCases.begin(), matchingTestCases.end(), rng ); } break; case RunTests::InDeclarationOrder: // already in declaration order break; } } std::set m_functions; std::vector m_functionsInOrder; std::vector m_nonHiddenFunctions; size_t m_unnamedCount; }; /////////////////////////////////////////////////////////////////////////// class FreeFunctionTestCase : public SharedImpl { public: FreeFunctionTestCase( TestFunction fun ) : m_fun( fun ) {} virtual void invoke() const { m_fun(); } private: virtual ~FreeFunctionTestCase(); TestFunction m_fun; }; inline std::string extractClassName( std::string const& classOrQualifiedMethodName ) { std::string className = classOrQualifiedMethodName; if( startsWith( className, "&" ) ) { std::size_t lastColons = className.rfind( "::" ); std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); if( penultimateColons == std::string::npos ) penultimateColons = 1; className = className.substr( penultimateColons, lastColons-penultimateColons ); } return className; } /////////////////////////////////////////////////////////////////////////// AutoReg::AutoReg( TestFunction function, SourceLineInfo const& lineInfo, NameAndDesc const& nameAndDesc ) { registerTestCase( new FreeFunctionTestCase( function ), "", nameAndDesc, lineInfo ); } AutoReg::~AutoReg() {} void AutoReg::registerTestCase( ITestCase* testCase, char const* classOrQualifiedMethodName, NameAndDesc const& nameAndDesc, SourceLineInfo const& lineInfo ) { getMutableRegistryHub().registerTest ( makeTestCase( testCase, extractClassName( classOrQualifiedMethodName ), nameAndDesc.name, nameAndDesc.description, lineInfo ) ); } } // end namespace Catch // #included from: catch_reporter_registry.hpp #define TWOBLUECUBES_CATCH_REPORTER_REGISTRY_HPP_INCLUDED #include namespace Catch { class ReporterRegistry : public IReporterRegistry { public: virtual ~ReporterRegistry() { deleteAllValues( m_factories ); } virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const { FactoryMap::const_iterator it = m_factories.find( name ); if( it == m_factories.end() ) return NULL; return it->second->create( ReporterConfig( config ) ); } void registerReporter( std::string const& name, IReporterFactory* factory ) { m_factories.insert( std::make_pair( name, factory ) ); } FactoryMap const& getFactories() const { return m_factories; } private: FactoryMap m_factories; }; } // #included from: catch_exception_translator_registry.hpp #define TWOBLUECUBES_CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED #ifdef __OBJC__ #import "Foundation/Foundation.h" #endif namespace Catch { class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { public: ~ExceptionTranslatorRegistry() { deleteAll( m_translators ); } virtual void registerTranslator( const IExceptionTranslator* translator ) { m_translators.push_back( translator ); } virtual std::string translateActiveException() const { try { #ifdef __OBJC__ // In Objective-C try objective-c exceptions first @try { throw; } @catch (NSException *exception) { return Catch::toString( [exception description] ); } #else throw; #endif } catch( TestFailureException& ) { throw; } catch( std::exception& ex ) { return ex.what(); } catch( std::string& msg ) { return msg; } catch( const char* msg ) { return msg; } catch(...) { return tryTranslators( m_translators.begin() ); } } std::string tryTranslators( std::vector::const_iterator it ) const { if( it == m_translators.end() ) return "Unknown exception"; try { return (*it)->translate(); } catch(...) { return tryTranslators( it+1 ); } } private: std::vector m_translators; }; } namespace Catch { namespace { class RegistryHub : public IRegistryHub, public IMutableRegistryHub { RegistryHub( RegistryHub const& ); void operator=( RegistryHub const& ); public: // IRegistryHub RegistryHub() { } virtual IReporterRegistry const& getReporterRegistry() const { return m_reporterRegistry; } virtual ITestCaseRegistry const& getTestCaseRegistry() const { return m_testCaseRegistry; } virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() { return m_exceptionTranslatorRegistry; } public: // IMutableRegistryHub virtual void registerReporter( std::string const& name, IReporterFactory* factory ) { m_reporterRegistry.registerReporter( name, factory ); } virtual void registerTest( TestCase const& testInfo ) { m_testCaseRegistry.registerTest( testInfo ); } virtual void registerTranslator( const IExceptionTranslator* translator ) { m_exceptionTranslatorRegistry.registerTranslator( translator ); } private: TestRegistry m_testCaseRegistry; ReporterRegistry m_reporterRegistry; ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; }; // Single, global, instance inline RegistryHub*& getTheRegistryHub() { static RegistryHub* theRegistryHub = NULL; if( !theRegistryHub ) theRegistryHub = new RegistryHub(); return theRegistryHub; } } IRegistryHub& getRegistryHub() { return *getTheRegistryHub(); } IMutableRegistryHub& getMutableRegistryHub() { return *getTheRegistryHub(); } void cleanUp() { delete getTheRegistryHub(); getTheRegistryHub() = NULL; cleanUpContext(); } std::string translateActiveException() { return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); } } // end namespace Catch // #included from: catch_notimplemented_exception.hpp #define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_HPP_INCLUDED #include namespace Catch { NotImplementedException::NotImplementedException( SourceLineInfo const& lineInfo ) : m_lineInfo( lineInfo ) { std::ostringstream oss; oss << lineInfo << ": function "; oss << "not implemented"; m_what = oss.str(); } const char* NotImplementedException::what() const CATCH_NOEXCEPT { return m_what.c_str(); } } // end namespace Catch // #included from: catch_context_impl.hpp #define TWOBLUECUBES_CATCH_CONTEXT_IMPL_HPP_INCLUDED // #included from: catch_stream.hpp #define TWOBLUECUBES_CATCH_STREAM_HPP_INCLUDED // #included from: catch_streambuf.h #define TWOBLUECUBES_CATCH_STREAMBUF_H_INCLUDED #include namespace Catch { class StreamBufBase : public std::streambuf { public: virtual ~StreamBufBase() CATCH_NOEXCEPT; }; } #include #include #include namespace Catch { template class StreamBufImpl : public StreamBufBase { char data[bufferSize]; WriterF m_writer; public: StreamBufImpl() { setp( data, data + sizeof(data) ); } ~StreamBufImpl() CATCH_NOEXCEPT { sync(); } private: int overflow( int c ) { sync(); if( c != EOF ) { if( pbase() == epptr() ) m_writer( std::string( 1, static_cast( c ) ) ); else sputc( static_cast( c ) ); } return 0; } int sync() { if( pbase() != pptr() ) { m_writer( std::string( pbase(), static_cast( pptr() - pbase() ) ) ); setp( pbase(), epptr() ); } return 0; } }; /////////////////////////////////////////////////////////////////////////// struct OutputDebugWriter { void operator()( std::string const&str ) { writeToDebugConsole( str ); } }; Stream::Stream() : streamBuf( NULL ), isOwned( false ) {} Stream::Stream( std::streambuf* _streamBuf, bool _isOwned ) : streamBuf( _streamBuf ), isOwned( _isOwned ) {} void Stream::release() { if( isOwned ) { delete streamBuf; streamBuf = NULL; isOwned = false; } } #ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement this functions std::ostream& cout() { return std::cout; } std::ostream& cerr() { return std::cerr; } #endif } namespace Catch { class Context : public IMutableContext { Context() : m_config( NULL ), m_runner( NULL ), m_resultCapture( NULL ) {} Context( Context const& ); void operator=( Context const& ); public: // IContext virtual IResultCapture* getResultCapture() { return m_resultCapture; } virtual IRunner* getRunner() { return m_runner; } virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) { return getGeneratorsForCurrentTest() .getGeneratorInfo( fileInfo, totalSize ) .getCurrentIndex(); } virtual bool advanceGeneratorsForCurrentTest() { IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); return generators && generators->moveNext(); } virtual Ptr getConfig() const { return m_config; } public: // IMutableContext virtual void setResultCapture( IResultCapture* resultCapture ) { m_resultCapture = resultCapture; } virtual void setRunner( IRunner* runner ) { m_runner = runner; } virtual void setConfig( Ptr const& config ) { m_config = config; } friend IMutableContext& getCurrentMutableContext(); private: IGeneratorsForTest* findGeneratorsForCurrentTest() { std::string testName = getResultCapture()->getCurrentTestName(); std::map::const_iterator it = m_generatorsByTestName.find( testName ); return it != m_generatorsByTestName.end() ? it->second : NULL; } IGeneratorsForTest& getGeneratorsForCurrentTest() { IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); if( !generators ) { std::string testName = getResultCapture()->getCurrentTestName(); generators = createGeneratorsForTest(); m_generatorsByTestName.insert( std::make_pair( testName, generators ) ); } return *generators; } private: Ptr m_config; IRunner* m_runner; IResultCapture* m_resultCapture; std::map m_generatorsByTestName; }; namespace { Context* currentContext = NULL; } IMutableContext& getCurrentMutableContext() { if( !currentContext ) currentContext = new Context(); return *currentContext; } IContext& getCurrentContext() { return getCurrentMutableContext(); } Stream createStream( std::string const& streamName ) { if( streamName == "stdout" ) return Stream( Catch::cout().rdbuf(), false ); if( streamName == "stderr" ) return Stream( Catch::cerr().rdbuf(), false ); if( streamName == "debug" ) return Stream( new StreamBufImpl, true ); throw std::domain_error( "Unknown stream: " + streamName ); } void cleanUpContext() { delete currentContext; currentContext = NULL; } } // #included from: catch_console_colour_impl.hpp #define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_IMPL_HPP_INCLUDED namespace Catch { namespace { struct IColourImpl { virtual ~IColourImpl() {} virtual void use( Colour::Code _colourCode ) = 0; }; struct NoColourImpl : IColourImpl { void use( Colour::Code ) {} static IColourImpl* instance() { static NoColourImpl s_instance; return &s_instance; } }; } // anon namespace } // namespace Catch #if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) # ifdef CATCH_PLATFORM_WINDOWS # define CATCH_CONFIG_COLOUR_WINDOWS # else # define CATCH_CONFIG_COLOUR_ANSI # endif #endif #if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) ///////////////////////////////////////// #ifndef NOMINMAX #define NOMINMAX #endif #ifdef __AFXDLL #include #else #include #endif namespace Catch { namespace { class Win32ColourImpl : public IColourImpl { public: Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) { CONSOLE_SCREEN_BUFFER_INFO csbiInfo; GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); originalAttributes = csbiInfo.wAttributes; } virtual void use( Colour::Code _colourCode ) { switch( _colourCode ) { case Colour::None: return setTextAttribute( originalAttributes ); case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); case Colour::Red: return setTextAttribute( FOREGROUND_RED ); case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); case Colour::Grey: return setTextAttribute( 0 ); case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); case Colour::Bright: throw std::logic_error( "not a colour" ); } } private: void setTextAttribute( WORD _textAttribute ) { SetConsoleTextAttribute( stdoutHandle, _textAttribute ); } HANDLE stdoutHandle; WORD originalAttributes; }; IColourImpl* platformColourInstance() { static Win32ColourImpl s_instance; return &s_instance; } } // end anon namespace } // end namespace Catch #elif defined( CATCH_CONFIG_COLOUR_ANSI ) ////////////////////////////////////// #include namespace Catch { namespace { // use POSIX/ ANSI console terminal codes // Thanks to Adam Strzelecki for original contribution // (http://github.com/nanoant) // https://github.com/philsquared/Catch/pull/131 class PosixColourImpl : public IColourImpl { public: virtual void use( Colour::Code _colourCode ) { switch( _colourCode ) { case Colour::None: case Colour::White: return setColour( "[0m" ); case Colour::Red: return setColour( "[0;31m" ); case Colour::Green: return setColour( "[0;32m" ); case Colour::Blue: return setColour( "[0:34m" ); case Colour::Cyan: return setColour( "[0;36m" ); case Colour::Yellow: return setColour( "[0;33m" ); case Colour::Grey: return setColour( "[1;30m" ); case Colour::LightGrey: return setColour( "[0;37m" ); case Colour::BrightRed: return setColour( "[1;31m" ); case Colour::BrightGreen: return setColour( "[1;32m" ); case Colour::BrightWhite: return setColour( "[1;37m" ); case Colour::Bright: throw std::logic_error( "not a colour" ); } } static IColourImpl* instance() { static PosixColourImpl s_instance; return &s_instance; } private: void setColour( const char* _escapeCode ) { Catch::cout() << '\033' << _escapeCode; } }; IColourImpl* platformColourInstance() { Ptr config = getCurrentContext().getConfig(); return (config && config->forceColour()) || isatty(STDOUT_FILENO) ? PosixColourImpl::instance() : NoColourImpl::instance(); } } // end anon namespace } // end namespace Catch #else // not Windows or ANSI /////////////////////////////////////////////// namespace Catch { static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } } // end namespace Catch #endif // Windows/ ANSI/ None namespace Catch { Colour::Colour( Code _colourCode ) : m_moved( false ) { use( _colourCode ); } Colour::Colour( Colour const& _other ) : m_moved( false ) { const_cast( _other ).m_moved = true; } Colour::~Colour(){ if( !m_moved ) use( None ); } void Colour::use( Code _colourCode ) { static IColourImpl* impl = isDebuggerActive() ? NoColourImpl::instance() : platformColourInstance(); impl->use( _colourCode ); } } // end namespace Catch // #included from: catch_generators_impl.hpp #define TWOBLUECUBES_CATCH_GENERATORS_IMPL_HPP_INCLUDED #include #include #include namespace Catch { struct GeneratorInfo : IGeneratorInfo { GeneratorInfo( std::size_t size ) : m_size( size ), m_currentIndex( 0 ) {} bool moveNext() { if( ++m_currentIndex == m_size ) { m_currentIndex = 0; return false; } return true; } std::size_t getCurrentIndex() const { return m_currentIndex; } std::size_t m_size; std::size_t m_currentIndex; }; /////////////////////////////////////////////////////////////////////////// class GeneratorsForTest : public IGeneratorsForTest { public: ~GeneratorsForTest() { deleteAll( m_generatorsInOrder ); } IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) { std::map::const_iterator it = m_generatorsByName.find( fileInfo ); if( it == m_generatorsByName.end() ) { IGeneratorInfo* info = new GeneratorInfo( size ); m_generatorsByName.insert( std::make_pair( fileInfo, info ) ); m_generatorsInOrder.push_back( info ); return *info; } return *it->second; } bool moveNext() { std::vector::const_iterator it = m_generatorsInOrder.begin(); std::vector::const_iterator itEnd = m_generatorsInOrder.end(); for(; it != itEnd; ++it ) { if( (*it)->moveNext() ) return true; } return false; } private: std::map m_generatorsByName; std::vector m_generatorsInOrder; }; IGeneratorsForTest* createGeneratorsForTest() { return new GeneratorsForTest(); } } // end namespace Catch // #included from: catch_assertionresult.hpp #define TWOBLUECUBES_CATCH_ASSERTIONRESULT_HPP_INCLUDED namespace Catch { AssertionInfo::AssertionInfo( std::string const& _macroName, SourceLineInfo const& _lineInfo, std::string const& _capturedExpression, ResultDisposition::Flags _resultDisposition ) : macroName( _macroName ), lineInfo( _lineInfo ), capturedExpression( _capturedExpression ), resultDisposition( _resultDisposition ) {} AssertionResult::AssertionResult() {} AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) : m_info( info ), m_resultData( data ) {} AssertionResult::~AssertionResult() {} // Result was a success bool AssertionResult::succeeded() const { return Catch::isOk( m_resultData.resultType ); } // Result was a success, or failure is suppressed bool AssertionResult::isOk() const { return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); } ResultWas::OfType AssertionResult::getResultType() const { return m_resultData.resultType; } bool AssertionResult::hasExpression() const { return !m_info.capturedExpression.empty(); } bool AssertionResult::hasMessage() const { return !m_resultData.message.empty(); } std::string AssertionResult::getExpression() const { if( isFalseTest( m_info.resultDisposition ) ) return "!" + m_info.capturedExpression; else return m_info.capturedExpression; } std::string AssertionResult::getExpressionInMacro() const { if( m_info.macroName.empty() ) return m_info.capturedExpression; else return m_info.macroName + "( " + m_info.capturedExpression + " )"; } bool AssertionResult::hasExpandedExpression() const { return hasExpression() && getExpandedExpression() != getExpression(); } std::string AssertionResult::getExpandedExpression() const { return m_resultData.reconstructedExpression; } std::string AssertionResult::getMessage() const { return m_resultData.message; } SourceLineInfo AssertionResult::getSourceInfo() const { return m_info.lineInfo; } std::string AssertionResult::getTestMacroName() const { return m_info.macroName; } } // end namespace Catch // #included from: catch_test_case_info.hpp #define TWOBLUECUBES_CATCH_TEST_CASE_INFO_HPP_INCLUDED namespace Catch { inline TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { if( startsWith( tag, "." ) || tag == "hide" || tag == "!hide" ) return TestCaseInfo::IsHidden; else if( tag == "!throws" ) return TestCaseInfo::Throws; else if( tag == "!shouldfail" ) return TestCaseInfo::ShouldFail; else if( tag == "!mayfail" ) return TestCaseInfo::MayFail; else return TestCaseInfo::None; } inline bool isReservedTag( std::string const& tag ) { return TestCaseInfo::None && tag.size() > 0 && !isalnum( tag[0] ); } inline void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { if( isReservedTag( tag ) ) { { Colour colourGuard( Colour::Red ); Catch::cerr() << "Tag name [" << tag << "] not allowed.\n" << "Tag names starting with non alpha-numeric characters are reserved\n"; } { Colour colourGuard( Colour::FileName ); Catch::cerr() << _lineInfo << std::endl; } exit(1); } } TestCase makeTestCase( ITestCase* _testCase, std::string const& _className, std::string const& _name, std::string const& _descOrTags, SourceLineInfo const& _lineInfo ) { bool isHidden( startsWith( _name, "./" ) ); // Legacy support // Parse out tags std::set tags; std::string desc, tag; bool inTag = false; for( std::size_t i = 0; i < _descOrTags.size(); ++i ) { char c = _descOrTags[i]; if( !inTag ) { if( c == '[' ) inTag = true; else desc += c; } else { if( c == ']' ) { TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag ); if( prop == TestCaseInfo::IsHidden ) isHidden = true; else if( prop == TestCaseInfo::None ) enforceNotReservedTag( tag, _lineInfo ); tags.insert( tag ); tag.clear(); inTag = false; } else tag += c; } } if( isHidden ) { tags.insert( "hide" ); tags.insert( "." ); } TestCaseInfo info( _name, _className, desc, tags, _lineInfo ); return TestCase( _testCase, info ); } TestCaseInfo::TestCaseInfo( std::string const& _name, std::string const& _className, std::string const& _description, std::set const& _tags, SourceLineInfo const& _lineInfo ) : name( _name ), className( _className ), description( _description ), tags( _tags ), lineInfo( _lineInfo ), properties( None ) { std::ostringstream oss; for( std::set::const_iterator it = _tags.begin(), itEnd = _tags.end(); it != itEnd; ++it ) { oss << "[" << *it << "]"; std::string lcaseTag = toLower( *it ); properties = static_cast( properties | parseSpecialTag( lcaseTag ) ); lcaseTags.insert( lcaseTag ); } tagsAsString = oss.str(); } TestCaseInfo::TestCaseInfo( TestCaseInfo const& other ) : name( other.name ), className( other.className ), description( other.description ), tags( other.tags ), lcaseTags( other.lcaseTags ), tagsAsString( other.tagsAsString ), lineInfo( other.lineInfo ), properties( other.properties ) {} bool TestCaseInfo::isHidden() const { return ( properties & IsHidden ) != 0; } bool TestCaseInfo::throws() const { return ( properties & Throws ) != 0; } bool TestCaseInfo::okToFail() const { return ( properties & (ShouldFail | MayFail ) ) != 0; } bool TestCaseInfo::expectedToFail() const { return ( properties & (ShouldFail ) ) != 0; } TestCase::TestCase( ITestCase* testCase, TestCaseInfo const& info ) : TestCaseInfo( info ), test( testCase ) {} TestCase::TestCase( TestCase const& other ) : TestCaseInfo( other ), test( other.test ) {} TestCase TestCase::withName( std::string const& _newName ) const { TestCase other( *this ); other.name = _newName; return other; } void TestCase::swap( TestCase& other ) { test.swap( other.test ); name.swap( other.name ); className.swap( other.className ); description.swap( other.description ); tags.swap( other.tags ); lcaseTags.swap( other.lcaseTags ); tagsAsString.swap( other.tagsAsString ); std::swap( TestCaseInfo::properties, static_cast( other ).properties ); std::swap( lineInfo, other.lineInfo ); } void TestCase::invoke() const { test->invoke(); } bool TestCase::operator == ( TestCase const& other ) const { return test.get() == other.test.get() && name == other.name && className == other.className; } bool TestCase::operator < ( TestCase const& other ) const { return name < other.name; } TestCase& TestCase::operator = ( TestCase const& other ) { TestCase temp( other ); swap( temp ); return *this; } TestCaseInfo const& TestCase::getTestCaseInfo() const { return *this; } } // end namespace Catch // #included from: catch_version.hpp #define TWOBLUECUBES_CATCH_VERSION_HPP_INCLUDED namespace Catch { // These numbers are maintained by a script Version libraryVersion( 1, 1, 14, "develop" ); } // #included from: catch_message.hpp #define TWOBLUECUBES_CATCH_MESSAGE_HPP_INCLUDED namespace Catch { MessageInfo::MessageInfo( std::string const& _macroName, SourceLineInfo const& _lineInfo, ResultWas::OfType _type ) : macroName( _macroName ), lineInfo( _lineInfo ), type( _type ), sequence( ++globalCount ) {} // This may need protecting if threading support is added unsigned int MessageInfo::globalCount = 0; //////////////////////////////////////////////////////////////////////////// ScopedMessage::ScopedMessage( MessageBuilder const& builder ) : m_info( builder.m_info ) { m_info.message = builder.m_stream.str(); getResultCapture().pushScopedMessage( m_info ); } ScopedMessage::ScopedMessage( ScopedMessage const& other ) : m_info( other.m_info ) {} ScopedMessage::~ScopedMessage() { getResultCapture().popScopedMessage( m_info ); } } // end namespace Catch // #included from: catch_legacy_reporter_adapter.hpp #define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_HPP_INCLUDED // #included from: catch_legacy_reporter_adapter.h #define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_H_INCLUDED namespace Catch { // Deprecated struct IReporter : IShared { virtual ~IReporter(); virtual bool shouldRedirectStdout() const = 0; virtual void StartTesting() = 0; virtual void EndTesting( Totals const& totals ) = 0; virtual void StartGroup( std::string const& groupName ) = 0; virtual void EndGroup( std::string const& groupName, Totals const& totals ) = 0; virtual void StartTestCase( TestCaseInfo const& testInfo ) = 0; virtual void EndTestCase( TestCaseInfo const& testInfo, Totals const& totals, std::string const& stdOut, std::string const& stdErr ) = 0; virtual void StartSection( std::string const& sectionName, std::string const& description ) = 0; virtual void EndSection( std::string const& sectionName, Counts const& assertions ) = 0; virtual void NoAssertionsInSection( std::string const& sectionName ) = 0; virtual void NoAssertionsInTestCase( std::string const& testName ) = 0; virtual void Aborted() = 0; virtual void Result( AssertionResult const& result ) = 0; }; class LegacyReporterAdapter : public SharedImpl { public: LegacyReporterAdapter( Ptr const& legacyReporter ); virtual ~LegacyReporterAdapter(); virtual ReporterPreferences getPreferences() const; virtual void noMatchingTestCases( std::string const& ); virtual void testRunStarting( TestRunInfo const& ); virtual void testGroupStarting( GroupInfo const& groupInfo ); virtual void testCaseStarting( TestCaseInfo const& testInfo ); virtual void sectionStarting( SectionInfo const& sectionInfo ); virtual void assertionStarting( AssertionInfo const& ); virtual bool assertionEnded( AssertionStats const& assertionStats ); virtual void sectionEnded( SectionStats const& sectionStats ); virtual void testCaseEnded( TestCaseStats const& testCaseStats ); virtual void testGroupEnded( TestGroupStats const& testGroupStats ); virtual void testRunEnded( TestRunStats const& testRunStats ); virtual void skipTest( TestCaseInfo const& ); private: Ptr m_legacyReporter; }; } namespace Catch { LegacyReporterAdapter::LegacyReporterAdapter( Ptr const& legacyReporter ) : m_legacyReporter( legacyReporter ) {} LegacyReporterAdapter::~LegacyReporterAdapter() {} ReporterPreferences LegacyReporterAdapter::getPreferences() const { ReporterPreferences prefs; prefs.shouldRedirectStdOut = m_legacyReporter->shouldRedirectStdout(); return prefs; } void LegacyReporterAdapter::noMatchingTestCases( std::string const& ) {} void LegacyReporterAdapter::testRunStarting( TestRunInfo const& ) { m_legacyReporter->StartTesting(); } void LegacyReporterAdapter::testGroupStarting( GroupInfo const& groupInfo ) { m_legacyReporter->StartGroup( groupInfo.name ); } void LegacyReporterAdapter::testCaseStarting( TestCaseInfo const& testInfo ) { m_legacyReporter->StartTestCase( testInfo ); } void LegacyReporterAdapter::sectionStarting( SectionInfo const& sectionInfo ) { m_legacyReporter->StartSection( sectionInfo.name, sectionInfo.description ); } void LegacyReporterAdapter::assertionStarting( AssertionInfo const& ) { // Not on legacy interface } bool LegacyReporterAdapter::assertionEnded( AssertionStats const& assertionStats ) { if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); it != itEnd; ++it ) { if( it->type == ResultWas::Info ) { ResultBuilder rb( it->macroName.c_str(), it->lineInfo, "", ResultDisposition::Normal ); rb << it->message; rb.setResultType( ResultWas::Info ); AssertionResult result = rb.build(); m_legacyReporter->Result( result ); } } } m_legacyReporter->Result( assertionStats.assertionResult ); return true; } void LegacyReporterAdapter::sectionEnded( SectionStats const& sectionStats ) { if( sectionStats.missingAssertions ) m_legacyReporter->NoAssertionsInSection( sectionStats.sectionInfo.name ); m_legacyReporter->EndSection( sectionStats.sectionInfo.name, sectionStats.assertions ); } void LegacyReporterAdapter::testCaseEnded( TestCaseStats const& testCaseStats ) { m_legacyReporter->EndTestCase ( testCaseStats.testInfo, testCaseStats.totals, testCaseStats.stdOut, testCaseStats.stdErr ); } void LegacyReporterAdapter::testGroupEnded( TestGroupStats const& testGroupStats ) { if( testGroupStats.aborting ) m_legacyReporter->Aborted(); m_legacyReporter->EndGroup( testGroupStats.groupInfo.name, testGroupStats.totals ); } void LegacyReporterAdapter::testRunEnded( TestRunStats const& testRunStats ) { m_legacyReporter->EndTesting( testRunStats.totals ); } void LegacyReporterAdapter::skipTest( TestCaseInfo const& ) { } } // #included from: catch_timer.hpp #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wc++11-long-long" #endif #ifdef CATCH_PLATFORM_WINDOWS #include #else #include #endif namespace Catch { namespace { #ifdef CATCH_PLATFORM_WINDOWS uint64_t getCurrentTicks() { static uint64_t hz=0, hzo=0; if (!hz) { QueryPerformanceFrequency((LARGE_INTEGER*)&hz); QueryPerformanceCounter((LARGE_INTEGER*)&hzo); } uint64_t t; QueryPerformanceCounter((LARGE_INTEGER*)&t); return ((t-hzo)*1000000)/hz; } #else uint64_t getCurrentTicks() { timeval t; gettimeofday(&t,NULL); return static_cast( t.tv_sec ) * 1000000ull + static_cast( t.tv_usec ); } #endif } void Timer::start() { m_ticks = getCurrentTicks(); } unsigned int Timer::getElapsedMicroseconds() const { return static_cast(getCurrentTicks() - m_ticks); } unsigned int Timer::getElapsedMilliseconds() const { return static_cast(getElapsedMicroseconds()/1000); } double Timer::getElapsedSeconds() const { return getElapsedMicroseconds()/1000000.0; } } // namespace Catch #ifdef __clang__ #pragma clang diagnostic pop #endif // #included from: catch_common.hpp #define TWOBLUECUBES_CATCH_COMMON_HPP_INCLUDED namespace Catch { bool startsWith( std::string const& s, std::string const& prefix ) { return s.size() >= prefix.size() && s.substr( 0, prefix.size() ) == prefix; } bool endsWith( std::string const& s, std::string const& suffix ) { return s.size() >= suffix.size() && s.substr( s.size()-suffix.size(), suffix.size() ) == suffix; } bool contains( std::string const& s, std::string const& infix ) { return s.find( infix ) != std::string::npos; } void toLowerInPlace( std::string& s ) { std::transform( s.begin(), s.end(), s.begin(), ::tolower ); } std::string toLower( std::string const& s ) { std::string lc = s; toLowerInPlace( lc ); return lc; } std::string trim( std::string const& str ) { static char const* whitespaceChars = "\n\r\t "; std::string::size_type start = str.find_first_not_of( whitespaceChars ); std::string::size_type end = str.find_last_not_of( whitespaceChars ); return start != std::string::npos ? str.substr( start, 1+end-start ) : ""; } bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { bool replaced = false; std::size_t i = str.find( replaceThis ); while( i != std::string::npos ) { replaced = true; str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() ); if( i < str.size()-withThis.size() ) i = str.find( replaceThis, i+withThis.size() ); else i = std::string::npos; } return replaced; } pluralise::pluralise( std::size_t count, std::string const& label ) : m_count( count ), m_label( label ) {} std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { os << pluraliser.m_count << " " << pluraliser.m_label; if( pluraliser.m_count != 1 ) os << "s"; return os; } SourceLineInfo::SourceLineInfo() : line( 0 ){} SourceLineInfo::SourceLineInfo( char const* _file, std::size_t _line ) : file( _file ), line( _line ) {} SourceLineInfo::SourceLineInfo( SourceLineInfo const& other ) : file( other.file ), line( other.line ) {} bool SourceLineInfo::empty() const { return file.empty(); } bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const { return line == other.line && file == other.file; } bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const { return line < other.line || ( line == other.line && file < other.file ); } std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { #ifndef __GNUG__ os << info.file << "(" << info.line << ")"; #else os << info.file << ":" << info.line; #endif return os; } void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ) { std::ostringstream oss; oss << locationInfo << ": Internal Catch error: '" << message << "'"; if( alwaysTrue() ) throw std::logic_error( oss.str() ); } } // #included from: catch_section.hpp #define TWOBLUECUBES_CATCH_SECTION_HPP_INCLUDED namespace Catch { SectionInfo::SectionInfo ( SourceLineInfo const& _lineInfo, std::string const& _name, std::string const& _description ) : name( _name ), description( _description ), lineInfo( _lineInfo ) {} Section::Section( SectionInfo const& info ) : m_info( info ), m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) { m_timer.start(); } Section::~Section() { if( m_sectionIncluded ) getResultCapture().sectionEnded( m_info, m_assertions, m_timer.getElapsedSeconds() ); } // This indicates whether the section should be executed or not Section::operator bool() const { return m_sectionIncluded; } } // end namespace Catch // #included from: catch_debugger.hpp #define TWOBLUECUBES_CATCH_DEBUGGER_HPP_INCLUDED #include #ifdef CATCH_PLATFORM_MAC #include #include #include #include #include namespace Catch{ // The following function is taken directly from the following technical note: // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html // Returns true if the current process is being debugged (either // running under the debugger or has a debugger attached post facto). bool isDebuggerActive(){ int mib[4]; struct kinfo_proc info; size_t size; // Initialize the flags so that, if sysctl fails for some bizarre // reason, we get a predictable result. info.kp_proc.p_flag = 0; // Initialize mib, which tells sysctl the info we want, in this case // we're looking for information about a specific process ID. mib[0] = CTL_KERN; mib[1] = KERN_PROC; mib[2] = KERN_PROC_PID; mib[3] = getpid(); // Call sysctl. size = sizeof(info); if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0) != 0 ) { Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; return false; } // We're being debugged if the P_TRACED flag is set. return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); } } // namespace Catch #elif defined(_MSC_VER) extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); namespace Catch { bool isDebuggerActive() { return IsDebuggerPresent() != 0; } } #elif defined(__MINGW32__) extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); namespace Catch { bool isDebuggerActive() { return IsDebuggerPresent() != 0; } } #else namespace Catch { inline bool isDebuggerActive() { return false; } } #endif // Platform #ifdef CATCH_PLATFORM_WINDOWS extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA( const char* ); namespace Catch { void writeToDebugConsole( std::string const& text ) { ::OutputDebugStringA( text.c_str() ); } } #else namespace Catch { void writeToDebugConsole( std::string const& text ) { // !TBD: Need a version for Mac/ XCode and other IDEs Catch::cout() << text; } } #endif // Platform // #included from: catch_tostring.hpp #define TWOBLUECUBES_CATCH_TOSTRING_HPP_INCLUDED namespace Catch { namespace Detail { std::string unprintableString = "{?}"; namespace { struct Endianness { enum Arch { Big, Little }; static Arch which() { union _{ int asInt; char asChar[sizeof (int)]; } u; u.asInt = 1; return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little; } }; } std::string rawMemoryToString( const void *object, std::size_t size ) { // Reverse order for little endian architectures int i = 0, end = static_cast( size ), inc = 1; if( Endianness::which() == Endianness::Little ) { i = end-1; end = inc = -1; } unsigned char const *bytes = static_cast(object); std::ostringstream os; os << "0x" << std::setfill('0') << std::hex; for( ; i != end; i += inc ) os << std::setw(2) << static_cast(bytes[i]); return os.str(); } } std::string toString( std::string const& value ) { std::string s = value; if( getCurrentContext().getConfig()->showInvisibles() ) { for(size_t i = 0; i < s.size(); ++i ) { std::string subs; switch( s[i] ) { case '\n': subs = "\\n"; break; case '\t': subs = "\\t"; break; default: break; } if( !subs.empty() ) { s = s.substr( 0, i ) + subs + s.substr( i+1 ); ++i; } } } return "\"" + s + "\""; } std::string toString( std::wstring const& value ) { std::string s; s.reserve( value.size() ); for(size_t i = 0; i < value.size(); ++i ) s += value[i] <= 0xff ? static_cast( value[i] ) : '?'; return Catch::toString( s ); } std::string toString( const char* const value ) { return value ? Catch::toString( std::string( value ) ) : std::string( "{null string}" ); } std::string toString( char* const value ) { return Catch::toString( static_cast( value ) ); } std::string toString( const wchar_t* const value ) { return value ? Catch::toString( std::wstring(value) ) : std::string( "{null string}" ); } std::string toString( wchar_t* const value ) { return Catch::toString( static_cast( value ) ); } std::string toString( int value ) { std::ostringstream oss; if( value > 8192 ) oss << "0x" << std::hex << value; else oss << value; return oss.str(); } std::string toString( unsigned long value ) { std::ostringstream oss; if( value > 8192 ) oss << "0x" << std::hex << value; else oss << value; return oss.str(); } std::string toString( unsigned int value ) { return Catch::toString( static_cast( value ) ); } template std::string fpToString( T value, int precision ) { std::ostringstream oss; oss << std::setprecision( precision ) << std::fixed << value; std::string d = oss.str(); std::size_t i = d.find_last_not_of( '0' ); if( i != std::string::npos && i != d.size()-1 ) { if( d[i] == '.' ) i++; d = d.substr( 0, i+1 ); } return d; } std::string toString( const double value ) { return fpToString( value, 10 ); } std::string toString( const float value ) { return fpToString( value, 5 ) + "f"; } std::string toString( bool value ) { return value ? "true" : "false"; } std::string toString( char value ) { return value < ' ' ? toString( static_cast( value ) ) : Detail::makeString( value ); } std::string toString( signed char value ) { return toString( static_cast( value ) ); } std::string toString( unsigned char value ) { return toString( static_cast( value ) ); } #ifdef CATCH_CONFIG_CPP11_NULLPTR std::string toString( std::nullptr_t ) { return "nullptr"; } #endif #ifdef __OBJC__ std::string toString( NSString const * const& nsstring ) { if( !nsstring ) return "nil"; return "@" + toString([nsstring UTF8String]); } std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ) { if( !nsstring ) return "nil"; return "@" + toString([nsstring UTF8String]); } std::string toString( NSObject* const& nsObject ) { return toString( [nsObject description] ); } #endif } // end namespace Catch // #included from: catch_result_builder.hpp #define TWOBLUECUBES_CATCH_RESULT_BUILDER_HPP_INCLUDED namespace Catch { ResultBuilder::ResultBuilder( char const* macroName, SourceLineInfo const& lineInfo, char const* capturedExpression, ResultDisposition::Flags resultDisposition ) : m_assertionInfo( macroName, lineInfo, capturedExpression, resultDisposition ), m_shouldDebugBreak( false ), m_shouldThrow( false ) {} ResultBuilder& ResultBuilder::setResultType( ResultWas::OfType result ) { m_data.resultType = result; return *this; } ResultBuilder& ResultBuilder::setResultType( bool result ) { m_data.resultType = result ? ResultWas::Ok : ResultWas::ExpressionFailed; return *this; } ResultBuilder& ResultBuilder::setLhs( std::string const& lhs ) { m_exprComponents.lhs = lhs; return *this; } ResultBuilder& ResultBuilder::setRhs( std::string const& rhs ) { m_exprComponents.rhs = rhs; return *this; } ResultBuilder& ResultBuilder::setOp( std::string const& op ) { m_exprComponents.op = op; return *this; } void ResultBuilder::endExpression() { m_exprComponents.testFalse = isFalseTest( m_assertionInfo.resultDisposition ); captureExpression(); } void ResultBuilder::useActiveException( ResultDisposition::Flags resultDisposition ) { m_assertionInfo.resultDisposition = resultDisposition; m_stream.oss << Catch::translateActiveException(); captureResult( ResultWas::ThrewException ); } void ResultBuilder::captureResult( ResultWas::OfType resultType ) { setResultType( resultType ); captureExpression(); } void ResultBuilder::captureExpression() { AssertionResult result = build(); getResultCapture().assertionEnded( result ); if( !result.isOk() ) { if( getCurrentContext().getConfig()->shouldDebugBreak() ) m_shouldDebugBreak = true; if( getCurrentContext().getRunner()->aborting() || m_assertionInfo.resultDisposition == ResultDisposition::Normal ) m_shouldThrow = true; } } void ResultBuilder::react() { if( m_shouldThrow ) throw Catch::TestFailureException(); } bool ResultBuilder::shouldDebugBreak() const { return m_shouldDebugBreak; } bool ResultBuilder::allowThrows() const { return getCurrentContext().getConfig()->allowThrows(); } AssertionResult ResultBuilder::build() const { assert( m_data.resultType != ResultWas::Unknown ); AssertionResultData data = m_data; // Flip bool results if testFalse is set if( m_exprComponents.testFalse ) { if( data.resultType == ResultWas::Ok ) data.resultType = ResultWas::ExpressionFailed; else if( data.resultType == ResultWas::ExpressionFailed ) data.resultType = ResultWas::Ok; } data.message = m_stream.oss.str(); data.reconstructedExpression = reconstructExpression(); if( m_exprComponents.testFalse ) { if( m_exprComponents.op == "" ) data.reconstructedExpression = "!" + data.reconstructedExpression; else data.reconstructedExpression = "!(" + data.reconstructedExpression + ")"; } return AssertionResult( m_assertionInfo, data ); } std::string ResultBuilder::reconstructExpression() const { if( m_exprComponents.op == "" ) return m_exprComponents.lhs.empty() ? m_assertionInfo.capturedExpression : m_exprComponents.op + m_exprComponents.lhs; else if( m_exprComponents.op == "matches" ) return m_exprComponents.lhs + " " + m_exprComponents.rhs; else if( m_exprComponents.op != "!" ) { if( m_exprComponents.lhs.size() + m_exprComponents.rhs.size() < 40 && m_exprComponents.lhs.find("\n") == std::string::npos && m_exprComponents.rhs.find("\n") == std::string::npos ) return m_exprComponents.lhs + " " + m_exprComponents.op + " " + m_exprComponents.rhs; else return m_exprComponents.lhs + "\n" + m_exprComponents.op + "\n" + m_exprComponents.rhs; } else return "{can't expand - use " + m_assertionInfo.macroName + "_FALSE( " + m_assertionInfo.capturedExpression.substr(1) + " ) instead of " + m_assertionInfo.macroName + "( " + m_assertionInfo.capturedExpression + " ) for better diagnostics}"; } } // end namespace Catch // #included from: catch_tag_alias_registry.hpp #define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED // #included from: catch_tag_alias_registry.h #define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_H_INCLUDED #include namespace Catch { class TagAliasRegistry : public ITagAliasRegistry { public: virtual ~TagAliasRegistry(); virtual Option find( std::string const& alias ) const; virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const; void add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); static TagAliasRegistry& get(); private: std::map m_registry; }; } // end namespace Catch #include #include namespace Catch { TagAliasRegistry::~TagAliasRegistry() {} Option TagAliasRegistry::find( std::string const& alias ) const { std::map::const_iterator it = m_registry.find( alias ); if( it != m_registry.end() ) return it->second; else return Option(); } std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { std::string expandedTestSpec = unexpandedTestSpec; for( std::map::const_iterator it = m_registry.begin(), itEnd = m_registry.end(); it != itEnd; ++it ) { std::size_t pos = expandedTestSpec.find( it->first ); if( pos != std::string::npos ) { expandedTestSpec = expandedTestSpec.substr( 0, pos ) + it->second.tag + expandedTestSpec.substr( pos + it->first.size() ); } } return expandedTestSpec; } void TagAliasRegistry::add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { if( !startsWith( alias, "[@" ) || !endsWith( alias, "]" ) ) { std::ostringstream oss; oss << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n" << lineInfo; throw std::domain_error( oss.str().c_str() ); } if( !m_registry.insert( std::make_pair( alias, TagAlias( tag, lineInfo ) ) ).second ) { std::ostringstream oss; oss << "error: tag alias, \"" << alias << "\" already registered.\n" << "\tFirst seen at " << find(alias)->lineInfo << "\n" << "\tRedefined at " << lineInfo; throw std::domain_error( oss.str().c_str() ); } } TagAliasRegistry& TagAliasRegistry::get() { static TagAliasRegistry instance; return instance; } ITagAliasRegistry::~ITagAliasRegistry() {} ITagAliasRegistry const& ITagAliasRegistry::get() { return TagAliasRegistry::get(); } RegistrarForTagAliases::RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { try { TagAliasRegistry::get().add( alias, tag, lineInfo ); } catch( std::exception& ex ) { Colour colourGuard( Colour::Red ); Catch::cerr() << ex.what() << std::endl; exit(1); } } } // end namespace Catch // #included from: ../reporters/catch_reporter_xml.hpp #define TWOBLUECUBES_CATCH_REPORTER_XML_HPP_INCLUDED // #included from: catch_reporter_bases.hpp #define TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED #include namespace Catch { struct StreamingReporterBase : SharedImpl { StreamingReporterBase( ReporterConfig const& _config ) : m_config( _config.fullConfig() ), stream( _config.stream() ) {} virtual ~StreamingReporterBase(); virtual void noMatchingTestCases( std::string const& ) {} virtual void testRunStarting( TestRunInfo const& _testRunInfo ) { currentTestRunInfo = _testRunInfo; } virtual void testGroupStarting( GroupInfo const& _groupInfo ) { currentGroupInfo = _groupInfo; } virtual void testCaseStarting( TestCaseInfo const& _testInfo ) { currentTestCaseInfo = _testInfo; } virtual void sectionStarting( SectionInfo const& _sectionInfo ) { m_sectionStack.push_back( _sectionInfo ); } virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) { m_sectionStack.pop_back(); } virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) { currentTestCaseInfo.reset(); } virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) { currentGroupInfo.reset(); } virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) { currentTestCaseInfo.reset(); currentGroupInfo.reset(); currentTestRunInfo.reset(); } virtual void skipTest( TestCaseInfo const& ) { // Don't do anything with this by default. // It can optionally be overridden in the derived class. } Ptr m_config; std::ostream& stream; LazyStat currentTestRunInfo; LazyStat currentGroupInfo; LazyStat currentTestCaseInfo; std::vector m_sectionStack; }; struct CumulativeReporterBase : SharedImpl { template struct Node : SharedImpl<> { explicit Node( T const& _value ) : value( _value ) {} virtual ~Node() {} typedef std::vector > ChildNodes; T value; ChildNodes children; }; struct SectionNode : SharedImpl<> { explicit SectionNode( SectionStats const& _stats ) : stats( _stats ) {} virtual ~SectionNode(); bool operator == ( SectionNode const& other ) const { return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; } bool operator == ( Ptr const& other ) const { return operator==( *other ); } SectionStats stats; typedef std::vector > ChildSections; typedef std::vector Assertions; ChildSections childSections; Assertions assertions; std::string stdOut; std::string stdErr; }; struct BySectionInfo { BySectionInfo( SectionInfo const& other ) : m_other( other ) {} BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} bool operator() ( Ptr const& node ) const { return node->stats.sectionInfo.lineInfo == m_other.lineInfo; } private: void operator=( BySectionInfo const& ); SectionInfo const& m_other; }; typedef Node TestCaseNode; typedef Node TestGroupNode; typedef Node TestRunNode; CumulativeReporterBase( ReporterConfig const& _config ) : m_config( _config.fullConfig() ), stream( _config.stream() ) {} ~CumulativeReporterBase(); virtual void testRunStarting( TestRunInfo const& ) {} virtual void testGroupStarting( GroupInfo const& ) {} virtual void testCaseStarting( TestCaseInfo const& ) {} virtual void sectionStarting( SectionInfo const& sectionInfo ) { SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); Ptr node; if( m_sectionStack.empty() ) { if( !m_rootSection ) m_rootSection = new SectionNode( incompleteStats ); node = m_rootSection; } else { SectionNode& parentNode = *m_sectionStack.back(); SectionNode::ChildSections::const_iterator it = std::find_if( parentNode.childSections.begin(), parentNode.childSections.end(), BySectionInfo( sectionInfo ) ); if( it == parentNode.childSections.end() ) { node = new SectionNode( incompleteStats ); parentNode.childSections.push_back( node ); } else node = *it; } m_sectionStack.push_back( node ); m_deepestSection = node; } virtual void assertionStarting( AssertionInfo const& ) {} virtual bool assertionEnded( AssertionStats const& assertionStats ) { assert( !m_sectionStack.empty() ); SectionNode& sectionNode = *m_sectionStack.back(); sectionNode.assertions.push_back( assertionStats ); return true; } virtual void sectionEnded( SectionStats const& sectionStats ) { assert( !m_sectionStack.empty() ); SectionNode& node = *m_sectionStack.back(); node.stats = sectionStats; m_sectionStack.pop_back(); } virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { Ptr node = new TestCaseNode( testCaseStats ); assert( m_sectionStack.size() == 0 ); node->children.push_back( m_rootSection ); m_testCases.push_back( node ); m_rootSection.reset(); assert( m_deepestSection ); m_deepestSection->stdOut = testCaseStats.stdOut; m_deepestSection->stdErr = testCaseStats.stdErr; } virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { Ptr node = new TestGroupNode( testGroupStats ); node->children.swap( m_testCases ); m_testGroups.push_back( node ); } virtual void testRunEnded( TestRunStats const& testRunStats ) { Ptr node = new TestRunNode( testRunStats ); node->children.swap( m_testGroups ); m_testRuns.push_back( node ); testRunEndedCumulative(); } virtual void testRunEndedCumulative() = 0; virtual void skipTest( TestCaseInfo const& ) {} Ptr m_config; std::ostream& stream; std::vector m_assertions; std::vector > > m_sections; std::vector > m_testCases; std::vector > m_testGroups; std::vector > m_testRuns; Ptr m_rootSection; Ptr m_deepestSection; std::vector > m_sectionStack; }; template char const* getLineOfChars() { static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; if( !*line ) { memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; } return line; } } // end namespace Catch // #included from: ../internal/catch_reporter_registrars.hpp #define TWOBLUECUBES_CATCH_REPORTER_REGISTRARS_HPP_INCLUDED namespace Catch { template class LegacyReporterRegistrar { class ReporterFactory : public IReporterFactory { virtual IStreamingReporter* create( ReporterConfig const& config ) const { return new LegacyReporterAdapter( new T( config ) ); } virtual std::string getDescription() const { return T::getDescription(); } }; public: LegacyReporterRegistrar( std::string const& name ) { getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); } }; template class ReporterRegistrar { class ReporterFactory : public IReporterFactory { // *** Please Note ***: // - If you end up here looking at a compiler error because it's trying to register // your custom reporter class be aware that the native reporter interface has changed // to IStreamingReporter. The "legacy" interface, IReporter, is still supported via // an adapter. Just use REGISTER_LEGACY_REPORTER to take advantage of the adapter. // However please consider updating to the new interface as the old one is now // deprecated and will probably be removed quite soon! // Please contact me via github if you have any questions at all about this. // In fact, ideally, please contact me anyway to let me know you've hit this - as I have // no idea who is actually using custom reporters at all (possibly no-one!). // The new interface is designed to minimise exposure to interface changes in the future. virtual IStreamingReporter* create( ReporterConfig const& config ) const { return new T( config ); } virtual std::string getDescription() const { return T::getDescription(); } }; public: ReporterRegistrar( std::string const& name ) { getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); } }; } #define INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) \ namespace{ Catch::LegacyReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } #define INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) \ namespace{ Catch::ReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } // #included from: ../internal/catch_xmlwriter.hpp #define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED #include #include #include namespace Catch { class XmlWriter { public: class ScopedElement { public: ScopedElement( XmlWriter* writer ) : m_writer( writer ) {} ScopedElement( ScopedElement const& other ) : m_writer( other.m_writer ){ other.m_writer = NULL; } ~ScopedElement() { if( m_writer ) m_writer->endElement(); } ScopedElement& writeText( std::string const& text, bool indent = true ) { m_writer->writeText( text, indent ); return *this; } template ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { m_writer->writeAttribute( name, attribute ); return *this; } private: mutable XmlWriter* m_writer; }; XmlWriter() : m_tagIsOpen( false ), m_needsNewline( false ), m_os( &Catch::cout() ) {} XmlWriter( std::ostream& os ) : m_tagIsOpen( false ), m_needsNewline( false ), m_os( &os ) {} ~XmlWriter() { while( !m_tags.empty() ) endElement(); } //# ifndef CATCH_CPP11_OR_GREATER // XmlWriter& operator = ( XmlWriter const& other ) { // XmlWriter temp( other ); // swap( temp ); // return *this; // } //# else // XmlWriter( XmlWriter const& ) = default; // XmlWriter( XmlWriter && ) = default; // XmlWriter& operator = ( XmlWriter const& ) = default; // XmlWriter& operator = ( XmlWriter && ) = default; //# endif // // void swap( XmlWriter& other ) { // std::swap( m_tagIsOpen, other.m_tagIsOpen ); // std::swap( m_needsNewline, other.m_needsNewline ); // std::swap( m_tags, other.m_tags ); // std::swap( m_indent, other.m_indent ); // std::swap( m_os, other.m_os ); // } XmlWriter& startElement( std::string const& name ) { ensureTagClosed(); newlineIfNecessary(); stream() << m_indent << "<" << name; m_tags.push_back( name ); m_indent += " "; m_tagIsOpen = true; return *this; } ScopedElement scopedElement( std::string const& name ) { ScopedElement scoped( this ); startElement( name ); return scoped; } XmlWriter& endElement() { newlineIfNecessary(); m_indent = m_indent.substr( 0, m_indent.size()-2 ); if( m_tagIsOpen ) { stream() << "/>\n"; m_tagIsOpen = false; } else { stream() << m_indent << "\n"; } m_tags.pop_back(); return *this; } XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ) { if( !name.empty() && !attribute.empty() ) { stream() << " " << name << "=\""; writeEncodedText( attribute ); stream() << "\""; } return *this; } XmlWriter& writeAttribute( std::string const& name, bool attribute ) { stream() << " " << name << "=\"" << ( attribute ? "true" : "false" ) << "\""; return *this; } template XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { if( !name.empty() ) stream() << " " << name << "=\"" << attribute << "\""; return *this; } XmlWriter& writeText( std::string const& text, bool indent = true ) { if( !text.empty() ){ bool tagWasOpen = m_tagIsOpen; ensureTagClosed(); if( tagWasOpen && indent ) stream() << m_indent; writeEncodedText( text ); m_needsNewline = true; } return *this; } XmlWriter& writeComment( std::string const& text ) { ensureTagClosed(); stream() << m_indent << ""; m_needsNewline = true; return *this; } XmlWriter& writeBlankLine() { ensureTagClosed(); stream() << "\n"; return *this; } void setStream( std::ostream& os ) { m_os = &os; } private: XmlWriter( XmlWriter const& ); void operator=( XmlWriter const& ); std::ostream& stream() { return *m_os; } void ensureTagClosed() { if( m_tagIsOpen ) { stream() << ">\n"; m_tagIsOpen = false; } } void newlineIfNecessary() { if( m_needsNewline ) { stream() << "\n"; m_needsNewline = false; } } void writeEncodedText( std::string const& text ) { static const char* charsToEncode = "<&\""; std::string mtext = text; std::string::size_type pos = mtext.find_first_of( charsToEncode ); while( pos != std::string::npos ) { stream() << mtext.substr( 0, pos ); switch( mtext[pos] ) { case '<': stream() << "<"; break; case '&': stream() << "&"; break; case '\"': stream() << """; break; } mtext = mtext.substr( pos+1 ); pos = mtext.find_first_of( charsToEncode ); } stream() << mtext; } bool m_tagIsOpen; bool m_needsNewline; std::vector m_tags; std::string m_indent; std::ostream* m_os; }; } namespace Catch { class XmlReporter : public StreamingReporterBase { public: XmlReporter( ReporterConfig const& _config ) : StreamingReporterBase( _config ), m_sectionDepth( 0 ) {} virtual ~XmlReporter(); static std::string getDescription() { return "Reports test results as an XML document"; } public: // StreamingReporterBase virtual ReporterPreferences getPreferences() const { ReporterPreferences prefs; prefs.shouldRedirectStdOut = true; return prefs; } virtual void noMatchingTestCases( std::string const& s ) { StreamingReporterBase::noMatchingTestCases( s ); } virtual void testRunStarting( TestRunInfo const& testInfo ) { StreamingReporterBase::testRunStarting( testInfo ); m_xml.setStream( stream ); m_xml.startElement( "Catch" ); if( !m_config->name().empty() ) m_xml.writeAttribute( "name", m_config->name() ); } virtual void testGroupStarting( GroupInfo const& groupInfo ) { StreamingReporterBase::testGroupStarting( groupInfo ); m_xml.startElement( "Group" ) .writeAttribute( "name", groupInfo.name ); } virtual void testCaseStarting( TestCaseInfo const& testInfo ) { StreamingReporterBase::testCaseStarting(testInfo); m_xml.startElement( "TestCase" ).writeAttribute( "name", trim( testInfo.name ) ); if ( m_config->showDurations() == ShowDurations::Always ) m_testCaseTimer.start(); } virtual void sectionStarting( SectionInfo const& sectionInfo ) { StreamingReporterBase::sectionStarting( sectionInfo ); if( m_sectionDepth++ > 0 ) { m_xml.startElement( "Section" ) .writeAttribute( "name", trim( sectionInfo.name ) ) .writeAttribute( "description", sectionInfo.description ); } } virtual void assertionStarting( AssertionInfo const& ) { } virtual bool assertionEnded( AssertionStats const& assertionStats ) { const AssertionResult& assertionResult = assertionStats.assertionResult; // Print any info messages in tags. if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); it != itEnd; ++it ) { if( it->type == ResultWas::Info ) { m_xml.scopedElement( "Info" ) .writeText( it->message ); } else if ( it->type == ResultWas::Warning ) { m_xml.scopedElement( "Warning" ) .writeText( it->message ); } } } // Drop out if result was successful but we're not printing them. if( !m_config->includeSuccessfulResults() && isOk(assertionResult.getResultType()) ) return true; // Print the expression if there is one. if( assertionResult.hasExpression() ) { m_xml.startElement( "Expression" ) .writeAttribute( "success", assertionResult.succeeded() ) .writeAttribute( "type", assertionResult.getTestMacroName() ) .writeAttribute( "filename", assertionResult.getSourceInfo().file ) .writeAttribute( "line", assertionResult.getSourceInfo().line ); m_xml.scopedElement( "Original" ) .writeText( assertionResult.getExpression() ); m_xml.scopedElement( "Expanded" ) .writeText( assertionResult.getExpandedExpression() ); } // And... Print a result applicable to each result type. switch( assertionResult.getResultType() ) { case ResultWas::ThrewException: m_xml.scopedElement( "Exception" ) .writeAttribute( "filename", assertionResult.getSourceInfo().file ) .writeAttribute( "line", assertionResult.getSourceInfo().line ) .writeText( assertionResult.getMessage() ); break; case ResultWas::FatalErrorCondition: m_xml.scopedElement( "Fatal Error Condition" ) .writeAttribute( "filename", assertionResult.getSourceInfo().file ) .writeAttribute( "line", assertionResult.getSourceInfo().line ) .writeText( assertionResult.getMessage() ); break; case ResultWas::Info: m_xml.scopedElement( "Info" ) .writeText( assertionResult.getMessage() ); break; case ResultWas::Warning: // Warning will already have been written break; case ResultWas::ExplicitFailure: m_xml.scopedElement( "Failure" ) .writeText( assertionResult.getMessage() ); break; default: break; } if( assertionResult.hasExpression() ) m_xml.endElement(); return true; } virtual void sectionEnded( SectionStats const& sectionStats ) { StreamingReporterBase::sectionEnded( sectionStats ); if( --m_sectionDepth > 0 ) { XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); e.writeAttribute( "successes", sectionStats.assertions.passed ); e.writeAttribute( "failures", sectionStats.assertions.failed ); e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); if ( m_config->showDurations() == ShowDurations::Always ) e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); m_xml.endElement(); } } virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { StreamingReporterBase::testCaseEnded( testCaseStats ); XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); if ( m_config->showDurations() == ShowDurations::Always ) e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); m_xml.endElement(); } virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { StreamingReporterBase::testGroupEnded( testGroupStats ); // TODO: Check testGroupStats.aborting and act accordingly. m_xml.scopedElement( "OverallResults" ) .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); m_xml.endElement(); } virtual void testRunEnded( TestRunStats const& testRunStats ) { StreamingReporterBase::testRunEnded( testRunStats ); m_xml.scopedElement( "OverallResults" ) .writeAttribute( "successes", testRunStats.totals.assertions.passed ) .writeAttribute( "failures", testRunStats.totals.assertions.failed ) .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); m_xml.endElement(); } private: Timer m_testCaseTimer; XmlWriter m_xml; int m_sectionDepth; }; INTERNAL_CATCH_REGISTER_REPORTER( "xml", XmlReporter ) } // end namespace Catch // #included from: ../reporters/catch_reporter_junit.hpp #define TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED #include namespace Catch { class JunitReporter : public CumulativeReporterBase { public: JunitReporter( ReporterConfig const& _config ) : CumulativeReporterBase( _config ), xml( _config.stream() ) {} ~JunitReporter(); static std::string getDescription() { return "Reports test results in an XML format that looks like Ant's junitreport target"; } virtual void noMatchingTestCases( std::string const& /*spec*/ ) {} virtual ReporterPreferences getPreferences() const { ReporterPreferences prefs; prefs.shouldRedirectStdOut = true; return prefs; } virtual void testRunStarting( TestRunInfo const& runInfo ) { CumulativeReporterBase::testRunStarting( runInfo ); xml.startElement( "testsuites" ); } virtual void testGroupStarting( GroupInfo const& groupInfo ) { suiteTimer.start(); stdOutForSuite.str(""); stdErrForSuite.str(""); unexpectedExceptions = 0; CumulativeReporterBase::testGroupStarting( groupInfo ); } virtual bool assertionEnded( AssertionStats const& assertionStats ) { if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException ) unexpectedExceptions++; return CumulativeReporterBase::assertionEnded( assertionStats ); } virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { stdOutForSuite << testCaseStats.stdOut; stdErrForSuite << testCaseStats.stdErr; CumulativeReporterBase::testCaseEnded( testCaseStats ); } virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { double suiteTime = suiteTimer.getElapsedSeconds(); CumulativeReporterBase::testGroupEnded( testGroupStats ); writeGroup( *m_testGroups.back(), suiteTime ); } virtual void testRunEndedCumulative() { xml.endElement(); } void writeGroup( TestGroupNode const& groupNode, double suiteTime ) { XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); TestGroupStats const& stats = groupNode.value; xml.writeAttribute( "name", stats.groupInfo.name ); xml.writeAttribute( "errors", unexpectedExceptions ); xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); xml.writeAttribute( "tests", stats.totals.assertions.total() ); xml.writeAttribute( "hostname", "tbd" ); // !TBD if( m_config->showDurations() == ShowDurations::Never ) xml.writeAttribute( "time", "" ); else xml.writeAttribute( "time", suiteTime ); xml.writeAttribute( "timestamp", "tbd" ); // !TBD // Write test cases for( TestGroupNode::ChildNodes::const_iterator it = groupNode.children.begin(), itEnd = groupNode.children.end(); it != itEnd; ++it ) writeTestCase( **it ); xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite.str() ), false ); xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite.str() ), false ); } void writeTestCase( TestCaseNode const& testCaseNode ) { TestCaseStats const& stats = testCaseNode.value; // All test cases have exactly one section - which represents the // test case itself. That section may have 0-n nested sections assert( testCaseNode.children.size() == 1 ); SectionNode const& rootSection = *testCaseNode.children.front(); std::string className = stats.testInfo.className; if( className.empty() ) { if( rootSection.childSections.empty() ) className = "global"; } writeSection( className, "", rootSection ); } void writeSection( std::string const& className, std::string const& rootName, SectionNode const& sectionNode ) { std::string name = trim( sectionNode.stats.sectionInfo.name ); if( !rootName.empty() ) name = rootName + "/" + name; if( !sectionNode.assertions.empty() || !sectionNode.stdOut.empty() || !sectionNode.stdErr.empty() ) { XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); if( className.empty() ) { xml.writeAttribute( "classname", name ); xml.writeAttribute( "name", "root" ); } else { xml.writeAttribute( "classname", className ); xml.writeAttribute( "name", name ); } xml.writeAttribute( "time", Catch::toString( sectionNode.stats.durationInSeconds ) ); writeAssertions( sectionNode ); if( !sectionNode.stdOut.empty() ) xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); if( !sectionNode.stdErr.empty() ) xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); } for( SectionNode::ChildSections::const_iterator it = sectionNode.childSections.begin(), itEnd = sectionNode.childSections.end(); it != itEnd; ++it ) if( className.empty() ) writeSection( name, "", **it ); else writeSection( className, name, **it ); } void writeAssertions( SectionNode const& sectionNode ) { for( SectionNode::Assertions::const_iterator it = sectionNode.assertions.begin(), itEnd = sectionNode.assertions.end(); it != itEnd; ++it ) writeAssertion( *it ); } void writeAssertion( AssertionStats const& stats ) { AssertionResult const& result = stats.assertionResult; if( !result.isOk() ) { std::string elementName; switch( result.getResultType() ) { case ResultWas::ThrewException: case ResultWas::FatalErrorCondition: elementName = "error"; break; case ResultWas::ExplicitFailure: elementName = "failure"; break; case ResultWas::ExpressionFailed: elementName = "failure"; break; case ResultWas::DidntThrowException: elementName = "failure"; break; // We should never see these here: case ResultWas::Info: case ResultWas::Warning: case ResultWas::Ok: case ResultWas::Unknown: case ResultWas::FailureBit: case ResultWas::Exception: elementName = "internalError"; break; } XmlWriter::ScopedElement e = xml.scopedElement( elementName ); xml.writeAttribute( "message", result.getExpandedExpression() ); xml.writeAttribute( "type", result.getTestMacroName() ); std::ostringstream oss; if( !result.getMessage().empty() ) oss << result.getMessage() << "\n"; for( std::vector::const_iterator it = stats.infoMessages.begin(), itEnd = stats.infoMessages.end(); it != itEnd; ++it ) if( it->type == ResultWas::Info ) oss << it->message << "\n"; oss << "at " << result.getSourceInfo(); xml.writeText( oss.str(), false ); } } XmlWriter xml; Timer suiteTimer; std::ostringstream stdOutForSuite; std::ostringstream stdErrForSuite; unsigned int unexpectedExceptions; }; INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter ) } // end namespace Catch // #included from: ../reporters/catch_reporter_console.hpp #define TWOBLUECUBES_CATCH_REPORTER_CONSOLE_HPP_INCLUDED namespace Catch { struct ConsoleReporter : StreamingReporterBase { ConsoleReporter( ReporterConfig const& _config ) : StreamingReporterBase( _config ), m_headerPrinted( false ) {} virtual ~ConsoleReporter(); static std::string getDescription() { return "Reports test results as plain lines of text"; } virtual ReporterPreferences getPreferences() const { ReporterPreferences prefs; prefs.shouldRedirectStdOut = false; return prefs; } virtual void noMatchingTestCases( std::string const& spec ) { stream << "No test cases matched '" << spec << "'" << std::endl; } virtual void assertionStarting( AssertionInfo const& ) { } virtual bool assertionEnded( AssertionStats const& _assertionStats ) { AssertionResult const& result = _assertionStats.assertionResult; bool printInfoMessages = true; // Drop out if result was successful and we're not printing those if( !m_config->includeSuccessfulResults() && result.isOk() ) { if( result.getResultType() != ResultWas::Warning ) return false; printInfoMessages = false; } lazyPrint(); AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); printer.print(); stream << std::endl; return true; } virtual void sectionStarting( SectionInfo const& _sectionInfo ) { m_headerPrinted = false; StreamingReporterBase::sectionStarting( _sectionInfo ); } virtual void sectionEnded( SectionStats const& _sectionStats ) { if( _sectionStats.missingAssertions ) { lazyPrint(); Colour colour( Colour::ResultError ); if( m_sectionStack.size() > 1 ) stream << "\nNo assertions in section"; else stream << "\nNo assertions in test case"; stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; } if( m_headerPrinted ) { if( m_config->showDurations() == ShowDurations::Always ) stream << "Completed in " << _sectionStats.durationInSeconds << "s" << std::endl; m_headerPrinted = false; } else { if( m_config->showDurations() == ShowDurations::Always ) stream << _sectionStats.sectionInfo.name << " completed in " << _sectionStats.durationInSeconds << "s" << std::endl; } StreamingReporterBase::sectionEnded( _sectionStats ); } virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) { StreamingReporterBase::testCaseEnded( _testCaseStats ); m_headerPrinted = false; } virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) { if( currentGroupInfo.used ) { printSummaryDivider(); stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; printTotals( _testGroupStats.totals ); stream << "\n" << std::endl; } StreamingReporterBase::testGroupEnded( _testGroupStats ); } virtual void testRunEnded( TestRunStats const& _testRunStats ) { printTotalsDivider( _testRunStats.totals ); printTotals( _testRunStats.totals ); stream << std::endl; StreamingReporterBase::testRunEnded( _testRunStats ); } private: class AssertionPrinter { void operator= ( AssertionPrinter const& ); public: AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) : stream( _stream ), stats( _stats ), result( _stats.assertionResult ), colour( Colour::None ), message( result.getMessage() ), messages( _stats.infoMessages ), printInfoMessages( _printInfoMessages ) { switch( result.getResultType() ) { case ResultWas::Ok: colour = Colour::Success; passOrFail = "PASSED"; //if( result.hasMessage() ) if( _stats.infoMessages.size() == 1 ) messageLabel = "with message"; if( _stats.infoMessages.size() > 1 ) messageLabel = "with messages"; break; case ResultWas::ExpressionFailed: if( result.isOk() ) { colour = Colour::Success; passOrFail = "FAILED - but was ok"; } else { colour = Colour::Error; passOrFail = "FAILED"; } if( _stats.infoMessages.size() == 1 ) messageLabel = "with message"; if( _stats.infoMessages.size() > 1 ) messageLabel = "with messages"; break; case ResultWas::ThrewException: colour = Colour::Error; passOrFail = "FAILED"; messageLabel = "due to unexpected exception with message"; break; case ResultWas::FatalErrorCondition: colour = Colour::Error; passOrFail = "FAILED"; messageLabel = "due to a fatal error condition"; break; case ResultWas::DidntThrowException: colour = Colour::Error; passOrFail = "FAILED"; messageLabel = "because no exception was thrown where one was expected"; break; case ResultWas::Info: messageLabel = "info"; break; case ResultWas::Warning: messageLabel = "warning"; break; case ResultWas::ExplicitFailure: passOrFail = "FAILED"; colour = Colour::Error; if( _stats.infoMessages.size() == 1 ) messageLabel = "explicitly with message"; if( _stats.infoMessages.size() > 1 ) messageLabel = "explicitly with messages"; break; // These cases are here to prevent compiler warnings case ResultWas::Unknown: case ResultWas::FailureBit: case ResultWas::Exception: passOrFail = "** internal error **"; colour = Colour::Error; break; } } void print() const { printSourceInfo(); if( stats.totals.assertions.total() > 0 ) { if( result.isOk() ) stream << "\n"; printResultType(); printOriginalExpression(); printReconstructedExpression(); } else { stream << "\n"; } printMessage(); } private: void printResultType() const { if( !passOrFail.empty() ) { Colour colourGuard( colour ); stream << passOrFail << ":\n"; } } void printOriginalExpression() const { if( result.hasExpression() ) { Colour colourGuard( Colour::OriginalExpression ); stream << " "; stream << result.getExpressionInMacro(); stream << "\n"; } } void printReconstructedExpression() const { if( result.hasExpandedExpression() ) { stream << "with expansion:\n"; Colour colourGuard( Colour::ReconstructedExpression ); stream << Text( result.getExpandedExpression(), TextAttributes().setIndent(2) ) << "\n"; } } void printMessage() const { if( !messageLabel.empty() ) stream << messageLabel << ":" << "\n"; for( std::vector::const_iterator it = messages.begin(), itEnd = messages.end(); it != itEnd; ++it ) { // If this assertion is a warning ignore any INFO messages if( printInfoMessages || it->type != ResultWas::Info ) stream << Text( it->message, TextAttributes().setIndent(2) ) << "\n"; } } void printSourceInfo() const { Colour colourGuard( Colour::FileName ); stream << result.getSourceInfo() << ": "; } std::ostream& stream; AssertionStats const& stats; AssertionResult const& result; Colour::Code colour; std::string passOrFail; std::string messageLabel; std::string message; std::vector messages; bool printInfoMessages; }; void lazyPrint() { if( !currentTestRunInfo.used ) lazyPrintRunInfo(); if( !currentGroupInfo.used ) lazyPrintGroupInfo(); if( !m_headerPrinted ) { printTestCaseAndSectionHeader(); m_headerPrinted = true; } } void lazyPrintRunInfo() { stream << "\n" << getLineOfChars<'~'>() << "\n"; Colour colour( Colour::SecondaryText ); stream << currentTestRunInfo->name << " is a Catch v" << libraryVersion.majorVersion << "." << libraryVersion.minorVersion << " b" << libraryVersion.buildNumber; if( libraryVersion.branchName != std::string( "master" ) ) stream << " (" << libraryVersion.branchName << ")"; stream << " host application.\n" << "Run with -? for options\n\n"; if( m_config->rngSeed() != 0 ) stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; currentTestRunInfo.used = true; } void lazyPrintGroupInfo() { if( !currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1 ) { printClosedHeader( "Group: " + currentGroupInfo->name ); currentGroupInfo.used = true; } } void printTestCaseAndSectionHeader() { assert( !m_sectionStack.empty() ); printOpenHeader( currentTestCaseInfo->name ); if( m_sectionStack.size() > 1 ) { Colour colourGuard( Colour::Headers ); std::vector::const_iterator it = m_sectionStack.begin()+1, // Skip first section (test case) itEnd = m_sectionStack.end(); for( ; it != itEnd; ++it ) printHeaderString( it->name, 2 ); } SourceLineInfo lineInfo = m_sectionStack.front().lineInfo; if( !lineInfo.empty() ){ stream << getLineOfChars<'-'>() << "\n"; Colour colourGuard( Colour::FileName ); stream << lineInfo << "\n"; } stream << getLineOfChars<'.'>() << "\n" << std::endl; } void printClosedHeader( std::string const& _name ) { printOpenHeader( _name ); stream << getLineOfChars<'.'>() << "\n"; } void printOpenHeader( std::string const& _name ) { stream << getLineOfChars<'-'>() << "\n"; { Colour colourGuard( Colour::Headers ); printHeaderString( _name ); } } // if string has a : in first line will set indent to follow it on // subsequent lines void printHeaderString( std::string const& _string, std::size_t indent = 0 ) { std::size_t i = _string.find( ": " ); if( i != std::string::npos ) i+=2; else i = 0; stream << Text( _string, TextAttributes() .setIndent( indent+i) .setInitialIndent( indent ) ) << "\n"; } struct SummaryColumn { SummaryColumn( std::string const& _label, Colour::Code _colour ) : label( _label ), colour( _colour ) {} SummaryColumn addRow( std::size_t count ) { std::ostringstream oss; oss << count; std::string row = oss.str(); for( std::vector::iterator it = rows.begin(); it != rows.end(); ++it ) { while( it->size() < row.size() ) *it = " " + *it; while( it->size() > row.size() ) row = " " + row; } rows.push_back( row ); return *this; } std::string label; Colour::Code colour; std::vector rows; }; void printTotals( Totals const& totals ) { if( totals.testCases.total() == 0 ) { stream << Colour( Colour::Warning ) << "No tests ran\n"; } else if( totals.assertions.total() > 0 && totals.assertions.allPassed() ) { stream << Colour( Colour::ResultSuccess ) << "All tests passed"; stream << " (" << pluralise( totals.assertions.passed, "assertion" ) << " in " << pluralise( totals.testCases.passed, "test case" ) << ")" << "\n"; } else { std::vector columns; columns.push_back( SummaryColumn( "", Colour::None ) .addRow( totals.testCases.total() ) .addRow( totals.assertions.total() ) ); columns.push_back( SummaryColumn( "passed", Colour::Success ) .addRow( totals.testCases.passed ) .addRow( totals.assertions.passed ) ); columns.push_back( SummaryColumn( "failed", Colour::ResultError ) .addRow( totals.testCases.failed ) .addRow( totals.assertions.failed ) ); columns.push_back( SummaryColumn( "failed as expected", Colour::ResultExpectedFailure ) .addRow( totals.testCases.failedButOk ) .addRow( totals.assertions.failedButOk ) ); printSummaryRow( "test cases", columns, 0 ); printSummaryRow( "assertions", columns, 1 ); } } void printSummaryRow( std::string const& label, std::vector const& cols, std::size_t row ) { for( std::vector::const_iterator it = cols.begin(); it != cols.end(); ++it ) { std::string value = it->rows[row]; if( it->label.empty() ) { stream << label << ": "; if( value != "0" ) stream << value; else stream << Colour( Colour::Warning ) << "- none -"; } else if( value != "0" ) { stream << Colour( Colour::LightGrey ) << " | "; stream << Colour( it->colour ) << value << " " << it->label; } } stream << "\n"; } static std::size_t makeRatio( std::size_t number, std::size_t total ) { std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number/ total : 0; return ( ratio == 0 && number > 0 ) ? 1 : ratio; } static std::size_t& findMax( std::size_t& i, std::size_t& j, std::size_t& k ) { if( i > j && i > k ) return i; else if( j > k ) return j; else return k; } void printTotalsDivider( Totals const& totals ) { if( totals.testCases.total() > 0 ) { std::size_t failedRatio = makeRatio( totals.testCases.failed, totals.testCases.total() ); std::size_t failedButOkRatio = makeRatio( totals.testCases.failedButOk, totals.testCases.total() ); std::size_t passedRatio = makeRatio( totals.testCases.passed, totals.testCases.total() ); while( failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH-1 ) findMax( failedRatio, failedButOkRatio, passedRatio )++; while( failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH-1 ) findMax( failedRatio, failedButOkRatio, passedRatio )--; stream << Colour( Colour::Error ) << std::string( failedRatio, '=' ); stream << Colour( Colour::ResultExpectedFailure ) << std::string( failedButOkRatio, '=' ); if( totals.testCases.allPassed() ) stream << Colour( Colour::ResultSuccess ) << std::string( passedRatio, '=' ); else stream << Colour( Colour::Success ) << std::string( passedRatio, '=' ); } else { stream << Colour( Colour::Warning ) << std::string( CATCH_CONFIG_CONSOLE_WIDTH-1, '=' ); } stream << "\n"; } void printSummaryDivider() { stream << getLineOfChars<'-'>() << "\n"; } private: bool m_headerPrinted; }; INTERNAL_CATCH_REGISTER_REPORTER( "console", ConsoleReporter ) } // end namespace Catch // #included from: ../reporters/catch_reporter_compact.hpp #define TWOBLUECUBES_CATCH_REPORTER_COMPACT_HPP_INCLUDED namespace Catch { struct CompactReporter : StreamingReporterBase { CompactReporter( ReporterConfig const& _config ) : StreamingReporterBase( _config ) {} virtual ~CompactReporter(); static std::string getDescription() { return "Reports test results on a single line, suitable for IDEs"; } virtual ReporterPreferences getPreferences() const { ReporterPreferences prefs; prefs.shouldRedirectStdOut = false; return prefs; } virtual void noMatchingTestCases( std::string const& spec ) { stream << "No test cases matched '" << spec << "'" << std::endl; } virtual void assertionStarting( AssertionInfo const& ) { } virtual bool assertionEnded( AssertionStats const& _assertionStats ) { AssertionResult const& result = _assertionStats.assertionResult; bool printInfoMessages = true; // Drop out if result was successful and we're not printing those if( !m_config->includeSuccessfulResults() && result.isOk() ) { if( result.getResultType() != ResultWas::Warning ) return false; printInfoMessages = false; } AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); printer.print(); stream << std::endl; return true; } virtual void testRunEnded( TestRunStats const& _testRunStats ) { printTotals( _testRunStats.totals ); stream << "\n" << std::endl; StreamingReporterBase::testRunEnded( _testRunStats ); } private: class AssertionPrinter { void operator= ( AssertionPrinter const& ); public: AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) : stream( _stream ) , stats( _stats ) , result( _stats.assertionResult ) , messages( _stats.infoMessages ) , itMessage( _stats.infoMessages.begin() ) , printInfoMessages( _printInfoMessages ) {} void print() { printSourceInfo(); itMessage = messages.begin(); switch( result.getResultType() ) { case ResultWas::Ok: printResultType( Colour::ResultSuccess, passedString() ); printOriginalExpression(); printReconstructedExpression(); if ( ! result.hasExpression() ) printRemainingMessages( Colour::None ); else printRemainingMessages(); break; case ResultWas::ExpressionFailed: if( result.isOk() ) printResultType( Colour::ResultSuccess, failedString() + std::string( " - but was ok" ) ); else printResultType( Colour::Error, failedString() ); printOriginalExpression(); printReconstructedExpression(); printRemainingMessages(); break; case ResultWas::ThrewException: printResultType( Colour::Error, failedString() ); printIssue( "unexpected exception with message:" ); printMessage(); printExpressionWas(); printRemainingMessages(); break; case ResultWas::FatalErrorCondition: printResultType( Colour::Error, failedString() ); printIssue( "fatal error condition with message:" ); printMessage(); printExpressionWas(); printRemainingMessages(); break; case ResultWas::DidntThrowException: printResultType( Colour::Error, failedString() ); printIssue( "expected exception, got none" ); printExpressionWas(); printRemainingMessages(); break; case ResultWas::Info: printResultType( Colour::None, "info" ); printMessage(); printRemainingMessages(); break; case ResultWas::Warning: printResultType( Colour::None, "warning" ); printMessage(); printRemainingMessages(); break; case ResultWas::ExplicitFailure: printResultType( Colour::Error, failedString() ); printIssue( "explicitly" ); printRemainingMessages( Colour::None ); break; // These cases are here to prevent compiler warnings case ResultWas::Unknown: case ResultWas::FailureBit: case ResultWas::Exception: printResultType( Colour::Error, "** internal error **" ); break; } } private: // Colour::LightGrey static Colour::Code dimColour() { return Colour::FileName; } #ifdef CATCH_PLATFORM_MAC static const char* failedString() { return "FAILED"; } static const char* passedString() { return "PASSED"; } #else static const char* failedString() { return "failed"; } static const char* passedString() { return "passed"; } #endif void printSourceInfo() const { Colour colourGuard( Colour::FileName ); stream << result.getSourceInfo() << ":"; } void printResultType( Colour::Code colour, std::string passOrFail ) const { if( !passOrFail.empty() ) { { Colour colourGuard( colour ); stream << " " << passOrFail; } stream << ":"; } } void printIssue( std::string issue ) const { stream << " " << issue; } void printExpressionWas() { if( result.hasExpression() ) { stream << ";"; { Colour colour( dimColour() ); stream << " expression was:"; } printOriginalExpression(); } } void printOriginalExpression() const { if( result.hasExpression() ) { stream << " " << result.getExpression(); } } void printReconstructedExpression() const { if( result.hasExpandedExpression() ) { { Colour colour( dimColour() ); stream << " for: "; } stream << result.getExpandedExpression(); } } void printMessage() { if ( itMessage != messages.end() ) { stream << " '" << itMessage->message << "'"; ++itMessage; } } void printRemainingMessages( Colour::Code colour = dimColour() ) { if ( itMessage == messages.end() ) return; // using messages.end() directly yields compilation error: std::vector::const_iterator itEnd = messages.end(); const std::size_t N = static_cast( std::distance( itMessage, itEnd ) ); { Colour colourGuard( colour ); stream << " with " << pluralise( N, "message" ) << ":"; } for(; itMessage != itEnd; ) { // If this assertion is a warning ignore any INFO messages if( printInfoMessages || itMessage->type != ResultWas::Info ) { stream << " '" << itMessage->message << "'"; if ( ++itMessage != itEnd ) { Colour colourGuard( dimColour() ); stream << " and"; } } } } private: std::ostream& stream; AssertionStats const& stats; AssertionResult const& result; std::vector messages; std::vector::const_iterator itMessage; bool printInfoMessages; }; // Colour, message variants: // - white: No tests ran. // - red: Failed [both/all] N test cases, failed [both/all] M assertions. // - white: Passed [both/all] N test cases (no assertions). // - red: Failed N tests cases, failed M assertions. // - green: Passed [both/all] N tests cases with M assertions. std::string bothOrAll( std::size_t count ) const { return count == 1 ? "" : count == 2 ? "both " : "all " ; } void printTotals( const Totals& totals ) const { if( totals.testCases.total() == 0 ) { stream << "No tests ran."; } else if( totals.testCases.failed == totals.testCases.total() ) { Colour colour( Colour::ResultError ); const std::string qualify_assertions_failed = totals.assertions.failed == totals.assertions.total() ? bothOrAll( totals.assertions.failed ) : ""; stream << "Failed " << bothOrAll( totals.testCases.failed ) << pluralise( totals.testCases.failed, "test case" ) << ", " "failed " << qualify_assertions_failed << pluralise( totals.assertions.failed, "assertion" ) << "."; } else if( totals.assertions.total() == 0 ) { stream << "Passed " << bothOrAll( totals.testCases.total() ) << pluralise( totals.testCases.total(), "test case" ) << " (no assertions)."; } else if( totals.assertions.failed ) { Colour colour( Colour::ResultError ); stream << "Failed " << pluralise( totals.testCases.failed, "test case" ) << ", " "failed " << pluralise( totals.assertions.failed, "assertion" ) << "."; } else { Colour colour( Colour::ResultSuccess ); stream << "Passed " << bothOrAll( totals.testCases.passed ) << pluralise( totals.testCases.passed, "test case" ) << " with " << pluralise( totals.assertions.passed, "assertion" ) << "."; } } }; INTERNAL_CATCH_REGISTER_REPORTER( "compact", CompactReporter ) } // end namespace Catch namespace Catch { NonCopyable::~NonCopyable() {} IShared::~IShared() {} StreamBufBase::~StreamBufBase() CATCH_NOEXCEPT {} IContext::~IContext() {} IResultCapture::~IResultCapture() {} ITestCase::~ITestCase() {} ITestCaseRegistry::~ITestCaseRegistry() {} IRegistryHub::~IRegistryHub() {} IMutableRegistryHub::~IMutableRegistryHub() {} IExceptionTranslator::~IExceptionTranslator() {} IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() {} IReporter::~IReporter() {} IReporterFactory::~IReporterFactory() {} IReporterRegistry::~IReporterRegistry() {} IStreamingReporter::~IStreamingReporter() {} AssertionStats::~AssertionStats() {} SectionStats::~SectionStats() {} TestCaseStats::~TestCaseStats() {} TestGroupStats::~TestGroupStats() {} TestRunStats::~TestRunStats() {} CumulativeReporterBase::SectionNode::~SectionNode() {} CumulativeReporterBase::~CumulativeReporterBase() {} StreamingReporterBase::~StreamingReporterBase() {} ConsoleReporter::~ConsoleReporter() {} CompactReporter::~CompactReporter() {} IRunner::~IRunner() {} IMutableContext::~IMutableContext() {} IConfig::~IConfig() {} XmlReporter::~XmlReporter() {} JunitReporter::~JunitReporter() {} TestRegistry::~TestRegistry() {} FreeFunctionTestCase::~FreeFunctionTestCase() {} IGeneratorInfo::~IGeneratorInfo() {} IGeneratorsForTest::~IGeneratorsForTest() {} TestSpec::Pattern::~Pattern() {} TestSpec::NamePattern::~NamePattern() {} TestSpec::TagPattern::~TagPattern() {} TestSpec::ExcludedPattern::~ExcludedPattern() {} Matchers::Impl::StdString::Equals::~Equals() {} Matchers::Impl::StdString::Contains::~Contains() {} Matchers::Impl::StdString::StartsWith::~StartsWith() {} Matchers::Impl::StdString::EndsWith::~EndsWith() {} void Config::dummy() {} } #ifdef __clang__ #pragma clang diagnostic pop #endif #endif #ifdef CATCH_CONFIG_MAIN // #included from: internal/catch_default_main.hpp #define TWOBLUECUBES_CATCH_DEFAULT_MAIN_HPP_INCLUDED #ifndef __OBJC__ // Standard C/C++ main entry point int main (int argc, char * const argv[]) { return Catch::Session().run( argc, argv ); } #else // __OBJC__ // Objective-C entry point int main (int argc, char * const argv[]) { #if !CATCH_ARC_ENABLED NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; #endif Catch::registerTestMethods(); int result = Catch::Session().run( argc, (char* const*)argv ); #if !CATCH_ARC_ENABLED [pool drain]; #endif return result; } #endif // __OBJC__ #endif #ifdef CLARA_CONFIG_MAIN_NOT_DEFINED # undef CLARA_CONFIG_MAIN #endif ////// // If this config identifier is defined then all CATCH macros are prefixed with CATCH_ #ifdef CATCH_CONFIG_PREFIX_ALL #define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE" ) #define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "CATCH_REQUIRE_FALSE" ) #define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS" ) #define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS_AS" ) #define CATCH_REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_NOTHROW" ) #define CATCH_CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK" ) #define CATCH_CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CATCH_CHECK_FALSE" ) #define CATCH_CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_IF" ) #define CATCH_CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_ELSE" ) #define CATCH_CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CATCH_CHECK_NOFAIL" ) #define CATCH_CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS" ) #define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS_AS" ) #define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_NOTHROW" ) #define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THAT" ) #define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THAT" ) #define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" ) #define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "CATCH_WARN", msg ) #define CATCH_SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" ) #define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" ) #define CATCH_SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" ) #ifdef CATCH_CONFIG_VARIADIC_MACROS #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", __VA_ARGS__ ) #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", __VA_ARGS__ ) #else #define CATCH_TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) #define CATCH_TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) #define CATCH_METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) #define CATCH_SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) #define CATCH_FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", msg ) #define CATCH_SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", msg ) #endif #define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) #define CATCH_REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) #define CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) #define CATCH_GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) // "BDD-style" convenience wrappers #ifdef CATCH_CONFIG_VARIADIC_MACROS #define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) #define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) #else #define CATCH_SCENARIO( name, tags ) CATCH_TEST_CASE( "Scenario: " name, tags ) #define CATCH_SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) #endif #define CATCH_GIVEN( desc ) CATCH_SECTION( "Given: " desc, "" ) #define CATCH_WHEN( desc ) CATCH_SECTION( " When: " desc, "" ) #define CATCH_AND_WHEN( desc ) CATCH_SECTION( " And: " desc, "" ) #define CATCH_THEN( desc ) CATCH_SECTION( " Then: " desc, "" ) #define CATCH_AND_THEN( desc ) CATCH_SECTION( " And: " desc, "" ) // If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required #else #define REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "REQUIRE" ) #define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "REQUIRE_FALSE" ) #define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "REQUIRE_THROWS" ) #define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "REQUIRE_THROWS_AS" ) #define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "REQUIRE_NOTHROW" ) #define CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK" ) #define CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CHECK_FALSE" ) #define CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_IF" ) #define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_ELSE" ) #define CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CHECK_NOFAIL" ) #define CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS" ) #define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS_AS" ) #define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_NOTHROW" ) #define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THAT" ) #define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "REQUIRE_THAT" ) #define INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" ) #define WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "WARN", msg ) #define SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" ) #define CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" ) #define SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" ) #ifdef CATCH_CONFIG_VARIADIC_MACROS #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) #define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) #define FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", __VA_ARGS__ ) #define SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", __VA_ARGS__ ) #else #define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) #define TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) #define METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) #define SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) #define FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", msg ) #define SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", msg ) #endif #define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) #define REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) #define REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) #define GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) #endif #define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) // "BDD-style" convenience wrappers #ifdef CATCH_CONFIG_VARIADIC_MACROS #define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) #define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) #else #define SCENARIO( name, tags ) TEST_CASE( "Scenario: " name, tags ) #define SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) #endif #define GIVEN( desc ) SECTION( " Given: " desc, "" ) #define WHEN( desc ) SECTION( " When: " desc, "" ) #define AND_WHEN( desc ) SECTION( "And when: " desc, "" ) #define THEN( desc ) SECTION( " Then: " desc, "" ) #define AND_THEN( desc ) SECTION( " And: " desc, "" ) using Catch::Detail::Approx; // #included from: internal/catch_reenable_warnings.h #define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED #ifdef __clang__ # ifdef __ICC // icpc defines the __clang__ macro # pragma warning(pop) # else # pragma clang diagnostic pop # endif #elif defined __GNUC__ # pragma GCC diagnostic pop #endif #endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED nzbget-16.4/COPYING0000644000175000017500000004313112630544544013615 0ustar andreasandreas GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. nzbget-16.4/daemon/0000755000175000017500000000000012630544544014023 5ustar andreasandreasnzbget-16.4/daemon/windows/0000755000175000017500000000000012630544544015515 5ustar andreasandreasnzbget-16.4/daemon/windows/WinConsole.h0000644000175000017500000000661712630544544017760 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2014-2015 Andrey Prygunkov * * 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 WINCONSOLE_H #define WINCONSOLE_H #include "Thread.h" class WinConsole : public Thread { private: bool m_bAppMode; char** m_pDefaultArguments; char** m_pInitialArguments; int m_iInitialArgumentCount; HWND m_hTrayWindow; NOTIFYICONDATA* m_pNidIcon; UINT UM_TASKBARCREATED; HMENU m_hMenu; HINSTANCE m_hInstance; bool m_bModal; HFONT m_hLinkFont; HFONT m_hNameFont; HFONT m_hTitleFont; HCURSOR m_hHandCursor; HICON m_hAboutIcon; HICON m_hRunningIcon; HICON m_hIdleIcon; HICON m_hWorkingIcon; HICON m_hPausedIcon; bool m_bAutostart; bool m_bTray; bool m_bConsole; bool m_bWebUI; bool m_bAutoParam; bool m_bRunning; bool m_bRunningService; bool m_bDoubleClick; void CreateResources(); void CreateTrayIcon(); void ShowWebUI(); void ShowMenu(); void ShowInExplorer(const char* szFileName); void ShowAboutBox(); void OpenConfigFileInTextEdit(); void ShowPrefsDialog(); void SavePrefs(); void LoadPrefs(); void ApplyPrefs(); void ShowRunningDialog(); void CheckRunning(); void UpdateTrayIcon(); void BuildMenu(); void ShowCategoryDir(int iCatIndex); void SetupConfigFile(); void SetupScripts(); void ShowFactoryResetDialog(); void ResetFactoryDefaults(); static BOOL WINAPI ConsoleCtrlHandler(DWORD dwCtrlType); static LRESULT CALLBACK TrayWndProcStat(HWND hwndWin, UINT uMsg, WPARAM wParam, LPARAM lParam); LRESULT TrayWndProc(HWND hwndWin, UINT uMsg, WPARAM wParam, LPARAM lParam); static BOOL CALLBACK AboutDialogProcStat(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); BOOL AboutDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); static BOOL CALLBACK PrefsDialogProcStat(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); BOOL PrefsDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); static BOOL CALLBACK RunningDialogProcStat(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); BOOL RunningDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); static BOOL CALLBACK FactoryResetDialogProcStat(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); BOOL FactoryResetDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); protected: virtual void Run(); public: WinConsole(); ~WinConsole(); virtual void Stop(); void InitAppMode(); bool GetAppMode() { return m_bAppMode; } void SetupFirstStart(); }; #endif nzbget-16.4/daemon/windows/NTService.cpp0000644000175000017500000001205012630544544020061 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2014 Andrey Prygunkov * * 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 #endif #include "win32.h" #include #include #include "nzbget.h" #include "Log.h" #include "NTService.h" extern void ExitProc(); RunProc Run = NULL; char* strServiceName = "NZBGet"; SERVICE_STATUS_HANDLE nServiceStatusHandle; DWORD nServiceCurrentStatus; BOOL UpdateServiceStatus(DWORD dwCurrentState, DWORD dwWaitHint) { BOOL success; SERVICE_STATUS nServiceStatus; nServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; nServiceStatus.dwCurrentState = dwCurrentState; if (dwCurrentState == SERVICE_START_PENDING) { nServiceStatus.dwControlsAccepted = 0; } else { nServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; } nServiceStatus.dwWin32ExitCode = NO_ERROR; nServiceStatus.dwServiceSpecificExitCode = 0; nServiceStatus.dwCheckPoint = 0; nServiceStatus.dwWaitHint = dwWaitHint; success = SetServiceStatus(nServiceStatusHandle, &nServiceStatus); return success; } void ServiceCtrlHandler(DWORD nControlCode) { switch(nControlCode) { case SERVICE_CONTROL_SHUTDOWN: case SERVICE_CONTROL_STOP: nServiceCurrentStatus = SERVICE_STOP_PENDING; UpdateServiceStatus(SERVICE_STOP_PENDING, 10000); ExitProc(); return; default: break; } UpdateServiceStatus(nServiceCurrentStatus, 0); } void ServiceMain(DWORD argc, LPTSTR *argv) { BOOL success; nServiceStatusHandle = RegisterServiceCtrlHandler(strServiceName, (LPHANDLER_FUNCTION)ServiceCtrlHandler); if(!nServiceStatusHandle) { return; } success = UpdateServiceStatus(SERVICE_START_PENDING, 10000); if(!success) { return; } nServiceCurrentStatus=SERVICE_RUNNING; success=UpdateServiceStatus(SERVICE_RUNNING, 0); if(!success) { return; } Run(); UpdateServiceStatus(SERVICE_STOPPED, 0); } void StartService(RunProc RunProcPtr) { Run = RunProcPtr; SERVICE_TABLE_ENTRY servicetable[]= { {strServiceName,(LPSERVICE_MAIN_FUNCTION)ServiceMain}, {NULL,NULL} }; BOOL success = StartServiceCtrlDispatcher(servicetable); if(!success) { error("Could not start service"); } } void InstallService(int argc, char *argv[]) { SC_HANDLE scm = OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE); if(!scm) { printf("Could not install service\n"); return; } char szExeName[1024]; GetModuleFileName(NULL, szExeName, 1024); szExeName[1024-1] = '\0'; char szCmdLine[1024]; snprintf(szCmdLine, 1024, "%s -D", szExeName); szCmdLine[1024-1] = '\0'; SC_HANDLE hService = CreateService(scm, strServiceName, strServiceName, SERVICE_ALL_ACCESS,SERVICE_WIN32_OWN_PROCESS,SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, szCmdLine, 0,0,0,0,0); if(!hService) { CloseServiceHandle(scm); printf("Could not install service\n"); return; } CloseServiceHandle(hService); CloseServiceHandle(scm); printf("Service \"%s\" sucessfully installed\n", strServiceName); } void UnInstallService() { BOOL success; SC_HANDLE scm = OpenSCManager(0,0,SC_MANAGER_CONNECT); if(!scm) { printf("Could not uninstall service\n"); return; } SC_HANDLE hService = OpenService(scm, strServiceName, STANDARD_RIGHTS_REQUIRED); if(!hService) { CloseServiceHandle(scm); printf("Could not uninstall service\n"); return; } success = DeleteService(hService); if(!success) { error("Could not uninstall service"); } CloseServiceHandle(hService); CloseServiceHandle(scm); printf("Service \"%s\" sucessfully uninstalled\n", strServiceName); } void InstallUninstallServiceCheck(int argc, char *argv[]) { if (argc > 1 && (!strcasecmp(argv[1], "/install") || !strcasecmp(argv[1], "-install"))) { InstallService(argc, argv); exit(0); } else if (argc > 1 && (!strcasecmp(argv[1], "/uninstall") || !strcasecmp(argv[1], "/remove") || !strcasecmp(argv[1], "-uninstall") || !strcasecmp(argv[1], "-remove"))) { UnInstallService(); exit(0); } } bool IsServiceRunning() { SC_HANDLE scm = OpenSCManager(0, 0, 0); if (!scm) { return false; } SC_HANDLE hService = OpenService(scm, "NZBGet", SERVICE_QUERY_STATUS); SERVICE_STATUS ServiceStatus; bool bRunning = false; if (hService && QueryServiceStatus(hService, &ServiceStatus)) { bRunning = ServiceStatus.dwCurrentState != SERVICE_STOPPED; } CloseServiceHandle(scm); return bRunning; } nzbget-16.4/daemon/windows/WinConsole.cpp0000644000175000017500000007263412630544544020315 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2014-2015 Andrey Prygunkov * * 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 #endif #define SKIP_DEFAULT_WINDOWS_HEADERS #include "win32.h" #include #include #include #include #include #include #include #include "nzbget.h" #include "Log.h" #include "Options.h" #include "Util.h" #include "FeedCoordinator.h" #include "StatMeter.h" #include "WinConsole.h" #include "NTService.h" #include "resource.h" extern Options* g_pOptions; extern char* (*g_szArguments)[]; extern int g_iArgumentCount; extern void ExitProc(); extern void Reload(); extern WinConsole* g_pWinConsole; extern FeedCoordinator* g_pFeedCoordinator; extern StatMeter* g_pStatMeter; #define UM_TRAYICON (WM_USER + 1) #define UM_QUIT (WM_USER + 2) #define UM_SHOWWEBUI (WM_USER + 3) #define UM_PREFSCHANGED (WM_USER + 4) #pragma comment(linker, \ "\"/manifestdependency:type='Win32' "\ "name='Microsoft.Windows.Common-Controls' version='6.0.0.0' "\ "processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") bool bMayStartBrowser = true; BOOL WINAPI WinConsole::ConsoleCtrlHandler(DWORD dwCtrlType) { switch (dwCtrlType) { case CTRL_C_EVENT: case CTRL_BREAK_EVENT: case CTRL_CLOSE_EVENT: case CTRL_LOGOFF_EVENT: case CTRL_SHUTDOWN_EVENT: ExitProc(); while (g_pWinConsole) { usleep(20 * 1000); } return TRUE; default: return FALSE; } } WinConsole::WinConsole() { m_pInitialArguments = NULL; m_iInitialArgumentCount = 0; m_pDefaultArguments = NULL; m_pNidIcon = NULL; m_bModal = false; m_bAutostart = false; m_bTray = true; m_bConsole = false; m_bWebUI = true; m_bAutoParam = false; m_bDoubleClick = false; m_hTrayWindow = 0; m_bRunning = false; m_bRunningService = false; } WinConsole::~WinConsole() { if (m_pInitialArguments) { g_szArguments = (char*(*)[])m_pInitialArguments; g_iArgumentCount = m_iInitialArgumentCount; } free(m_pDefaultArguments); delete m_pNidIcon; } void WinConsole::InitAppMode() { SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE); m_hInstance = (HINSTANCE)GetModuleHandle(0); DWORD dwProcessId; GetWindowThreadProcessId(GetConsoleWindow(), &dwProcessId); m_bAppMode = false; if (GetCurrentProcessId() == dwProcessId && g_iArgumentCount == 1) { m_pInitialArguments = (char**)g_szArguments; m_iInitialArgumentCount = g_iArgumentCount; // make command line to start in server mode m_pDefaultArguments = (char**)malloc(sizeof(char*) * 3); m_pDefaultArguments[0] = (*g_szArguments)[0]; m_pDefaultArguments[1] = "-s"; m_pDefaultArguments[2] = NULL; g_szArguments = (char*(*)[])m_pDefaultArguments; g_iArgumentCount = 2; m_bAppMode = true; } else if (GetCurrentProcessId() == dwProcessId && g_iArgumentCount > 1) { for (int i = 1; i < g_iArgumentCount; i++) { if (!strcmp((*g_szArguments)[i], "-D")) { break; } if (!strcmp((*g_szArguments)[i], "-app")) { m_bAppMode = true; } if (!strcmp((*g_szArguments)[i], "-auto")) { m_bAutoParam = true; } } if (m_bAppMode) { m_pInitialArguments = (char**)g_szArguments; m_iInitialArgumentCount = g_iArgumentCount; // remove "-app" from command line int argc = g_iArgumentCount - 1 - (m_bAutoParam ? 1 : 0); m_pDefaultArguments = (char**)malloc(sizeof(char*) * (argc + 2)); int p = 0; for (int i = 0; i < g_iArgumentCount; i++) { if (strcmp((*g_szArguments)[i], "-app") && strcmp((*g_szArguments)[i], "-auto")) { m_pDefaultArguments[p++] = (*g_szArguments)[i]; } } m_pDefaultArguments[p] = NULL; g_szArguments = (char*(*)[])m_pDefaultArguments; g_iArgumentCount = p; } } // m_bAppMode indicates whether the program was started as a standalone app // (not from a dos box window). In that case we hide the console window, // show the tray icon and start in server mode if (m_bAppMode) { CreateResources(); CheckRunning(); } } void WinConsole::Run() { if (!m_bAppMode) { return; } CreateTrayIcon(); LoadPrefs(); ApplyPrefs(); BuildMenu(); if (m_bWebUI && !m_bAutoParam && bMayStartBrowser) { ShowWebUI(); } bMayStartBrowser = false; int iCounter = 0; while (!IsStopped()) { MSG msg; if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } else { usleep(20 * 1000); iCounter += 20; if (iCounter >= 200) { UpdateTrayIcon(); iCounter = 0; } } } Shell_NotifyIcon(NIM_DELETE, m_pNidIcon); } void WinConsole::Stop() { if (m_bAppMode) { PostMessage(m_hTrayWindow, WM_QUIT, 0, 0); } Thread::Stop(); } void WinConsole::CreateResources() { m_hMenu = LoadMenu(m_hInstance, MAKEINTRESOURCE(IDR_TRAYMENU)); m_hMenu = GetSubMenu(m_hMenu, 0); HDC hdc = GetDC(NULL); long lfHeight = -MulDiv(8, GetDeviceCaps(hdc, LOGPIXELSY), 72); m_hLinkFont = CreateFont(lfHeight, 0, 0, 0, 0, 0, TRUE, 0, 0, 0, 0, 0, 0, "Tahoma"); lfHeight = -MulDiv(11, GetDeviceCaps(hdc, LOGPIXELSY), 72); m_hNameFont = CreateFont(lfHeight, 0, 0, 0, FW_BOLD, 0, 0, 0, 0, 0, 0, 0, 0, "Tahoma"); lfHeight = -MulDiv(10, GetDeviceCaps(hdc, LOGPIXELSY), 72); m_hTitleFont = CreateFont(lfHeight, 0, 0, 0, FW_BOLD, 0, 0, 0, 0, 0, 0, 0, 0, "Tahoma"); ReleaseDC(NULL, hdc); m_hHandCursor = LoadCursor(NULL, MAKEINTRESOURCE(IDC_HAND)); m_hAboutIcon = (HICON)LoadImage(m_hInstance, MAKEINTRESOURCE(IDI_MAINICON), IMAGE_ICON, 64, 64, 0); m_hRunningIcon = (HICON)LoadImage(m_hInstance, MAKEINTRESOURCE(IDI_MAINICON), IMAGE_ICON, 48, 48, 0); m_hIdleIcon = LoadIcon(m_hInstance, MAKEINTRESOURCE(IDI_TRAYICON_IDLE)); m_hWorkingIcon = LoadIcon(m_hInstance, MAKEINTRESOURCE(IDI_TRAYICON_WORKING)); m_hPausedIcon = LoadIcon(m_hInstance, MAKEINTRESOURCE(IDI_TRAYICON_PAUSED)); } void WinConsole::CreateTrayIcon() { UM_TASKBARCREATED = RegisterWindowMessageA("TaskbarCreated") ; char className[] = "NZBGet tray window"; WNDCLASSEX wnd; memset(&wnd, 0, sizeof(wnd)); wnd.hInstance = m_hInstance; wnd.lpszClassName = className; wnd.lpfnWndProc = TrayWndProcStat; wnd.style = 0; wnd.cbSize = sizeof(WNDCLASSEX); RegisterClassEx(&wnd); m_hTrayWindow = CreateWindowEx(0, className, "NZBGet", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, m_hInstance, NULL); m_pNidIcon = new NOTIFYICONDATA; memset(m_pNidIcon, 0, sizeof(NOTIFYICONDATA)); m_pNidIcon->cbSize = sizeof(NOTIFYICONDATA); m_pNidIcon->hWnd = m_hTrayWindow; m_pNidIcon->uID = 100; m_pNidIcon->uCallbackMessage = UM_TRAYICON; m_pNidIcon->hIcon = m_hWorkingIcon; strncpy(m_pNidIcon->szTip, "NZBGet", sizeof(m_pNidIcon->szTip)); m_pNidIcon->uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; } LRESULT CALLBACK WinConsole::TrayWndProcStat(HWND hwndWin, UINT uMsg, WPARAM wParam, LPARAM lParam) { return g_pWinConsole->TrayWndProc(hwndWin, uMsg, wParam, lParam); } LRESULT WinConsole::TrayWndProc(HWND hwndWin, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (uMsg == UM_TASKBARCREATED) { ApplyPrefs(); return 0; } switch (uMsg) { case UM_TRAYICON: if (lParam == WM_LBUTTONUP && !m_bDoubleClick) { g_pOptions->SetPauseDownload(!g_pOptions->GetPauseDownload()); g_pOptions->SetPausePostProcess(g_pOptions->GetPauseDownload()); g_pOptions->SetPauseScan(g_pOptions->GetPauseDownload()); g_pOptions->SetResumeTime(0); UpdateTrayIcon(); } else if (lParam == WM_LBUTTONDBLCLK && m_bDoubleClick) { ShowWebUI(); } else if (lParam == WM_RBUTTONDOWN) { ShowMenu(); } return 0; case UM_QUIT: ExitProc(); return 0; case UM_SHOWWEBUI: ShowWebUI(); return 0; case UM_PREFSCHANGED: LoadPrefs(); ApplyPrefs(); return 0; default: return DefWindowProc(hwndWin, uMsg, wParam, lParam); } } void WinConsole::ShowMenu() { POINT curPoint; GetCursorPos(&curPoint); SetForegroundWindow(m_hTrayWindow); UINT iItemID = TrackPopupMenu(m_hMenu, TPM_RETURNCMD | TPM_NONOTIFY, curPoint.x, curPoint.y, 0, m_hTrayWindow, NULL); switch(iItemID) { case ID_SHOWWEBUI: ShowWebUI(); break; case ID_SHOW_DESTDIR: ShowInExplorer(g_pOptions->GetDestDir()); break; case ID_SHOW_INTERDIR: ShowInExplorer(g_pOptions->GetInterDir()); break; case ID_SHOW_NZBDIR: ShowInExplorer(g_pOptions->GetNzbDir()); break; case ID_SHOW_CONFIGFILE: ShowInExplorer(g_pOptions->GetConfigFilename()); break; case ID_SHOW_LOGFILE: ShowInExplorer(g_pOptions->GetLogFile()); break; case ID_SHOW_SCRIPTDIR: ShowInExplorer(g_pOptions->GetScriptDir()); break; case ID_INFO_HOMEPAGE: ShellExecute(0, "open", "http://nzbget.net", NULL, NULL, SW_SHOWNORMAL); break; case ID_INFO_DOWNLOADS: ShellExecute(0, "open", "http://nzbget.net/download", NULL, NULL, SW_SHOWNORMAL); break; case ID_INFO_FORUM: ShellExecute(0, "open", "http://nzbget.net/forum", NULL, NULL, SW_SHOWNORMAL); break; case ID_ABOUT: ShowAboutBox(); break; case ID_PREFERENCES: ShowPrefsDialog(); break; case ID_EXIT: ExitProc(); break; case ID_TROUBLESHOOTING_RESTART: bMayStartBrowser = true; Reload(); break; case ID_TROUBLESHOOTING_OPENCONFIG: OpenConfigFileInTextEdit(); break; case ID_TROUBLESHOOTING_FACTORYRESET: ShowFactoryResetDialog(); break; } if (iItemID >= ID_SHOW_DESTDIR + 1000 && iItemID < ID_SHOW_DESTDIR + 2000) { ShowCategoryDir(iItemID - (ID_SHOW_DESTDIR + 1000)); } } void WinConsole::ShowWebUI() { const char* szIP = g_pOptions->GetControlIP(); if (!strcmp(g_pOptions->GetControlIP(), "localhost") || !strcmp(g_pOptions->GetControlIP(), "0.0.0.0")) { szIP = "127.0.0.1"; } char szURL[1024]; snprintf(szURL, 1024, "http://%s:%i", szIP, g_pOptions->GetControlPort()); szURL[1024-1] = '\0'; ShellExecute(0, "open", szURL, NULL, NULL, SW_SHOWNORMAL); } void WinConsole::ShowInExplorer(const char* szFileName) { char szFileName2[MAX_PATH + 1]; strncpy(szFileName2, szFileName, MAX_PATH); szFileName2[MAX_PATH] = '\0'; Util::NormalizePathSeparators(szFileName2); if (*szFileName2 && szFileName2[strlen(szFileName2) - 1] == PATH_SEPARATOR) szFileName2[strlen(szFileName2) - 1] = '\0'; // trim slash if (!Util::FileExists(szFileName2) && !Util::DirectoryExists(szFileName2)) { char szMessage[400]; snprintf(szMessage, 400, "Directory or file %s doesn't exist (yet).", szFileName2); szMessage[400-1] = '\0'; MessageBox(m_hTrayWindow, szMessage, "Information", MB_ICONINFORMATION); return; } WCHAR wszFileName2[MAX_PATH + 1]; MultiByteToWideChar(0, 0, szFileName2, strlen(szFileName2) + 1, wszFileName2, MAX_PATH); CoInitialize(NULL); LPITEMIDLIST pidl; HRESULT H = SHParseDisplayName(wszFileName2, NULL, &pidl, 0, NULL); H = SHOpenFolderAndSelectItems(pidl, 0, 0, 0); } void WinConsole::ShowAboutBox() { if (m_bModal) { return; } m_bModal = true; DialogBox(m_hInstance, MAKEINTRESOURCE(IDD_ABOUTBOX), m_hTrayWindow, AboutDialogProcStat); m_bModal = false; } BOOL CALLBACK WinConsole::AboutDialogProcStat(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { return g_pWinConsole->AboutDialogProc(hwndDlg, uMsg, wParam, lParam); } BOOL WinConsole::AboutDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch(uMsg) { case WM_INITDIALOG: SendDlgItemMessage(hwndDlg, IDC_ABOUT_NAME, WM_SETFONT, (WPARAM)m_hNameFont, 0); char szVersion[100]; snprintf(szVersion, 100, "Version %s", Util::VersionRevision()); SetDlgItemText(hwndDlg, IDC_ABOUT_VERSION, szVersion); SendDlgItemMessage(hwndDlg, IDC_ABOUT_ICON, STM_SETICON, (WPARAM)m_hAboutIcon, 0); SendDlgItemMessage(hwndDlg, IDC_ABOUT_HOMEPAGE, WM_SETFONT, (WPARAM)m_hLinkFont, 0); SendDlgItemMessage(hwndDlg, IDC_ABOUT_GPL, WM_SETFONT, (WPARAM)m_hLinkFont, 0); return FALSE; case WM_CLOSE: EndDialog(hwndDlg, 0); return TRUE; case WM_COMMAND: if (LOWORD(wParam) == IDOK) { EndDialog(hwndDlg, 0); } else if (LOWORD(wParam) == IDC_ABOUT_HOMEPAGE && HIWORD(wParam) == STN_CLICKED) { ShellExecute(0, "open", "http://nzbget.net", NULL, NULL, SW_SHOWNORMAL); } else if (LOWORD(wParam) == IDC_ABOUT_GPL && HIWORD(wParam) == STN_CLICKED) { ShellExecute(0, "open", "http://www.gnu.org/licenses/gpl-2.0.html", NULL, NULL, SW_SHOWNORMAL); } return TRUE; case WM_CTLCOLORSTATIC: if ((HWND)lParam == GetDlgItem(hwndDlg, IDC_ABOUT_HOMEPAGE) || (HWND)lParam == GetDlgItem(hwndDlg, IDC_ABOUT_GPL)) { SetTextColor((HDC)wParam, RGB(0, 0, 255)); SetBkColor((HDC)wParam, GetSysColor(COLOR_BTNFACE)); return (INT_PTR)GetSysColorBrush(COLOR_BTNFACE); } return FALSE; case WM_SETCURSOR: if ((HWND)wParam == GetDlgItem(hwndDlg, IDC_ABOUT_HOMEPAGE) || (HWND)wParam == GetDlgItem(hwndDlg, IDC_ABOUT_GPL)) { SetCursor(m_hHandCursor); SetWindowLong(hwndDlg, DWL_MSGRESULT, TRUE); return TRUE; } return FALSE; default: return FALSE; } } void WinConsole::ShowPrefsDialog() { if (m_bModal) { return; } m_bModal = true; DialogBox(m_hInstance, MAKEINTRESOURCE(IDD_PREFDIALOG), m_hTrayWindow, PrefsDialogProcStat); m_bModal = false; } BOOL CALLBACK WinConsole::PrefsDialogProcStat(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { return g_pWinConsole->PrefsDialogProc(hwndDlg, uMsg, wParam, lParam); } BOOL WinConsole::PrefsDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch(uMsg) { case WM_INITDIALOG: LoadPrefs(); SendDlgItemMessage(hwndDlg, IDC_PREF_AUTOSTART, BM_SETCHECK, m_bAutostart, 0); SendDlgItemMessage(hwndDlg, IDC_PREF_TRAY, BM_SETCHECK, m_bTray, 0); SendDlgItemMessage(hwndDlg, IDC_PREF_CONSOLE, BM_SETCHECK, m_bConsole, 0); SendDlgItemMessage(hwndDlg, IDC_PREF_WEBUI, BM_SETCHECK, m_bWebUI, 0); SendDlgItemMessage(hwndDlg, IDC_PREF_TRAYPAUSE, BM_SETCHECK, !m_bDoubleClick, 0); SendDlgItemMessage(hwndDlg, IDC_PREF_TRAYWEBUI, BM_SETCHECK, m_bDoubleClick, 0); return FALSE; case WM_CLOSE: EndDialog(hwndDlg, 0); return TRUE; case WM_COMMAND: if (LOWORD(wParam) == IDOK) { m_bAutostart = SendDlgItemMessage(hwndDlg, IDC_PREF_AUTOSTART, BM_GETCHECK, 0, 0) == BST_CHECKED; m_bTray = SendDlgItemMessage(hwndDlg, IDC_PREF_TRAY, BM_GETCHECK, 0, 0) == BST_CHECKED; m_bConsole = SendDlgItemMessage(hwndDlg, IDC_PREF_CONSOLE, BM_GETCHECK, 0, 0) == BST_CHECKED; m_bWebUI = SendDlgItemMessage(hwndDlg, IDC_PREF_WEBUI, BM_GETCHECK, 0, 0) == BST_CHECKED; m_bDoubleClick = SendDlgItemMessage(hwndDlg, IDC_PREF_TRAYWEBUI, BM_GETCHECK, 0, 0) == BST_CHECKED; SavePrefs(); if (!m_bRunning) { ApplyPrefs(); } EndDialog(hwndDlg, 0); } else if (LOWORD(wParam) == IDCANCEL) { EndDialog(hwndDlg, 0); } return TRUE; default: return FALSE; } } void WinConsole::SavePrefs() { DWORD val; HKEY hKey; RegCreateKey(HKEY_CURRENT_USER, "Software\\NZBGet", &hKey); val = m_bTray; RegSetValueEx(hKey, "ShowTrayIcon", 0, REG_DWORD, (BYTE*)&val, sizeof(val)); val = m_bConsole; RegSetValueEx(hKey, "ShowConsole", 0, REG_DWORD, (BYTE*)&val, sizeof(val)); val = m_bWebUI; RegSetValueEx(hKey, "ShowWebUI", 0, REG_DWORD, (BYTE*)&val, sizeof(val)); val = m_bDoubleClick; RegSetValueEx(hKey, "TrayDoubleClick", 0, REG_DWORD, (BYTE*)&val, sizeof(val)); RegCloseKey(hKey); // Autostart-setting RegCreateKey(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Run", &hKey); if (m_bAutostart) { char szFilename[MAX_PATH + 1]; GetModuleFileName(NULL, szFilename, sizeof(szFilename)); char szStartCommand[1024]; snprintf(szStartCommand, sizeof(szStartCommand), "\"%s\" -s -app -auto", szFilename); szStartCommand[1024-1] = '\0'; RegSetValueEx(hKey, "NZBGet", 0, REG_SZ, (BYTE*)szStartCommand, strlen(szStartCommand) + 1); } else { RegDeleteValue(hKey, "NZBGet"); } RegCloseKey(hKey); } void WinConsole::LoadPrefs() { DWORD val; DWORD cval; DWORD typ; HKEY hKey; if (RegOpenKey(HKEY_CURRENT_USER, "Software\\NZBGet", &hKey) == ERROR_SUCCESS) { if (RegQueryValueEx(hKey, "ShowTrayIcon", 0, &typ, (LPBYTE)&val, &cval) == ERROR_SUCCESS) { m_bTray = val; } if (RegQueryValueEx(hKey, "ShowConsole", 0, &typ, (LPBYTE)&val, &cval) == ERROR_SUCCESS) { m_bConsole = val; } if (RegQueryValueEx(hKey, "ShowWebUI", 0, &typ, (LPBYTE)&val, &cval) == ERROR_SUCCESS) { m_bWebUI = val; } if (RegQueryValueEx(hKey, "TrayDoubleClick", 0, &typ, (LPBYTE)&val, &cval) == ERROR_SUCCESS) { m_bDoubleClick = val; } RegCloseKey(hKey); } // Autostart-setting if (RegOpenKey(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Run", &hKey) == ERROR_SUCCESS) { m_bAutostart = RegQueryValueEx(hKey, "NZBGet", 0, &typ, NULL, NULL) == ERROR_SUCCESS; RegCloseKey(hKey); } } void WinConsole::ApplyPrefs() { ShowWindow(GetConsoleWindow(), m_bConsole ? SW_SHOW : SW_HIDE); if (m_bTray) { UpdateTrayIcon(); } Shell_NotifyIcon(m_bTray ? NIM_ADD : NIM_DELETE, m_pNidIcon); } void WinConsole::CheckRunning() { HWND hTrayWindow = FindWindow("NZBGet tray window", NULL); if (hTrayWindow) { ShowRunningDialog(); ExitProcess(1); } if (IsServiceRunning()) { m_bRunningService = true; ShowRunningDialog(); ExitProcess(1); } } void WinConsole::ShowRunningDialog() { ShowWindow(GetConsoleWindow(), m_bConsole ? SW_SHOW : SW_HIDE); HWND hTrayWindow = FindWindow("NZBGet tray window", NULL); m_bRunning = true; int iResult = DialogBox(m_hInstance, MAKEINTRESOURCE(IDD_RUNNINGDIALOG), m_hTrayWindow, RunningDialogProcStat); switch (iResult) { case IDC_RUNNING_WEBUI: PostMessage(hTrayWindow, UM_SHOWWEBUI, 0, 0); break; case IDC_RUNNING_PREFS: ShowPrefsDialog(); PostMessage(hTrayWindow, UM_PREFSCHANGED, 0, 0); break; case IDC_RUNNING_QUIT: PostMessage(hTrayWindow, UM_QUIT, 0, 0); break; } } BOOL CALLBACK WinConsole::RunningDialogProcStat(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { return g_pWinConsole->RunningDialogProc(hwndDlg, uMsg, wParam, lParam); } BOOL WinConsole::RunningDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch(uMsg) { case WM_INITDIALOG: SendDlgItemMessage(hwndDlg, IDC_RUNNING_ICON, STM_SETICON, (WPARAM)m_hRunningIcon, 0); SendDlgItemMessage(hwndDlg, IDC_RUNNING_TITLE, WM_SETFONT, (WPARAM)m_hTitleFont, 0); if (m_bRunningService) { SetDlgItemText(hwndDlg, IDC_RUNNING_TEXT, "Another instance of NZBGet is running as Windows Service. Please use Management Console to control the service."); ShowWindow(GetDlgItem(hwndDlg, IDC_RUNNING_WEBUI), SW_HIDE); ShowWindow(GetDlgItem(hwndDlg, IDC_RUNNING_PREFS), SW_HIDE); ShowWindow(GetDlgItem(hwndDlg, IDC_RUNNING_QUIT), SW_HIDE); ShowWindow(GetDlgItem(hwndDlg, IDC_RUNNING_OK), SW_SHOW); } return FALSE; case WM_CLOSE: EndDialog(hwndDlg, 0); return TRUE; case WM_COMMAND: EndDialog(hwndDlg, LOWORD(wParam)); return TRUE; default: return FALSE; } } void WinConsole::UpdateTrayIcon() { if (!m_bTray) { return; } HICON hOldIcon = m_pNidIcon->hIcon; char szOldTip[200]; strncpy(szOldTip, m_pNidIcon->szTip, sizeof(m_pNidIcon->szTip)); szOldTip[200-1] = '\0'; if (g_pOptions->GetPauseDownload()) { m_pNidIcon->hIcon = m_hPausedIcon; strncpy(m_pNidIcon->szTip, "NZBGet - paused", sizeof(m_pNidIcon->szTip)); } else if (!g_pStatMeter->GetStandBy()) { m_pNidIcon->hIcon = m_hWorkingIcon; char szSpeed[100]; Util::FormatSpeed(szSpeed, sizeof(szSpeed), g_pStatMeter->CalcCurrentDownloadSpeed()); char szTip[200]; snprintf(szTip, sizeof(szTip), "NZBGet - downloading at %s", szSpeed); szTip[200-1] = '\0'; strncpy(m_pNidIcon->szTip, szTip, sizeof(m_pNidIcon->szTip)); } else { DownloadQueue *pDownloadQueue = DownloadQueue::Lock(); int iPostJobCount = 0; int iUrlCount = 0; for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; iPostJobCount += pNZBInfo->GetPostInfo() ? 1 : 0; iUrlCount += pNZBInfo->GetKind() == NZBInfo::nkUrl ? 1 : 0; } DownloadQueue::Unlock(); if (iPostJobCount > 0) { m_pNidIcon->hIcon = m_hWorkingIcon; strncpy(m_pNidIcon->szTip, "NZBGet - post-processing", sizeof(m_pNidIcon->szTip)); } else if (iUrlCount > 0) { m_pNidIcon->hIcon = m_hWorkingIcon; strncpy(m_pNidIcon->szTip, "NZBGet - fetching URLs", sizeof(m_pNidIcon->szTip)); } else if (g_pFeedCoordinator->HasActiveDownloads()) { m_pNidIcon->hIcon = m_hWorkingIcon; strncpy(m_pNidIcon->szTip, "NZBGet - fetching feeds", sizeof(m_pNidIcon->szTip)); } else { m_pNidIcon->hIcon = m_hIdleIcon; strncpy(m_pNidIcon->szTip, "NZBGet - idle", sizeof(m_pNidIcon->szTip)); } } if (m_pNidIcon->hIcon != hOldIcon || strcmp(szOldTip, m_pNidIcon->szTip)) { Shell_NotifyIcon(NIM_MODIFY, m_pNidIcon); } } void WinConsole::BuildMenu() { int iIndex = 0; for (Options::Categories::iterator it = g_pOptions->GetCategories()->begin(); it != g_pOptions->GetCategories()->end(); it++, iIndex++) { Options::Category* pCategory = *it; char szCaption[250]; snprintf(szCaption, 250, "Category %i: %s", iIndex + 1, pCategory->GetName()); szCaption[250 - 1] = '\0'; MENUITEMINFO item; ZeroMemory(&item, sizeof(MENUITEMINFO)); item.cbSize = sizeof(MENUITEMINFO); item.fMask = MIIM_ID | MIIM_STRING; item.fType = MFT_STRING; item.fState = MFS_DEFAULT; item.wID = ID_SHOW_DESTDIR + 1000 + iIndex; item.dwTypeData = szCaption; InsertMenuItem(GetSubMenu(m_hMenu, 1), 2 + iIndex, TRUE, &item); } /* BOOL DeleteMenu( HMENU hMenu, // handle to menu UINT uPosition, // menu item identifier or position UINT uFlags // menu item flag ); */ } void WinConsole::ShowCategoryDir(int iCatIndex) { Options::Category* pCategory = g_pOptions->GetCategories()->at(iCatIndex); char szDestDir[1024]; if (!Util::EmptyStr(pCategory->GetDestDir())) { snprintf(szDestDir, 1024, "%s", pCategory->GetDestDir()); szDestDir[1024-1] = '\0'; } else { char szCategoryDir[1024]; strncpy(szCategoryDir, pCategory->GetName(), 1024); szCategoryDir[1024 - 1] = '\0'; Util::MakeValidFilename(szCategoryDir, '_', true); snprintf(szDestDir, 1024, "%s%s", g_pOptions->GetDestDir(), szCategoryDir); szDestDir[1024-1] = '\0'; } ShowInExplorer(szDestDir); } void WinConsole::OpenConfigFileInTextEdit() { char szParam[MAX_PATH + 3]; snprintf(szParam, sizeof(szParam), "\"%s\"", g_pOptions->GetConfigFilename()); szParam[sizeof(szParam)-1] = '\0'; ShellExecute(0, "open", "notepad.exe", szParam, NULL, SW_SHOWNORMAL); } void WinConsole::SetupFirstStart() { SetupConfigFile(); SetupScripts(); } void WinConsole::SetupConfigFile() { // create new config-file from config template char szFilename[MAX_PATH + 30]; char szCommonAppDataPath[MAX_PATH]; SHGetFolderPath(NULL, CSIDL_COMMON_APPDATA, NULL, 0, szCommonAppDataPath); snprintf(szFilename, sizeof(szFilename), "%s\\NZBGet\\nzbget.conf", szCommonAppDataPath); szFilename[sizeof(szFilename)-1] = '\0'; char szAppDataPath[MAX_PATH + 1]; snprintf(szAppDataPath, sizeof(szAppDataPath), "%s\\NZBGet", szCommonAppDataPath); szAppDataPath[sizeof(szAppDataPath)-1] = '\0'; Util::CreateDirectory(szAppDataPath); char szConfTemplateFilename[MAX_PATH + 30]; snprintf(szConfTemplateFilename, sizeof(szConfTemplateFilename), "%s\\nzbget.conf.template", g_pOptions->GetAppDir()); szConfTemplateFilename[sizeof(szConfTemplateFilename)-1] = '\0'; CopyFile(szConfTemplateFilename, szFilename, FALSE); // set MainDir in the config-file int iSize = 0; char* szConfig = NULL; if (Util::LoadFileIntoBuffer(szFilename, &szConfig, &iSize)) { const char* SIGNATURE = "MainDir=${AppDir}\\downloads"; char* p = strstr(szConfig, SIGNATURE); if (p) { FILE* outfile = fopen(szFilename, FOPEN_WBP); if (outfile) { fwrite(szConfig, 1, p - szConfig, outfile); fwrite("MainDir=", 1, 8, outfile); fwrite(szAppDataPath, 1, strlen(szAppDataPath), outfile); fwrite(p + strlen(SIGNATURE), 1, iSize - (p + strlen(SIGNATURE) - szConfig) - 1, outfile); fclose(outfile); } } free(szConfig); } // create default destination directory (which is not created on start automatically) snprintf(szFilename, sizeof(szFilename), "%s\\NZBGet\\complete", szCommonAppDataPath); szFilename[sizeof(szFilename)-1] = '\0'; Util::CreateDirectory(szFilename); } void WinConsole::SetupScripts() { // copy default scripts char szAppDataPath[MAX_PATH]; SHGetFolderPath(NULL, CSIDL_COMMON_APPDATA, NULL, 0, szAppDataPath); char szDestDir[MAX_PATH + 1]; snprintf(szDestDir, sizeof(szDestDir), "%s\\NZBGet\\scripts", szAppDataPath); szDestDir[sizeof(szDestDir)-1] = '\0'; Util::CreateDirectory(szDestDir); char szSrcDir[MAX_PATH + 30]; snprintf(szSrcDir, sizeof(szSrcDir), "%s\\scripts", g_pOptions->GetAppDir()); szSrcDir[sizeof(szSrcDir)-1] = '\0'; DirBrowser dir(szSrcDir); while (const char* szFilename = dir.Next()) { if (strcmp(szFilename, ".") && strcmp(szFilename, "..")) { char szSrcFullFilename[1024]; snprintf(szSrcFullFilename, 1024, "%s\\%s", szSrcDir, szFilename); szSrcFullFilename[1024-1] = '\0'; char szDstFullFilename[1024]; snprintf(szDstFullFilename, 1024, "%s\\%s", szDestDir, szFilename); szDstFullFilename[1024-1] = '\0'; CopyFile(szSrcFullFilename, szDstFullFilename, FALSE); } } } void WinConsole::ShowFactoryResetDialog() { HWND hTrayWindow = FindWindow("NZBGet tray window", NULL); m_bRunning = true; int iResult = DialogBox(m_hInstance, MAKEINTRESOURCE(IDD_FACTORYRESETDIALOG), m_hTrayWindow, FactoryResetDialogProcStat); switch (iResult) { case IDC_FACTORYRESET_RESET: ResetFactoryDefaults(); break; } } BOOL CALLBACK WinConsole::FactoryResetDialogProcStat(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { return g_pWinConsole->FactoryResetDialogProc(hwndDlg, uMsg, wParam, lParam); } BOOL WinConsole::FactoryResetDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch(uMsg) { case WM_INITDIALOG: SendDlgItemMessage(hwndDlg, IDC_FACTORYRESET_ICON, STM_SETICON, (WPARAM)m_hRunningIcon, 0); SendDlgItemMessage(hwndDlg, IDC_FACTORYRESET_TITLE, WM_SETFONT, (WPARAM)m_hTitleFont, 0); return FALSE; case WM_CLOSE: EndDialog(hwndDlg, 0); return TRUE; case WM_COMMAND: EndDialog(hwndDlg, LOWORD(wParam)); return TRUE; default: return FALSE; } } void WinConsole::ResetFactoryDefaults() { char szPath[MAX_PATH + 100]; char szMessage[1024]; char szErrBuf[200]; g_pOptions->SetPauseDownload(true); g_pOptions->SetPausePostProcess(true); char szCommonAppDataPath[MAX_PATH]; SHGetFolderPath(NULL, CSIDL_COMMON_APPDATA, NULL, 0, szCommonAppDataPath); // delete default directories const char* DefDirs[] = {"nzb", "tmp", "queue", "scripts"}; for (int i=0; i < 4; i++) { snprintf(szPath, sizeof(szPath), "%s\\NZBGet\\%s", szCommonAppDataPath, DefDirs[i]); szPath[sizeof(szPath)-1] = '\0'; // try to delete the directory int iRetry = 10; while (iRetry > 0 && Util::DirectoryExists(szPath) && !Util::DeleteDirectoryWithContent(szPath, szErrBuf, sizeof(szErrBuf))) { usleep(200 * 1000); iRetry--; } if (Util::DirectoryExists(szPath)) { snprintf(szMessage, 1024, "Could not delete directory %s:\n%s.\nPlease delete the directory manually and try again.", szPath, szErrBuf); szMessage[1024-1] = '\0'; MessageBox(m_hTrayWindow, szMessage, "NZBGet", MB_ICONERROR); return; } } // delete old config file in the program's directory snprintf(szPath, sizeof(szPath), "%s\\nzbget.conf", g_pOptions->GetAppDir()); remove(szPath); Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf)); if (Util::FileExists(szPath)) { snprintf(szMessage, 1024, "Could not delete file %s:\n%s.\nPlease delete the file manually and try again.", szPath, szErrBuf); szMessage[1024-1] = '\0'; MessageBox(m_hTrayWindow, szMessage, "NZBGet", MB_ICONERROR); return; } // delete config file in default directory snprintf(szPath, sizeof(szPath), "%s\\NZBGet\\nzbget.conf", szCommonAppDataPath); szPath[sizeof(szPath)-1] = '\0'; remove(szPath); Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf)); if (Util::FileExists(szPath)) { snprintf(szMessage, 1024, "Could not delete file %s:\n%s.\nPlease delete the file manually and try again.", szPath, szErrBuf); szMessage[1024-1] = '\0'; MessageBox(m_hTrayWindow, szMessage, "NZBGet", MB_ICONERROR); return; } // delete log files in default directory snprintf(szPath, sizeof(szPath), "%s\\NZBGet", szCommonAppDataPath); szPath[sizeof(szPath)-1] = '\0'; DirBrowser dir(szPath); while (const char* szFilename = dir.Next()) { if (Util::MatchFileExt(szFilename, ".log", ",")) { char szFullFilename[1024]; snprintf(szFullFilename, 1024, "%s%c%s", szPath, PATH_SEPARATOR, szFilename); szFullFilename[1024-1] = '\0'; remove(szFullFilename); // ignore errors } } MessageBox(m_hTrayWindow, "The program has been reset to factory defaults.", "NZBGet", MB_ICONINFORMATION); bMayStartBrowser = true; Reload(); } nzbget-16.4/daemon/windows/NTService.h0000644000175000017500000000207712630544544017536 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2014 Andrey Prygunkov * * 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 NTSERVICE_H #define NTSERVICE_H typedef void (*RunProc)(void); void InstallUninstallServiceCheck(int argc, char *argv[]); void StartService(RunProc RunProcPtr); bool IsServiceRunning(); #endif nzbget-16.4/daemon/windows/win32.h0000644000175000017500000000506312630544544016634 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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$ * */ /* win32.h - Defines and standard includes for MS Windows / Visual C++ 2005 */ /* Define to 1 to not use curses */ //#define DISABLE_CURSES /* Define to 1 to disable smart par-verification and restoration */ //#define DISABLE_PARCHECK /* Define to 1 to disable TLS/SSL-support. */ //#define DISABLE_TLS #ifndef DISABLE_TLS /* Define to 1 to use OpenSSL library for TLS/SSL-support */ #define HAVE_OPENSSL /* Define to 1 to use GnuTLS library for TLS/SSL-support */ //#define HAVE_LIBGNUTLS #endif /* Define to the name of macro which returns the name of function being compiled */ #define FUNCTION_MACRO_NAME __FUNCTION__ /* Define to 1 if ctime_r takes 2 arguments */ #undef HAVE_CTIME_R_2 /* Define to 1 if ctime_r takes 3 arguments */ #define HAVE_CTIME_R_3 /* Define to 1 if getopt_long is supported */ #undef HAVE_GETOPT_LONG /* Define to 1 if variadic macros are supported */ #define HAVE_VARIADIC_MACROS /* Define to 1 if libpar2 supports cancelling (needs a special patch) */ #define HAVE_PAR2_CANCEL /* Define to 1 if function GetAddrInfo is supported */ #define HAVE_GETADDRINFO /* Determine what socket length (socklen_t) data type is */ #define SOCKLEN_T socklen_t /* Define to 1 if you have the header file. */ #define HAVE_REGEX_H 1 #define VERSION "16.4" /* Suppress warnings */ #define _CRT_SECURE_NO_DEPRECATE /* Suppress warnings */ #define _CRT_NONSTDC_NO_WARNINGS #define _USE_32BIT_TIME_T #if _WIN32_WINNT < 0x0501 #undef _WIN32_WINNT #define _WIN32_WINNT 0x0501 #endif #ifdef _DEBUG // detection of memory leaks #define _CRTDBG_MAP_ALLOC #include #include #endif #ifndef SKIP_DEFAULT_WINDOWS_HEADERS #include #include #endif nzbget-16.4/daemon/connect/0000755000175000017500000000000012630544544015454 5ustar andreasandreasnzbget-16.4/daemon/connect/TLS.cpp0000644000175000017500000002651612630544544016634 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2008-2015 Andrey Prygunkov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision$ * $Date$ * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifdef WIN32 #define SKIP_DEFAULT_WINDOWS_HEADERS #include "win32.h" #endif #ifndef DISABLE_TLS #include #include #include #ifdef WIN32 #include #include #else #include #endif #include #include #include #include #include #ifdef WIN32 #include "nzbget.h" #endif #ifdef HAVE_LIBGNUTLS #include #if GNUTLS_VERSION_NUMBER <= 0x020b00 #define NEED_GCRYPT_LOCKING #endif #ifdef NEED_GCRYPT_LOCKING #include #endif /* NEED_GCRYPT_LOCKING */ #endif /* HAVE_LIBGNUTLS */ #ifdef HAVE_OPENSSL #include #include #endif /* HAVE_OPENSSL */ #ifndef WIN32 #include "nzbget.h" #endif #include "TLS.h" #include "Thread.h" #include "Log.h" #ifdef HAVE_LIBGNUTLS #ifdef NEED_GCRYPT_LOCKING /** * Mutexes for gcryptlib */ typedef std::list Mutexes; Mutexes* g_pGCryptLibMutexes; static int gcry_mutex_init(void **priv) { Mutex* pMutex = new Mutex(); g_pGCryptLibMutexes->push_back(pMutex); *priv = pMutex; return 0; } static int gcry_mutex_destroy(void **lock) { Mutex* pMutex = ((Mutex*)*lock); g_pGCryptLibMutexes->remove(pMutex); delete pMutex; return 0; } static int gcry_mutex_lock(void **lock) { ((Mutex*)*lock)->Lock(); return 0; } static int gcry_mutex_unlock(void **lock) { ((Mutex*)*lock)->Unlock(); return 0; } static struct gcry_thread_cbs gcry_threads_Mutex = { GCRY_THREAD_OPTION_USER, NULL, gcry_mutex_init, gcry_mutex_destroy, gcry_mutex_lock, gcry_mutex_unlock, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; #endif /* NEED_GCRYPT_LOCKING */ #endif /* HAVE_LIBGNUTLS */ #ifdef HAVE_OPENSSL /** * Mutexes for OpenSSL */ Mutex* *g_pOpenSSLMutexes; static void openssl_locking(int mode, int n, const char *file, int line) { Mutex* mutex = g_pOpenSSLMutexes[n]; if (mode & CRYPTO_LOCK) { mutex->Lock(); } else { mutex->Unlock(); } } /* static unsigned long openssl_thread_id(void) { #ifdef WIN32 return (unsigned long)GetCurrentThreadId(); #else return (unsigned long)pthread_self(); #endif } */ static struct CRYPTO_dynlock_value* openssl_dynlock_create(const char *file, int line) { return (CRYPTO_dynlock_value*)new Mutex(); } static void openssl_dynlock_destroy(struct CRYPTO_dynlock_value *l, const char *file, int line) { Mutex* mutex = (Mutex*)l; delete mutex; } static void openssl_dynlock_lock(int mode, struct CRYPTO_dynlock_value *l, const char *file, int line) { Mutex* mutex = (Mutex*)l; if (mode & CRYPTO_LOCK) { mutex->Lock(); } else { mutex->Unlock(); } } #endif /* HAVE_OPENSSL */ void TLSSocket::Init() { debug("Initializing TLS library"); #ifdef HAVE_LIBGNUTLS #ifdef NEED_GCRYPT_LOCKING g_pGCryptLibMutexes = new Mutexes(); #endif /* NEED_GCRYPT_LOCKING */ int error_code; #ifdef NEED_GCRYPT_LOCKING error_code = gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_Mutex); if (error_code != 0) { error("Could not initialize libcrypt"); return; } #endif /* NEED_GCRYPT_LOCKING */ error_code = gnutls_global_init(); if (error_code != 0) { error("Could not initialize libgnutls"); return; } #endif /* HAVE_LIBGNUTLS */ #ifdef HAVE_OPENSSL int iMaxMutexes = CRYPTO_num_locks(); g_pOpenSSLMutexes = (Mutex**)malloc(sizeof(Mutex*)*iMaxMutexes); for (int i=0; i < iMaxMutexes; i++) { g_pOpenSSLMutexes[i] = new Mutex(); } SSL_load_error_strings(); SSL_library_init(); OpenSSL_add_all_algorithms(); CRYPTO_set_locking_callback(openssl_locking); //CRYPTO_set_id_callback(openssl_thread_id); CRYPTO_set_dynlock_create_callback(openssl_dynlock_create); CRYPTO_set_dynlock_destroy_callback(openssl_dynlock_destroy); CRYPTO_set_dynlock_lock_callback(openssl_dynlock_lock); #endif /* HAVE_OPENSSL */ } void TLSSocket::Final() { debug("Finalizing TLS library"); #ifdef HAVE_LIBGNUTLS gnutls_global_deinit(); #ifdef NEED_GCRYPT_LOCKING // fixing memory leak in gcryptlib for (Mutexes::iterator it = g_pGCryptLibMutexes->begin(); it != g_pGCryptLibMutexes->end(); it++) { delete *it; } delete g_pGCryptLibMutexes; #endif /* NEED_GCRYPT_LOCKING */ #endif /* HAVE_LIBGNUTLS */ #ifdef HAVE_OPENSSL int iMaxMutexes = CRYPTO_num_locks(); for (int i=0; i < iMaxMutexes; i++) { delete g_pOpenSSLMutexes[i]; } free(g_pOpenSSLMutexes); #endif /* HAVE_OPENSSL */ } TLSSocket::TLSSocket(SOCKET iSocket, bool bIsClient, const char* szCertFile, const char* szKeyFile, const char* szCipher) { m_iSocket = iSocket; m_bIsClient = bIsClient; m_szCertFile = szCertFile ? strdup(szCertFile) : NULL; m_szKeyFile = szKeyFile ? strdup(szKeyFile) : NULL; m_szCipher = szCipher && strlen(szCipher) > 0 ? strdup(szCipher) : NULL; m_pContext = NULL; m_pSession = NULL; m_bSuppressErrors = false; m_bInitialized = false; m_bConnected = false; } TLSSocket::~TLSSocket() { free(m_szCertFile); free(m_szKeyFile); free(m_szCipher); Close(); } void TLSSocket::ReportError(const char* szErrMsg) { char szMessage[1024]; #ifdef HAVE_LIBGNUTLS const char* errstr = gnutls_strerror(m_iRetCode); if (m_bSuppressErrors) { debug("%s: %s", szErrMsg, errstr); } else { snprintf(szMessage, sizeof(szMessage), "%s: %s", szErrMsg, errstr); szMessage[sizeof(szMessage) - 1] = '\0'; PrintError(szMessage); } #endif /* HAVE_LIBGNUTLS */ #ifdef HAVE_OPENSSL int errcode; do { errcode = ERR_get_error(); char errstr[1024]; ERR_error_string_n(errcode, errstr, sizeof(errstr)); errstr[1024-1] = '\0'; if (m_bSuppressErrors) { debug("%s: %s", szErrMsg, errstr); } else if (errcode != 0) { snprintf(szMessage, sizeof(szMessage), "%s: %s", szErrMsg, errstr); szMessage[sizeof(szMessage) - 1] = '\0'; PrintError(szMessage); } else { PrintError(szErrMsg); } } while (errcode); #endif /* HAVE_OPENSSL */ } void TLSSocket::PrintError(const char* szErrMsg) { error("%s", szErrMsg); } bool TLSSocket::Start() { #ifdef HAVE_LIBGNUTLS gnutls_certificate_credentials_t cred; m_iRetCode = gnutls_certificate_allocate_credentials(&cred); if (m_iRetCode != 0) { ReportError("Could not create TLS context"); return false; } m_pContext = cred; if (m_szCertFile && m_szKeyFile) { m_iRetCode = gnutls_certificate_set_x509_key_file((gnutls_certificate_credentials_t)m_pContext, m_szCertFile, m_szKeyFile, GNUTLS_X509_FMT_PEM); if (m_iRetCode != 0) { ReportError("Could not load certificate or key file"); Close(); return false; } } gnutls_session_t sess; m_iRetCode = gnutls_init(&sess, m_bIsClient ? GNUTLS_CLIENT : GNUTLS_SERVER); if (m_iRetCode != 0) { ReportError("Could not create TLS session"); Close(); return false; } m_pSession = sess; m_bInitialized = true; const char* szPriority = m_szCipher ? m_szCipher : "NORMAL"; m_iRetCode = gnutls_priority_set_direct((gnutls_session_t)m_pSession, szPriority, NULL); if (m_iRetCode != 0) { ReportError("Could not select cipher for TLS session"); Close(); return false; } m_iRetCode = gnutls_credentials_set((gnutls_session_t)m_pSession, GNUTLS_CRD_CERTIFICATE, (gnutls_certificate_credentials_t*)m_pContext); if (m_iRetCode != 0) { ReportError("Could not initialize TLS session"); Close(); return false; } gnutls_transport_set_ptr((gnutls_session_t)m_pSession, (gnutls_transport_ptr_t)(size_t)m_iSocket); m_iRetCode = gnutls_handshake((gnutls_session_t)m_pSession); if (m_iRetCode != 0) { ReportError("TLS handshake failed"); Close(); return false; } m_bConnected = true; return true; #endif /* HAVE_LIBGNUTLS */ #ifdef HAVE_OPENSSL m_pContext = SSL_CTX_new(SSLv23_method()); if (!m_pContext) { ReportError("Could not create TLS context"); return false; } if (m_szCertFile && m_szKeyFile) { if (SSL_CTX_use_certificate_file((SSL_CTX*)m_pContext, m_szCertFile, SSL_FILETYPE_PEM) != 1) { ReportError("Could not load certificate file"); Close(); return false; } if (SSL_CTX_use_PrivateKey_file((SSL_CTX*)m_pContext, m_szKeyFile, SSL_FILETYPE_PEM) != 1) { ReportError("Could not load key file"); Close(); return false; } } m_pSession = SSL_new((SSL_CTX*)m_pContext); if (!m_pSession) { ReportError("Could not create TLS session"); Close(); return false; } if (m_szCipher && !SSL_set_cipher_list((SSL*)m_pSession, m_szCipher)) { ReportError("Could not select cipher for TLS"); Close(); return false; } if (!SSL_set_fd((SSL*)m_pSession, m_iSocket)) { ReportError("Could not set the file descriptor for TLS"); Close(); return false; } int error_code = m_bIsClient ? SSL_connect((SSL*)m_pSession) : SSL_accept((SSL*)m_pSession); if (error_code < 1) { ReportError("TLS handshake failed"); Close(); return false; } m_bConnected = true; return true; #endif /* HAVE_OPENSSL */ } void TLSSocket::Close() { if (m_pSession) { #ifdef HAVE_LIBGNUTLS if (m_bConnected) { gnutls_bye((gnutls_session_t)m_pSession, GNUTLS_SHUT_WR); } if (m_bInitialized) { gnutls_deinit((gnutls_session_t)m_pSession); } #endif /* HAVE_LIBGNUTLS */ #ifdef HAVE_OPENSSL if (m_bConnected) { SSL_shutdown((SSL*)m_pSession); } SSL_free((SSL*)m_pSession); #endif /* HAVE_OPENSSL */ m_pSession = NULL; } if (m_pContext) { #ifdef HAVE_LIBGNUTLS gnutls_certificate_free_credentials((gnutls_certificate_credentials_t)m_pContext); #endif /* HAVE_LIBGNUTLS */ #ifdef HAVE_OPENSSL SSL_CTX_free((SSL_CTX*)m_pContext); #endif /* HAVE_OPENSSL */ m_pContext = NULL; } } int TLSSocket::Send(const char* pBuffer, int iSize) { int ret; #ifdef HAVE_LIBGNUTLS ret = gnutls_record_send((gnutls_session_t)m_pSession, pBuffer, iSize); #endif /* HAVE_LIBGNUTLS */ #ifdef HAVE_OPENSSL ret = SSL_write((SSL*)m_pSession, pBuffer, iSize); #endif /* HAVE_OPENSSL */ if (ret < 0) { #ifdef HAVE_OPENSSL if (ERR_peek_error() == 0) { ReportError("Could not write to TLS-Socket: Connection closed by remote host"); } else #endif /* HAVE_OPENSSL */ ReportError("Could not write to TLS-Socket"); return -1; } return ret; } int TLSSocket::Recv(char* pBuffer, int iSize) { int ret; #ifdef HAVE_LIBGNUTLS ret = gnutls_record_recv((gnutls_session_t)m_pSession, pBuffer, iSize); #endif /* HAVE_LIBGNUTLS */ #ifdef HAVE_OPENSSL ret = SSL_read((SSL*)m_pSession, pBuffer, iSize); #endif /* HAVE_OPENSSL */ if (ret < 0) { #ifdef HAVE_OPENSSL if (ERR_peek_error() == 0) { ReportError("Could not read from TLS-Socket: Connection closed by remote host"); } else #endif /* HAVE_OPENSSL */ { ReportError("Could not read from TLS-Socket"); } return -1; } return ret; } #endif nzbget-16.4/daemon/connect/WebDownloader.cpp0000644000175000017500000003603112630544544020717 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2012-2015 Andrey Prygunkov * * 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 #include #include #ifdef WIN32 #include #else #include #include #endif #include #include #include "nzbget.h" #include "WebDownloader.h" #include "Log.h" #include "Options.h" #include "Util.h" WebDownloader::WebDownloader() { debug("Creating WebDownloader"); m_szURL = NULL; m_szOutputFilename = NULL; m_pConnection = NULL; m_szInfoName = NULL; m_bConfirmedLength = false; m_eStatus = adUndefined; m_szOriginalFilename = NULL; m_bForce = false; m_bRetry = true; SetLastUpdateTimeNow(); } WebDownloader::~WebDownloader() { debug("Destroying WebDownloader"); free(m_szURL); free(m_szInfoName); free(m_szOutputFilename); free(m_szOriginalFilename); } void WebDownloader::SetOutputFilename(const char* v) { m_szOutputFilename = strdup(v); } void WebDownloader::SetInfoName(const char* v) { m_szInfoName = strdup(v); } void WebDownloader::SetURL(const char * szURL) { free(m_szURL); m_szURL = WebUtil::URLEncode(szURL); } void WebDownloader::SetStatus(EStatus eStatus) { m_eStatus = eStatus; Notify(NULL); } void WebDownloader::Run() { debug("Entering WebDownloader-loop"); SetStatus(adRunning); int iRemainedDownloadRetries = g_pOptions->GetRetries() > 0 ? g_pOptions->GetRetries() : 1; int iRemainedConnectRetries = iRemainedDownloadRetries > 10 ? iRemainedDownloadRetries : 10; if (!m_bRetry) { iRemainedDownloadRetries = 1; iRemainedConnectRetries = 1; } EStatus Status = adFailed; while (!IsStopped() && iRemainedDownloadRetries > 0 && iRemainedConnectRetries > 0) { SetLastUpdateTimeNow(); Status = DownloadWithRedirects(5); if ((((Status == adFailed) && (iRemainedDownloadRetries > 1)) || ((Status == adConnectError) && (iRemainedConnectRetries > 1))) && !IsStopped() && !(!m_bForce && g_pOptions->GetPauseDownload())) { detail("Waiting %i sec to retry", g_pOptions->GetRetryInterval()); int msec = 0; while (!IsStopped() && (msec < g_pOptions->GetRetryInterval() * 1000) && !(!m_bForce && g_pOptions->GetPauseDownload())) { usleep(100 * 1000); msec += 100; } } if (IsStopped() || (!m_bForce && g_pOptions->GetPauseDownload())) { Status = adRetry; break; } if (Status == adFinished || Status == adFatalError || Status == adNotFound) { break; } if (Status != adConnectError) { iRemainedDownloadRetries--; } else { iRemainedConnectRetries--; } } if (Status != adFinished && Status != adRetry) { Status = adFailed; } if (Status == adFailed) { if (IsStopped()) { detail("Download %s cancelled", m_szInfoName); } else { error("Download %s failed", m_szInfoName); } } if (Status == adFinished) { detail("Download %s completed", m_szInfoName); } SetStatus(Status); debug("Exiting WebDownloader-loop"); } WebDownloader::EStatus WebDownloader::Download() { EStatus Status = adRunning; URL url(m_szURL); Status = CreateConnection(&url); if (Status != adRunning) { return Status; } m_pConnection->SetTimeout(g_pOptions->GetUrlTimeout()); m_pConnection->SetSuppressErrors(false); // connection bool bConnected = m_pConnection->Connect(); if (!bConnected || IsStopped()) { FreeConnection(); return adConnectError; } // Okay, we got a Connection. Now start downloading. detail("Downloading %s", m_szInfoName); SendHeaders(&url); Status = DownloadHeaders(); if (Status == adRunning) { Status = DownloadBody(); } if (IsStopped()) { Status = adFailed; } FreeConnection(); if (Status != adFinished) { // Download failed, delete broken output file remove(m_szOutputFilename); } return Status; } WebDownloader::EStatus WebDownloader::DownloadWithRedirects(int iMaxRedirects) { // do sync download, following redirects EStatus eStatus = adRedirect; while (eStatus == adRedirect && iMaxRedirects >= 0) { iMaxRedirects--; eStatus = Download(); } if (eStatus == adRedirect && iMaxRedirects < 0) { warn("Too many redirects for %s", m_szInfoName); eStatus = adFailed; } return eStatus; } WebDownloader::EStatus WebDownloader::CreateConnection(URL *pUrl) { if (!pUrl->IsValid()) { error("URL is not valid: %s", pUrl->GetAddress()); return adFatalError; } int iPort = pUrl->GetPort(); if (iPort == 0 && !strcasecmp(pUrl->GetProtocol(), "http")) { iPort = 80; } if (iPort == 0 && !strcasecmp(pUrl->GetProtocol(), "https")) { iPort = 443; } if (strcasecmp(pUrl->GetProtocol(), "http") && strcasecmp(pUrl->GetProtocol(), "https")) { error("Unsupported protocol in URL: %s", pUrl->GetAddress()); return adFatalError; } #ifdef DISABLE_TLS if (!strcasecmp(pUrl->GetProtocol(), "https")) { error("Program was compiled without TLS/SSL-support. Cannot download using https protocol. URL: %s", pUrl->GetAddress()); return adFatalError; } #endif bool bTLS = !strcasecmp(pUrl->GetProtocol(), "https"); m_pConnection = new Connection(pUrl->GetHost(), iPort, bTLS); return adRunning; } void WebDownloader::SendHeaders(URL *pUrl) { char tmp[1024]; // retrieve file snprintf(tmp, 1024, "GET %s HTTP/1.0\r\n", pUrl->GetResource()); tmp[1024-1] = '\0'; m_pConnection->WriteLine(tmp); snprintf(tmp, 1024, "User-Agent: nzbget/%s\r\n", Util::VersionRevision()); tmp[1024-1] = '\0'; m_pConnection->WriteLine(tmp); if ((!strcasecmp(pUrl->GetProtocol(), "http") && (pUrl->GetPort() == 80 || pUrl->GetPort() == 0)) || (!strcasecmp(pUrl->GetProtocol(), "https") && (pUrl->GetPort() == 443 || pUrl->GetPort() == 0))) { snprintf(tmp, 1024, "Host: %s\r\n", pUrl->GetHost()); } else { snprintf(tmp, 1024, "Host: %s:%i\r\n", pUrl->GetHost(), pUrl->GetPort()); } tmp[1024-1] = '\0'; m_pConnection->WriteLine(tmp); m_pConnection->WriteLine("Accept: */*\r\n"); #ifndef DISABLE_GZIP m_pConnection->WriteLine("Accept-Encoding: gzip\r\n"); #endif m_pConnection->WriteLine("Connection: close\r\n"); m_pConnection->WriteLine("\r\n"); } WebDownloader::EStatus WebDownloader::DownloadHeaders() { EStatus Status = adRunning; m_bConfirmedLength = false; const int LineBufSize = 1024*10; char* szLineBuf = (char*)malloc(LineBufSize); m_iContentLen = -1; bool bFirstLine = true; m_bGZip = false; m_bRedirecting = false; m_bRedirected = false; // Headers while (!IsStopped()) { SetLastUpdateTimeNow(); int iLen = 0; char* line = m_pConnection->ReadLine(szLineBuf, LineBufSize, &iLen); if (bFirstLine) { Status = CheckResponse(szLineBuf); if (Status != adRunning) { break; } bFirstLine = false; } // Have we encountered a timeout? if (!line) { if (!IsStopped()) { warn("URL %s failed: Unexpected end of file", m_szInfoName); } Status = adFailed; break; } debug("Header: %s", line); // detect body of response if (*line == '\r' || *line == '\n') { break; } Util::TrimRight(line); ProcessHeader(line); if (m_bRedirected) { Status = adRedirect; break; } } free(szLineBuf); return Status; } WebDownloader::EStatus WebDownloader::DownloadBody() { EStatus Status = adRunning; m_pOutFile = NULL; bool bEnd = false; const int LineBufSize = 1024*10; char* szLineBuf = (char*)malloc(LineBufSize); int iWrittenLen = 0; #ifndef DISABLE_GZIP m_pGUnzipStream = NULL; if (m_bGZip) { m_pGUnzipStream = new GUnzipStream(1024*10); } #endif // Body while (!IsStopped()) { SetLastUpdateTimeNow(); char* szBuffer; int iLen; m_pConnection->ReadBuffer(&szBuffer, &iLen); if (iLen == 0) { iLen = m_pConnection->TryRecv(szLineBuf, LineBufSize); szBuffer = szLineBuf; } // Connection closed or timeout? if (iLen <= 0) { if (iLen == 0 && m_iContentLen == -1 && iWrittenLen > 0) { bEnd = true; break; } if (!IsStopped()) { warn("URL %s failed: Unexpected end of file", m_szInfoName); } Status = adFailed; break; } // write to output file if (!Write(szBuffer, iLen)) { Status = adFatalError; break; } iWrittenLen += iLen; //detect end of file if (iWrittenLen == m_iContentLen || (m_iContentLen == -1 && m_bGZip && m_bConfirmedLength)) { bEnd = true; break; } } free(szLineBuf); #ifndef DISABLE_GZIP delete m_pGUnzipStream; #endif if (m_pOutFile) { fclose(m_pOutFile); } if (!bEnd && Status == adRunning && !IsStopped()) { warn("URL %s failed: file incomplete", m_szInfoName); Status = adFailed; } if (bEnd) { Status = adFinished; } return Status; } WebDownloader::EStatus WebDownloader::CheckResponse(const char* szResponse) { if (!szResponse) { if (!IsStopped()) { warn("URL %s: Connection closed by remote host", m_szInfoName); } return adConnectError; } const char* szHTTPResponse = strchr(szResponse, ' '); if (strncmp(szResponse, "HTTP", 4) || !szHTTPResponse) { warn("URL %s failed: %s", m_szInfoName, szResponse); return adFailed; } szHTTPResponse++; if (!strncmp(szHTTPResponse, "400", 3) || !strncmp(szHTTPResponse, "499", 3)) { warn("URL %s failed: %s", m_szInfoName, szHTTPResponse); return adConnectError; } else if (!strncmp(szHTTPResponse, "404", 3)) { 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 return adRunning; } else { // unknown error, no special handling warn("URL %s failed: %s", m_szInfoName, szResponse); return adFailed; } } void WebDownloader::ProcessHeader(const char* szLine) { if (!strncasecmp(szLine, "Content-Length: ", 16)) { m_iContentLen = atoi(szLine + 16); m_bConfirmedLength = true; } else if (!strncasecmp(szLine, "Content-Encoding: gzip", 22)) { m_bGZip = true; } 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) { // Examples: // Content-Disposition: attachment; filename="fname.ext" // Content-Disposition: attachement;filename=fname.ext // Content-Disposition: attachement;filename=fname.ext; const char *p = strstr(szContentDisposition, "filename"); if (!p) { return; } p = strchr(p, '='); if (!p) { return; } p++; while (*p == ' ') p++; char fname[1024]; strncpy(fname, p, 1024); fname[1024-1] = '\0'; char *pe = fname + strlen(fname) - 1; while ((*pe == ' ' || *pe == '\n' || *pe == '\r' || *pe == ';') && pe > fname) { *pe = '\0'; pe--; } WebUtil::HttpUnquote(fname); 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()) { // redirect within host char szResource[1024]; URL oldUrl(m_szURL); if (*szLocation == '/') { // absolute path within host strncpy(szResource, szLocation, 1024); szResource[1024-1] = '\0'; } else { // relative path within host strncpy(szResource, oldUrl.GetResource(), 1024); szResource[1024-1] = '\0'; char* p = strchr(szResource, '?'); if (p) { *p = '\0'; } p = strrchr(szResource, '/'); if (p) { p[1] = '\0'; } strncat(szResource, szLocation, 1024 - strlen(szResource)); szResource[1024-1] = '\0'; } if (oldUrl.GetPort() > 0) { snprintf(szUrlBuf, 1024, "%s://%s:%i%s", oldUrl.GetProtocol(), oldUrl.GetHost(), oldUrl.GetPort(), szResource); } else { snprintf(szUrlBuf, 1024, "%s://%s%s", oldUrl.GetProtocol(), oldUrl.GetHost(), szResource); } 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()) { return false; } #ifndef DISABLE_GZIP if (m_bGZip) { m_pGUnzipStream->Write(pBuffer, iLen); const void *pOutBuf; int iOutLen = 1; while (iOutLen > 0) { GUnzipStream::EStatus eGZStatus = m_pGUnzipStream->Read(&pOutBuf, &iOutLen); if (eGZStatus == GUnzipStream::zlError) { error("URL %s: GUnzip failed", m_szInfoName); return false; } if (iOutLen > 0 && fwrite(pOutBuf, 1, iOutLen, m_pOutFile) <= 0) { return false; } if (eGZStatus == GUnzipStream::zlFinished) { m_bConfirmedLength = true; return true; } } return true; } else #endif return fwrite(pBuffer, 1, iLen, m_pOutFile) > 0; } bool WebDownloader::PrepareFile() { // prepare file for writing const char* szFilename = m_szOutputFilename; m_pOutFile = fopen(szFilename, FOPEN_WB); if (!m_pOutFile) { error("Could not %s file %s", "create", szFilename); return false; } if (g_pOptions->GetWriteBuffer() > 0) { setvbuf(m_pOutFile, NULL, _IOFBF, g_pOptions->GetWriteBuffer() * 1024); } return true; } void WebDownloader::LogDebugInfo() { char szTime[50]; #ifdef HAVE_CTIME_R_3 ctime_r(&m_tLastUpdateTime, szTime, 50); #else ctime_r(&m_tLastUpdateTime, szTime); #endif info(" Web-Download: status=%i, LastUpdateTime=%s, filename=%s", m_eStatus, szTime, Util::BaseFileName(m_szOutputFilename)); } void WebDownloader::Stop() { debug("Trying to stop WebDownloader"); Thread::Stop(); m_mutexConnection.Lock(); if (m_pConnection) { m_pConnection->SetSuppressErrors(true); m_pConnection->Cancel(); } m_mutexConnection.Unlock(); debug("WebDownloader stopped successfully"); } bool WebDownloader::Terminate() { Connection* pConnection = m_pConnection; bool terminated = Kill(); if (terminated && pConnection) { debug("Terminating connection"); pConnection->SetSuppressErrors(true); pConnection->Cancel(); pConnection->Disconnect(); delete pConnection; } return terminated; } void WebDownloader::FreeConnection() { if (m_pConnection) { debug("Releasing connection"); m_mutexConnection.Lock(); if (m_pConnection->GetStatus() == Connection::csCancelled) { m_pConnection->Disconnect(); } delete m_pConnection; m_pConnection = NULL; m_mutexConnection.Unlock(); } } nzbget-16.4/daemon/connect/Connection.h0000644000175000017500000001023612630544544017726 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 CONNECTION_H #define CONNECTION_H #ifndef HAVE_GETADDRINFO #ifndef HAVE_GETHOSTBYNAME_R #include "Thread.h" #endif #endif #ifndef DISABLE_TLS #include "TLS.h" #endif class Connection { public: enum EStatus { csConnected, csDisconnected, csListening, csCancelled }; protected: char* m_szHost; int m_iPort; SOCKET m_iSocket; bool m_bTLS; char* m_szCipher; char* m_szReadBuf; int m_iBufAvail; char* m_szBufPtr; EStatus m_eStatus; int m_iTimeout; bool m_bSuppressErrors; char m_szRemoteAddr[20]; int m_iTotalBytesRead; bool m_bBroken; bool m_bGracefull; struct SockAddr { int ai_family; int ai_socktype; int ai_protocol; bool operator==(const SockAddr& rhs) const { return memcmp(this, &rhs, sizeof(SockAddr)) == 0; } }; #ifndef DISABLE_TLS class ConTLSSocket: public TLSSocket { private: Connection* m_pOwner; protected: virtual void PrintError(const char* szErrMsg) { m_pOwner->PrintError(szErrMsg); } public: ConTLSSocket(SOCKET iSocket, bool bIsClient, const char* szCertFile, const char* szKeyFile, const char* szCipher, Connection* pOwner): TLSSocket(iSocket, bIsClient, szCertFile, szKeyFile, szCipher), m_pOwner(pOwner) {} }; ConTLSSocket* m_pTLSSocket; bool m_bTLSError; #endif #ifndef HAVE_GETADDRINFO #ifndef HAVE_GETHOSTBYNAME_R static Mutex* m_pMutexGetHostByName; #endif #endif Connection(SOCKET iSocket, bool bTLS); void ReportError(const char* szMsgPrefix, const char* szMsgArg, bool PrintErrCode, int herrno); virtual void PrintError(const char* szErrMsg); bool DoConnect(); bool DoDisconnect(); bool InitSocketOpts(); bool ConnectWithTimeout(void* address, int address_len); #ifndef HAVE_GETADDRINFO unsigned int ResolveHostAddr(const char* szHost); #endif #ifndef DISABLE_TLS int recv(SOCKET s, char* buf, int len, int flags); int send(SOCKET s, const char* buf, int len, int flags); void CloseTLS(); #endif public: Connection(const char* szHost, int iPort, bool bTLS); virtual ~Connection(); static void Init(); static void Final(); virtual bool Connect(); virtual bool Disconnect(); bool Bind(); bool Send(const char* pBuffer, int iSize); bool Recv(char* pBuffer, int iSize); int TryRecv(char* pBuffer, int iSize); char* ReadLine(char* pBuffer, int iSize, int* pBytesRead); void ReadBuffer(char** pBuffer, int *iBufLen); int WriteLine(const char* pBuffer); Connection* Accept(); void Cancel(); const char* GetHost() { return m_szHost; } int GetPort() { return m_iPort; } bool GetTLS() { return m_bTLS; } const char* GetCipher() { return m_szCipher; } void SetCipher(const char* szCipher); void SetTimeout(int iTimeout) { m_iTimeout = iTimeout; } EStatus GetStatus() { return m_eStatus; } void SetSuppressErrors(bool bSuppressErrors); bool GetSuppressErrors() { return m_bSuppressErrors; } const char* GetRemoteAddr(); bool GetGracefull() { return m_bGracefull; } void SetGracefull(bool bGracefull) { m_bGracefull = bGracefull; } #ifndef DISABLE_TLS bool StartTLS(bool bIsClient, const char* szCertFile, const char* szKeyFile); #endif int FetchTotalBytesRead(); }; #endif nzbget-16.4/daemon/connect/WebDownloader.h0000644000175000017500000000603212630544544020362 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2012-2013 Andrey Prygunkov * * 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 WEBDOWNLOADER_H #define WEBDOWNLOADER_H #include #include "Observer.h" #include "Thread.h" #include "Connection.h" #include "Util.h" class WebDownloader : public Thread, public Subject { public: enum EStatus { adUndefined, adRunning, adFinished, adFailed, adRetry, adNotFound, adRedirect, adConnectError, adFatalError }; private: char* m_szURL; char* m_szOutputFilename; Connection* m_pConnection; Mutex m_mutexConnection; EStatus m_eStatus; time_t m_tLastUpdateTime; char* m_szInfoName; FILE* m_pOutFile; int m_iContentLen; bool m_bConfirmedLength; char* m_szOriginalFilename; bool m_bForce; bool m_bRedirecting; bool m_bRedirected; bool m_bGZip; bool m_bRetry; #ifndef DISABLE_GZIP GUnzipStream* m_pGUnzipStream; #endif void SetStatus(EStatus eStatus); bool Write(void* pBuffer, int iLen); bool PrepareFile(); void FreeConnection(); EStatus CheckResponse(const char* szResponse); EStatus CreateConnection(URL *pUrl); void ParseFilename(const char* szContentDisposition); void SendHeaders(URL *pUrl); EStatus DownloadHeaders(); EStatus DownloadBody(); void ParseRedirect(const char* szLocation); protected: virtual void ProcessHeader(const char* szLine); public: WebDownloader(); virtual ~WebDownloader(); EStatus GetStatus() { return m_eStatus; } virtual void Run(); virtual void Stop(); EStatus Download(); EStatus DownloadWithRedirects(int iMaxRedirects); bool Terminate(); void SetInfoName(const char* v); const char* GetInfoName() { return m_szInfoName; } void SetURL(const char* szURL); const char* GetOutputFilename() { return m_szOutputFilename; } void SetOutputFilename(const char* v); time_t GetLastUpdateTime() { return m_tLastUpdateTime; } void SetLastUpdateTimeNow() { m_tLastUpdateTime = ::time(NULL); } bool GetConfirmedLength() { return m_bConfirmedLength; } const char* GetOriginalFilename() { return m_szOriginalFilename; } void SetForce(bool bForce) { m_bForce = bForce; } void SetRetry(bool bRetry) { m_bRetry = bRetry; } void LogDebugInfo(); }; #endif nzbget-16.4/daemon/connect/Connection.cpp0000644000175000017500000005265012630544544020267 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 // SKIP_DEFAULT_WINDOWS_HEADERS prevents the including of , which includes "winsock.h", // but we need "winsock2.h" here (they conflicts with each other) #define SKIP_DEFAULT_WINDOWS_HEADERS #include "win32.h" #endif #include #include #include #ifdef WIN32 #include #include #else #include #include #include #include #include #include #include #include #endif #include #include #include "nzbget.h" #include "Connection.h" #include "Log.h" static const int CONNECTION_READBUFFER_SIZE = 1024; #ifndef HAVE_GETADDRINFO #ifndef HAVE_GETHOSTBYNAME_R Mutex* Connection::m_pMutexGetHostByName = NULL; #endif #endif void closesocket_gracefully(SOCKET iSocket) { char buf[1024]; struct linger linger; // Set linger option to avoid socket hanging out after close. This prevent // ephemeral port exhaust problem under high QPS. linger.l_onoff = 1; linger.l_linger = 1; setsockopt(iSocket, SOL_SOCKET, SO_LINGER, (char *) &linger, sizeof(linger)); // Send FIN to the client shutdown(iSocket, SHUT_WR); // Set non-blocking mode #ifdef WIN32 unsigned long on = 1; ioctlsocket(iSocket, FIONBIO, &on); #else int flags; flags = fcntl(iSocket, F_GETFL, 0); fcntl(iSocket, F_SETFL, flags | O_NONBLOCK); #endif // Read and discard pending incoming data. If we do not do that and close the // socket, the data in the send buffer may be discarded. This // behaviour is seen on Windows, when client keeps sending data // when server decides to close the connection; then when client // does recv() it gets no data back. int n; do { n = recv(iSocket, buf, sizeof(buf), 0); } while (n > 0); // Now we know that our FIN is ACK-ed, safe to close closesocket(iSocket); } void Connection::Init() { debug("Initializing global connection data"); #ifdef WIN32 WSADATA wsaData; int err = WSAStartup(MAKEWORD(2, 0), &wsaData); if (err != 0) { error("Could not initialize socket library"); return; } if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE( wsaData.wVersion ) != 0) { error("Could not initialize socket library"); WSACleanup(); return; } #endif #ifndef DISABLE_TLS TLSSocket::Init(); #endif #ifndef HAVE_GETADDRINFO #ifndef HAVE_GETHOSTBYNAME_R m_pMutexGetHostByName = new Mutex(); #endif #endif } void Connection::Final() { debug("Finalizing global connection data"); #ifdef WIN32 WSACleanup(); #endif #ifndef DISABLE_TLS TLSSocket::Final(); #endif #ifndef HAVE_GETADDRINFO #ifndef HAVE_GETHOSTBYNAME_R delete m_pMutexGetHostByName; #endif #endif } Connection::Connection(const char* szHost, int iPort, bool bTLS) { debug("Creating Connection"); m_szHost = NULL; m_iPort = iPort; m_bTLS = bTLS; m_szCipher = NULL; m_eStatus = csDisconnected; m_iSocket = INVALID_SOCKET; m_iBufAvail = 0; m_iTimeout = 60; m_bSuppressErrors = true; m_szReadBuf = (char*)malloc(CONNECTION_READBUFFER_SIZE + 1); m_iTotalBytesRead = 0; m_bBroken = false; m_bGracefull = false; #ifndef DISABLE_TLS m_pTLSSocket = NULL; m_bTLSError = false; #endif if (szHost) { m_szHost = strdup(szHost); } } Connection::Connection(SOCKET iSocket, bool bTLS) { debug("Creating Connection"); m_szHost = NULL; m_iPort = 0; m_bTLS = bTLS; m_szCipher = NULL; m_eStatus = csConnected; m_iSocket = iSocket; m_iBufAvail = 0; m_iTimeout = 60; m_bSuppressErrors = true; m_szReadBuf = (char*)malloc(CONNECTION_READBUFFER_SIZE + 1); #ifndef DISABLE_TLS m_pTLSSocket = NULL; m_bTLSError = false; #endif } Connection::~Connection() { debug("Destroying Connection"); Disconnect(); free(m_szHost); free(m_szCipher); free(m_szReadBuf); #ifndef DISABLE_TLS delete m_pTLSSocket; #endif } void Connection::SetSuppressErrors(bool bSuppressErrors) { m_bSuppressErrors = bSuppressErrors; #ifndef DISABLE_TLS if (m_pTLSSocket) { m_pTLSSocket->SetSuppressErrors(bSuppressErrors); } #endif } void Connection::SetCipher(const char* szCipher) { free(m_szCipher); m_szCipher = szCipher ? strdup(szCipher) : NULL; } bool Connection::Connect() { debug("Connecting"); if (m_eStatus == csConnected) { return true; } bool bRes = DoConnect(); if (bRes) { m_eStatus = csConnected; } else { DoDisconnect(); } return bRes; } bool Connection::Disconnect() { debug("Disconnecting"); if (m_eStatus == csDisconnected) { return true; } bool bRes = DoDisconnect(); m_eStatus = csDisconnected; m_iSocket = INVALID_SOCKET; m_iBufAvail = 0; return bRes; } bool Connection::Bind() { debug("Binding"); if (m_eStatus == csListening) { return true; } #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) { ReportError("Could not resolve hostname %s", m_szHost, false, 0); return false; } m_bBroken = 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); #ifdef WIN32 SetHandleInformation((HANDLE)m_iSocket, HANDLE_FLAG_INHERIT, 0); #endif 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 true; } int Connection::WriteLine(const char* pBuffer) { //debug("Connection::WriteLine"); if (m_eStatus != csConnected) { return -1; } int iRes = send(m_iSocket, pBuffer, strlen(pBuffer), 0); if (iRes <= 0) { m_bBroken = true; } return iRes; } bool Connection::Send(const char* pBuffer, int iSize) { debug("Sending data"); if (m_eStatus != csConnected) { return false; } int iBytesSent = 0; while (iBytesSent < iSize) { int iRes = send(m_iSocket, pBuffer + iBytesSent, iSize-iBytesSent, 0); if (iRes <= 0) { m_bBroken = true; return false; } iBytesSent += iRes; } return true; } char* Connection::ReadLine(char* pBuffer, int iSize, int* pBytesRead) { if (m_eStatus != csConnected) { return NULL; } 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); m_bBroken = true; 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; } m_iTotalBytesRead += iBytesRead; if (pBufPtr == pBuffer) { return NULL; } return pBuffer; } Connection* Connection::Accept() { debug("Accepting connection"); if (m_eStatus != csListening) { return NULL; } 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(iSocket, m_bTLS); return pCon; } int Connection::TryRecv(char* pBuffer, int iSize) { debug("Receiving data"); memset(pBuffer, 0, iSize); int iReceived = recv(m_iSocket, pBuffer, iSize, 0); if (iReceived < 0) { ReportError("Could not receive data on socket", NULL, true, 0); } return iReceived; } bool Connection::Recv(char * pBuffer, int iSize) { debug("Receiving data (full buffer)"); memset(pBuffer, 0, iSize); char* pBufPtr = (char*)pBuffer; int NeedBytes = iSize; if (m_iBufAvail > 0) { int len = iSize > m_iBufAvail ? m_iBufAvail : iSize; memcpy(pBufPtr, m_szBufPtr, len); pBufPtr += len; m_szBufPtr += len; m_iBufAvail -= len; NeedBytes -= len; } // Read from the socket until nothing remains while (NeedBytes > 0) { int iReceived = recv(m_iSocket, pBufPtr, NeedBytes, 0); // Did the recv succeed? if (iReceived <= 0) { ReportError("Could not receive data on socket", NULL, true, 0); return false; } pBufPtr += iReceived; NeedBytes -= iReceived; } return true; } bool Connection::DoConnect() { debug("Do connecting"); m_iSocket = INVALID_SOCKET; m_bBroken = false; #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, sprintf(iPortStr, "%d", m_iPort); int res = getaddrinfo(m_szHost, iPortStr, &addr_hints, &addr_list); if (res != 0) { ReportError("Could not resolve hostname %s", m_szHost, true, 0); return false; } std::vector triedAddr; bool bConnected = false; for (addr = addr_list; addr != NULL; addr = addr->ai_next) { // don't try the same combinations of ai_family, ai_socktype, ai_protocol multiple times SockAddr sa = { addr->ai_family, addr->ai_socktype, addr->ai_protocol }; if (std::find(triedAddr.begin(), triedAddr.end(), sa) != triedAddr.end()) { continue; } triedAddr.push_back(sa); if (m_iSocket != INVALID_SOCKET) { closesocket(m_iSocket); } m_iSocket = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); #ifdef WIN32 SetHandleInformation((HANDLE)m_iSocket, HANDLE_FLAG_INHERIT, 0); #endif if (m_iSocket == INVALID_SOCKET) { // try another addr/family/protocol continue; } if (ConnectWithTimeout(addr->ai_addr, addr->ai_addrlen)) { // Connection established bConnected = true; break; } } if (m_iSocket == INVALID_SOCKET && addr_list) { ReportError("Socket creation failed for %s", m_szHost, true, 0); } if (!bConnected && m_iSocket != INVALID_SOCKET) { ReportError("Connection to %s failed", m_szHost, true, 0); closesocket(m_iSocket); m_iSocket = INVALID_SOCKET; } freeaddrinfo(addr_list); if (m_iSocket == INVALID_SOCKET) { return false; } #else struct sockaddr_in sSocketAddress; memset(&sSocketAddress, 0, sizeof(sSocketAddress)); sSocketAddress.sin_family = AF_INET; sSocketAddress.sin_port = htons(m_iPort); sSocketAddress.sin_addr.s_addr = ResolveHostAddr(m_szHost); if (sSocketAddress.sin_addr.s_addr == (unsigned int)-1) { return false; } 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; } if (!ConnectWithTimeout(&sSocketAddress, sizeof(sSocketAddress))) { ReportError("Connection to %s failed", m_szHost, true, 0); closesocket(m_iSocket); m_iSocket = INVALID_SOCKET; return false; } #endif if (!InitSocketOpts()) { return false; } #ifndef DISABLE_TLS if (m_bTLS && !StartTLS(true, NULL, NULL)) { return false; } #endif return true; } bool Connection::InitSocketOpts() { char* optbuf = NULL; int optsize = 0; #ifdef WIN32 int MSecVal = m_iTimeout * 1000; optbuf = (char*)&MSecVal; optsize = sizeof(MSecVal); #else struct timeval TimeVal; TimeVal.tv_sec = m_iTimeout; TimeVal.tv_usec = 0; optbuf = (char*)&TimeVal; optsize = sizeof(TimeVal); #endif int err = setsockopt(m_iSocket, SOL_SOCKET, SO_RCVTIMEO, optbuf, optsize); if (err != 0) { ReportError("Socket initialization failed for %s", m_szHost, true, 0); return false; } err = setsockopt(m_iSocket, SOL_SOCKET, SO_SNDTIMEO, optbuf, optsize); if (err != 0) { ReportError("Socket initialization failed for %s", m_szHost, true, 0); return false; } return true; } bool Connection::ConnectWithTimeout(void* address, int address_len) { int flags = 0, error = 0, ret = 0; fd_set rset, wset; socklen_t len = sizeof(error); struct timeval ts; ts.tv_sec = m_iTimeout; ts.tv_usec = 0; //clear out descriptor sets for select //add socket to the descriptor sets FD_ZERO(&rset); FD_SET(m_iSocket, &rset); wset = rset; //structure assignment ok //set socket nonblocking flag #ifdef WIN32 u_long mode = 1; if (ioctlsocket(m_iSocket, FIONBIO, &mode) != 0) { return false; } #else flags = fcntl(m_iSocket, F_GETFL, 0); if (flags < 0) { return false; } if (fcntl(m_iSocket, F_SETFL, flags | O_NONBLOCK) < 0) { return false; } #endif //initiate non-blocking connect ret = connect(m_iSocket, (struct sockaddr*)address, address_len); if (ret < 0) { #ifdef WIN32 int err = WSAGetLastError(); if (err != WSAEWOULDBLOCK) { return false; } #else if (errno != EINPROGRESS) { return false; } #endif } //connect succeeded right away? if (ret != 0) { ret = select(m_iSocket + 1, &rset, &wset, NULL, m_iTimeout ? &ts : NULL); //we are waiting for connect to complete now if (ret < 0) { return false; } if (ret == 0) { //we had a timeout #ifdef WIN32 WSASetLastError(WSAETIMEDOUT); #else errno = ETIMEDOUT; #endif return false; } if (!(FD_ISSET(m_iSocket, &rset) || FD_ISSET(m_iSocket, &wset))) { return false; } //we had a positivite return so a descriptor is ready if (getsockopt(m_iSocket, SOL_SOCKET, SO_ERROR, (char*)&error, &len) < 0) { return false; } //check if we had a socket error if (error) { errno = error; return false; } } //put socket back in blocking mode #ifdef WIN32 mode = 0; if (ioctlsocket(m_iSocket, FIONBIO, &mode) != 0) { return false; } #else if (fcntl(m_iSocket, F_SETFL, flags) < 0) { return false; } #endif return true; } bool Connection::DoDisconnect() { debug("Do disconnecting"); if (m_iSocket != INVALID_SOCKET) { #ifndef DISABLE_TLS CloseTLS(); #endif if (m_bGracefull) { closesocket_gracefully(m_iSocket); } else { closesocket(m_iSocket); } m_iSocket = INVALID_SOCKET; } m_eStatus = csDisconnected; return true; } void Connection::ReadBuffer(char** pBuffer, int *iBufLen) { *iBufLen = m_iBufAvail; *pBuffer = m_szBufPtr; m_iBufAvail = 0; }; void Connection::Cancel() { debug("Cancelling connection"); if (m_iSocket != INVALID_SOCKET) { m_eStatus = csCancelled; int r = shutdown(m_iSocket, SHUT_RDWR); if (r == -1) { ReportError("Could not shutdown connection", NULL, true, 0); } } } void Connection::ReportError(const char* szMsgPrefix, const char* szMsgArg, bool PrintErrCode, int herrno) { #ifndef DISABLE_TLS if (m_bTLSError) { // TLS-Error was already reported m_bTLSError = false; return; } #endif char szErrPrefix[1024]; snprintf(szErrPrefix, 1024, szMsgPrefix, szMsgArg); szErrPrefix[1024-1] = '\0'; char szMessage[1024]; if (PrintErrCode) { #ifdef WIN32 int ErrCode = WSAGetLastError(); char szErrMsg[1024]; szErrMsg[0] = '\0'; FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, ErrCode, 0, szErrMsg, 1024, NULL); szErrMsg[1024-1] = '\0'; #else const char *szErrMsg = NULL; int ErrCode = herrno; if (herrno == 0) { ErrCode = errno; szErrMsg = strerror(ErrCode); } else { szErrMsg = hstrerror(ErrCode); } #endif if (m_bSuppressErrors) { debug("%s: ErrNo %i, %s", szErrPrefix, ErrCode, szErrMsg); } else { snprintf(szMessage, sizeof(szMessage), "%s: ErrNo %i, %s", szErrPrefix, ErrCode, szErrMsg); szMessage[sizeof(szMessage) - 1] = '\0'; PrintError(szMessage); } } else { if (m_bSuppressErrors) { debug(szErrPrefix); } else { PrintError(szErrPrefix); } } } void Connection::PrintError(const char* szErrMsg) { error("%s", szErrMsg); } #ifndef DISABLE_TLS bool Connection::StartTLS(bool bIsClient, const char* szCertFile, const char* szKeyFile) { debug("Starting TLS"); delete m_pTLSSocket; m_pTLSSocket = new ConTLSSocket(m_iSocket, bIsClient, szCertFile, szKeyFile, m_szCipher, this); m_pTLSSocket->SetSuppressErrors(m_bSuppressErrors); return m_pTLSSocket->Start(); } void Connection::CloseTLS() { if (m_pTLSSocket) { m_pTLSSocket->Close(); delete m_pTLSSocket; m_pTLSSocket = NULL; } } int Connection::recv(SOCKET s, char* buf, int len, int flags) { int iReceived = 0; if (m_pTLSSocket) { m_bTLSError = false; iReceived = m_pTLSSocket->Recv(buf, len); if (iReceived < 0) { m_bTLSError = true; return -1; } } else { iReceived = ::recv(s, buf, len, flags); } return iReceived; } int Connection::send(SOCKET s, const char* buf, int len, int flags) { int iSent = 0; if (m_pTLSSocket) { m_bTLSError = false; iSent = m_pTLSSocket->Send(buf, len); if (iSent < 0) { m_bTLSError = true; return -1; } return iSent; } else { iSent = ::send(s, buf, len, flags); return iSent; } } #endif #ifndef HAVE_GETADDRINFO unsigned int Connection::ResolveHostAddr(const char* szHost) { unsigned int uaddr = inet_addr(szHost); if (uaddr == (unsigned int)-1) { struct hostent* hinfo; bool err = false; int h_errnop = 0; #ifdef HAVE_GETHOSTBYNAME_R struct hostent hinfobuf; char strbuf[1024]; #ifdef HAVE_GETHOSTBYNAME_R_6 err = gethostbyname_r(szHost, &hinfobuf, strbuf, sizeof(strbuf), &hinfo, &h_errnop); err = err || (hinfo == NULL); // error on null hinfo (means 'no entry') #endif #ifdef HAVE_GETHOSTBYNAME_R_5 hinfo = gethostbyname_r(szHost, &hinfobuf, strbuf, sizeof(strbuf), &h_errnop); err = hinfo == NULL; #endif #ifdef HAVE_GETHOSTBYNAME_R_3 //NOTE: gethostbyname_r with three parameters were not tested struct hostent_data hinfo_data; hinfo = gethostbyname_r((char*)szHost, (struct hostent*)hinfobuf, &hinfo_data); err = hinfo == NULL; #endif #else m_pMutexGetHostByName->Lock(); hinfo = gethostbyname(szHost); err = hinfo == NULL; #endif if (err) { #ifndef HAVE_GETHOSTBYNAME_R m_pMutexGetHostByName->Unlock(); #endif ReportError("Could not resolve hostname %s", szHost, true, h_errnop); return (unsigned int)-1; } memcpy(&uaddr, hinfo->h_addr_list[0], sizeof(uaddr)); #ifndef HAVE_GETHOSTBYNAME_R m_pMutexGetHostByName->Unlock(); #endif } return uaddr; } #endif const char* Connection::GetRemoteAddr() { struct sockaddr_in PeerName; int iPeerNameLength = sizeof(PeerName); if (getpeername(m_iSocket, (struct sockaddr*)&PeerName, (SOCKLEN_T*) &iPeerNameLength) >= 0) { #ifdef WIN32 strncpy(m_szRemoteAddr, inet_ntoa(PeerName.sin_addr), sizeof(m_szRemoteAddr)); #else inet_ntop(AF_INET, &PeerName.sin_addr, m_szRemoteAddr, sizeof(m_szRemoteAddr)); #endif } m_szRemoteAddr[sizeof(m_szRemoteAddr)-1] = '\0'; return m_szRemoteAddr; } int Connection::FetchTotalBytesRead() { int iTotal = m_iTotalBytesRead; m_iTotalBytesRead = 0; return iTotal; }nzbget-16.4/daemon/connect/TLS.h0000644000175000017500000000345512630544544016276 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2008-2015 Andrey Prygunkov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision$ * $Date$ * */ #ifndef TLS_H #define TLS_H #ifndef DISABLE_TLS class TLSSocket { private: bool m_bIsClient; char* m_szCertFile; char* m_szKeyFile; char* m_szCipher; SOCKET m_iSocket; bool m_bSuppressErrors; int m_iRetCode; bool m_bInitialized; bool m_bConnected; // using "void*" to prevent the including of GnuTLS/OpenSSL header files into TLS.h void* m_pContext; void* m_pSession; void ReportError(const char* szErrMsg); protected: virtual void PrintError(const char* szErrMsg); public: TLSSocket(SOCKET iSocket, bool bIsClient, const char* szCertFile, const char* szKeyFile, const char* szCipher); virtual ~TLSSocket(); static void Init(); static void Final(); bool Start(); void Close(); int Send(const char* pBuffer, int iSize); int Recv(char* pBuffer, int iSize); void SetSuppressErrors(bool bSuppressErrors) { m_bSuppressErrors = bSuppressErrors; } }; #endif #endif nzbget-16.4/daemon/remote/0000755000175000017500000000000012630544544015316 5ustar andreasandreasnzbget-16.4/daemon/remote/MessageBase.h0000644000175000017500000005353512630544544017661 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2005 Bo Cordes Petersen * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 MESSAGEBASE_H #define MESSAGEBASE_H #if (!(defined(WIN32) && _MSC_VER < 1600)) #include #endif static const int32_t NZBMESSAGE_SIGNATURE = 0x6E7A6228; // = "nzb-XX" (protocol version) static const int NZBREQUESTFILENAMESIZE = 512; static const int NZBREQUESTPASSWORDSIZE = 32; /** * NZBGet communication protocol uses only two basic data types: integer and char. * Integer values are passed using network byte order (Big-Endian). * Use function "htonl" and "ntohl" to convert integers to/from machine * (host) byte order. * All char-strings ends with NULL-char. * * NOTE: * NZBGet communication protocol is intended for usage only by NZBGet itself. * The communication works only if server and client has the same version. * The compatibility with previous program versions is not provided. * Third-party programs should use JSON-RPC or XML-RPC to communicate with NZBGet. */ // Possible values for field "m_iType" of struct "SNZBRequestBase": enum eRemoteRequest { eRemoteRequestDownload = 1, eRemoteRequestPauseUnpause, eRemoteRequestList, eRemoteRequestSetDownloadRate, eRemoteRequestDumpDebug, eRemoteRequestEditQueue, eRemoteRequestLog, eRemoteRequestShutdown, eRemoteRequestReload, eRemoteRequestVersion, eRemoteRequestPostQueue, eRemoteRequestWriteLog, eRemoteRequestScan, eRemoteRequestHistory }; // Possible values for field "m_iAction" of struct "SNZBPauseUnpauseRequest": enum eRemotePauseUnpauseAction { eRemotePauseUnpauseActionDownload = 1, // pause/unpause download queue eRemotePauseUnpauseActionPostProcess, // pause/unpause post-processor queue eRemotePauseUnpauseActionScan // pause/unpause scan of incoming nzb-directory }; // Possible values for field "m_iMatchMode" of struct "SNZBEditQueueRequest": enum eRemoteMatchMode { eRemoteMatchModeID = 1, // ID eRemoteMatchModeName, // Name eRemoteMatchModeRegEx, // RegEx }; // The basic SNZBRequestBase struct, used in all requests 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_szUsername[NZBREQUESTPASSWORDSIZE]; // User name char m_szPassword[NZBREQUESTPASSWORDSIZE]; // Password }; // The basic SNZBResposneBase struct, used in all responses struct SNZBResponseBase { int32_t m_iSignature; // Signature must be NZBMESSAGE_SIGNATURE in integer-value int32_t m_iStructSize; // Size of the entire struct }; // A download request struct SNZBDownloadRequest { SNZBRequestBase m_MessageBase; // Must be the first in the struct char m_szNZBFilename[NZBREQUESTFILENAMESIZE];// Name of nzb-file. For URLs can be empty, then the filename is read from URL download response char m_szCategory[NZBREQUESTFILENAMESIZE]; // Category, can be empty int32_t m_bAddFirst; // 1 - add file to the top of download queue int32_t m_bAddPaused; // 1 - pause added files int32_t m_iPriority; // Priority for files (0 - default) int32_t m_iDupeScore; // Duplicate score int32_t m_iDupeMode; // Duplicate mode (EDupeMode) char m_szDupeKey[NZBREQUESTFILENAMESIZE]; // Duplicate key int32_t m_iTrailingDataLength; // Length of nzb-file in bytes //char m_szContent[m_iTrailingDataLength]; // variable sized }; // A download response struct SNZBDownloadResponse { SNZBResponseBase m_MessageBase; // Must be the first in the struct int32_t m_bSuccess; // 0 - command failed, 1 - command executed successfully int32_t m_iTrailingDataLength; // Length of Text-string (m_szText), following to this record //char m_szText[m_iTrailingDataLength]; // variable sized }; // A list and status request struct SNZBListRequest { SNZBRequestBase m_MessageBase; // Must be the first in the struct int32_t m_bFileList; // 1 - return file list int32_t m_bServerState; // 1 - return server state int32_t m_iMatchMode; // File/Group match mode, see enum eRemoteMatchMode (only values eRemoteMatchModeID (no filter) and eRemoteMatchModeRegEx are allowed) int32_t m_bMatchGroup; // 0 - match files; 1 - match nzbs (when m_iMatchMode == eRemoteMatchModeRegEx) char m_szPattern[NZBREQUESTFILENAMESIZE]; // RegEx Pattern (when m_iMatchMode == eRemoteMatchModeRegEx) }; // A list response struct SNZBListResponse { SNZBResponseBase m_MessageBase; // Must be the first in the struct int32_t m_iEntrySize; // Size of the SNZBListResponseEntry-struct int32_t m_iRemainingSizeLo; // Remaining size in bytes, Low 32-bits of 64-bit value int32_t m_iRemainingSizeHi; // Remaining size in bytes, High 32-bits of 64-bit value int32_t m_iDownloadRate; // Current download speed, in Bytes pro Second int32_t m_iDownloadLimit; // Current download limit, in Bytes pro Second int32_t m_bDownloadPaused; // 1 - download queue is currently in paused-state int32_t m_bDownload2Paused; // 1 - download queue is currently in paused-state (second pause-register) int32_t m_bDownloadStandBy; // 0 - there are currently downloads running, 1 - no downloads in progress (download queue paused or all download jobs completed) int32_t m_bPostPaused; // 1 - post-processor queue is currently in paused-state int32_t m_bScanPaused; // 1 - scaning of incoming directory is currently in paused-state int32_t m_iThreadCount; // Number of threads running int32_t m_iPostJobCount; // Number of jobs in post-processor queue (including current job) int32_t m_iUpTimeSec; // Server up time in seconds int32_t m_iDownloadTimeSec; // Server download time in seconds (up_time - standby_time) int32_t m_iDownloadedBytesLo; // Amount of data downloaded since server start, Low 32-bits of 64-bit value int32_t m_iDownloadedBytesHi; // Amount of data downloaded since server start, High 32-bits of 64-bit value int32_t m_bRegExValid; // 0 - error in RegEx-pattern, 1 - RegEx-pattern is valid (only when Request has eRemoteMatchModeRegEx) int32_t m_iNrTrailingNZBEntries; // Number of List-NZB-entries, following to this structure int32_t m_iNrTrailingPPPEntries; // Number of List-PPP-entries, following to this structure int32_t m_iNrTrailingFileEntries; // Number of List-File-entries, following to this structure int32_t m_iTrailingDataLength; // Length of all List-entries, following to this structure // SNZBListResponseEntry m_NZBEntries[m_iNrTrailingNZBEntries] // variable sized // SNZBListResponseEntry m_PPPEntries[m_iNrTrailingPPPEntries] // variable sized // SNZBListResponseEntry m_FileEntries[m_iNrTrailingFileEntries] // variable sized }; // A list response nzb entry struct SNZBListResponseNZBEntry { int32_t m_iID; // NZB-ID int32_t m_iKind; // Item Kind (see NZBInfo::Kind) int32_t m_iSizeLo; // Size of all files in bytes, Low 32-bits of 64-bit value int32_t m_iSizeHi; // Size of all files in bytes, High 32-bits of 64-bit value int32_t m_iRemainingSizeLo; // Size of remaining (unpaused) files in bytes, Low 32-bits of 64-bit value int32_t m_iRemainingSizeHi; // Size of remaining (unpaused) files in bytes, High 32-bits of 64-bit value int32_t m_iPausedSizeLo; // Size of npaused files in bytes, Low 32-bits of 64-bit value int32_t m_iPausedSizeHi; // Size of paused files in bytes, High 32-bits of 64-bit value int32_t m_iPausedCount; // Number of paused files int32_t m_iRemainingParCount; // Number of remaining par-files int32_t m_iPriority; // Download priority int32_t m_bMatch; // 1 - group matches the pattern (only when Request has eRemoteMatchModeRegEx) int32_t m_iFilenameLen; // Length of Filename-string (m_szFilename), following to this record int32_t m_iNameLen; // Length of Name-string (m_szName), following to this record int32_t m_iDestDirLen; // Length of DestDir-string (m_szDestDir), following to this record int32_t m_iCategoryLen; // Length of Category-string (m_szCategory), following to this record int32_t m_iQueuedFilenameLen; // Length of queued file name (m_szQueuedFilename), following to this record //char m_szFilename[m_iFilenameLen]; // variable sized //char m_szName[m_iNameLen]; // variable sized //char m_szDestDir[m_iDestDirLen]; // variable sized //char m_szCategory[m_iCategoryLen]; // variable sized //char m_szQueuedFilename[m_iQueuedFilenameLen]; // variable sized }; // A list response pp-parameter entry struct SNZBListResponsePPPEntry { int32_t m_iNZBIndex; // Index of NZB-Entry in m_NZBEntries-list int32_t m_iNameLen; // Length of Name-string (m_szName), following to this record int32_t m_iValueLen; // Length of Value-string (m_szValue), following to this record //char m_szName[m_iNameLen]; // variable sized //char m_szValue[m_iValueLen]; // variable sized }; // A list response file entry struct SNZBListResponseFileEntry { int32_t m_iID; // Entry-ID int32_t m_iNZBIndex; // Index of NZB-Entry in m_NZBEntries-list int32_t m_iFileSizeLo; // Filesize in bytes, Low 32-bits of 64-bit value int32_t m_iFileSizeHi; // Filesize in bytes, High 32-bits of 64-bit value int32_t m_iRemainingSizeLo; // Remaining size in bytes, Low 32-bits of 64-bit value int32_t m_iRemainingSizeHi; // Remaining size in bytes, High 32-bits of 64-bit value int32_t m_bPaused; // 1 - file is paused int32_t m_bFilenameConfirmed; // 1 - Filename confirmed (read from article body), 0 - Filename parsed from subject (can be changed after reading of article) int32_t m_iActiveDownloads; // Number of active downloads for this file int32_t m_bMatch; // 1 - file matches the pattern (only when Request has eRemoteMatchModeRegEx) int32_t m_iSubjectLen; // Length of Subject-string (m_szSubject), following to this record int32_t m_iFilenameLen; // Length of Filename-string (m_szFilename), following to this record //char m_szSubject[m_iSubjectLen]; // variable sized //char m_szFilename[m_iFilenameLen]; // variable sized }; // A log request struct SNZBLogRequest { SNZBRequestBase m_MessageBase; // Must be the first in the struct int32_t m_iIDFrom; // Only one of these two parameters int32_t m_iLines; // can be set. The another one must be set to "0". }; // A log response struct SNZBLogResponse { SNZBResponseBase m_MessageBase; // Must be the first in the struct int32_t m_iEntrySize; // Size of the SNZBLogResponseEntry-struct int32_t m_iNrTrailingEntries; // Number of Log-entries, following to this structure int32_t m_iTrailingDataLength; // Length of all Log-entries, following to this structure // SNZBLogResponseEntry m_Entries[m_iNrTrailingEntries] // variable sized }; // A log response entry struct SNZBLogResponseEntry { int32_t m_iID; // ID of Log-entry int32_t m_iKind; // see Message::Kind in "Log.h" int32_t m_tTime; // time since the Epoch (00:00:00 UTC, January 1, 1970), measured in seconds. int32_t m_iTextLen; // Length of Text-string (m_szText), following to this record //char m_szText[m_iTextLen]; // variable sized }; // A Pause/Unpause request struct SNZBPauseUnpauseRequest { SNZBRequestBase m_MessageBase; // Must be the first in the struct int32_t m_bPause; // 1 - server must be paused, 0 - server must be unpaused int32_t m_iAction; // Action to be executed, see enum eRemotePauseUnpauseAction }; // A Pause/Unpause response struct SNZBPauseUnpauseResponse { SNZBResponseBase m_MessageBase; // Must be the first in the struct int32_t m_bSuccess; // 0 - command failed, 1 - command executed successfully int32_t m_iTrailingDataLength; // Length of Text-string (m_szText), following to this record //char m_szText[m_iTrailingDataLength]; // variable sized }; // Request setting the download rate struct SNZBSetDownloadRateRequest { SNZBRequestBase m_MessageBase; // Must be the first in the struct int32_t m_iDownloadRate; // Speed limit, in Bytes pro Second }; // A setting download rate response struct SNZBSetDownloadRateResponse { SNZBResponseBase m_MessageBase; // Must be the first in the struct int32_t m_bSuccess; // 0 - command failed, 1 - command executed successfully int32_t m_iTrailingDataLength; // Length of Text-string (m_szText), following to this record //char m_szText[m_iTrailingDataLength]; // variable sized }; // edit queue request struct SNZBEditQueueRequest { SNZBRequestBase m_MessageBase; // Must be the first in the struct int32_t m_iAction; // Action to be executed, see enum DownloadQueue::EEditAction int32_t m_iOffset; // Offset to move (for m_iAction = 0) int32_t m_iMatchMode; // File/Group match mode, see enum eRemoteMatchMode int32_t m_iNrTrailingIDEntries; // Number of ID-entries, following to this structure int32_t m_iNrTrailingNameEntries; // Number of Name-entries, following to this structure int32_t m_iTrailingNameEntriesLen; // Length of all Name-entries, following to this structure int32_t m_iTextLen; // Length of Text-string (m_szText), following to this record int32_t m_iTrailingDataLength; // Length of Text-string and all ID-entries, following to this structure //char m_szText[m_iTextLen]; // variable sized //int32_t m_iIDs[m_iNrTrailingIDEntries]; // variable sized array of IDs. For File-Actions - ID of file, for Group-Actions - ID of any file belonging to group //char* m_szNames[m_iNrTrailingNameEntries]; // variable sized array of strings. For File-Actions - name of file incl. nzb-name as path, for Group-Actions - name of group }; // An edit queue response struct SNZBEditQueueResponse { SNZBResponseBase m_MessageBase; // Must be the first in the struct int32_t m_bSuccess; // 0 - command failed, 1 - command executed successfully int32_t m_iTrailingDataLength; // Length of Text-string (m_szText), following to this record //char m_szText[m_iTrailingDataLength]; // variable sized }; // Request dumping of debug info struct SNZBDumpDebugRequest { SNZBRequestBase m_MessageBase; // Must be the first in the struct }; // Dumping of debug response struct SNZBDumpDebugResponse { SNZBResponseBase m_MessageBase; // Must be the first in the struct int32_t m_bSuccess; // 0 - command failed, 1 - command executed successfully int32_t m_iTrailingDataLength; // Length of Text-string (m_szText), following to this record //char m_szText[m_iTrailingDataLength]; // variable sized }; // Shutdown server request struct SNZBShutdownRequest { SNZBRequestBase m_MessageBase; // Must be the first in the struct }; // Shutdown server response struct SNZBShutdownResponse { SNZBResponseBase m_MessageBase; // Must be the first in the struct int32_t m_bSuccess; // 0 - command failed, 1 - command executed successfully int32_t m_iTrailingDataLength; // Length of Text-string (m_szText), following to this record //char m_szText[m_iTrailingDataLength]; // variable sized }; // Reload server request struct SNZBReloadRequest { SNZBRequestBase m_MessageBase; // Must be the first in the struct }; // Reload server response struct SNZBReloadResponse { SNZBResponseBase m_MessageBase; // Must be the first in the struct int32_t m_bSuccess; // 0 - command failed, 1 - command executed successfully int32_t m_iTrailingDataLength; // Length of Text-string (m_szText), following to this record //char m_szText[m_iTrailingDataLength]; // variable sized }; // Server version request struct SNZBVersionRequest { SNZBRequestBase m_MessageBase; // Must be the first in the struct }; // Server version response struct SNZBVersionResponse { SNZBResponseBase m_MessageBase; // Must be the first in the struct int32_t m_bSuccess; // 0 - command failed, 1 - command executed successfully int32_t m_iTrailingDataLength; // Length of Text-string (m_szText), following to this record //char m_szText[m_iTrailingDataLength]; // variable sized }; // PostQueue request struct SNZBPostQueueRequest { SNZBRequestBase m_MessageBase; // Must be the first in the struct }; // A PostQueue response struct SNZBPostQueueResponse { SNZBResponseBase m_MessageBase; // Must be the first in the struct int32_t m_iEntrySize; // Size of the SNZBPostQueueResponseEntry-struct int32_t m_iNrTrailingEntries; // Number of PostQueue-entries, following to this structure int32_t m_iTrailingDataLength; // Length of all PostQueue-entries, following to this structure // SNZBPostQueueResponseEntry m_Entries[m_iNrTrailingEntries] // variable sized }; // A PostQueue response entry struct SNZBPostQueueResponseEntry { int32_t m_iID; // ID of Post-entry int32_t m_iStage; // See PrePostProcessor::EPostJobStage int32_t m_iStageProgress; // Progress of current stage, value in range 0..1000 int32_t m_iFileProgress; // Progress of current file, value in range 0..1000 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_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_szInfoName[m_iInfoNameLen]; // variable sized //char m_szDestDir[m_iDestDirLen]; // variable sized //char m_szProgressLabel[m_iProgressLabelLen]; // variable sized }; // Write log request struct SNZBWriteLogRequest { SNZBRequestBase m_MessageBase; // Must be the first in the struct int32_t m_iKind; // see Message::Kind in "Log.h" int32_t m_iTrailingDataLength; // Length of nzb-file in bytes //char m_szText[m_iTrailingDataLength]; // variable sized }; // Write log response struct SNZBWriteLogResponse { SNZBResponseBase m_MessageBase; // Must be the first in the struct int32_t m_bSuccess; // 0 - command failed, 1 - command executed successfully int32_t m_iTrailingDataLength; // Length of Text-string (m_szText), following to this record //char m_szText[m_iTrailingDataLength]; // variable sized }; // Scan nzb directory request struct SNZBScanRequest { SNZBRequestBase m_MessageBase; // Must be the first in the struct int32_t m_bSyncMode; // 0 - asynchronous Scan (the command returns immediately), 1 - synchronous Scan (the command returns when the scan is completed) }; // Scan nzb directory response struct SNZBScanResponse { SNZBResponseBase m_MessageBase; // Must be the first in the struct int32_t m_bSuccess; // 0 - command failed, 1 - command executed successfully int32_t m_iTrailingDataLength; // Length of Text-string (m_szText), following to this record //char m_szText[m_iTrailingDataLength]; // variable sized }; // A history request struct SNZBHistoryRequest { SNZBRequestBase m_MessageBase; // Must be the first in the struct int32_t m_bHidden; // 0 - only return visible records, 1 - also return hidden records }; // history response struct SNZBHistoryResponse { SNZBResponseBase m_MessageBase; // Must be the first in the struct int32_t m_iEntrySize; // Size of the SNZBHistoryResponseEntry-struct int32_t m_iNrTrailingEntries; // Number of History-entries, following to this structure int32_t m_iTrailingDataLength; // Length of all History-entries, following to this structure // SNZBHistoryResponseEntry m_Entries[m_iNrTrailingEntries] // variable sized }; // history entry struct SNZBHistoryResponseEntry { int32_t m_iID; // History-ID int32_t m_iKind; // Kind of Item: 1 - Collection (NZB), 2 - URL, 3 - DUP (hidden record) int32_t m_tTime; // When the item was added to history. time since the Epoch (00:00:00 UTC, January 1, 1970), measured in seconds. int32_t m_iNicenameLen; // Length of Nicename-string (m_szNicename), following to this record // for Collection and Dup items (m_iKind = 1 or 2) int32_t m_iSizeLo; // Size of all files in bytes, Low 32-bits of 64-bit value int32_t m_iSizeHi; // Size of all files in bytes, High 32-bits of 64-bit value // for Collection items (m_iKind = 1) int32_t m_iFileCount; // Initial number of files included in NZB-file int32_t m_iParStatus; // See NZBInfo::EParStatus int32_t m_iScriptStatus; // See NZBInfo::EScriptStatus // for URL items (m_iKind = 2) int32_t m_iUrlStatus; // See NZBInfo::EUrlStatus // trailing data //char m_szNicename[m_iNicenameLen]; // variable sized }; #endif nzbget-16.4/daemon/remote/RemoteServer.h0000644000175000017500000000301612630544544020111 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2005 Bo Cordes Petersen * Copyright (C) 2007-2013 Andrey Prygunkov * * 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 REMOTESERVER_H #define REMOTESERVER_H #include "Thread.h" #include "Connection.h" class RemoteServer : public Thread { private: bool m_bTLS; Connection* m_pConnection; public: RemoteServer(bool bTLS); ~RemoteServer(); virtual void Run(); virtual void Stop(); }; class RequestProcessor : public Thread { private: bool m_bTLS; Connection* m_pConnection; public: ~RequestProcessor(); virtual void Run(); void SetTLS(bool bTLS) { m_bTLS = bTLS; } void SetConnection(Connection* pConnection) { m_pConnection = pConnection; } }; #endif nzbget-16.4/daemon/remote/RemoteClient.h0000644000175000017500000000525112630544544020064 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2005 Bo Cordes Petersen * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 REMOTECLIENT_H #define REMOTECLIENT_H #include "Options.h" #include "MessageBase.h" #include "Connection.h" #include "DownloadInfo.h" class RemoteClient { private: class MatchedNZBInfo: public NZBInfo { public: bool m_bMatch; }; class MatchedFileInfo: public FileInfo { public: bool m_bMatch; }; Connection* m_pConnection; bool m_bVerbose; bool InitConnection(); void InitMessageBase(SNZBRequestBase* pMessageBase, int iRequest, int iSize); bool ReceiveBoolResponse(); void printf(const char* msg, ...); void perror(const char* msg); public: RemoteClient(); ~RemoteClient(); void SetVerbose(bool bVerbose) { m_bVerbose = bVerbose; }; bool RequestServerDownload(const char* szNZBFilename, const char* szNZBContent, const char* szCategory, bool bAddFirst, bool bAddPaused, int iPriority, const char* szDupeKey, int iDupeMode, int iDupeScore); bool RequestServerList(bool bFiles, bool bGroups, const char* szPattern); bool RequestServerPauseUnpause(bool bPause, eRemotePauseUnpauseAction iAction); bool RequestServerSetDownloadRate(int iRate); bool RequestServerDumpDebug(); bool RequestServerEditQueue(DownloadQueue::EEditAction eAction, int iOffset, const char* szText, int* pIDList, int iIDCount, NameList* pNameList, eRemoteMatchMode iMatchMode); bool RequestServerLog(int iLines); bool RequestServerShutdown(); bool RequestServerReload(); bool RequestServerVersion(); bool RequestPostQueue(); bool RequestWriteLog(int iKind, const char* szText); bool RequestScan(bool bSyncMode); bool RequestHistory(bool bWithHidden); void BuildFileList(SNZBListResponse* pListResponse, const char* pTrailingData, DownloadQueue* pDownloadQueue); }; #endif nzbget-16.4/daemon/remote/RemoteServer.cpp0000644000175000017500000001276412630544544020456 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2005 Bo Cordes Petersen * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #include #ifndef WIN32 #include #include #include #include #endif #include "nzbget.h" #include "RemoteServer.h" #include "BinRpc.h" #include "WebServer.h" #include "Log.h" #include "Options.h" #include "Util.h" //***************************************************************** // RemoteServer RemoteServer::RemoteServer(bool bTLS) { debug("Creating RemoteServer"); m_bTLS = bTLS; m_pConnection = NULL; } RemoteServer::~RemoteServer() { debug("Destroying RemoteServer"); delete m_pConnection; } void RemoteServer::Run() { debug("Entering RemoteServer-loop"); #ifndef DISABLE_TLS if (m_bTLS) { if (strlen(g_pOptions->GetSecureCert()) == 0 || !Util::FileExists(g_pOptions->GetSecureCert())) { error("Could not initialize TLS, secure certificate is not configured or the cert-file was not found. Check option "); return; } if (strlen(g_pOptions->GetSecureKey()) == 0 || !Util::FileExists(g_pOptions->GetSecureKey())) { error("Could not initialize TLS, secure key is not configured or the key-file was not found. Check option "); return; } } #endif while (!IsStopped()) { bool bBind = true; if (!m_pConnection) { m_pConnection = new Connection(g_pOptions->GetControlIP(), m_bTLS ? g_pOptions->GetSecurePort() : g_pOptions->GetControlPort(), m_bTLS); m_pConnection->SetTimeout(g_pOptions->GetUrlTimeout()); m_pConnection->SetSuppressErrors(false); bBind = m_pConnection->Bind(); } // Accept connections and store the new Connection Connection* pAcceptedConnection = NULL; if (bBind) { pAcceptedConnection = m_pConnection->Accept(); } if (!bBind || pAcceptedConnection == NULL) { // Remote server could not bind or accept connection, waiting 1/2 sec and try again if (IsStopped()) { break; } usleep(500 * 1000); delete m_pConnection; m_pConnection = NULL; continue; } RequestProcessor* commandThread = new RequestProcessor(); commandThread->SetAutoDestroy(true); commandThread->SetConnection(pAcceptedConnection); #ifndef DISABLE_TLS commandThread->SetTLS(m_bTLS); #endif commandThread->Start(); } if (m_pConnection) { m_pConnection->Disconnect(); } debug("Exiting RemoteServer-loop"); } void RemoteServer::Stop() { Thread::Stop(); if (m_pConnection) { m_pConnection->SetSuppressErrors(true); m_pConnection->Cancel(); #ifdef WIN32 m_pConnection->Disconnect(); #endif } } //***************************************************************** // RequestProcessor RequestProcessor::~RequestProcessor() { m_pConnection->Disconnect(); delete m_pConnection; } void RequestProcessor::Run() { bool bOK = false; m_pConnection->SetSuppressErrors(true); #ifndef DISABLE_TLS if (m_bTLS && !m_pConnection->StartTLS(false, g_pOptions->GetSecureCert(), g_pOptions->GetSecureKey())) { debug("Could not establish secure connection to web-client: Start TLS failed"); return; } #endif // Read the first 4 bytes to determine request type int iSignature = 0; if (!m_pConnection->Recv((char*)&iSignature, 4)) { debug("Could not read request signature"); return; } if ((int)ntohl(iSignature) == (int)NZBMESSAGE_SIGNATURE) { // binary request received bOK = true; BinRpcProcessor processor; processor.SetConnection(m_pConnection); processor.Execute(); } else if (!strncmp((char*)&iSignature, "POST", 4) || !strncmp((char*)&iSignature, "GET ", 4) || !strncmp((char*)&iSignature, "OPTI", 4)) { // HTTP request received char szBuffer[1024]; if (m_pConnection->ReadLine(szBuffer, sizeof(szBuffer), NULL)) { WebProcessor::EHttpMethod eHttpMethod = WebProcessor::hmGet; char* szUrl = szBuffer; if (!strncmp((char*)&iSignature, "POST", 4)) { eHttpMethod = WebProcessor::hmPost; szUrl++; } if (!strncmp((char*)&iSignature, "OPTI", 4) && strlen(szUrl) > 4) { eHttpMethod = WebProcessor::hmOptions; szUrl += 4; } if (char* p = strchr(szUrl, ' ')) { *p = '\0'; } debug("url: %s", szUrl); WebProcessor processor; processor.SetConnection(m_pConnection); processor.SetUrl(szUrl); processor.SetHttpMethod(eHttpMethod); processor.Execute(); m_pConnection->SetGracefull(true); m_pConnection->Disconnect(); bOK = true; } } if (!bOK) { warn("Non-nzbget request received on port %i from %s", m_bTLS ? g_pOptions->GetSecurePort() : g_pOptions->GetControlPort(), m_pConnection->GetRemoteAddr()); } } nzbget-16.4/daemon/remote/WebServer.h0000644000175000017500000000425512630544544017401 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2012-2015 Andrey Prygunkov * * 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 WEBSERVER_H #define WEBSERVER_H #include "Connection.h" class WebProcessor { public: enum EHttpMethod { hmPost, hmGet, hmOptions }; enum EUserAccess { uaControl, uaRestricted, uaAdd }; private: Connection* m_pConnection; char* m_szRequest; char* m_szUrl; EHttpMethod m_eHttpMethod; EUserAccess m_eUserAccess; bool m_bGZip; char* m_szOrigin; int m_iContentLen; char m_szAuthInfo[256+1]; char m_szAuthToken[48+1]; static char m_szServerAuthToken[3][48+1]; void Dispatch(); void SendAuthResponse(); void SendOptionsResponse(); void SendErrorResponse(const char* szErrCode); void SendFileResponse(const char* szFilename); 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); void ParseHeaders(); void ParseURL(); bool CheckCredentials(); public: WebProcessor(); ~WebProcessor(); static void Init(); void Execute(); void SetConnection(Connection* pConnection) { m_pConnection = pConnection; } void SetUrl(const char* szUrl); void SetHttpMethod(EHttpMethod eHttpMethod) { m_eHttpMethod = eHttpMethod; } }; #endif nzbget-16.4/daemon/remote/XmlRpc.cpp0000644000175000017500000031505512630544544017240 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #include #include #ifndef WIN32 #include #endif #include "nzbget.h" #include "XmlRpc.h" #include "Log.h" #include "Options.h" #include "Scanner.h" #include "FeedCoordinator.h" #include "ServerPool.h" #include "Util.h" #include "Maintenance.h" #include "StatMeter.h" #include "ArticleWriter.h" #include "DiskState.h" #include "ScriptConfig.h" #include "QueueScript.h" extern void ExitProc(); extern void Reload(); 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, 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 { protected: int m_iIDFrom; int m_iNrEntries; virtual MessageList* LockMessages(); virtual void UnlockMessages(); public: virtual void Execute(); }; class NzbInfoXmlCommand: public XmlCommand { protected: void AppendNZBInfoFields(NZBInfo* pNZBInfo); void AppendPostInfoFields(PostInfo* pPostInfo, int iLogEntries, bool bPostQueue); }; class ListFilesXmlCommand: public XmlCommand { public: virtual void Execute(); }; class ListGroupsXmlCommand: public NzbInfoXmlCommand { private: const char* DetectStatus(NZBInfo* pNZBInfo); public: virtual void Execute(); }; class EditQueueXmlCommand: public XmlCommand { public: virtual void Execute(); }; class DownloadXmlCommand: public XmlCommand { public: virtual void Execute(); }; class PostQueueXmlCommand: public NzbInfoXmlCommand { 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 NzbInfoXmlCommand { private: const char* DetectStatus(HistoryInfo* pHistoryInfo); 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(); }; class ConfigTemplatesXmlCommand: public XmlCommand { public: virtual void Execute(); }; class ViewFeedXmlCommand: public XmlCommand { private: bool m_bPreview; public: ViewFeedXmlCommand(bool bPreview); virtual void Execute(); }; class FetchFeedXmlCommand: public XmlCommand { public: virtual void Execute(); }; class EditServerXmlCommand: public XmlCommand { public: virtual void Execute(); }; class ReadUrlXmlCommand: public XmlCommand { public: virtual void Execute(); }; class CheckUpdatesXmlCommand: public XmlCommand { public: virtual void Execute(); }; class StartUpdateXmlCommand: public XmlCommand { public: virtual void Execute(); }; class LogUpdateXmlCommand: public LogXmlCommand { protected: virtual MessageList* LockMessages(); virtual void UnlockMessages(); }; class ServerVolumesXmlCommand: public XmlCommand { public: virtual void Execute(); }; class ResetServerVolumeXmlCommand: public XmlCommand { public: virtual void Execute(); }; class LoadLogXmlCommand: public LogXmlCommand { private: MessageList m_messages; int m_iNZBID; NZBInfo* m_pNZBInfo; protected: virtual void Execute(); virtual MessageList* LockMessages(); virtual void UnlockMessages(); }; class TestServerXmlCommand: public XmlCommand { private: char* m_szErrText; class TestConnection : public NNTPConnection { protected: TestServerXmlCommand* m_pOwner; virtual void PrintError(const char* szErrMsg) { m_pOwner->PrintError(szErrMsg); } public: TestConnection(NewsServer* pNewsServer, TestServerXmlCommand* pOwner): NNTPConnection(pNewsServer), m_pOwner(pOwner) {} }; void PrintError(const char* szErrMsg); public: virtual void Execute(); }; //***************************************************************** // XmlRpcProcessor XmlRpcProcessor::XmlRpcProcessor() { m_szRequest = NULL; m_eProtocol = rpUndefined; m_eHttpMethod = hmPost; m_szUrl = NULL; m_szContentType = NULL; } XmlRpcProcessor::~XmlRpcProcessor() { free(m_szUrl); } void XmlRpcProcessor::SetUrl(const char* szUrl) { m_szUrl = strdup(szUrl); WebUtil::URLDecode(m_szUrl); } bool XmlRpcProcessor::IsRpcRequest(const char* szUrl) { return !strcmp(szUrl, "/xmlrpc") || !strncmp(szUrl, "/xmlrpc/", 8) || !strcmp(szUrl, "/jsonrpc") || !strncmp(szUrl, "/jsonrpc/", 9) || !strcmp(szUrl, "/jsonprpc") || !strncmp(szUrl, "/jsonprpc/", 10); } void XmlRpcProcessor::Execute() { m_eProtocol = rpUndefined; if (!strcmp(m_szUrl, "/xmlrpc") || !strncmp(m_szUrl, "/xmlrpc/", 8)) { m_eProtocol = XmlRpcProcessor::rpXmlRpc; } else if (!strcmp(m_szUrl, "/jsonrpc") || !strncmp(m_szUrl, "/jsonrpc/", 9)) { m_eProtocol = rpJsonRpc; } else if (!strcmp(m_szUrl, "/jsonprpc") || !strncmp(m_szUrl, "/jsonprpc/", 10)) { m_eProtocol = rpJsonPRpc; } else { error("internal error: invalid rpc-request: %s", m_szUrl); return; } Dispatch(); } void XmlRpcProcessor::Dispatch() { char* szRequest = m_szRequest; char szMethodName[100]; szMethodName[0] = '\0'; char szRequestId[100]; szRequestId[0] = '\0'; if (m_eHttpMethod == hmGet) { szRequest = m_szUrl + 1; char* pstart = strchr(szRequest, '/'); if (pstart) { char* pend = strchr(pstart + 1, '?'); if (pend) { int iLen = (int)(pend - pstart - 1 < (int)sizeof(szMethodName) - 1 ? pend - pstart - 1 : (int)sizeof(szMethodName) - 1); iLen = iLen >= sizeof(szMethodName) ? sizeof(szMethodName) - 1 : iLen; strncpy(szMethodName, pstart + 1, iLen); szMethodName[iLen] = '\0'; szRequest = pend + 1; } else { strncpy(szMethodName, pstart + 1, sizeof(szMethodName)); szMethodName[sizeof(szMethodName) - 1] = '\0'; szRequest = szRequest + strlen(szRequest); } } } else if (m_eProtocol == rpXmlRpc) { WebUtil::XmlParseTagValue(m_szRequest, "methodName", szMethodName, sizeof(szMethodName), NULL); } else if (m_eProtocol == rpJsonRpc) { int iValueLen = 0; if (const char* szMethodPtr = WebUtil::JsonFindField(m_szRequest, "method", &iValueLen)) { iValueLen = iValueLen >= sizeof(szMethodName) ? sizeof(szMethodName) - 1 : iValueLen; strncpy(szMethodName, szMethodPtr + 1, iValueLen - 2); szMethodName[iValueLen - 2] = '\0'; } if (const char* szRequestIdPtr = WebUtil::JsonFindField(m_szRequest, "id", &iValueLen)) { iValueLen = iValueLen >= sizeof(szRequestId) ? sizeof(szRequestId) - 1 : iValueLen; strncpy(szRequestId, szRequestIdPtr, iValueLen); szRequestId[iValueLen] = '\0'; } } debug("MethodName=%s", szMethodName); if (!strcasecmp(szMethodName, "system.multicall") && m_eProtocol == rpXmlRpc && m_eHttpMethod == hmPost) { MutliCall(); } else { XmlCommand* command = CreateCommand(szMethodName); command->SetRequest(szRequest); command->SetProtocol(m_eProtocol); command->SetHttpMethod(m_eHttpMethod); command->SetUserAccess(m_eUserAccess); command->PrepareParams(); command->Execute(); BuildResponse(command->GetResponse(), command->GetCallbackFunc(), command->GetFault(), szRequestId); delete command; } } void XmlRpcProcessor::MutliCall() { bool bError = false; StringBuilder cStringBuilder; cStringBuilder.Append(""); char* szRequestPtr = m_szRequest; char* szCallEnd = strstr(szRequestPtr, ""); while (szCallEnd) { *szCallEnd = '\0'; debug("MutliCall, request=%s", szRequestPtr); char* szNameEnd = strstr(szRequestPtr, ""); if (!szNameEnd) { bError = true; break; } char szMethodName[100]; szMethodName[0] = '\0'; WebUtil::XmlParseTagValue(szNameEnd, "string", szMethodName, sizeof(szMethodName), NULL); debug("MutliCall, MethodName=%s", szMethodName); XmlCommand* command = CreateCommand(szMethodName); command->SetRequest(szRequestPtr); command->Execute(); debug("MutliCall, Response=%s", command->GetResponse()); bool bFault = !strncmp(command->GetResponse(), "", 7); bool bArray = !bFault && !strncmp(command->GetResponse(), "", 7); if (!bFault && !bArray) { cStringBuilder.Append(""); } cStringBuilder.Append(""); cStringBuilder.Append(command->GetResponse()); cStringBuilder.Append(""); if (!bFault && !bArray) { cStringBuilder.Append(""); } delete command; szRequestPtr = szCallEnd + 9; //strlen("") szCallEnd = strstr(szRequestPtr, ""); } if (bError) { XmlCommand* command = new ErrorXmlCommand(4, "Parse error"); command->SetRequest(m_szRequest); command->SetProtocol(rpXmlRpc); command->PrepareParams(); command->Execute(); BuildResponse(command->GetResponse(), "", command->GetFault(), NULL); delete command; } else { cStringBuilder.Append(""); BuildResponse(cStringBuilder.GetBuffer(), "", false, NULL); } } void XmlRpcProcessor::BuildResponse(const char* szResponse, const char* szCallbackFunc, bool bFault, const char* szRequestId) { const char XML_HEADER[] = "\n\n"; const char XML_FOOTER[] = ""; const char XML_OK_OPEN[] = ""; const char XML_OK_CLOSE[] = "\n"; const char XML_FAULT_OPEN[] = ""; const char XML_FAULT_CLOSE[] = "\n"; const char JSON_HEADER[] = "{\n\"version\" : \"1.1\",\n"; const char JSON_ID_OPEN[] = "\"id\" : "; const char JSON_ID_CLOSE[] = ",\n"; const char JSON_FOOTER[] = "\n}"; const char JSON_OK_OPEN[] = "\"result\" : "; const char JSON_OK_CLOSE[] = ""; const char JSON_FAULT_OPEN[] = "\"error\" : "; const char JSON_FAULT_CLOSE[] = ""; const char JSONP_CALLBACK_HEADER[] = "("; const char JSONP_CALLBACK_FOOTER[] = ")"; bool bXmlRpc = m_eProtocol == rpXmlRpc; const char* szCallbackHeader = m_eProtocol == rpJsonPRpc ? JSONP_CALLBACK_HEADER : ""; const char* szHeader = bXmlRpc ? XML_HEADER : JSON_HEADER; const char* szFooter = bXmlRpc ? XML_FOOTER : JSON_FOOTER; const char* szOpenTag = bFault ? (bXmlRpc ? XML_FAULT_OPEN : JSON_FAULT_OPEN) : (bXmlRpc ? XML_OK_OPEN : JSON_OK_OPEN); const char* szCloseTag = bFault ? (bXmlRpc ? XML_FAULT_CLOSE : JSON_FAULT_CLOSE ) : (bXmlRpc ? XML_OK_CLOSE : JSON_OK_CLOSE); const char* szCallbackFooter = m_eProtocol == rpJsonPRpc ? JSONP_CALLBACK_FOOTER : ""; debug("Response=%s", szResponse); if (szCallbackFunc) { m_cResponse.Append(szCallbackFunc); } m_cResponse.Append(szCallbackHeader); m_cResponse.Append(szHeader); if (!bXmlRpc && szRequestId && *szRequestId) { m_cResponse.Append(JSON_ID_OPEN); m_cResponse.Append(szRequestId); m_cResponse.Append(JSON_ID_CLOSE); } m_cResponse.Append(szOpenTag); m_cResponse.Append(szResponse); m_cResponse.Append(szCloseTag); m_cResponse.Append(szFooter); m_cResponse.Append(szCallbackFooter); m_szContentType = bXmlRpc ? "text/xml" : "application/json"; } XmlCommand* XmlRpcProcessor::CreateCommand(const char* szMethodName) { XmlCommand* command = NULL; if (m_eUserAccess == uaAdd && !(!strcasecmp(szMethodName, "append") || !strcasecmp(szMethodName, "appendurl") || !strcasecmp(szMethodName, "version"))) { command = new ErrorXmlCommand(401, "Access denied"); warn("Received request \"%s\" from add-user, access denied", szMethodName); } else if (m_eUserAccess == uaRestricted && !strcasecmp(szMethodName, "saveconfig")) { command = new ErrorXmlCommand(401, "Access denied"); warn("Received request \"%s\" from restricted user, access denied", szMethodName); } else if (!strcasecmp(szMethodName, "pause") || !strcasecmp(szMethodName, "pausedownload") || !strcasecmp(szMethodName, "pausedownload2")) { command = new PauseUnpauseXmlCommand(true, PauseUnpauseXmlCommand::paDownload); } else if (!strcasecmp(szMethodName, "resume") || !strcasecmp(szMethodName, "resumedownload") || !strcasecmp(szMethodName, "resumedownload2")) { command = new PauseUnpauseXmlCommand(false, PauseUnpauseXmlCommand::paDownload); } else if (!strcasecmp(szMethodName, "shutdown")) { command = new ShutdownXmlCommand(); } else if (!strcasecmp(szMethodName, "reload")) { command = new ReloadXmlCommand(); } else if (!strcasecmp(szMethodName, "version")) { command = new VersionXmlCommand(); } else if (!strcasecmp(szMethodName, "dump")) { command = new DumpDebugXmlCommand(); } else if (!strcasecmp(szMethodName, "rate")) { command = new SetDownloadRateXmlCommand(); } else if (!strcasecmp(szMethodName, "status")) { command = new StatusXmlCommand(); } else if (!strcasecmp(szMethodName, "log")) { command = new LogXmlCommand(); } else if (!strcasecmp(szMethodName, "listfiles")) { command = new ListFilesXmlCommand(); } else if (!strcasecmp(szMethodName, "listgroups")) { command = new ListGroupsXmlCommand(); } else if (!strcasecmp(szMethodName, "editqueue")) { command = new EditQueueXmlCommand(); } else if (!strcasecmp(szMethodName, "append") || !strcasecmp(szMethodName, "appendurl")) { command = new DownloadXmlCommand(); } else if (!strcasecmp(szMethodName, "postqueue")) { command = new PostQueueXmlCommand(); } else if (!strcasecmp(szMethodName, "writelog")) { command = new WriteLogXmlCommand(); } else if (!strcasecmp(szMethodName, "clearlog")) { command = new ClearLogXmlCommand(); } else if (!strcasecmp(szMethodName, "loadlog")) { command = new LoadLogXmlCommand(); } else if (!strcasecmp(szMethodName, "scan")) { command = new ScanXmlCommand(); } else if (!strcasecmp(szMethodName, "pausepost")) { command = new PauseUnpauseXmlCommand(true, PauseUnpauseXmlCommand::paPostProcess); } else if (!strcasecmp(szMethodName, "resumepost")) { command = new PauseUnpauseXmlCommand(false, PauseUnpauseXmlCommand::paPostProcess); } else if (!strcasecmp(szMethodName, "pausescan")) { command = new PauseUnpauseXmlCommand(true, PauseUnpauseXmlCommand::paScan); } else if (!strcasecmp(szMethodName, "resumescan")) { command = new PauseUnpauseXmlCommand(false, PauseUnpauseXmlCommand::paScan); } else if (!strcasecmp(szMethodName, "scheduleresume")) { command = new ScheduleResumeXmlCommand(); } else if (!strcasecmp(szMethodName, "history")) { command = new HistoryXmlCommand(); } else if (!strcasecmp(szMethodName, "urlqueue")) { command = new UrlQueueXmlCommand(); } else if (!strcasecmp(szMethodName, "config")) { command = new ConfigXmlCommand(); } else if (!strcasecmp(szMethodName, "loadconfig")) { command = new LoadConfigXmlCommand(); } else if (!strcasecmp(szMethodName, "saveconfig")) { command = new SaveConfigXmlCommand(); } else if (!strcasecmp(szMethodName, "configtemplates")) { command = new ConfigTemplatesXmlCommand(); } else if (!strcasecmp(szMethodName, "viewfeed")) { command = new ViewFeedXmlCommand(false); } else if (!strcasecmp(szMethodName, "previewfeed")) { command = new ViewFeedXmlCommand(true); } else if (!strcasecmp(szMethodName, "fetchfeed")) { command = new FetchFeedXmlCommand(); } else if (!strcasecmp(szMethodName, "editserver")) { command = new EditServerXmlCommand(); } else if (!strcasecmp(szMethodName, "readurl")) { command = new ReadUrlXmlCommand(); } else if (!strcasecmp(szMethodName, "checkupdates")) { command = new CheckUpdatesXmlCommand(); } else if (!strcasecmp(szMethodName, "startupdate")) { command = new StartUpdateXmlCommand(); } else if (!strcasecmp(szMethodName, "logupdate")) { command = new LogUpdateXmlCommand(); } else if (!strcasecmp(szMethodName, "servervolumes")) { command = new ServerVolumesXmlCommand(); } else if (!strcasecmp(szMethodName, "resetservervolume")) { command = new ResetServerVolumeXmlCommand(); } else if (!strcasecmp(szMethodName, "testserver")) { command = new TestServerXmlCommand(); } else { command = new ErrorXmlCommand(1, "Invalid procedure"); } return command; } //***************************************************************** // Base command XmlCommand::XmlCommand() { m_szRequest = NULL; m_szRequestPtr = NULL; m_szCallbackFunc = NULL; m_bFault = false; m_eProtocol = XmlRpcProcessor::rpUndefined; m_StringBuilder.SetGrowSize(1024 * 10); } bool XmlCommand::IsJson() { return m_eProtocol == XmlRpcProcessor::rpJsonRpc || m_eProtocol == XmlRpcProcessor::rpJsonPRpc; } void XmlCommand::AppendResponse(const char* szPart) { m_StringBuilder.Append(szPart); } void XmlCommand::AppendFmtResponse(const char* szFormat, ...) { va_list args; va_start(args, szFormat); m_StringBuilder.AppendFmtV(szFormat, args); va_end(args); } void XmlCommand::AppendCondResponse(const char* szPart, bool bCond) { if (bCond) { m_StringBuilder.Append(szPart); } } void XmlCommand::OptimizeResponse(int iRecordCount) { // Reduce the number of memory allocations when building response buffer int iGrowSize = iRecordCount * m_StringBuilder.GetUsedSize() / 10; if (iGrowSize > 1024 * 10) { m_StringBuilder.SetGrowSize(iGrowSize); } } void XmlCommand::BuildErrorResponse(int iErrCode, const char* szErrText, ...) { const char* XML_RESPONSE_ERROR_BODY = "\n" "faultCode%i\n" "faultString%s\n" "\n"; const char* JSON_RESPONSE_ERROR_BODY = "{\n" "\"name\" : \"JSONRPCError\",\n" "\"code\" : %i,\n" "\"message\" : \"%s\"\n" "}"; char szFullText[1024]; va_list ap; va_start(ap, szErrText); vsnprintf(szFullText, 1024, szErrText, ap); szFullText[1024-1] = '\0'; va_end(ap); char* xmlText = EncodeStr(szFullText); char szContent[1024]; snprintf(szContent, 1024, IsJson() ? JSON_RESPONSE_ERROR_BODY : XML_RESPONSE_ERROR_BODY, iErrCode, xmlText); szContent[1024-1] = '\0'; free(xmlText); AppendResponse(szContent); m_bFault = true; } void XmlCommand::BuildBoolResponse(bool bOK) { const char* XML_RESPONSE_BOOL_BODY = "%s"; const char* JSON_RESPONSE_BOOL_BODY = "%s"; char szContent[1024]; snprintf(szContent, 1024, IsJson() ? JSON_RESPONSE_BOOL_BODY : XML_RESPONSE_BOOL_BODY, BoolToStr(bOK)); szContent[1024-1] = '\0'; AppendResponse(szContent); } void XmlCommand::BuildIntResponse(int iValue) { const char* XML_RESPONSE_INT_BODY = "%i"; const char* JSON_RESPONSE_INT_BODY = "%i"; char szContent[1024]; snprintf(szContent, 1024, IsJson() ? JSON_RESPONSE_INT_BODY : XML_RESPONSE_INT_BODY, iValue); szContent[1024-1] = '\0'; AppendResponse(szContent); } void XmlCommand::PrepareParams() { if (IsJson() && m_eHttpMethod == XmlRpcProcessor::hmPost) { char* szParams = strstr(m_szRequestPtr, "\"params\""); if (!szParams) { m_szRequestPtr[0] = '\0'; return; } m_szRequestPtr = szParams + 8; // strlen("\"params\"") } if (m_eProtocol == XmlRpcProcessor::rpJsonPRpc) { NextParamAsStr(&m_szCallbackFunc); } } char* XmlCommand::XmlNextValue(char* szXml, const char* szTag, int* pValueLength) { int iValueLen; const char* szValue = WebUtil::XmlFindTag(szXml, "value", &iValueLen); if (szValue) { char* szTagContent = (char*)WebUtil::XmlFindTag(szValue, szTag, pValueLength); if (szTagContent <= szValue + iValueLen) { return szTagContent; } } return NULL; } bool XmlCommand::NextParamAsInt(int* iValue) { if (m_eHttpMethod == XmlRpcProcessor::hmGet) { char* szParam = strchr(m_szRequestPtr, '='); if (!szParam) { return false; } *iValue = atoi(szParam + 1); m_szRequestPtr = szParam + 1; while (strchr("-+0123456789&", *m_szRequestPtr)) { m_szRequestPtr++; } return true; } else if (IsJson()) { int iLen = 0; char* szParam = (char*)WebUtil::JsonNextValue(m_szRequestPtr, &iLen); if (!szParam || !strchr("-+0123456789", *szParam)) { return false; } *iValue = atoi(szParam); m_szRequestPtr = szParam + iLen + 1; return true; } else { int iLen = 0; int iTagLen = 4; //strlen(""); char* szParam = XmlNextValue(m_szRequestPtr, "i4", &iLen); if (!szParam) { szParam = XmlNextValue(m_szRequestPtr, "int", &iLen); iTagLen = 5; //strlen(""); } if (!szParam || !strchr("-+0123456789", *szParam)) { return false; } *iValue = atoi(szParam); m_szRequestPtr = szParam + iLen + iTagLen; return true; } } bool XmlCommand::NextParamAsBool(bool* bValue) { if (m_eHttpMethod == XmlRpcProcessor::hmGet) { char* szParam; if (!NextParamAsStr(&szParam)) { return false; } if (IsJson()) { if (!strncmp(szParam, "true", 4)) { *bValue = true; return true; } else if (!strncmp(szParam, "false", 5)) { *bValue = false; return true; } } else { *bValue = szParam[0] == '1'; return true; } return false; } else if (IsJson()) { int iLen = 0; char* szParam = (char*)WebUtil::JsonNextValue(m_szRequestPtr, &iLen); if (!szParam) { return false; } if (iLen == 4 && !strncmp(szParam, "true", 4)) { *bValue = true; m_szRequestPtr = szParam + iLen + 1; return true; } else if (iLen == 5 && !strncmp(szParam, "false", 5)) { *bValue = false; m_szRequestPtr = szParam + iLen + 1; return true; } else { return false; } } else { int iLen = 0; char* szParam = XmlNextValue(m_szRequestPtr, "boolean", &iLen); if (!szParam) { return false; } *bValue = szParam[0] == '1'; m_szRequestPtr = szParam + iLen + 9; //strlen(""); return true; } } bool XmlCommand::NextParamAsStr(char** szValue) { if (m_eHttpMethod == XmlRpcProcessor::hmGet) { char* szParam = strchr(m_szRequestPtr, '='); if (!szParam) { return false; } szParam++; // skip '=' int iLen = 0; char* szParamEnd = strchr(szParam, '&'); if (szParamEnd) { iLen = (int)(szParamEnd - szParam); szParam[iLen] = '\0'; } else { iLen = strlen(szParam) - 1; } m_szRequestPtr = szParam + iLen + 1; *szValue = szParam; return true; } else if (IsJson()) { int iLen = 0; char* szParam = (char*)WebUtil::JsonNextValue(m_szRequestPtr, &iLen); if (!szParam || iLen < 2 || szParam[0] != '"' || szParam[iLen - 1] != '"') { return false; } szParam++; // skip first '"' szParam[iLen - 2] = '\0'; // skip last '"' m_szRequestPtr = szParam + iLen; *szValue = szParam; return true; } else { int iLen = 0; char* szParam = XmlNextValue(m_szRequestPtr, "string", &iLen); if (!szParam) { return false; } szParam[iLen] = '\0'; m_szRequestPtr = szParam + iLen + 8; //strlen("") *szValue = szParam; return true; } } const char* XmlCommand::BoolToStr(bool bValue) { return IsJson() ? (bValue ? "true" : "false") : (bValue ? "1" : "0"); } char* XmlCommand::EncodeStr(const char* szStr) { if (!szStr) { return strdup(""); } if (IsJson()) { return WebUtil::JsonEncode(szStr); } else { return WebUtil::XmlEncode(szStr); } } void XmlCommand::DecodeStr(char* szStr) { if (IsJson()) { WebUtil::JsonDecode(szStr); } else { WebUtil::XmlDecode(szStr); } } bool XmlCommand::CheckSafeMethod() { bool bSafe = m_eHttpMethod == XmlRpcProcessor::hmPost || m_eProtocol == XmlRpcProcessor::rpJsonPRpc; if (!bSafe) { BuildErrorResponse(4, "Not safe procedure for HTTP-Method GET. Use Method POST instead"); } return bSafe; } //***************************************************************** // Commands ErrorXmlCommand::ErrorXmlCommand(int iErrCode, const char* szErrText) { m_iErrCode = iErrCode; m_szErrText = szErrText; } void ErrorXmlCommand::Execute() { BuildErrorResponse(m_iErrCode, m_szErrText); } PauseUnpauseXmlCommand::PauseUnpauseXmlCommand(bool bPause, EPauseAction eEPauseAction) { m_bPause = bPause; m_eEPauseAction = eEPauseAction; } void PauseUnpauseXmlCommand::Execute() { if (!CheckSafeMethod()) { return; } bool bOK = true; g_pOptions->SetResumeTime(0); switch (m_eEPauseAction) { case paDownload: g_pOptions->SetPauseDownload(m_bPause); break; case paPostProcess: g_pOptions->SetPausePostProcess(m_bPause); break; case paScan: g_pOptions->SetPauseScan(m_bPause); break; default: bOK = false; } BuildBoolResponse(bOK); } // bool scheduleresume(int Seconds) void ScheduleResumeXmlCommand::Execute() { if (!CheckSafeMethod()) { return; } int iSeconds = 0; if (!NextParamAsInt(&iSeconds) || iSeconds < 0) { BuildErrorResponse(2, "Invalid parameter"); return; } time_t tCurTime = time(NULL); g_pOptions->SetResumeTime(tCurTime + iSeconds); BuildBoolResponse(true); } void ShutdownXmlCommand::Execute() { if (!CheckSafeMethod()) { return; } BuildBoolResponse(true); ExitProc(); } void ReloadXmlCommand::Execute() { if (!CheckSafeMethod()) { return; } BuildBoolResponse(true); Reload(); } void VersionXmlCommand::Execute() { const char* XML_RESPONSE_STRING_BODY = "%s"; const char* JSON_RESPONSE_STRING_BODY = "\"%s\""; char szContent[1024]; snprintf(szContent, 1024, IsJson() ? JSON_RESPONSE_STRING_BODY : XML_RESPONSE_STRING_BODY, Util::VersionRevision()); szContent[1024-1] = '\0'; AppendResponse(szContent); } void DumpDebugXmlCommand::Execute() { g_pLog->LogDebugInfo(); BuildBoolResponse(true); } void SetDownloadRateXmlCommand::Execute() { if (!CheckSafeMethod()) { return; } int iRate = 0; if (!NextParamAsInt(&iRate) || iRate < 0) { BuildErrorResponse(2, "Invalid parameter"); return; } g_pOptions->SetDownloadRate(iRate * 1024); BuildBoolResponse(true); } void StatusXmlCommand::Execute() { const char* XML_STATUS_START = "\n" "RemainingSizeLo%u\n" "RemainingSizeHi%u\n" "RemainingSizeMB%i\n" "ForcedSizeLo%u\n" "ForcedSizeHi%u\n" "ForcedSizeMB%i\n" "DownloadedSizeLo%u\n" "DownloadedSizeHi%u\n" "DownloadedSizeMB%i\n" "ArticleCacheLo%u\n" "ArticleCacheHi%u\n" "ArticleCacheMB%i\n" "DownloadRate%i\n" "AverageDownloadRate%i\n" "DownloadLimit%i\n" "ThreadCount%i\n" "ParJobCount%i\n" // deprecated (renamed to PostJobCount) "PostJobCount%i\n" "UrlCount%i\n" "UpTimeSec%i\n" "DownloadTimeSec%i\n" "ServerPaused%s\n" // deprecated (renamed to DownloadPaused) "DownloadPaused%s\n" "Download2Paused%s\n" // deprecated (same as DownloadPaused) "ServerStandBy%s\n" "PostPaused%s\n" "ScanPaused%s\n" "FreeDiskSpaceLo%u\n" "FreeDiskSpaceHi%u\n" "FreeDiskSpaceMB%i\n" "ServerTime%i\n" "ResumeTime%i\n" "FeedActive%s\n" "QueueScriptCount%i\n" "NewsServers\n"; const char* XML_STATUS_END = "\n" "\n"; const char* JSON_STATUS_START = "{\n" "\"RemainingSizeLo\" : %u,\n" "\"RemainingSizeHi\" : %u,\n" "\"RemainingSizeMB\" : %i,\n" "\"ForcedSizeLo\" : %u,\n" "\"ForcedSizeHi\" : %u,\n" "\"ForcedSizeMB\" : %i,\n" "\"DownloadedSizeLo\" : %u,\n" "\"DownloadedSizeHi\" : %u,\n" "\"DownloadedSizeMB\" : %i,\n" "\"ArticleCacheLo\" : %u,\n" "\"ArticleCacheHi\" : %u,\n" "\"ArticleCacheMB\" : %i,\n" "\"DownloadRate\" : %i,\n" "\"AverageDownloadRate\" : %i,\n" "\"DownloadLimit\" : %i,\n" "\"ThreadCount\" : %i,\n" "\"ParJobCount\" : %i,\n" // deprecated (renamed to PostJobCount) "\"PostJobCount\" : %i,\n" "\"UrlCount\" : %i,\n" "\"UpTimeSec\" : %i,\n" "\"DownloadTimeSec\" : %i,\n" "\"ServerPaused\" : %s,\n" // deprecated (renamed to DownloadPaused) "\"DownloadPaused\" : %s,\n" "\"Download2Paused\" : %s,\n" // deprecated (same as DownloadPaused) "\"ServerStandBy\" : %s,\n" "\"PostPaused\" : %s,\n" "\"ScanPaused\" : %s,\n" "\"FreeDiskSpaceLo\" : %u,\n" "\"FreeDiskSpaceHi\" : %u,\n" "\"FreeDiskSpaceMB\" : %i,\n" "\"ServerTime\" : %i,\n" "\"ResumeTime\" : %i,\n" "\"FeedActive\" : %s,\n" "\"QueueScriptCount\" : %i,\n" "\"NewsServers\" : [\n"; const char* JSON_STATUS_END = "]\n" "}"; const char* XML_NEWSSERVER_ITEM = "\n" "ID%i\n" "Active%s\n" "\n"; const char* JSON_NEWSSERVER_ITEM = "{\n" "\"ID\" : %i,\n" "\"Active\" : %s\n" "}"; DownloadQueue *pDownloadQueue = DownloadQueue::Lock(); int iPostJobCount = 0; int iUrlCount = 0; for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; iPostJobCount += pNZBInfo->GetPostInfo() ? 1 : 0; iUrlCount += pNZBInfo->GetKind() == NZBInfo::nkUrl ? 1 : 0; } long long iRemainingSize, iForcedSize; pDownloadQueue->CalcRemainingSize(&iRemainingSize, &iForcedSize); DownloadQueue::Unlock(); unsigned long iRemainingSizeHi, iRemainingSizeLo; Util::SplitInt64(iRemainingSize, &iRemainingSizeHi, &iRemainingSizeLo); int iRemainingMBytes = (int)(iRemainingSize / 1024 / 1024); unsigned long iForcedSizeHi, iForcedSizeLo; Util::SplitInt64(iForcedSize, &iForcedSizeHi, &iForcedSizeLo); int iForcedMBytes = (int)(iForcedSize / 1024 / 1024); long long iArticleCache = g_pArticleCache->GetAllocated(); unsigned long iArticleCacheHi, iArticleCacheLo; Util::SplitInt64(iArticleCache, &iArticleCacheHi, &iArticleCacheLo); int iArticleCacheMBytes = (int)(iArticleCache / 1024 / 1024); int iDownloadRate = (int)(g_pStatMeter->CalcCurrentDownloadSpeed()); int iDownloadLimit = (int)(g_pOptions->GetDownloadRate()); bool bDownloadPaused = g_pOptions->GetPauseDownload(); bool bPostPaused = g_pOptions->GetPausePostProcess(); bool bScanPaused = g_pOptions->GetPauseScan(); int iThreadCount = Thread::GetThreadCount() - 1; // not counting itself unsigned long iDownloadedSizeHi, iDownloadedSizeLo; int iUpTimeSec, iDownloadTimeSec; long long iAllBytes; bool bServerStandBy; g_pStatMeter->CalcTotalStat(&iUpTimeSec, &iDownloadTimeSec, &iAllBytes, &bServerStandBy); int iDownloadedMBytes = (int)(iAllBytes / 1024 / 1024); Util::SplitInt64(iAllBytes, &iDownloadedSizeHi, &iDownloadedSizeLo); int iAverageDownloadRate = (int)(iDownloadTimeSec > 0 ? iAllBytes / iDownloadTimeSec : 0); unsigned long iFreeDiskSpaceHi, iFreeDiskSpaceLo; long long iFreeDiskSpace = Util::FreeDiskSize(g_pOptions->GetDestDir()); Util::SplitInt64(iFreeDiskSpace, &iFreeDiskSpaceHi, &iFreeDiskSpaceLo); int iFreeDiskSpaceMB = (int)(iFreeDiskSpace / 1024 / 1024); int iServerTime = time(NULL); int iResumeTime = g_pOptions->GetResumeTime(); bool bFeedActive = g_pFeedCoordinator->HasActiveDownloads(); int iQueuedScripts = g_pQueueScriptCoordinator->GetQueueSize(); AppendFmtResponse(IsJson() ? JSON_STATUS_START : XML_STATUS_START, iRemainingSizeLo, iRemainingSizeHi, iRemainingMBytes, iForcedSizeLo, iForcedSizeHi, iForcedMBytes, iDownloadedSizeLo, iDownloadedSizeHi, iDownloadedMBytes, iArticleCacheLo, iArticleCacheHi, iArticleCacheMBytes, iDownloadRate, iAverageDownloadRate, iDownloadLimit, iThreadCount, iPostJobCount, iPostJobCount, iUrlCount, iUpTimeSec, iDownloadTimeSec, BoolToStr(bDownloadPaused), BoolToStr(bDownloadPaused), BoolToStr(bDownloadPaused), BoolToStr(bServerStandBy), BoolToStr(bPostPaused), BoolToStr(bScanPaused), iFreeDiskSpaceLo, iFreeDiskSpaceHi, iFreeDiskSpaceMB, iServerTime, iResumeTime, BoolToStr(bFeedActive), iQueuedScripts); int index = 0; for (Servers::iterator it = g_pServerPool->GetServers()->begin(); it != g_pServerPool->GetServers()->end(); it++) { NewsServer* pServer = *it; AppendCondResponse(",\n", IsJson() && index++ > 0); AppendFmtResponse(IsJson() ? JSON_NEWSSERVER_ITEM : XML_NEWSSERVER_ITEM, pServer->GetID(), BoolToStr(pServer->GetActive())); } AppendResponse(IsJson() ? JSON_STATUS_END : XML_STATUS_END); } // struct[] log(idfrom, entries) void LogXmlCommand::Execute() { m_iIDFrom = 0; m_iNrEntries = 0; if (!NextParamAsInt(&m_iIDFrom) || !NextParamAsInt(&m_iNrEntries) || (m_iNrEntries > 0 && m_iIDFrom > 0)) { BuildErrorResponse(2, "Invalid parameter"); return; } debug("iIDFrom=%i", m_iIDFrom); debug("iNrEntries=%i", m_iNrEntries); AppendResponse(IsJson() ? "[\n" : "\n"); MessageList* pMessages = LockMessages(); int iStart = pMessages->size(); if (m_iNrEntries > 0) { if (m_iNrEntries > (int)pMessages->size()) { m_iNrEntries = pMessages->size(); } iStart = pMessages->size() - m_iNrEntries; } if (m_iIDFrom > 0 && !pMessages->empty()) { m_iNrEntries = pMessages->size(); iStart = m_iIDFrom - pMessages->front()->GetID(); if (iStart < 0) { iStart = 0; } } const char* XML_LOG_ITEM = "\n" "ID%i\n" "Kind%s\n" "Time%i\n" "Text%s\n" "\n"; const char* JSON_LOG_ITEM = "{\n" "\"ID\" : %i,\n" "\"Kind\" : \"%s\",\n" "\"Time\" : %i,\n" "\"Text\" : \"%s\"\n" "}"; const char* szMessageType[] = { "INFO", "WARNING", "ERROR", "DEBUG", "DETAIL" }; int index = 0; for (unsigned int i = (unsigned int)iStart; i < pMessages->size(); i++) { Message* pMessage = (*pMessages)[i]; char* xmltext = EncodeStr(pMessage->GetText()); AppendCondResponse(",\n", IsJson() && index++ > 0); AppendFmtResponse(IsJson() ? JSON_LOG_ITEM : XML_LOG_ITEM, pMessage->GetID(), szMessageType[pMessage->GetKind()], pMessage->GetTime(), xmltext); free(xmltext); } UnlockMessages(); AppendResponse(IsJson() ? "\n]" : "\n"); } MessageList* LogXmlCommand::LockMessages() { return g_pLog->LockMessages(); } void LogXmlCommand::UnlockMessages() { g_pLog->UnlockMessages(); } // struct[] listfiles(int IDFrom, int IDTo, int NZBID) // For backward compatibility with 0.8 parameter "NZBID" is optional void ListFilesXmlCommand::Execute() { int iIDStart = 0; int iIDEnd = 0; if (NextParamAsInt(&iIDStart) && (!NextParamAsInt(&iIDEnd) || iIDEnd < iIDStart)) { BuildErrorResponse(2, "Invalid parameter"); return; } // For backward compatibility with 0.8 parameter "NZBID" is optional (error checking omitted) int iNZBID = 0; NextParamAsInt(&iNZBID); if (iNZBID > 0 && (iIDStart != 0 || iIDEnd != 0)) { BuildErrorResponse(2, "Invalid parameter"); return; } debug("iIDStart=%i", iIDStart); debug("iIDEnd=%i", iIDEnd); AppendResponse(IsJson() ? "[\n" : "\n"); DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); const char* XML_LIST_ITEM = "\n" "ID%i\n" "FileSizeLo%u\n" "FileSizeHi%u\n" "RemainingSizeLo%u\n" "RemainingSizeHi%u\n" "PostTime%i\n" "FilenameConfirmed%s\n" "Paused%s\n" "NZBID%i\n" "NZBName%s\n" "NZBNicename%s\n" // deprecated, use "NZBName" instead "NZBFilename%s\n" "Subject%s\n" "Filename%s\n" "DestDir%s\n" "Category%s\n" "Priority%i\n" // deprecated, use "Priority" of group instead "ActiveDownloads%i\n" "Progress%u\n" "\n"; const char* JSON_LIST_ITEM = "{\n" "\"ID\" : %i,\n" "\"FileSizeLo\" : %u,\n" "\"FileSizeHi\" : %u,\n" "\"RemainingSizeLo\" : %u,\n" "\"RemainingSizeHi\" : %u,\n" "\"PostTime\" : %i,\n" "\"FilenameConfirmed\" : %s,\n" "\"Paused\" : %s,\n" "\"NZBID\" : %i,\n" "\"NZBName\" : \"%s\",\n" "\"NZBNicename\" : \"%s\",\n" // deprecated, use "NZBName" instead "\"NZBFilename\" : \"%s\",\n" "\"Subject\" : \"%s\",\n" "\"Filename\" : \"%s\",\n" "\"DestDir\" : \"%s\",\n" "\"Category\" : \"%s\",\n" "\"Priority\" : %i,\n" // deprecated, use "Priority" of group instead "\"ActiveDownloads\" : %i,\n" "\"Progress\" : %i\n" "}"; int index = 0; for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; for (FileList::iterator it2 = pNZBInfo->GetFileList()->begin(); it2 != pNZBInfo->GetFileList()->end(); it2++) { FileInfo* pFileInfo = *it2; if ((iNZBID > 0 && iNZBID == pFileInfo->GetNZBInfo()->GetID()) || (iNZBID == 0 && (iIDStart == 0 || (iIDStart <= pFileInfo->GetID() && pFileInfo->GetID() <= iIDEnd)))) { unsigned long iFileSizeHi, iFileSizeLo; unsigned long iRemainingSizeLo, iRemainingSizeHi; Util::SplitInt64(pFileInfo->GetSize(), &iFileSizeHi, &iFileSizeLo); Util::SplitInt64(pFileInfo->GetRemainingSize(), &iRemainingSizeHi, &iRemainingSizeLo); char* xmlNZBFilename = EncodeStr(pFileInfo->GetNZBInfo()->GetFilename()); char* xmlSubject = EncodeStr(pFileInfo->GetSubject()); char* xmlFilename = EncodeStr(pFileInfo->GetFilename()); char* xmlDestDir = EncodeStr(pFileInfo->GetNZBInfo()->GetDestDir()); char* xmlCategory = EncodeStr(pFileInfo->GetNZBInfo()->GetCategory()); char* xmlNZBNicename = EncodeStr(pFileInfo->GetNZBInfo()->GetName()); int iProgress = pFileInfo->GetFailedSize() == 0 && pFileInfo->GetSuccessSize() == 0 ? 0 : (int)(1000 - pFileInfo->GetRemainingSize() * 1000 / (pFileInfo->GetSize() - pFileInfo->GetMissedSize())); AppendCondResponse(",\n", IsJson() && index++ > 0); AppendFmtResponse(IsJson() ? JSON_LIST_ITEM : XML_LIST_ITEM, pFileInfo->GetID(), iFileSizeLo, iFileSizeHi, iRemainingSizeLo, iRemainingSizeHi, pFileInfo->GetTime(), BoolToStr(pFileInfo->GetFilenameConfirmed()), BoolToStr(pFileInfo->GetPaused()), pFileInfo->GetNZBInfo()->GetID(), xmlNZBNicename, xmlNZBNicename, xmlNZBFilename, xmlSubject, xmlFilename, xmlDestDir, xmlCategory, pFileInfo->GetNZBInfo()->GetPriority(), pFileInfo->GetActiveDownloads(), iProgress); free(xmlNZBFilename); free(xmlSubject); free(xmlFilename); free(xmlDestDir); free(xmlCategory); free(xmlNZBNicename); } } } DownloadQueue::Unlock(); AppendResponse(IsJson() ? "\n]" : "\n"); } void NzbInfoXmlCommand::AppendNZBInfoFields(NZBInfo* pNZBInfo) { const char* XML_NZB_ITEM_START = "NZBID%i\n" "NZBName%s\n" "NZBNicename%s\n" // deprecated, use "NZBName" instead "Kind%s\n" "URL%s\n" "NZBFilename%s\n" "DestDir%s\n" "FinalDir%s\n" "Category%s\n" "ParStatus%s\n" "ExParStatus%s\n" "UnpackStatus%s\n" "MoveStatus%s\n" "ScriptStatus%s\n" "DeleteStatus%s\n" "MarkStatus%s\n" "UrlStatus%s\n" "FileSizeLo%u\n" "FileSizeHi%u\n" "FileSizeMB%i\n" "FileCount%i\n" "MinPostTime%i\n" "MaxPostTime%i\n" "TotalArticles%i\n" "SuccessArticles%i\n" "FailedArticles%i\n" "Health%i\n" "CriticalHealth%i\n" "DupeKey%s\n" "DupeScore%i\n" "DupeMode%s\n" "Deleted%s\n" // deprecated, use "DeleteStatus" instead "DownloadedSizeLo%u\n" "DownloadedSizeHi%u\n" "DownloadedSizeMB%i\n" "DownloadTimeSec%i\n" "PostTotalTimeSec%i\n" "ParTimeSec%i\n" "RepairTimeSec%i\n" "UnpackTimeSec%i\n" "MessageCount%i\n" "ExtraParBlocks%i\n" "Parameters\n"; const char* XML_NZB_ITEM_SCRIPT_START = "\n" "ScriptStatuses\n"; const char* XML_NZB_ITEM_STATS_START = "\n" "ServerStats\n"; const char* XML_NZB_ITEM_END = "\n"; const char* JSON_NZB_ITEM_START = "\"NZBID\" : %i,\n" "\"NZBName\" : \"%s\",\n" "\"NZBNicename\" : \"%s\",\n" // deprecated, use NZBName instead "\"Kind\" : \"%s\",\n" "\"URL\" : \"%s\",\n" "\"NZBFilename\" : \"%s\",\n" "\"DestDir\" : \"%s\",\n" "\"FinalDir\" : \"%s\",\n" "\"Category\" : \"%s\",\n" "\"ParStatus\" : \"%s\",\n" "\"ExParStatus\" : \"%s\",\n" "\"UnpackStatus\" : \"%s\",\n" "\"MoveStatus\" : \"%s\",\n" "\"ScriptStatus\" : \"%s\",\n" "\"DeleteStatus\" : \"%s\",\n" "\"MarkStatus\" : \"%s\",\n" "\"UrlStatus\" : \"%s\",\n" "\"FileSizeLo\" : %u,\n" "\"FileSizeHi\" : %u,\n" "\"FileSizeMB\" : %i,\n" "\"FileCount\" : %i,\n" "\"MinPostTime\" : %i,\n" "\"MaxPostTime\" : %i,\n" "\"TotalArticles\" : %i,\n" "\"SuccessArticles\" : %i,\n" "\"FailedArticles\" : %i,\n" "\"Health\" : %i,\n" "\"CriticalHealth\" : %i,\n" "\"DupeKey\" : \"%s\",\n" "\"DupeScore\" : %i,\n" "\"DupeMode\" : \"%s\",\n" "\"Deleted\" : %s,\n" // deprecated, use "DeleteStatus" instead "\"DownloadedSizeLo\" : %u,\n" "\"DownloadedSizeHi\" : %u,\n" "\"DownloadedSizeMB\" : %i,\n" "\"DownloadTimeSec\" : %i,\n" "\"PostTotalTimeSec\" : %i,\n" "\"ParTimeSec\" : %i,\n" "\"RepairTimeSec\" : %i,\n" "\"UnpackTimeSec\" : %i,\n" "\"MessageCount\" : %i,\n" "\"ExtraParBlocks\" : %i,\n" "\"Parameters\" : [\n"; const char* JSON_NZB_ITEM_SCRIPT_START = "],\n" "\"ScriptStatuses\" : [\n"; const char* JSON_NZB_ITEM_STATS_START = "],\n" "\"ServerStats\" : [\n"; const char* JSON_NZB_ITEM_END = "]\n"; const char* XML_PARAMETER_ITEM = "\n" "Name%s\n" "Value%s\n" "\n"; const char* JSON_PARAMETER_ITEM = "{\n" "\"Name\" : \"%s\",\n" "\"Value\" : \"%s\"\n" "}"; const char* XML_SCRIPT_ITEM = "\n" "Name%s\n" "Status%s\n" "\n"; const char* JSON_SCRIPT_ITEM = "{\n" "\"Name\" : \"%s\",\n" "\"Status\" : \"%s\"\n" "}"; const char* XML_STAT_ITEM = "\n" "ServerID%i\n" "SuccessArticles%i\n" "FailedArticles%i\n" "\n"; const char* JSON_STAT_ITEM = "{\n" "\"ServerID\" : %i,\n" "\"SuccessArticles\" : %i,\n" "\"FailedArticles\" : %i\n" "}"; const char* szKindName[] = { "NZB", "URL" }; const char* szParStatusName[] = { "NONE", "NONE", "FAILURE", "SUCCESS", "REPAIR_POSSIBLE", "MANUAL" }; const char* szUnpackStatusName[] = { "NONE", "NONE", "FAILURE", "SUCCESS", "SPACE", "PASSWORD" }; const char* szMoveStatusName[] = { "NONE", "FAILURE", "SUCCESS" }; const char* szScriptStatusName[] = { "NONE", "FAILURE", "SUCCESS" }; const char* szDeleteStatusName[] = { "NONE", "MANUAL", "HEALTH", "DUPE", "BAD", "GOOD", "COPY", "SCAN" }; const char* szMarkStatusName[] = { "NONE", "BAD", "GOOD", "SUCCESS" }; const char* szUrlStatusName[] = { "NONE", "UNKNOWN", "SUCCESS", "FAILURE", "UNKNOWN", "SCAN_SKIPPED", "SCAN_FAILURE" }; const char* szDupeModeName[] = { "SCORE", "ALL", "FORCE" }; unsigned long iFileSizeHi, iFileSizeLo, iFileSizeMB; Util::SplitInt64(pNZBInfo->GetSize(), &iFileSizeHi, &iFileSizeLo); iFileSizeMB = (int)(pNZBInfo->GetSize() / 1024 / 1024); unsigned long iDownloadedSizeHi, iDownloadedSizeLo, iDownloadedSizeMB; Util::SplitInt64(pNZBInfo->GetDownloadedSize(), &iDownloadedSizeHi, &iDownloadedSizeLo); iDownloadedSizeMB = (int)(pNZBInfo->GetDownloadedSize() / 1024 / 1024); int iMessageCount = pNZBInfo->GetMessageCount() > 0 ? pNZBInfo->GetMessageCount() : pNZBInfo->GetCachedMessageCount(); char* xmlURL = EncodeStr(pNZBInfo->GetURL()); char* xmlNZBFilename = EncodeStr(pNZBInfo->GetFilename()); char* xmlNZBNicename = EncodeStr(pNZBInfo->GetName()); char* xmlDestDir = EncodeStr(pNZBInfo->GetDestDir()); char* xmlFinalDir = EncodeStr(pNZBInfo->GetFinalDir()); char* xmlCategory = EncodeStr(pNZBInfo->GetCategory()); char* xmlDupeKey = EncodeStr(pNZBInfo->GetDupeKey()); const char* szExParStatus = pNZBInfo->GetExtraParBlocks() > 0 ? "RECIPIENT" : pNZBInfo->GetExtraParBlocks() < 0 ? "DONOR" : "NONE"; AppendFmtResponse(IsJson() ? JSON_NZB_ITEM_START : XML_NZB_ITEM_START, pNZBInfo->GetID(), xmlNZBNicename, xmlNZBNicename, szKindName[pNZBInfo->GetKind()], xmlURL, xmlNZBFilename, xmlDestDir, xmlFinalDir, xmlCategory, szParStatusName[pNZBInfo->GetParStatus()], szExParStatus, szUnpackStatusName[pNZBInfo->GetUnpackStatus()], szMoveStatusName[pNZBInfo->GetMoveStatus()], szScriptStatusName[pNZBInfo->GetScriptStatuses()->CalcTotalStatus()], szDeleteStatusName[pNZBInfo->GetDeleteStatus()], szMarkStatusName[pNZBInfo->GetMarkStatus()], szUrlStatusName[pNZBInfo->GetUrlStatus()], iFileSizeLo, iFileSizeHi, iFileSizeMB, pNZBInfo->GetFileCount(), pNZBInfo->GetMinTime(), pNZBInfo->GetMaxTime(), pNZBInfo->GetTotalArticles(), pNZBInfo->GetCurrentSuccessArticles(), pNZBInfo->GetCurrentFailedArticles(), pNZBInfo->CalcHealth(), pNZBInfo->CalcCriticalHealth(false), xmlDupeKey, pNZBInfo->GetDupeScore(), szDupeModeName[pNZBInfo->GetDupeMode()], BoolToStr(pNZBInfo->GetDeleteStatus() != NZBInfo::dsNone), iDownloadedSizeLo, iDownloadedSizeHi, iDownloadedSizeMB, pNZBInfo->GetDownloadSec(), pNZBInfo->GetPostInfo() && pNZBInfo->GetPostInfo()->GetStartTime() ? time(NULL) - pNZBInfo->GetPostInfo()->GetStartTime() : pNZBInfo->GetPostTotalSec(), pNZBInfo->GetParSec(), pNZBInfo->GetRepairSec(), pNZBInfo->GetUnpackSec(), iMessageCount, pNZBInfo->GetExtraParBlocks()); free(xmlURL); free(xmlNZBNicename); free(xmlNZBFilename); free(xmlCategory); free(xmlDestDir); free(xmlFinalDir); free(xmlDupeKey); // Post-processing parameters int iParamIndex = 0; for (NZBParameterList::iterator it = pNZBInfo->GetParameters()->begin(); it != pNZBInfo->GetParameters()->end(); it++) { NZBParameter* pParameter = *it; char* xmlName = EncodeStr(pParameter->GetName()); char* xmlValue = EncodeStr(pParameter->GetValue()); AppendCondResponse(",\n", IsJson() && iParamIndex++ > 0); AppendFmtResponse(IsJson() ? JSON_PARAMETER_ITEM : XML_PARAMETER_ITEM, xmlName, xmlValue); free(xmlName); free(xmlValue); } AppendResponse(IsJson() ? JSON_NZB_ITEM_SCRIPT_START : XML_NZB_ITEM_SCRIPT_START); // Script statuses int iScriptIndex = 0; for (ScriptStatusList::iterator it = pNZBInfo->GetScriptStatuses()->begin(); it != pNZBInfo->GetScriptStatuses()->end(); it++) { ScriptStatus* pScriptStatus = *it; char* xmlName = EncodeStr(pScriptStatus->GetName()); char* xmlStatus = EncodeStr(szScriptStatusName[pScriptStatus->GetStatus()]); AppendCondResponse(",\n", IsJson() && iScriptIndex++ > 0); AppendFmtResponse(IsJson() ? JSON_SCRIPT_ITEM : XML_SCRIPT_ITEM, xmlName, xmlStatus); free(xmlName); free(xmlStatus); } AppendResponse(IsJson() ? JSON_NZB_ITEM_STATS_START : XML_NZB_ITEM_STATS_START); // Server stats int iStatIndex = 0; for (ServerStatList::iterator it = pNZBInfo->GetCurrentServerStats()->begin(); it != pNZBInfo->GetCurrentServerStats()->end(); it++) { ServerStat* pServerStat = *it; AppendCondResponse(",\n", IsJson() && iStatIndex++ > 0); AppendFmtResponse(IsJson() ? JSON_STAT_ITEM : XML_STAT_ITEM, pServerStat->GetServerID(), pServerStat->GetSuccessArticles(), pServerStat->GetFailedArticles()); } AppendResponse(IsJson() ? JSON_NZB_ITEM_END : XML_NZB_ITEM_END); } void NzbInfoXmlCommand::AppendPostInfoFields(PostInfo* pPostInfo, int iLogEntries, bool bPostQueue) { const char* XML_GROUPQUEUE_ITEM_START = "PostInfoText%s\n" "PostStageProgress%i\n" "PostStageTimeSec%i\n"; // PostTotalTimeSec is printed by method "AppendNZBInfoFields" const char* XML_POSTQUEUE_ITEM_START = "ProgressLabel%s\n" "StageProgress%i\n" "StageTimeSec%i\n" "TotalTimeSec%i\n"; const char* XML_LOG_START = "Log\n"; const char* XML_POSTQUEUE_ITEM_END = "\n"; const char* JSON_GROUPQUEUE_ITEM_START = "\"PostInfoText\" : \"%s\",\n" "\"PostStageProgress\" : %i,\n" "\"PostStageTimeSec\" : %i,\n"; const char* JSON_POSTQUEUE_ITEM_START = "\"ProgressLabel\" : \"%s\",\n" "\"StageProgress\" : %i,\n" "\"StageTimeSec\" : %i,\n" "\"TotalTimeSec\" : %i,\n"; const char* JSON_LOG_START = "\"Log\" : [\n"; const char* JSON_POSTQUEUE_ITEM_END = "]\n"; const char* XML_LOG_ITEM = "\n" "ID%i\n" "Kind%s\n" "Time%i\n" "Text%s\n" "\n"; const char* JSON_LOG_ITEM = "{\n" "\"ID\" : %i,\n" "\"Kind\" : \"%s\",\n" "\"Time\" : %i,\n" "\"Text\" : \"%s\"\n" "}"; const char* szMessageType[] = { "INFO", "WARNING", "ERROR", "DEBUG", "DETAIL"}; const char* szItemStart = bPostQueue ? IsJson() ? JSON_POSTQUEUE_ITEM_START : XML_POSTQUEUE_ITEM_START : IsJson() ? JSON_GROUPQUEUE_ITEM_START : XML_GROUPQUEUE_ITEM_START; if (pPostInfo) { time_t tCurTime = time(NULL); char* xmlProgressLabel = EncodeStr(pPostInfo->GetProgressLabel()); AppendFmtResponse(szItemStart, xmlProgressLabel, pPostInfo->GetStageProgress(), pPostInfo->GetStageTime() ? tCurTime - pPostInfo->GetStageTime() : 0, pPostInfo->GetStartTime() ? tCurTime - pPostInfo->GetStartTime() : 0); free(xmlProgressLabel); } else { AppendFmtResponse(szItemStart, "NONE", "", 0, 0, 0, 0); } AppendResponse(IsJson() ? JSON_LOG_START : XML_LOG_START); if (iLogEntries > 0 && pPostInfo) { MessageList* pMessages = pPostInfo->GetNZBInfo()->LockCachedMessages(); if (!pMessages->empty()) { if (iLogEntries > (int)pMessages->size()) { iLogEntries = pMessages->size(); } int iStart = pMessages->size() - iLogEntries; int index = 0; for (unsigned int i = (unsigned int)iStart; i < pMessages->size(); i++) { Message* pMessage = (*pMessages)[i]; char* xmltext = EncodeStr(pMessage->GetText()); AppendCondResponse(",\n", IsJson() && index++ > 0); AppendFmtResponse(IsJson() ? JSON_LOG_ITEM : XML_LOG_ITEM, pMessage->GetID(), szMessageType[pMessage->GetKind()], pMessage->GetTime(), xmltext); free(xmltext); } } pPostInfo->GetNZBInfo()->UnlockCachedMessages(); } AppendResponse(IsJson() ? JSON_POSTQUEUE_ITEM_END : XML_POSTQUEUE_ITEM_END); } // struct[] listgroups(int NumberOfLogEntries) void ListGroupsXmlCommand::Execute() { int iNrEntries = 0; NextParamAsInt(&iNrEntries); AppendResponse(IsJson() ? "[\n" : "\n"); const char* XML_LIST_ITEM_START = "\n" "FirstID%i\n" // deprecated, use "NZBID" instead "LastID%i\n" // deprecated, use "NZBID" instead "RemainingSizeLo%u\n" "RemainingSizeHi%u\n" "RemainingSizeMB%i\n" "PausedSizeLo%u\n" "PausedSizeHi%u\n" "PausedSizeMB%i\n" "RemainingFileCount%i\n" "RemainingParCount%i\n" "MinPriority%i\n" "MaxPriority%i\n" "ActiveDownloads%i\n" "Status%s\n"; const char* XML_LIST_ITEM_END = "\n"; const char* JSON_LIST_ITEM_START = "{\n" "\"FirstID\" : %i,\n" // deprecated, use "NZBID" instead "\"LastID\" : %i,\n" // deprecated, use "NZBID" instead "\"RemainingSizeLo\" : %u,\n" "\"RemainingSizeHi\" : %u,\n" "\"RemainingSizeMB\" : %i,\n" "\"PausedSizeLo\" : %u,\n" "\"PausedSizeHi\" : %u,\n" "\"PausedSizeMB\" : %i,\n" "\"RemainingFileCount\" : %i,\n" "\"RemainingParCount\" : %i,\n" "\"MinPriority\" : %i,\n" "\"MaxPriority\" : %i,\n" "\"ActiveDownloads\" : %i,\n" "\"Status\" : \"%s\",\n"; const char* JSON_LIST_ITEM_END = "}"; int index = 0; DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; unsigned long iRemainingSizeLo, iRemainingSizeHi, iRemainingSizeMB; unsigned long iPausedSizeLo, iPausedSizeHi, iPausedSizeMB; Util::SplitInt64(pNZBInfo->GetRemainingSize(), &iRemainingSizeHi, &iRemainingSizeLo); iRemainingSizeMB = (int)(pNZBInfo->GetRemainingSize() / 1024 / 1024); Util::SplitInt64(pNZBInfo->GetPausedSize(), &iPausedSizeHi, &iPausedSizeLo); iPausedSizeMB = (int)(pNZBInfo->GetPausedSize() / 1024 / 1024); const char* szStatus = DetectStatus(pNZBInfo); AppendCondResponse(",\n", IsJson() && index++ > 0); AppendFmtResponse(IsJson() ? JSON_LIST_ITEM_START : XML_LIST_ITEM_START, pNZBInfo->GetID(), pNZBInfo->GetID(), iRemainingSizeLo, iRemainingSizeHi, iRemainingSizeMB, iPausedSizeLo, iPausedSizeHi, iPausedSizeMB, (int)pNZBInfo->GetFileList()->size(), pNZBInfo->GetRemainingParCount(), pNZBInfo->GetPriority(), pNZBInfo->GetPriority(), pNZBInfo->GetActiveDownloads(), szStatus); AppendNZBInfoFields(pNZBInfo); AppendCondResponse(",\n", IsJson()); AppendPostInfoFields(pNZBInfo->GetPostInfo(), iNrEntries, false); AppendResponse(IsJson() ? JSON_LIST_ITEM_END : XML_LIST_ITEM_END); if (it == pDownloadQueue->GetQueue()->begin()) { OptimizeResponse(pDownloadQueue->GetQueue()->size()); } } DownloadQueue::Unlock(); AppendResponse(IsJson() ? "\n]" : "\n"); } const char* ListGroupsXmlCommand::DetectStatus(NZBInfo* pNZBInfo) { const char* szPostStageName[] = { "PP_QUEUED", "LOADING_PARS", "VERIFYING_SOURCES", "REPAIRING", "VERIFYING_REPAIRED", "RENAMING", "UNPACKING", "MOVING", "EXECUTING_SCRIPT", "PP_FINISHED" }; const char* szStatus = NULL; if (pNZBInfo->GetPostInfo()) { bool bQueueScriptActive = false; if (pNZBInfo->GetPostInfo()->GetStage() == PostInfo::ptQueued && g_pQueueScriptCoordinator->HasJob(pNZBInfo->GetID(), &bQueueScriptActive)) { szStatus = bQueueScriptActive ? "QS_EXECUTING" : "QS_QUEUED"; } else { szStatus = szPostStageName[pNZBInfo->GetPostInfo()->GetStage()]; } } else if (pNZBInfo->GetActiveDownloads() > 0) { szStatus = pNZBInfo->GetKind() == NZBInfo::nkUrl ? "FETCHING" : "DOWNLOADING"; } else if ((pNZBInfo->GetPausedSize() > 0) && (pNZBInfo->GetRemainingSize() == pNZBInfo->GetPausedSize())) { szStatus = "PAUSED"; } else { szStatus = "QUEUED"; } return szStatus; } typedef struct { int iActionID; const char* szActionName; } EditCommandEntry; EditCommandEntry EditCommandNameMap[] = { { DownloadQueue::eaFileMoveOffset, "FileMoveOffset" }, { DownloadQueue::eaFileMoveTop, "FileMoveTop" }, { DownloadQueue::eaFileMoveBottom, "FileMoveBottom" }, { DownloadQueue::eaFilePause, "FilePause" }, { DownloadQueue::eaFileResume, "FileResume" }, { DownloadQueue::eaFileDelete, "FileDelete" }, { DownloadQueue::eaFilePauseAllPars, "FilePauseAllPars" }, { DownloadQueue::eaFilePauseExtraPars, "FilePauseExtraPars" }, { DownloadQueue::eaFileReorder, "FileReorder" }, { DownloadQueue::eaFileSplit, "FileSplit" }, { DownloadQueue::eaGroupMoveOffset, "GroupMoveOffset" }, { DownloadQueue::eaGroupMoveTop, "GroupMoveTop" }, { DownloadQueue::eaGroupMoveBottom, "GroupMoveBottom" }, { DownloadQueue::eaGroupPause, "GroupPause" }, { DownloadQueue::eaGroupResume, "GroupResume" }, { DownloadQueue::eaGroupDelete, "GroupDelete" }, { DownloadQueue::eaGroupDupeDelete, "GroupDupeDelete" }, { DownloadQueue::eaGroupFinalDelete, "GroupFinalDelete" }, { DownloadQueue::eaGroupPauseAllPars, "GroupPauseAllPars" }, { DownloadQueue::eaGroupPauseExtraPars, "GroupPauseExtraPars" }, { DownloadQueue::eaGroupSetPriority, "GroupSetPriority" }, { DownloadQueue::eaGroupSetCategory, "GroupSetCategory" }, { DownloadQueue::eaGroupApplyCategory, "GroupApplyCategory" }, { DownloadQueue::eaGroupMerge, "GroupMerge" }, { DownloadQueue::eaGroupSetParameter, "GroupSetParameter" }, { DownloadQueue::eaGroupSetName, "GroupSetName" }, { DownloadQueue::eaGroupSetDupeKey, "GroupSetDupeKey" }, { DownloadQueue::eaGroupSetDupeScore, "GroupSetDupeScore" }, { DownloadQueue::eaGroupSetDupeMode, "GroupSetDupeMode" }, { DownloadQueue::eaGroupSort, "GroupSort" }, { DownloadQueue::eaPostDelete, "PostDelete" }, { DownloadQueue::eaHistoryDelete, "HistoryDelete" }, { DownloadQueue::eaHistoryFinalDelete, "HistoryFinalDelete" }, { DownloadQueue::eaHistoryReturn, "HistoryReturn" }, { DownloadQueue::eaHistoryProcess, "HistoryProcess" }, { DownloadQueue::eaHistoryRedownload, "HistoryRedownload" }, { DownloadQueue::eaHistorySetParameter, "HistorySetParameter" }, { DownloadQueue::eaHistorySetDupeKey, "HistorySetDupeKey" }, { DownloadQueue::eaHistorySetDupeScore, "HistorySetDupeScore" }, { DownloadQueue::eaHistorySetDupeMode, "HistorySetDupeMode" }, { DownloadQueue::eaHistorySetDupeBackup, "HistorySetDupeBackup" }, { DownloadQueue::eaHistoryMarkBad, "HistoryMarkBad" }, { DownloadQueue::eaHistoryMarkGood, "HistoryMarkGood" }, { DownloadQueue::eaHistoryMarkSuccess, "HistoryMarkSuccess" }, { DownloadQueue::eaHistorySetCategory, "HistorySetCategory" }, { DownloadQueue::eaHistorySetName, "HistorySetName" }, { 0, NULL } }; void EditQueueXmlCommand::Execute() { if (!CheckSafeMethod()) { return; } char* szEditCommand; if (!NextParamAsStr(&szEditCommand)) { BuildErrorResponse(2, "Invalid parameter"); return; } debug("EditCommand=%s", szEditCommand); int iAction = -1; for (int i = 0; const char* szName = EditCommandNameMap[i].szActionName; i++) { if (!strcasecmp(szEditCommand, szName)) { iAction = EditCommandNameMap[i].iActionID; break; } } if (iAction == -1) { BuildErrorResponse(3, "Invalid action"); return; } int iOffset = 0; if (!NextParamAsInt(&iOffset)) { BuildErrorResponse(2, "Invalid parameter"); return; } char* szEditText; if (!NextParamAsStr(&szEditText)) { BuildErrorResponse(2, "Invalid parameter"); return; } debug("EditText=%s", szEditText); DecodeStr(szEditText); IDList cIDList; int iID = 0; while (NextParamAsInt(&iID)) { cIDList.push_back(iID); } DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); bool bOK = pDownloadQueue->EditList(&cIDList, NULL, DownloadQueue::mmID, (DownloadQueue::EEditAction)iAction, iOffset, szEditText); DownloadQueue::Unlock(); BuildBoolResponse(bOK); } // v16: // int append(string NZBFilename, string NZBContent, string Category, int Priority, bool AddToTop, bool AddPaused, string DupeKey, int DupeScore, string DupeMode, struct[] Parameters) // v13 (new param order and new result type): // int append(string NZBFilename, string NZBContent, string Category, int Priority, bool AddToTop, bool AddPaused, string DupeKey, int DupeScore, string DupeMode) // v12 (backward compatible, some params are optional): // bool append(string NZBFilename, string Category, int Priority, bool AddToTop, string Content, bool AddPaused, string DupeKey, int DupeScore, string DupeMode) void DownloadXmlCommand::Execute() { if (!CheckSafeMethod()) { return; } bool bV13 = true; char* szNZBFilename; if (!NextParamAsStr(&szNZBFilename)) { BuildErrorResponse(2, "Invalid parameter (NZBFileName)"); return; } char* szNZBContent; if (!NextParamAsStr(&szNZBContent)) { BuildErrorResponse(2, "Invalid parameter (NZBContent)"); return; } char* szCategory; if (!NextParamAsStr(&szCategory)) { bV13 = false; szCategory = szNZBContent; } DecodeStr(szNZBFilename); DecodeStr(szCategory); debug("FileName=%s", szNZBFilename); // For backward compatibility with 0.8 parameter "Priority" is optional (error checking omitted) int iPriority = 0; NextParamAsInt(&iPriority); bool bAddTop; if (!NextParamAsBool(&bAddTop)) { BuildErrorResponse(2, "Invalid parameter (AddTop)"); return; } if (!bV13 && !NextParamAsStr(&szNZBContent)) { BuildErrorResponse(2, "Invalid parameter (FileContent)"); return; } DecodeStr(szNZBContent); bool bAddPaused = false; char* szDupeKey = NULL; int iDupeScore = 0; EDupeMode eDupeMode = dmScore; if (NextParamAsBool(&bAddPaused)) { if (!NextParamAsStr(&szDupeKey)) { BuildErrorResponse(2, "Invalid parameter (DupeKey)"); return; } DecodeStr(szDupeKey); if (!NextParamAsInt(&iDupeScore)) { BuildErrorResponse(2, "Invalid parameter (DupeScore)"); return; } char* szDupeMode = NULL; if (!NextParamAsStr(&szDupeMode) || (strcasecmp(szDupeMode, "score") && strcasecmp(szDupeMode, "all") && strcasecmp(szDupeMode, "force"))) { BuildErrorResponse(2, "Invalid parameter (DupeMode)"); return; } eDupeMode = !strcasecmp(szDupeMode, "all") ? dmAll : !strcasecmp(szDupeMode, "force") ? dmForce : dmScore; } else if (bV13) { BuildErrorResponse(2, "Invalid parameter (AddPaused)"); return; } NZBParameterList Params; if (bV13) { char* szParamName = NULL; char* szParamValue = NULL; while (NextParamAsStr(&szParamName)) { if (!NextParamAsStr(&szParamValue)) { BuildErrorResponse(2, "Invalid parameter (Parameters)"); return; } Params.SetParameter(szParamName, szParamValue); } } if (!strncasecmp(szNZBContent, "http://", 6) || !strncasecmp(szNZBContent, "https://", 7)) { // add url NZBInfo* pNZBInfo = new NZBInfo(); pNZBInfo->SetKind(NZBInfo::nkUrl); pNZBInfo->SetURL(szNZBContent); pNZBInfo->SetFilename(szNZBFilename); pNZBInfo->SetCategory(szCategory); pNZBInfo->SetPriority(iPriority); pNZBInfo->SetAddUrlPaused(bAddPaused); pNZBInfo->SetDupeKey(szDupeKey ? szDupeKey : ""); pNZBInfo->SetDupeScore(iDupeScore); pNZBInfo->SetDupeMode(eDupeMode); pNZBInfo->GetParameters()->CopyFrom(&Params); int iNZBID = pNZBInfo->GetID(); char szNicename[1024]; pNZBInfo->MakeNiceUrlName(szNZBContent, szNZBFilename, szNicename, sizeof(szNicename)); info("Queue %s", szNicename); DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); pDownloadQueue->GetQueue()->Add(pNZBInfo, bAddTop); pDownloadQueue->Save(); DownloadQueue::Unlock(); if (bV13) { BuildIntResponse(iNZBID); } else { BuildBoolResponse(true); } } else { // add file content int iLen = WebUtil::DecodeBase64(szNZBContent, 0, szNZBContent); szNZBContent[iLen] = '\0'; //debug("FileContent=%s", szFileContent); int iNZBID = -1; g_pScanner->AddExternalFile(szNZBFilename, szCategory, iPriority, szDupeKey, iDupeScore, eDupeMode, Params.empty() ? NULL : &Params, bAddTop, bAddPaused, NULL, NULL, szNZBContent, iLen, &iNZBID); if (bV13) { BuildIntResponse(iNZBID); } else { BuildBoolResponse(iNZBID > 0); } } } // deprecated void PostQueueXmlCommand::Execute() { int iNrEntries = 0; NextParamAsInt(&iNrEntries); AppendResponse(IsJson() ? "[\n" : "\n"); const char* XML_POSTQUEUE_ITEM_START = "\n" "ID%i\n" "InfoName%s\n" "ParFilename\n" // deprecated, always empty "Stage%s\n" "FileProgress%i\n"; const char* XML_POSTQUEUE_ITEM_END = "\n"; const char* JSON_POSTQUEUE_ITEM_START = "{\n" "\"ID\" : %i,\n" "\"InfoName\" : \"%s\",\n" "\"ParFilename\" : \"\",\n" // deprecated, always empty "\"Stage\" : \"%s\",\n" "\"FileProgress\" : %i,\n"; const char* JSON_POSTQUEUE_ITEM_END = "}"; const char* szPostStageName[] = { "QUEUED", "LOADING_PARS", "VERIFYING_SOURCES", "REPAIRING", "VERIFYING_REPAIRED", "RENAMING", "UNPACKING", "MOVING", "EXECUTING_SCRIPT", "FINISHED" }; NZBList* pNZBList = DownloadQueue::Lock()->GetQueue(); int index = 0; for (NZBList::iterator it = pNZBList->begin(); it != pNZBList->end(); it++) { NZBInfo* pNZBInfo = *it; PostInfo* pPostInfo = pNZBInfo->GetPostInfo(); if (!pPostInfo) { continue; } char* xmlInfoName = EncodeStr(pPostInfo->GetNZBInfo()->GetName()); AppendCondResponse(",\n", IsJson() && index++ > 0); AppendFmtResponse(IsJson() ? JSON_POSTQUEUE_ITEM_START : XML_POSTQUEUE_ITEM_START, pNZBInfo->GetID(), xmlInfoName, szPostStageName[pPostInfo->GetStage()], pPostInfo->GetFileProgress()); free(xmlInfoName); AppendNZBInfoFields(pPostInfo->GetNZBInfo()); AppendCondResponse(",\n", IsJson()); AppendPostInfoFields(pPostInfo, iNrEntries, true); AppendResponse(IsJson() ? JSON_POSTQUEUE_ITEM_END : XML_POSTQUEUE_ITEM_END); } DownloadQueue::Unlock(); AppendResponse(IsJson() ? "\n]" : "\n"); } void WriteLogXmlCommand::Execute() { if (!CheckSafeMethod()) { return; } char* szKind; char* szText; if (!NextParamAsStr(&szKind) || !NextParamAsStr(&szText)) { BuildErrorResponse(2, "Invalid parameter"); return; } DecodeStr(szText); debug("Kind=%s, Text=%s", szKind, szText); if (!strcmp(szKind, "INFO")) { info(szText); } else if (!strcmp(szKind, "WARNING")) { warn(szText); } else if (!strcmp(szKind, "ERROR")) { error(szText); } else if (!strcmp(szKind, "DETAIL")) { detail(szText); } else if (!strcmp(szKind, "DEBUG")) { debug(szText); } else { BuildErrorResponse(3, "Invalid Kind"); return; } BuildBoolResponse(true); } void ClearLogXmlCommand::Execute() { if (!CheckSafeMethod()) { return; } g_pLog->Clear(); BuildBoolResponse(true); } void ScanXmlCommand::Execute() { if (!CheckSafeMethod()) { return; } bool bSyncMode = false; // optional parameter "SyncMode" NextParamAsBool(&bSyncMode); g_pScanner->ScanNZBDir(bSyncMode); BuildBoolResponse(true); } // struct[] history(bool hidden) // Parameter "hidden" is optional (new in v12) void HistoryXmlCommand::Execute() { AppendResponse(IsJson() ? "[\n" : "\n"); const char* XML_HISTORY_ITEM_START = "\n" "ID%i\n" // Deprecated, use "NZBID" instead "Name%s\n" "RemainingFileCount%i\n" "HistoryTime%i\n" "Status%s\n" "Log\n"; // Deprected, always empty const char* JSON_HISTORY_ITEM_START = "{\n" "\"ID\" : %i,\n" // Deprecated, use "NZBID" instead "\"Name\" : \"%s\",\n" "\"RemainingFileCount\" : %i,\n" "\"HistoryTime\" : %i,\n" "\"Status\" : \"%s\",\n" "\"Log\" : [],\n"; // Deprected, always empty const char* XML_HISTORY_ITEM_END = ""; const char* JSON_HISTORY_ITEM_END = "}"; const char* XML_HISTORY_DUP_ITEM = "\n" "ID%i\n" // Deprecated, use "NZBID" instead "NZBID%i\n" "Kind%s\n" "Name%s\n" "HistoryTime%i\n" "FileSizeLo%u\n" "FileSizeHi%u\n" "FileSizeMB%i\n" "DupeKey%s\n" "DupeScore%i\n" "DupeMode%s\n" "DupStatus%s\n" "Status%s\n"; const char* JSON_HISTORY_DUP_ITEM = "{\n" "\"ID\" : %i,\n" // Deprecated, use "NZBID" instead "\"NZBID\" : %i,\n" "\"Kind\" : \"%s\",\n" "\"Name\" : \"%s\",\n" "\"HistoryTime\" : %i,\n" "\"FileSizeLo\" : %i,\n" "\"FileSizeHi\" : %i,\n" "\"FileSizeMB\" : %i,\n" "\"DupeKey\" : \"%s\",\n" "\"DupeScore\" : %i,\n" "\"DupeMode\" : \"%s\",\n" "\"DupStatus\" : \"%s\",\n" "\"Status\" : \"%s\"\n"; const char* szDupStatusName[] = { "UNKNOWN", "SUCCESS", "FAILURE", "DELETED", "DUPE", "BAD", "GOOD" }; const char* szDupeModeName[] = { "SCORE", "ALL", "FORCE" }; bool bDup = false; NextParamAsBool(&bDup); DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); int index = 0; for (HistoryList::iterator it = pDownloadQueue->GetHistory()->begin(); it != pDownloadQueue->GetHistory()->end(); it++) { HistoryInfo* pHistoryInfo = *it; if (pHistoryInfo->GetKind() == HistoryInfo::hkDup && !bDup) { continue; } NZBInfo* pNZBInfo = NULL; char szNicename[1024]; pHistoryInfo->GetName(szNicename, sizeof(szNicename)); char *xmlNicename = EncodeStr(szNicename); const char* szStatus = DetectStatus(pHistoryInfo); AppendCondResponse(",\n", IsJson() && index++ > 0); if (pHistoryInfo->GetKind() == HistoryInfo::hkNzb || pHistoryInfo->GetKind() == HistoryInfo::hkUrl) { pNZBInfo = pHistoryInfo->GetNZBInfo(); AppendFmtResponse(IsJson() ? JSON_HISTORY_ITEM_START : XML_HISTORY_ITEM_START, pHistoryInfo->GetID(), xmlNicename, pNZBInfo->GetParkedFileCount(), pHistoryInfo->GetTime(), szStatus); } else if (pHistoryInfo->GetKind() == HistoryInfo::hkDup) { DupInfo* pDupInfo = pHistoryInfo->GetDupInfo(); unsigned long iFileSizeHi, iFileSizeLo, iFileSizeMB; Util::SplitInt64(pDupInfo->GetSize(), &iFileSizeHi, &iFileSizeLo); iFileSizeMB = (int)(pDupInfo->GetSize() / 1024 / 1024); char* xmlDupeKey = EncodeStr(pDupInfo->GetDupeKey()); AppendFmtResponse(IsJson() ? JSON_HISTORY_DUP_ITEM : XML_HISTORY_DUP_ITEM, pHistoryInfo->GetID(), pHistoryInfo->GetID(), "DUP", xmlNicename, pHistoryInfo->GetTime(), iFileSizeLo, iFileSizeHi, iFileSizeMB, xmlDupeKey, pDupInfo->GetDupeScore(), szDupeModeName[pDupInfo->GetDupeMode()], szDupStatusName[pDupInfo->GetStatus()], szStatus); free(xmlDupeKey); } free(xmlNicename); if (pNZBInfo) { AppendNZBInfoFields(pNZBInfo); } AppendResponse(IsJson() ? JSON_HISTORY_ITEM_END : XML_HISTORY_ITEM_END); if (it == pDownloadQueue->GetHistory()->begin()) { OptimizeResponse(pDownloadQueue->GetHistory()->size()); } } AppendResponse(IsJson() ? "\n]" : "\n"); DownloadQueue::Unlock(); } const char* HistoryXmlCommand::DetectStatus(HistoryInfo* pHistoryInfo) { const char* szStatus = "FAILURE/INTERNAL_ERROR"; if (pHistoryInfo->GetKind() == HistoryInfo::hkNzb || pHistoryInfo->GetKind() == HistoryInfo::hkUrl) { NZBInfo* pNZBInfo = pHistoryInfo->GetNZBInfo(); szStatus = pNZBInfo->MakeTextStatus(false); } else if (pHistoryInfo->GetKind() == HistoryInfo::hkDup) { DupInfo* pDupInfo = pHistoryInfo->GetDupInfo(); const char* szDupStatusName[] = { "FAILURE/INTERNAL_ERROR", "SUCCESS/HIDDEN", "FAILURE/HIDDEN", "DELETED/MANUAL", "DELETED/DUPE", "FAILURE/BAD", "SUCCESS/GOOD" }; szStatus = szDupStatusName[pDupInfo->GetStatus()]; } return szStatus; } // Deprecated in v13 void UrlQueueXmlCommand::Execute() { AppendResponse(IsJson() ? "[\n" : "\n"); const char* XML_URLQUEUE_ITEM = "\n" "ID%i\n" "NZBFilename%s\n" "URL%s\n" "Name%s\n" "Category%s\n" "Priority%i\n" "\n"; const char* JSON_URLQUEUE_ITEM = "{\n" "\"ID\" : %i,\n" "\"NZBFilename\" : \"%s\",\n" "\"URL\" : \"%s\",\n" "\"Name\" : \"%s\",\n" "\"Category\" : \"%s\",\n" "\"Priority\" : %i\n" "}"; DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); int index = 0; for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; if (pNZBInfo->GetKind() == NZBInfo::nkUrl) { char* xmlNicename = EncodeStr(pNZBInfo->GetName()); char* xmlNZBFilename = EncodeStr(pNZBInfo->GetFilename()); char* xmlURL = EncodeStr(pNZBInfo->GetURL()); char* xmlCategory = EncodeStr(pNZBInfo->GetCategory()); AppendCondResponse(",\n", IsJson() && index++ > 0); AppendFmtResponse(IsJson() ? JSON_URLQUEUE_ITEM : XML_URLQUEUE_ITEM, pNZBInfo->GetID(), xmlNZBFilename, xmlURL, xmlNicename, xmlCategory, pNZBInfo->GetPriority()); free(xmlNicename); free(xmlNZBFilename); free(xmlURL); free(xmlCategory); } } DownloadQueue::Unlock(); AppendResponse(IsJson() ? "\n]" : "\n"); } // struct[] config() void ConfigXmlCommand::Execute() { const char* XML_CONFIG_ITEM = "\n" "Name%s\n" "Value%s\n" "\n"; const char* JSON_CONFIG_ITEM = "{\n" "\"Name\" : \"%s\",\n" "\"Value\" : \"%s\"\n" "}"; AppendResponse(IsJson() ? "[\n" : "\n"); int index = 0; Options::OptEntries* pOptEntries = g_pOptions->LockOptEntries(); for (Options::OptEntries::iterator it = pOptEntries->begin(); it != pOptEntries->end(); it++) { Options::OptEntry* pOptEntry = *it; char* xmlName = EncodeStr(pOptEntry->GetName()); char* xmlValue = EncodeStr(m_eUserAccess == XmlRpcProcessor::uaRestricted && pOptEntry->Restricted() ? "***" : pOptEntry->GetValue()); AppendCondResponse(",\n", IsJson() && index++ > 0); AppendFmtResponse(IsJson() ? JSON_CONFIG_ITEM : XML_CONFIG_ITEM, xmlName, xmlValue); free(xmlName); free(xmlValue); } g_pOptions->UnlockOptEntries(); AppendResponse(IsJson() ? "\n]" : "\n"); } // struct[] loadconfig() void LoadConfigXmlCommand::Execute() { const char* XML_CONFIG_ITEM = "\n" "Name%s\n" "Value%s\n" "\n"; const char* JSON_CONFIG_ITEM = "{\n" "\"Name\" : \"%s\",\n" "\"Value\" : \"%s\"\n" "}"; Options::OptEntries* pOptEntries = new Options::OptEntries(); if (!g_pScriptConfig->LoadConfig(pOptEntries)) { BuildErrorResponse(3, "Could not read configuration file"); delete pOptEntries; return; } AppendResponse(IsJson() ? "[\n" : "\n"); int index = 0; for (Options::OptEntries::iterator it = pOptEntries->begin(); it != pOptEntries->end(); it++) { Options::OptEntry* pOptEntry = *it; char* xmlName = EncodeStr(pOptEntry->GetName()); char* xmlValue = EncodeStr(m_eUserAccess == XmlRpcProcessor::uaRestricted && pOptEntry->Restricted() ? "***" : pOptEntry->GetValue()); AppendCondResponse(",\n", IsJson() && index++ > 0); AppendFmtResponse(IsJson() ? JSON_CONFIG_ITEM : XML_CONFIG_ITEM, xmlName, xmlValue); free(xmlName); free(xmlValue); } delete pOptEntries; AppendResponse(IsJson() ? "\n]" : "\n"); } // bool saveconfig(struct[] data) void SaveConfigXmlCommand::Execute() { Options::OptEntries* pOptEntries = new Options::OptEntries(); char* szName; char* szValue; char* szDummy; while ((IsJson() && NextParamAsStr(&szDummy) && NextParamAsStr(&szName) && NextParamAsStr(&szDummy) && NextParamAsStr(&szValue)) || (!IsJson() && NextParamAsStr(&szName) && NextParamAsStr(&szValue))) { DecodeStr(szName); DecodeStr(szValue); pOptEntries->push_back(new Options::OptEntry(szName, szValue)); } // save to config file bool bOK = g_pScriptConfig->SaveConfig(pOptEntries); delete pOptEntries; BuildBoolResponse(bOK); } // struct[] configtemplates(bool loadFromDisk) // parameter "loadFromDisk" is optional (new in v14) void ConfigTemplatesXmlCommand::Execute() { const char* XML_CONFIG_ITEM = "\n" "Name%s\n" "DisplayName%s\n" "PostScript%s\n" "ScanScript%s\n" "QueueScript%s\n" "SchedulerScript%s\n" "FeedScript%s\n" "Template%s\n" "\n"; const char* JSON_CONFIG_ITEM = "{\n" "\"Name\" : \"%s\",\n" "\"DisplayName\" : \"%s\",\n" "\"PostScript\" : %s,\n" "\"ScanScript\" : %s,\n" "\"QueueScript\" : %s,\n" "\"SchedulerScript\" : %s,\n" "\"FeedScript\" : %s,\n" "\"Template\" : \"%s\"\n" "}"; bool bLoadFromDisk = false; NextParamAsBool(&bLoadFromDisk); ScriptConfig::ConfigTemplates* pConfigTemplates = g_pScriptConfig->GetConfigTemplates(); if (bLoadFromDisk) { pConfigTemplates = new ScriptConfig::ConfigTemplates(); if (!g_pScriptConfig->LoadConfigTemplates(pConfigTemplates)) { BuildErrorResponse(3, "Could not read configuration templates"); delete pConfigTemplates; return; } } AppendResponse(IsJson() ? "[\n" : "\n"); int index = 0; for (ScriptConfig::ConfigTemplates::iterator it = pConfigTemplates->begin(); it != pConfigTemplates->end(); it++) { ScriptConfig::ConfigTemplate* pConfigTemplate = *it; char* xmlName = EncodeStr(pConfigTemplate->GetScript() ? pConfigTemplate->GetScript()->GetName() : ""); char* xmlDisplayName = EncodeStr(pConfigTemplate->GetScript() ? pConfigTemplate->GetScript()->GetDisplayName() : ""); char* xmlTemplate = EncodeStr(pConfigTemplate->GetTemplate()); AppendCondResponse(",\n", IsJson() && index++ > 0); AppendFmtResponse(IsJson() ? JSON_CONFIG_ITEM : XML_CONFIG_ITEM, xmlName, xmlDisplayName, BoolToStr(pConfigTemplate->GetScript() && pConfigTemplate->GetScript()->GetPostScript()), BoolToStr(pConfigTemplate->GetScript() && pConfigTemplate->GetScript()->GetScanScript()), BoolToStr(pConfigTemplate->GetScript() && pConfigTemplate->GetScript()->GetQueueScript()), BoolToStr(pConfigTemplate->GetScript() && pConfigTemplate->GetScript()->GetSchedulerScript()), BoolToStr(pConfigTemplate->GetScript() && pConfigTemplate->GetScript()->GetFeedScript()), xmlTemplate); free(xmlName); free(xmlDisplayName); free(xmlTemplate); } if (bLoadFromDisk) { delete pConfigTemplates; } AppendResponse(IsJson() ? "\n]" : "\n"); } ViewFeedXmlCommand::ViewFeedXmlCommand(bool bPreview) { m_bPreview = bPreview; } // struct[] viewfeed(int id) // v12: // struct[] previewfeed(string name, string url, string filter, bool pauseNzb, string category, // int priority, bool includeNonMatching, int cacheTimeSec, string cacheId) // v16: // struct[] previewfeed(int id, string name, string url, string filter, bool backlog, bool pauseNzb, string category, // int priority, int interval, string feedfilter, bool includeNonMatching, int cacheTimeSec, string cacheId) void ViewFeedXmlCommand::Execute() { bool bOK = false; bool bIncludeNonMatching = false; FeedItemInfos* pFeedItemInfos = NULL; if (m_bPreview) { int iID = 0; char* szName; char* szUrl; char* szFilter; bool bBacklog = true; bool bPauseNzb; char* szCategory; int iInterval = 0; int iPriority; char* szFeedFilter = NULL; char* szCacheId; int iCacheTimeSec; // if the first parameter is int then it's the v16 signature bool bV16 = NextParamAsInt(&iID); if (!NextParamAsStr(&szName) || !NextParamAsStr(&szUrl) || !NextParamAsStr(&szFilter) || (bV16 && !NextParamAsBool(&bBacklog)) || !NextParamAsBool(&bPauseNzb) || !NextParamAsStr(&szCategory) || !NextParamAsInt(&iPriority) || (bV16 && (!NextParamAsInt(&iInterval) || !NextParamAsStr(&szFeedFilter))) || !NextParamAsBool(&bIncludeNonMatching) || !NextParamAsInt(&iCacheTimeSec) || !NextParamAsStr(&szCacheId)) { BuildErrorResponse(2, "Invalid parameter"); return; } DecodeStr(szName); DecodeStr(szUrl); DecodeStr(szFilter); DecodeStr(szCacheId); DecodeStr(szCategory); debug("Url=%s", szUrl); debug("Filter=%s", szFilter); bOK = g_pFeedCoordinator->PreviewFeed(iID, szName, szUrl, szFilter, bBacklog, bPauseNzb, szCategory, iPriority, iInterval, szFeedFilter, iCacheTimeSec, szCacheId, &pFeedItemInfos); } else { int iID = 0; if (!NextParamAsInt(&iID) || !NextParamAsBool(&bIncludeNonMatching)) { BuildErrorResponse(2, "Invalid parameter"); return; } debug("ID=%i", iID); bOK = g_pFeedCoordinator->ViewFeed(iID, &pFeedItemInfos); } if (!bOK) { BuildErrorResponse(3, "Could not read feed"); return; } const char* XML_FEED_ITEM = "\n" "Title%s\n" "Filename%s\n" "URL%s\n" "SizeLo%i\n" "SizeHi%i\n" "SizeMB%i\n" "Category%s\n" "AddCategory%s\n" "PauseNzb%s\n" "Priority%i\n" "Time%i\n" "Match%s\n" "Rule%i\n" "DupeKey%s\n" "DupeScore%i\n" "DupeMode%s\n" "Status%s\n" "\n"; const char* JSON_FEED_ITEM = "{\n" "\"Title\" : \"%s\",\n" "\"Filename\" : \"%s\",\n" "\"URL\" : \"%s\",\n" "\"SizeLo\" : %i,\n" "\"SizeHi\" : %i,\n" "\"SizeMB\" : %i,\n" "\"Category\" : \"%s\",\n" "\"AddCategory\" : \"%s\",\n" "\"PauseNzb\" : %s,\n" "\"Priority\" : %i,\n" "\"Time\" : %i,\n" "\"Match\" : \"%s\",\n" "\"Rule\" : %i,\n" "\"DupeKey\" : \"%s\",\n" "\"DupeScore\" : %i,\n" "\"DupeMode\" : \"%s\",\n" "\"Status\" : \"%s\"\n" "}"; const char* szStatusType[] = { "UNKNOWN", "BACKLOG", "FETCHED", "NEW" }; const char* szMatchStatusType[] = { "IGNORED", "ACCEPTED", "REJECTED" }; const char* szDupeModeType[] = { "SCORE", "ALL", "FORCE" }; AppendResponse(IsJson() ? "[\n" : "\n"); int index = 0; for (FeedItemInfos::iterator it = pFeedItemInfos->begin(); it != pFeedItemInfos->end(); it++) { FeedItemInfo* pFeedItemInfo = *it; if (bIncludeNonMatching || pFeedItemInfo->GetMatchStatus() == FeedItemInfo::msAccepted) { unsigned long iSizeHi, iSizeLo; Util::SplitInt64(pFeedItemInfo->GetSize(), &iSizeHi, &iSizeLo); int iSizeMB = (int)(pFeedItemInfo->GetSize() / 1024 / 1024); char* xmltitle = EncodeStr(pFeedItemInfo->GetTitle()); char* xmlfilename = EncodeStr(pFeedItemInfo->GetFilename()); char* xmlurl = EncodeStr(pFeedItemInfo->GetUrl()); char* xmlcategory = EncodeStr(pFeedItemInfo->GetCategory()); char* xmladdcategory = EncodeStr(pFeedItemInfo->GetAddCategory()); char* xmldupekey = EncodeStr(pFeedItemInfo->GetDupeKey()); AppendCondResponse(",\n", IsJson() && index++ > 0); AppendFmtResponse(IsJson() ? JSON_FEED_ITEM : XML_FEED_ITEM, xmltitle, xmlfilename, xmlurl, iSizeLo, iSizeHi, iSizeMB, xmlcategory, xmladdcategory, BoolToStr(pFeedItemInfo->GetPauseNzb()), pFeedItemInfo->GetPriority(), pFeedItemInfo->GetTime(), szMatchStatusType[pFeedItemInfo->GetMatchStatus()], pFeedItemInfo->GetMatchRule(), xmldupekey, pFeedItemInfo->GetDupeScore(), szDupeModeType[pFeedItemInfo->GetDupeMode()], szStatusType[pFeedItemInfo->GetStatus()]); free(xmltitle); free(xmlfilename); free(xmlurl); free(xmlcategory); free(xmladdcategory); free(xmldupekey); } } pFeedItemInfos->Release(); AppendResponse(IsJson() ? "\n]" : "\n"); } // bool fetchfeed(int ID) void FetchFeedXmlCommand::Execute() { if (!CheckSafeMethod()) { return; } int iID; if (!NextParamAsInt(&iID)) { BuildErrorResponse(2, "Invalid parameter (ID)"); return; } g_pFeedCoordinator->FetchFeed(iID); BuildBoolResponse(true); } // bool editserver(int ID, bool Active) void EditServerXmlCommand::Execute() { if (!CheckSafeMethod()) { return; } bool bOK = false; int bFirst = true; int iID; while (NextParamAsInt(&iID)) { bFirst = false; bool bActive; if (!NextParamAsBool(&bActive)) { BuildErrorResponse(2, "Invalid parameter"); return; } for (Servers::iterator it = g_pServerPool->GetServers()->begin(); it != g_pServerPool->GetServers()->end(); it++) { NewsServer* pServer = *it; if (pServer->GetID() == iID) { pServer->SetActive(bActive); bOK = true; } } } if (bFirst) { BuildErrorResponse(2, "Invalid parameter"); return; } if (bOK) { g_pServerPool->Changed(); } BuildBoolResponse(bOK); } // string readurl(string url, string infoname) void ReadUrlXmlCommand::Execute() { char* szURL; if (!NextParamAsStr(&szURL)) { BuildErrorResponse(2, "Invalid parameter (URL)"); return; } DecodeStr(szURL); char* szInfoName; if (!NextParamAsStr(&szInfoName)) { BuildErrorResponse(2, "Invalid parameter (InfoName)"); return; } DecodeStr(szInfoName); // generate temp file name char szTempFileName[1024]; int iNum = 1; while (iNum == 1 || Util::FileExists(szTempFileName)) { snprintf(szTempFileName, 1024, "%sreadurl-%i.tmp", g_pOptions->GetTempDir(), iNum); szTempFileName[1024-1] = '\0'; iNum++; } WebDownloader* pDownloader = new WebDownloader(); pDownloader->SetURL(szURL); pDownloader->SetForce(true); pDownloader->SetRetry(false); pDownloader->SetOutputFilename(szTempFileName); pDownloader->SetInfoName(szInfoName); // do sync download WebDownloader::EStatus eStatus = pDownloader->DownloadWithRedirects(5); bool bOK = eStatus == WebDownloader::adFinished; delete pDownloader; if (bOK) { char* szFileContent = NULL; int iFileContentLen = 0; Util::LoadFileIntoBuffer(szTempFileName, &szFileContent, &iFileContentLen); char* xmlContent = EncodeStr(szFileContent); free(szFileContent); AppendResponse(IsJson() ? "\"" : ""); AppendResponse(xmlContent); AppendResponse(IsJson() ? "\"" : ""); free(xmlContent); } else { BuildErrorResponse(3, "Could not read url"); } remove(szTempFileName); } // string checkupdates() void CheckUpdatesXmlCommand::Execute() { char* szUpdateInfo = NULL; bool bOK = g_pMaintenance->CheckUpdates(&szUpdateInfo); if (bOK) { char* xmlContent = EncodeStr(szUpdateInfo); free(szUpdateInfo); AppendResponse(IsJson() ? "\"" : ""); AppendResponse(xmlContent); AppendResponse(IsJson() ? "\"" : ""); free(xmlContent); } else { BuildErrorResponse(3, "Could not read update info from update-info-script"); } } // bool startupdate(string branch) void StartUpdateXmlCommand::Execute() { if (!CheckSafeMethod()) { return; } char* szBranch; if (!NextParamAsStr(&szBranch)) { BuildErrorResponse(2, "Invalid parameter (Branch)"); return; } DecodeStr(szBranch); Maintenance::EBranch eBranch; if (!strcasecmp(szBranch, "stable")) { eBranch = Maintenance::brStable; } else if (!strcasecmp(szBranch, "testing")) { eBranch = Maintenance::brTesting; } else if (!strcasecmp(szBranch, "devel")) { eBranch = Maintenance::brDevel; } else { BuildErrorResponse(2, "Invalid parameter (Branch)"); return; } bool bOK = g_pMaintenance->StartUpdate(eBranch); BuildBoolResponse(bOK); } // struct[] logupdate(idfrom, entries) MessageList* LogUpdateXmlCommand::LockMessages() { return g_pMaintenance->LockMessages(); } void LogUpdateXmlCommand::UnlockMessages() { g_pMaintenance->UnlockMessages(); } // struct[] servervolumes() void ServerVolumesXmlCommand::Execute() { const char* XML_VOLUME_ITEM_START = "\n" "ServerID%i\n" "DataTime%i\n" "FirstDay%i\n" "TotalSizeLo%u\n" "TotalSizeHi%u\n" "TotalSizeMB%i\n" "CustomSizeLo%u\n" "CustomSizeHi%u\n" "CustomSizeMB%i\n" "CustomTime%i\n" "SecSlot%i\n" "MinSlot%i\n" "HourSlot%i\n" "DaySlot%i\n"; const char* XML_BYTES_ARRAY_START = "%s\n"; const char* XML_BYTES_ARRAY_ITEM = "\n" "SizeLo%u\n" "SizeHi%u\n" "SizeMB%i\n" "\n"; const char* XML_BYTES_ARRAY_END = "\n"; const char* XML_VOLUME_ITEM_END = "\n"; const char* JSON_VOLUME_ITEM_START = "{\n" "\"ServerID\" : %i,\n" "\"DataTime\" : %i,\n" "\"FirstDay\" : %i,\n" "\"TotalSizeLo\" : %i,\n" "\"TotalSizeHi\" : %i,\n" "\"TotalSizeMB\" : %i,\n" "\"CustomSizeLo\" : %i,\n" "\"CustomSizeHi\" : %i,\n" "\"CustomSizeMB\" : %i,\n" "\"CustomTime\" : %i,\n" "\"SecSlot\" : %i,\n" "\"MinSlot\" : %i,\n" "\"HourSlot\" : %i,\n" "\"DaySlot\" : %i,\n"; const char* JSON_BYTES_ARRAY_START = "\"%s\" : [\n"; const char* JSON_BYTES_ARRAY_ITEM = "{\n" "\"SizeLo\" : %u,\n" "\"SizeHi\" : %u,\n" "\"SizeMB\" : %i\n" "}"; const char* JSON_BYTES_ARRAY_END = "]\n"; const char* JSON_VOLUME_ITEM_END = "}\n"; AppendResponse(IsJson() ? "[\n" : "\n"); ServerVolumes* pServerVolumes = g_pStatMeter->LockServerVolumes(); int index = 0; for (ServerVolumes::iterator it = pServerVolumes->begin(); it != pServerVolumes->end(); it++, index++) { ServerVolume* pServerVolume = *it; unsigned long iTotalSizeHi, iTotalSizeLo, iTotalSizeMB; Util::SplitInt64(pServerVolume->GetTotalBytes(), &iTotalSizeHi, &iTotalSizeLo); iTotalSizeMB = (int)(pServerVolume->GetTotalBytes() / 1024 / 1024); unsigned long iCustomSizeHi, iCustomSizeLo, iCustomSizeMB; Util::SplitInt64(pServerVolume->GetCustomBytes(), &iCustomSizeHi, &iCustomSizeLo); iCustomSizeMB = (int)(pServerVolume->GetCustomBytes() / 1024 / 1024); AppendCondResponse(",\n", IsJson() && index > 0); AppendFmtResponse(IsJson() ? JSON_VOLUME_ITEM_START : XML_VOLUME_ITEM_START, index, (int)pServerVolume->GetDataTime(), pServerVolume->GetFirstDay(), iTotalSizeLo, iTotalSizeHi, iTotalSizeMB, iCustomSizeLo, iCustomSizeHi, iCustomSizeMB, (int)pServerVolume->GetCustomTime(), pServerVolume->GetSecSlot(), pServerVolume->GetMinSlot(), pServerVolume->GetHourSlot(), pServerVolume->GetDaySlot()); ServerVolume::VolumeArray* VolumeArrays[] = { pServerVolume->BytesPerSeconds(), pServerVolume->BytesPerMinutes(), pServerVolume->BytesPerHours(), pServerVolume->BytesPerDays() }; const char* VolumeNames[] = { "BytesPerSeconds", "BytesPerMinutes", "BytesPerHours", "BytesPerDays" }; for (int i=0; i<4; i++) { ServerVolume::VolumeArray* pVolumeArray = VolumeArrays[i]; const char* szArrayName = VolumeNames[i]; AppendFmtResponse(IsJson() ? JSON_BYTES_ARRAY_START : XML_BYTES_ARRAY_START, szArrayName); int index2 = 0; for (ServerVolume::VolumeArray::iterator it2 = pVolumeArray->begin(); it2 != pVolumeArray->end(); it2++) { long long lBytes = *it2; unsigned long iSizeHi, iSizeLo, iSizeMB; Util::SplitInt64(lBytes, &iSizeHi, &iSizeLo); iSizeMB = (int)(lBytes / 1024 / 1024); AppendCondResponse(",\n", IsJson() && index2++ > 0); AppendFmtResponse(IsJson() ? JSON_BYTES_ARRAY_ITEM : XML_BYTES_ARRAY_ITEM, iSizeLo, iSizeHi, iSizeMB); } AppendResponse(IsJson() ? JSON_BYTES_ARRAY_END : XML_BYTES_ARRAY_END); AppendCondResponse(",\n", IsJson() && i < 3); } AppendResponse(IsJson() ? JSON_VOLUME_ITEM_END : XML_VOLUME_ITEM_END); } g_pStatMeter->UnlockServerVolumes(); AppendResponse(IsJson() ? "\n]" : "\n"); } // bool resetservervolume(int serverid, string counter); void ResetServerVolumeXmlCommand::Execute() { if (!CheckSafeMethod()) { return; } int iServerId; char* szCounter; if (!NextParamAsInt(&iServerId) || !NextParamAsStr(&szCounter)) { BuildErrorResponse(2, "Invalid parameter"); return; } if (strcmp(szCounter, "CUSTOM")) { BuildErrorResponse(3, "Invalid Counter"); return; } bool bOK = false; ServerVolumes* pServerVolumes = g_pStatMeter->LockServerVolumes(); int index = 0; for (ServerVolumes::iterator it = pServerVolumes->begin(); it != pServerVolumes->end(); it++, index++) { ServerVolume* pServerVolume = *it; if (index == iServerId || iServerId == -1) { pServerVolume->ResetCustom(); bOK = true; } } g_pStatMeter->UnlockServerVolumes(); BuildBoolResponse(bOK); } // struct[] loadlog(nzbid, logidfrom, logentries) void LoadLogXmlCommand::Execute() { m_pNZBInfo = NULL; m_iNZBID = 0; if (!NextParamAsInt(&m_iNZBID)) { BuildErrorResponse(2, "Invalid parameter"); return; } LogXmlCommand::Execute(); } MessageList* LoadLogXmlCommand::LockMessages() { // TODO: optimize for m_iIDFrom and m_iNrEntries g_pDiskState->LoadNZBMessages(m_iNZBID, &m_messages); if (m_messages.empty()) { DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); m_pNZBInfo = pDownloadQueue->GetQueue()->Find(m_iNZBID); if (m_pNZBInfo) { return m_pNZBInfo->LockCachedMessages(); } else { DownloadQueue::Unlock(); } } return &m_messages; } void LoadLogXmlCommand::UnlockMessages() { if (m_pNZBInfo) { m_pNZBInfo->UnlockCachedMessages(); DownloadQueue::Unlock(); } } // string testserver(string host, int port, string username, string password, bool encryption, string cipher, int timeout); void TestServerXmlCommand::Execute() { const char* XML_RESPONSE_STR_BODY = "%s"; const char* JSON_RESPONSE_STR_BODY = "\"%s\""; if (!CheckSafeMethod()) { return; } char* szHost; int iPort; char* szUsername; char* szPassword; bool bEncryption; char* szCipher; int iTimeout; if (!NextParamAsStr(&szHost) || !NextParamAsInt(&iPort) || !NextParamAsStr(&szUsername) || !NextParamAsStr(&szPassword) || !NextParamAsBool(&bEncryption) || !NextParamAsStr(&szCipher) || !NextParamAsInt(&iTimeout)) { BuildErrorResponse(2, "Invalid parameter"); return; } NewsServer server(0, true, "test server", szHost, iPort, szUsername, szPassword, false, bEncryption, szCipher, 1, 0, 0, 0); TestConnection* pConnection = new TestConnection(&server, this); pConnection->SetTimeout(iTimeout == 0 ? g_pOptions->GetArticleTimeout() : iTimeout); pConnection->SetSuppressErrors(false); m_szErrText = NULL; bool bOK = pConnection->Connect(); char szContent[1024]; snprintf(szContent, 1024, IsJson() ? JSON_RESPONSE_STR_BODY : XML_RESPONSE_STR_BODY, bOK ? "" : Util::EmptyStr(m_szErrText) ? "Unknown error" : m_szErrText); szContent[1024-1] = '\0'; AppendResponse(szContent); delete pConnection; } void TestServerXmlCommand::PrintError(const char* szErrMsg) { if (!m_szErrText) { m_szErrText = EncodeStr(szErrMsg); } } nzbget-16.4/daemon/remote/BinRpc.h0000644000175000017500000000242612630544544016650 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2005 Bo Cordes Petersen * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 BINRPC_H #define BINRPC_H #include "Connection.h" #include "MessageBase.h" class BinRpcProcessor { private: SNZBRequestBase m_MessageBase; Connection* m_pConnection; void Dispatch(); public: BinRpcProcessor(); void Execute(); void SetConnection(Connection* pConnection) { m_pConnection = pConnection; } }; #endif nzbget-16.4/daemon/remote/BinRpc.cpp0000644000175000017500000011064012630544544017201 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2005 Bo Cordes Petersen * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #include #ifndef WIN32 #include #include #include #include #endif #include "nzbget.h" #include "BinRpc.h" #include "Log.h" #include "Options.h" #include "QueueEditor.h" #include "Util.h" #include "DownloadInfo.h" #include "Scanner.h" #include "StatMeter.h" extern void ExitProc(); extern void Reload(); const char* g_szMessageRequestNames[] = { "N/A", "Download", "Pause/Unpause", "List", "Set download rate", "Dump debug", "Edit queue", "Log", "Quit", "Reload", "Version", "Post-queue", "Write log", "Scan", "Pause/Unpause postprocessor", "History" }; const unsigned int g_iMessageRequestSizes[] = { 0, sizeof(SNZBDownloadRequest), sizeof(SNZBPauseUnpauseRequest), sizeof(SNZBListRequest), sizeof(SNZBSetDownloadRateRequest), sizeof(SNZBDumpDebugRequest), sizeof(SNZBEditQueueRequest), sizeof(SNZBLogRequest), sizeof(SNZBShutdownRequest), sizeof(SNZBReloadRequest), sizeof(SNZBVersionRequest), sizeof(SNZBPostQueueRequest), sizeof(SNZBWriteLogRequest), sizeof(SNZBScanRequest), sizeof(SNZBHistoryRequest) }; class BinCommand { protected: Connection* m_pConnection; SNZBRequestBase* m_pMessageBase; bool ReceiveRequest(void* pBuffer, int iSize); void SendBoolResponse(bool bSuccess, const char* szText); public: virtual ~BinCommand() {} virtual void Execute() = 0; void SetConnection(Connection* pConnection) { m_pConnection = pConnection; } void SetMessageBase(SNZBRequestBase* pMessageBase) { m_pMessageBase = pMessageBase; } }; class DownloadBinCommand: public BinCommand { public: virtual void Execute(); }; class ListBinCommand: public BinCommand { public: virtual void Execute(); }; class LogBinCommand: public BinCommand { public: virtual void Execute(); }; class PauseUnpauseBinCommand: public BinCommand { public: virtual void Execute(); }; class EditQueueBinCommand: public BinCommand { public: virtual void Execute(); }; class SetDownloadRateBinCommand: public BinCommand { public: virtual void Execute(); }; class DumpDebugBinCommand: public BinCommand { public: virtual void Execute(); }; class ShutdownBinCommand: public BinCommand { public: virtual void Execute(); }; class ReloadBinCommand: public BinCommand { public: virtual void Execute(); }; class VersionBinCommand: public BinCommand { public: virtual void Execute(); }; class PostQueueBinCommand: public BinCommand { public: virtual void Execute(); }; class WriteLogBinCommand: public BinCommand { public: virtual void Execute(); }; class ScanBinCommand: public BinCommand { public: virtual void Execute(); }; class HistoryBinCommand: public BinCommand { public: virtual void Execute(); }; class UrlQueueBinCommand: public BinCommand { public: virtual void Execute(); }; //***************************************************************** // BinProcessor BinRpcProcessor::BinRpcProcessor() { m_MessageBase.m_iSignature = (int)NZBMESSAGE_SIGNATURE; } void BinRpcProcessor::Execute() { // Read the first package which needs to be a request if (!m_pConnection->Recv(((char*)&m_MessageBase) + sizeof(m_MessageBase.m_iSignature), sizeof(m_MessageBase) - sizeof(m_MessageBase.m_iSignature))) { warn("Non-nzbget request received on port %i from %s", g_pOptions->GetControlPort(), m_pConnection->GetRemoteAddr()); return; } 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 username or password invalid", g_pOptions->GetControlPort(), m_pConnection->GetRemoteAddr()); return; } debug("%s request received from %s", g_szMessageRequestNames[ntohl(m_MessageBase.m_iType)], m_pConnection->GetRemoteAddr()); Dispatch(); } void BinRpcProcessor::Dispatch() { if (ntohl(m_MessageBase.m_iType) >= (int)eRemoteRequestDownload && ntohl(m_MessageBase.m_iType) <= (int)eRemoteRequestHistory && g_iMessageRequestSizes[ntohl(m_MessageBase.m_iType)] != ntohl(m_MessageBase.m_iStructSize)) { error("Invalid size of request: expected %i Bytes, but received %i Bytes", g_iMessageRequestSizes[ntohl(m_MessageBase.m_iType)], ntohl(m_MessageBase.m_iStructSize)); return; } BinCommand* command = NULL; switch (ntohl(m_MessageBase.m_iType)) { case eRemoteRequestDownload: command = new DownloadBinCommand(); break; case eRemoteRequestList: command = new ListBinCommand(); break; case eRemoteRequestLog: command = new LogBinCommand(); break; case eRemoteRequestPauseUnpause: command = new PauseUnpauseBinCommand(); break; case eRemoteRequestEditQueue: command = new EditQueueBinCommand(); break; case eRemoteRequestSetDownloadRate: command = new SetDownloadRateBinCommand(); break; case eRemoteRequestDumpDebug: command = new DumpDebugBinCommand(); break; case eRemoteRequestShutdown: command = new ShutdownBinCommand(); break; case eRemoteRequestReload: command = new ReloadBinCommand(); break; case eRemoteRequestVersion: command = new VersionBinCommand(); break; case eRemoteRequestPostQueue: command = new PostQueueBinCommand(); break; case eRemoteRequestWriteLog: command = new WriteLogBinCommand(); break; case eRemoteRequestScan: command = new ScanBinCommand(); break; case eRemoteRequestHistory: command = new HistoryBinCommand(); break; default: error("Received unsupported request %i", ntohl(m_MessageBase.m_iType)); break; } if (command) { command->SetConnection(m_pConnection); command->SetMessageBase(&m_MessageBase); command->Execute(); delete command; } } //***************************************************************** // Commands void BinCommand::SendBoolResponse(bool bSuccess, const char* szText) { // all bool-responses have the same format of structure, we use SNZBDownloadResponse here SNZBDownloadResponse BoolResponse; memset(&BoolResponse, 0, sizeof(BoolResponse)); BoolResponse.m_MessageBase.m_iSignature = htonl(NZBMESSAGE_SIGNATURE); BoolResponse.m_MessageBase.m_iStructSize = htonl(sizeof(BoolResponse)); BoolResponse.m_bSuccess = htonl(bSuccess); int iTextLen = strlen(szText) + 1; BoolResponse.m_iTrailingDataLength = htonl(iTextLen); // Send the request answer m_pConnection->Send((char*) &BoolResponse, sizeof(BoolResponse)); m_pConnection->Send((char*)szText, iTextLen); } bool BinCommand::ReceiveRequest(void* pBuffer, int iSize) { memcpy(pBuffer, m_pMessageBase, sizeof(SNZBRequestBase)); iSize -= sizeof(SNZBRequestBase); if (iSize > 0) { if (!m_pConnection->Recv(((char*)pBuffer) + sizeof(SNZBRequestBase), iSize)) { error("invalid request"); return false; } } return true; } void PauseUnpauseBinCommand::Execute() { SNZBPauseUnpauseRequest PauseUnpauseRequest; if (!ReceiveRequest(&PauseUnpauseRequest, sizeof(PauseUnpauseRequest))) { return; } g_pOptions->SetResumeTime(0); switch (ntohl(PauseUnpauseRequest.m_iAction)) { case eRemotePauseUnpauseActionDownload: g_pOptions->SetPauseDownload(ntohl(PauseUnpauseRequest.m_bPause)); break; case eRemotePauseUnpauseActionPostProcess: g_pOptions->SetPausePostProcess(ntohl(PauseUnpauseRequest.m_bPause)); break; case eRemotePauseUnpauseActionScan: g_pOptions->SetPauseScan(ntohl(PauseUnpauseRequest.m_bPause)); break; } SendBoolResponse(true, "Pause-/Unpause-Command completed successfully"); } void SetDownloadRateBinCommand::Execute() { SNZBSetDownloadRateRequest SetDownloadRequest; if (!ReceiveRequest(&SetDownloadRequest, sizeof(SetDownloadRequest))) { return; } g_pOptions->SetDownloadRate(ntohl(SetDownloadRequest.m_iDownloadRate)); SendBoolResponse(true, "Rate-Command completed successfully"); } void DumpDebugBinCommand::Execute() { SNZBDumpDebugRequest DumpDebugRequest; if (!ReceiveRequest(&DumpDebugRequest, sizeof(DumpDebugRequest))) { return; } g_pLog->LogDebugInfo(); SendBoolResponse(true, "Debug-Command completed successfully"); } void ShutdownBinCommand::Execute() { SNZBShutdownRequest ShutdownRequest; if (!ReceiveRequest(&ShutdownRequest, sizeof(ShutdownRequest))) { return; } SendBoolResponse(true, "Stopping server"); ExitProc(); } void ReloadBinCommand::Execute() { SNZBReloadRequest ReloadRequest; if (!ReceiveRequest(&ReloadRequest, sizeof(ReloadRequest))) { return; } SendBoolResponse(true, "Reloading server"); Reload(); } void VersionBinCommand::Execute() { SNZBVersionRequest VersionRequest; if (!ReceiveRequest(&VersionRequest, sizeof(VersionRequest))) { return; } SendBoolResponse(true, Util::VersionRevision()); } void DownloadBinCommand::Execute() { SNZBDownloadRequest DownloadRequest; if (!ReceiveRequest(&DownloadRequest, sizeof(DownloadRequest))) { return; } int iBufLen = ntohl(DownloadRequest.m_iTrailingDataLength); char* szNZBContent = (char*)malloc(iBufLen); if (!m_pConnection->Recv(szNZBContent, iBufLen)) { error("invalid request"); free(szNZBContent); return; } int iPriority = ntohl(DownloadRequest.m_iPriority); bool bAddPaused = ntohl(DownloadRequest.m_bAddPaused); bool bAddTop = ntohl(DownloadRequest.m_bAddFirst); int iDupeMode = ntohl(DownloadRequest.m_iDupeMode); int iDupeScore = ntohl(DownloadRequest.m_iDupeScore); bool bOK = false; if (!strncasecmp(szNZBContent, "http://", 6) || !strncasecmp(szNZBContent, "https://", 7)) { // add url NZBInfo* pNZBInfo = new NZBInfo(); pNZBInfo->SetKind(NZBInfo::nkUrl); pNZBInfo->SetURL(szNZBContent); pNZBInfo->SetFilename(DownloadRequest.m_szNZBFilename); pNZBInfo->SetCategory(DownloadRequest.m_szCategory); pNZBInfo->SetPriority(iPriority); pNZBInfo->SetAddUrlPaused(bAddPaused); pNZBInfo->SetDupeKey(DownloadRequest.m_szDupeKey); pNZBInfo->SetDupeScore(iDupeScore); pNZBInfo->SetDupeMode((EDupeMode)iDupeMode); DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); pDownloadQueue->GetQueue()->Add(pNZBInfo, bAddTop); pDownloadQueue->Save(); DownloadQueue::Unlock(); bOK = true; } else { bOK = g_pScanner->AddExternalFile(DownloadRequest.m_szNZBFilename, DownloadRequest.m_szCategory, iPriority, DownloadRequest.m_szDupeKey, iDupeScore, (EDupeMode)iDupeMode, NULL, bAddTop, bAddPaused, NULL, NULL, szNZBContent, iBufLen, NULL) != Scanner::asFailed; } char tmp[1024]; snprintf(tmp, 1024, bOK ? "Collection %s added to queue" : "Download Request failed for %s", Util::BaseFileName(DownloadRequest.m_szNZBFilename)); tmp[1024-1] = '\0'; SendBoolResponse(bOK, tmp); free(szNZBContent); } void ListBinCommand::Execute() { SNZBListRequest ListRequest; if (!ReceiveRequest(&ListRequest, sizeof(ListRequest))) { return; } SNZBListResponse ListResponse; memset(&ListResponse, 0, sizeof(ListResponse)); ListResponse.m_MessageBase.m_iSignature = htonl(NZBMESSAGE_SIGNATURE); ListResponse.m_MessageBase.m_iStructSize = htonl(sizeof(ListResponse)); ListResponse.m_iEntrySize = htonl(sizeof(SNZBListResponseFileEntry)); ListResponse.m_bRegExValid = 0; char* buf = NULL; int bufsize = 0; if (ntohl(ListRequest.m_bFileList)) { eRemoteMatchMode eMatchMode = (eRemoteMatchMode)ntohl(ListRequest.m_iMatchMode); bool bMatchGroup = ntohl(ListRequest.m_bMatchGroup); const char* szPattern = ListRequest.m_szPattern; RegEx *pRegEx = NULL; if (eMatchMode == eRemoteMatchModeRegEx) { pRegEx = new RegEx(szPattern); ListResponse.m_bRegExValid = pRegEx->IsValid(); } // Make a data structure and copy all the elements of the list into it DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); // calculate required buffer size for nzbs int iNrNZBEntries = pDownloadQueue->GetQueue()->size(); int iNrPPPEntries = 0; bufsize += iNrNZBEntries * sizeof(SNZBListResponseNZBEntry); for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; bufsize += strlen(pNZBInfo->GetFilename()) + 1; bufsize += strlen(pNZBInfo->GetName()) + 1; bufsize += strlen(pNZBInfo->GetDestDir()) + 1; bufsize += strlen(pNZBInfo->GetCategory()) + 1; bufsize += strlen(pNZBInfo->GetQueuedFilename()) + 1; // align struct to 4-bytes, needed by ARM-processor (and may be others) bufsize += bufsize % 4 > 0 ? 4 - bufsize % 4 : 0; // calculate required buffer size for pp-parameters for (NZBParameterList::iterator it = pNZBInfo->GetParameters()->begin(); it != pNZBInfo->GetParameters()->end(); it++) { NZBParameter* pNZBParameter = *it; bufsize += sizeof(SNZBListResponsePPPEntry); bufsize += strlen(pNZBParameter->GetName()) + 1; bufsize += strlen(pNZBParameter->GetValue()) + 1; // align struct to 4-bytes, needed by ARM-processor (and may be others) bufsize += bufsize % 4 > 0 ? 4 - bufsize % 4 : 0; iNrPPPEntries++; } } // calculate required buffer size for files int iNrFileEntries = 0; for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; for (FileList::iterator it2 = pNZBInfo->GetFileList()->begin(); it2 != pNZBInfo->GetFileList()->end(); it2++) { FileInfo* pFileInfo = *it2; iNrFileEntries++; bufsize += sizeof(SNZBListResponseFileEntry); bufsize += strlen(pFileInfo->GetSubject()) + 1; bufsize += strlen(pFileInfo->GetFilename()) + 1; // align struct to 4-bytes, needed by ARM-processor (and may be others) bufsize += bufsize % 4 > 0 ? 4 - bufsize % 4 : 0; } } buf = (char*) malloc(bufsize); char* bufptr = buf; // write nzb entries for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; SNZBListResponseNZBEntry* pListAnswer = (SNZBListResponseNZBEntry*) bufptr; unsigned long iSizeHi, iSizeLo, iRemainingSizeHi, iRemainingSizeLo, iPausedSizeHi, iPausedSizeLo; Util::SplitInt64(pNZBInfo->GetSize(), &iSizeHi, &iSizeLo); Util::SplitInt64(pNZBInfo->GetRemainingSize(), &iRemainingSizeHi, &iRemainingSizeLo); Util::SplitInt64(pNZBInfo->GetPausedSize(), &iPausedSizeHi, &iPausedSizeLo); pListAnswer->m_iID = htonl(pNZBInfo->GetID()); pListAnswer->m_iKind = htonl(pNZBInfo->GetKind()); pListAnswer->m_iSizeLo = htonl(iSizeLo); pListAnswer->m_iSizeHi = htonl(iSizeHi); pListAnswer->m_iRemainingSizeLo = htonl(iRemainingSizeLo); pListAnswer->m_iRemainingSizeHi = htonl(iRemainingSizeHi); pListAnswer->m_iPausedSizeLo = htonl(iPausedSizeLo); pListAnswer->m_iPausedSizeHi = htonl(iPausedSizeHi); pListAnswer->m_iPausedCount = htonl(pNZBInfo->GetPausedFileCount()); pListAnswer->m_iRemainingParCount = htonl(pNZBInfo->GetRemainingParCount()); pListAnswer->m_iPriority = htonl(pNZBInfo->GetPriority()); pListAnswer->m_bMatch = htonl(bMatchGroup && (!pRegEx || pRegEx->Match(pNZBInfo->GetName()))); pListAnswer->m_iFilenameLen = htonl(strlen(pNZBInfo->GetFilename()) + 1); pListAnswer->m_iNameLen = htonl(strlen(pNZBInfo->GetName()) + 1); pListAnswer->m_iDestDirLen = htonl(strlen(pNZBInfo->GetDestDir()) + 1); pListAnswer->m_iCategoryLen = htonl(strlen(pNZBInfo->GetCategory()) + 1); pListAnswer->m_iQueuedFilenameLen = htonl(strlen(pNZBInfo->GetQueuedFilename()) + 1); bufptr += sizeof(SNZBListResponseNZBEntry); strcpy(bufptr, pNZBInfo->GetFilename()); bufptr += ntohl(pListAnswer->m_iFilenameLen); strcpy(bufptr, pNZBInfo->GetName()); bufptr += ntohl(pListAnswer->m_iNameLen); strcpy(bufptr, pNZBInfo->GetDestDir()); bufptr += ntohl(pListAnswer->m_iDestDirLen); strcpy(bufptr, pNZBInfo->GetCategory()); bufptr += ntohl(pListAnswer->m_iCategoryLen); strcpy(bufptr, pNZBInfo->GetQueuedFilename()); bufptr += ntohl(pListAnswer->m_iQueuedFilenameLen); // align struct to 4-bytes, needed by ARM-processor (and may be others) if ((size_t)bufptr % 4 > 0) { pListAnswer->m_iQueuedFilenameLen = htonl(ntohl(pListAnswer->m_iQueuedFilenameLen) + 4 - (size_t)bufptr % 4); memset(bufptr, 0, 4 - (size_t)bufptr % 4); //suppress valgrind warning "uninitialized data" bufptr += 4 - (size_t)bufptr % 4; } } // write ppp entries int iNZBIndex = 1; for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++, iNZBIndex++) { NZBInfo* pNZBInfo = *it; for (NZBParameterList::iterator it = pNZBInfo->GetParameters()->begin(); it != pNZBInfo->GetParameters()->end(); it++) { NZBParameter* pNZBParameter = *it; SNZBListResponsePPPEntry* pListAnswer = (SNZBListResponsePPPEntry*) bufptr; pListAnswer->m_iNZBIndex = htonl(iNZBIndex); pListAnswer->m_iNameLen = htonl(strlen(pNZBParameter->GetName()) + 1); pListAnswer->m_iValueLen = htonl(strlen(pNZBParameter->GetValue()) + 1); bufptr += sizeof(SNZBListResponsePPPEntry); strcpy(bufptr, pNZBParameter->GetName()); bufptr += ntohl(pListAnswer->m_iNameLen); strcpy(bufptr, pNZBParameter->GetValue()); bufptr += ntohl(pListAnswer->m_iValueLen); // align struct to 4-bytes, needed by ARM-processor (and may be others) if ((size_t)bufptr % 4 > 0) { pListAnswer->m_iValueLen = htonl(ntohl(pListAnswer->m_iValueLen) + 4 - (size_t)bufptr % 4); memset(bufptr, 0, 4 - (size_t)bufptr % 4); //suppress valgrind warning "uninitialized data" bufptr += 4 - (size_t)bufptr % 4; } } } // write file entries for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; for (FileList::iterator it2 = pNZBInfo->GetFileList()->begin(); it2 != pNZBInfo->GetFileList()->end(); it2++) { FileInfo* pFileInfo = *it2; unsigned long iSizeHi, iSizeLo; SNZBListResponseFileEntry* pListAnswer = (SNZBListResponseFileEntry*) bufptr; pListAnswer->m_iID = htonl(pFileInfo->GetID()); int iNZBIndex = 0; for (unsigned int i = 0; i < pDownloadQueue->GetQueue()->size(); i++) { iNZBIndex++; if (pDownloadQueue->GetQueue()->at(i) == pFileInfo->GetNZBInfo()) { break; } } pListAnswer->m_iNZBIndex = htonl(iNZBIndex); if (pRegEx && !bMatchGroup) { char szFilename[MAX_PATH]; snprintf(szFilename, sizeof(szFilename) - 1, "%s/%s", pFileInfo->GetNZBInfo()->GetName(), Util::BaseFileName(pFileInfo->GetFilename())); pListAnswer->m_bMatch = htonl(pRegEx->Match(szFilename)); } Util::SplitInt64(pFileInfo->GetSize(), &iSizeHi, &iSizeLo); pListAnswer->m_iFileSizeLo = htonl(iSizeLo); pListAnswer->m_iFileSizeHi = htonl(iSizeHi); Util::SplitInt64(pFileInfo->GetRemainingSize(), &iSizeHi, &iSizeLo); pListAnswer->m_iRemainingSizeLo = htonl(iSizeLo); pListAnswer->m_iRemainingSizeHi = htonl(iSizeHi); pListAnswer->m_bFilenameConfirmed = htonl(pFileInfo->GetFilenameConfirmed()); pListAnswer->m_bPaused = htonl(pFileInfo->GetPaused()); pListAnswer->m_iActiveDownloads = htonl(pFileInfo->GetActiveDownloads()); pListAnswer->m_iSubjectLen = htonl(strlen(pFileInfo->GetSubject()) + 1); pListAnswer->m_iFilenameLen = htonl(strlen(pFileInfo->GetFilename()) + 1); bufptr += sizeof(SNZBListResponseFileEntry); strcpy(bufptr, pFileInfo->GetSubject()); bufptr += ntohl(pListAnswer->m_iSubjectLen); strcpy(bufptr, pFileInfo->GetFilename()); bufptr += ntohl(pListAnswer->m_iFilenameLen); // align struct to 4-bytes, needed by ARM-processor (and may be others) if ((size_t)bufptr % 4 > 0) { pListAnswer->m_iFilenameLen = htonl(ntohl(pListAnswer->m_iFilenameLen) + 4 - (size_t)bufptr % 4); memset(bufptr, 0, 4 - (size_t)bufptr % 4); //suppress valgrind warning "uninitialized data" bufptr += 4 - (size_t)bufptr % 4; } } } DownloadQueue::Unlock(); delete pRegEx; ListResponse.m_iNrTrailingNZBEntries = htonl(iNrNZBEntries); ListResponse.m_iNrTrailingPPPEntries = htonl(iNrPPPEntries); ListResponse.m_iNrTrailingFileEntries = htonl(iNrFileEntries); ListResponse.m_iTrailingDataLength = htonl(bufsize); } if (htonl(ListRequest.m_bServerState)) { DownloadQueue *pDownloadQueue = DownloadQueue::Lock(); int iPostJobCount = 0; for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; iPostJobCount += pNZBInfo->GetPostInfo() ? 1 : 0; } long long lRemainingSize; pDownloadQueue->CalcRemainingSize(&lRemainingSize, NULL); DownloadQueue::Unlock(); unsigned long iSizeHi, iSizeLo; ListResponse.m_iDownloadRate = htonl(g_pStatMeter->CalcCurrentDownloadSpeed()); Util::SplitInt64(lRemainingSize, &iSizeHi, &iSizeLo); ListResponse.m_iRemainingSizeHi = htonl(iSizeHi); ListResponse.m_iRemainingSizeLo = htonl(iSizeLo); ListResponse.m_iDownloadLimit = htonl(g_pOptions->GetDownloadRate()); ListResponse.m_bDownloadPaused = htonl(g_pOptions->GetPauseDownload()); ListResponse.m_bPostPaused = htonl(g_pOptions->GetPausePostProcess()); ListResponse.m_bScanPaused = htonl(g_pOptions->GetPauseScan()); ListResponse.m_iThreadCount = htonl(Thread::GetThreadCount() - 1); // not counting itself ListResponse.m_iPostJobCount = htonl(iPostJobCount); int iUpTimeSec, iDnTimeSec; long long iAllBytes; bool bStandBy; g_pStatMeter->CalcTotalStat(&iUpTimeSec, &iDnTimeSec, &iAllBytes, &bStandBy); ListResponse.m_iUpTimeSec = htonl(iUpTimeSec); ListResponse.m_iDownloadTimeSec = htonl(iDnTimeSec); ListResponse.m_bDownloadStandBy = htonl(bStandBy); Util::SplitInt64(iAllBytes, &iSizeHi, &iSizeLo); ListResponse.m_iDownloadedBytesHi = htonl(iSizeHi); ListResponse.m_iDownloadedBytesLo = htonl(iSizeLo); } // Send the request answer m_pConnection->Send((char*) &ListResponse, sizeof(ListResponse)); // Send the data if (bufsize > 0) { m_pConnection->Send(buf, bufsize); } free(buf); } void LogBinCommand::Execute() { SNZBLogRequest LogRequest; if (!ReceiveRequest(&LogRequest, sizeof(LogRequest))) { return; } MessageList* pMessages = g_pLog->LockMessages(); int iNrEntries = ntohl(LogRequest.m_iLines); unsigned int iIDFrom = ntohl(LogRequest.m_iIDFrom); int iStart = pMessages->size(); if (iNrEntries > 0) { if (iNrEntries > (int)pMessages->size()) { iNrEntries = pMessages->size(); } iStart = pMessages->size() - iNrEntries; } if (iIDFrom > 0 && !pMessages->empty()) { iStart = iIDFrom - pMessages->front()->GetID(); if (iStart < 0) { iStart = 0; } iNrEntries = pMessages->size() - iStart; if (iNrEntries < 0) { iNrEntries = 0; } } // calculate required buffer size int bufsize = iNrEntries * sizeof(SNZBLogResponseEntry); for (unsigned int i = (unsigned int)iStart; i < pMessages->size(); i++) { Message* pMessage = (*pMessages)[i]; bufsize += strlen(pMessage->GetText()) + 1; // align struct to 4-bytes, needed by ARM-processor (and may be others) bufsize += bufsize % 4 > 0 ? 4 - bufsize % 4 : 0; } char* buf = (char*) malloc(bufsize); char* bufptr = buf; for (unsigned int i = (unsigned int)iStart; i < pMessages->size(); i++) { Message* pMessage = (*pMessages)[i]; SNZBLogResponseEntry* pLogAnswer = (SNZBLogResponseEntry*) bufptr; pLogAnswer->m_iID = htonl(pMessage->GetID()); pLogAnswer->m_iKind = htonl(pMessage->GetKind()); pLogAnswer->m_tTime = htonl((int)pMessage->GetTime()); pLogAnswer->m_iTextLen = htonl(strlen(pMessage->GetText()) + 1); bufptr += sizeof(SNZBLogResponseEntry); strcpy(bufptr, pMessage->GetText()); bufptr += ntohl(pLogAnswer->m_iTextLen); // align struct to 4-bytes, needed by ARM-processor (and may be others) if ((size_t)bufptr % 4 > 0) { pLogAnswer->m_iTextLen = htonl(ntohl(pLogAnswer->m_iTextLen) + 4 - (size_t)bufptr % 4); memset(bufptr, 0, 4 - (size_t)bufptr % 4); //suppress valgrind warning "uninitialized data" bufptr += 4 - (size_t)bufptr % 4; } } g_pLog->UnlockMessages(); SNZBLogResponse LogResponse; LogResponse.m_MessageBase.m_iSignature = htonl(NZBMESSAGE_SIGNATURE); LogResponse.m_MessageBase.m_iStructSize = htonl(sizeof(LogResponse)); LogResponse.m_iEntrySize = htonl(sizeof(SNZBLogResponseEntry)); LogResponse.m_iNrTrailingEntries = htonl(iNrEntries); LogResponse.m_iTrailingDataLength = htonl(bufsize); // Send the request answer m_pConnection->Send((char*) &LogResponse, sizeof(LogResponse)); // Send the data if (bufsize > 0) { m_pConnection->Send(buf, bufsize); } free(buf); } void EditQueueBinCommand::Execute() { SNZBEditQueueRequest EditQueueRequest; if (!ReceiveRequest(&EditQueueRequest, sizeof(EditQueueRequest))) { return; } int iNrIDEntries = ntohl(EditQueueRequest.m_iNrTrailingIDEntries); int iNrNameEntries = ntohl(EditQueueRequest.m_iNrTrailingNameEntries); int iNameEntriesLen = ntohl(EditQueueRequest.m_iTrailingNameEntriesLen); int iAction = ntohl(EditQueueRequest.m_iAction); int iMatchMode = ntohl(EditQueueRequest.m_iMatchMode); int iOffset = ntohl(EditQueueRequest.m_iOffset); int iTextLen = ntohl(EditQueueRequest.m_iTextLen); unsigned int iBufLength = ntohl(EditQueueRequest.m_iTrailingDataLength); if (iNrIDEntries * sizeof(int32_t) + iTextLen + iNameEntriesLen != iBufLength) { error("Invalid struct size"); return; } char* pBuf = (char*)malloc(iBufLength); if (!m_pConnection->Recv(pBuf, iBufLength)) { error("invalid request"); free(pBuf); return; } if (iNrIDEntries <= 0 && iNrNameEntries <= 0) { SendBoolResponse(false, "Edit-Command failed: no IDs/Names specified"); return; } char* szText = iTextLen > 0 ? pBuf : NULL; int32_t* pIDs = (int32_t*)(pBuf + iTextLen); char* pNames = (pBuf + iTextLen + iNrIDEntries * sizeof(int32_t)); IDList cIDList; NameList cNameList; if (iNrIDEntries > 0) { cIDList.reserve(iNrIDEntries); for (int i = 0; i < iNrIDEntries; i++) { cIDList.push_back(ntohl(pIDs[i])); } } if (iNrNameEntries > 0) { cNameList.reserve(iNrNameEntries); for (int i = 0; i < iNrNameEntries; i++) { cNameList.push_back(pNames); pNames += strlen(pNames) + 1; } } DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); bool bOK = pDownloadQueue->EditList( iNrIDEntries > 0 ? &cIDList : NULL, iNrNameEntries > 0 ? &cNameList : NULL, (DownloadQueue::EMatchMode)iMatchMode, (DownloadQueue::EEditAction)iAction, iOffset, szText); DownloadQueue::Unlock(); free(pBuf); if (bOK) { SendBoolResponse(true, "Edit-Command completed successfully"); } else { #ifndef HAVE_REGEX_H if ((QueueEditor::EMatchMode)iMatchMode == QueueEditor::mmRegEx) { SendBoolResponse(false, "Edit-Command failed: the program was compiled without RegEx-support"); return; } #endif SendBoolResponse(false, "Edit-Command failed"); } } void PostQueueBinCommand::Execute() { SNZBPostQueueRequest PostQueueRequest; if (!ReceiveRequest(&PostQueueRequest, sizeof(PostQueueRequest))) { return; } SNZBPostQueueResponse PostQueueResponse; memset(&PostQueueResponse, 0, sizeof(PostQueueResponse)); PostQueueResponse.m_MessageBase.m_iSignature = htonl(NZBMESSAGE_SIGNATURE); PostQueueResponse.m_MessageBase.m_iStructSize = htonl(sizeof(PostQueueResponse)); PostQueueResponse.m_iEntrySize = htonl(sizeof(SNZBPostQueueResponseEntry)); char* buf = NULL; int bufsize = 0; // Make a data structure and copy all the elements of the list into it NZBList* pNZBList = DownloadQueue::Lock()->GetQueue(); // calculate required buffer size int NrEntries = 0; for (NZBList::iterator it = pNZBList->begin(); it != pNZBList->end(); it++) { NZBInfo* pNZBInfo = *it; PostInfo* pPostInfo = pNZBInfo->GetPostInfo(); if (!pPostInfo) { continue; } NrEntries++; bufsize += sizeof(SNZBPostQueueResponseEntry); bufsize += strlen(pPostInfo->GetNZBInfo()->GetFilename()) + 1; bufsize += strlen(pPostInfo->GetNZBInfo()->GetName()) + 1; bufsize += strlen(pPostInfo->GetNZBInfo()->GetDestDir()) + 1; bufsize += strlen(pPostInfo->GetProgressLabel()) + 1; // align struct to 4-bytes, needed by ARM-processor (and may be others) bufsize += bufsize % 4 > 0 ? 4 - bufsize % 4 : 0; } time_t tCurTime = time(NULL); buf = (char*) malloc(bufsize); char* bufptr = buf; for (NZBList::iterator it = pNZBList->begin(); it != pNZBList->end(); it++) { NZBInfo* pNZBInfo = *it; PostInfo* pPostInfo = pNZBInfo->GetPostInfo(); if (!pPostInfo) { continue; } SNZBPostQueueResponseEntry* pPostQueueAnswer = (SNZBPostQueueResponseEntry*) bufptr; pPostQueueAnswer->m_iID = htonl(pNZBInfo->GetID()); pPostQueueAnswer->m_iStage = htonl(pPostInfo->GetStage()); pPostQueueAnswer->m_iStageProgress = htonl(pPostInfo->GetStageProgress()); pPostQueueAnswer->m_iFileProgress = htonl(pPostInfo->GetFileProgress()); 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_iInfoNameLen = htonl(strlen(pPostInfo->GetNZBInfo()->GetName()) + 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->GetNZBInfo()->GetName()); bufptr += ntohl(pPostQueueAnswer->m_iInfoNameLen); strcpy(bufptr, pPostInfo->GetNZBInfo()->GetDestDir()); bufptr += ntohl(pPostQueueAnswer->m_iDestDirLen); strcpy(bufptr, pPostInfo->GetProgressLabel()); bufptr += ntohl(pPostQueueAnswer->m_iProgressLabelLen); // align struct to 4-bytes, needed by ARM-processor (and may be others) if ((size_t)bufptr % 4 > 0) { pPostQueueAnswer->m_iProgressLabelLen = htonl(ntohl(pPostQueueAnswer->m_iProgressLabelLen) + 4 - (size_t)bufptr % 4); memset(bufptr, 0, 4 - (size_t)bufptr % 4); //suppress valgrind warning "uninitialized data" bufptr += 4 - (size_t)bufptr % 4; } } DownloadQueue::Unlock(); PostQueueResponse.m_iNrTrailingEntries = htonl(NrEntries); PostQueueResponse.m_iTrailingDataLength = htonl(bufsize); // Send the request answer m_pConnection->Send((char*) &PostQueueResponse, sizeof(PostQueueResponse)); // Send the data if (bufsize > 0) { m_pConnection->Send(buf, bufsize); } free(buf); } void WriteLogBinCommand::Execute() { SNZBWriteLogRequest WriteLogRequest; if (!ReceiveRequest(&WriteLogRequest, sizeof(WriteLogRequest))) { return; } char* pRecvBuffer = (char*)malloc(ntohl(WriteLogRequest.m_iTrailingDataLength) + 1); if (!m_pConnection->Recv(pRecvBuffer, ntohl(WriteLogRequest.m_iTrailingDataLength))) { error("invalid request"); free(pRecvBuffer); return; } bool OK = true; switch ((Message::EKind)ntohl(WriteLogRequest.m_iKind)) { case Message::mkDetail: detail(pRecvBuffer); break; case Message::mkInfo: info(pRecvBuffer); break; case Message::mkWarning: warn(pRecvBuffer); break; case Message::mkError: error(pRecvBuffer); break; case Message::mkDebug: debug(pRecvBuffer); break; default: OK = false; } SendBoolResponse(OK, OK ? "Message added to log" : "Invalid message-kind"); free(pRecvBuffer); } void ScanBinCommand::Execute() { SNZBScanRequest ScanRequest; if (!ReceiveRequest(&ScanRequest, sizeof(ScanRequest))) { return; } bool bSyncMode = ntohl(ScanRequest.m_bSyncMode); g_pScanner->ScanNZBDir(bSyncMode); SendBoolResponse(true, bSyncMode ? "Scan-Command completed" : "Scan-Command scheduled successfully"); } void HistoryBinCommand::Execute() { SNZBHistoryRequest HistoryRequest; if (!ReceiveRequest(&HistoryRequest, sizeof(HistoryRequest))) { return; } bool bShowHidden = ntohl(HistoryRequest.m_bHidden); SNZBHistoryResponse HistoryResponse; memset(&HistoryResponse, 0, sizeof(HistoryResponse)); HistoryResponse.m_MessageBase.m_iSignature = htonl(NZBMESSAGE_SIGNATURE); HistoryResponse.m_MessageBase.m_iStructSize = htonl(sizeof(HistoryResponse)); HistoryResponse.m_iEntrySize = htonl(sizeof(SNZBHistoryResponseEntry)); char* buf = NULL; int bufsize = 0; // Make a data structure and copy all the elements of the list into it DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); // calculate required buffer size for nzbs int iNrEntries = 0; for (HistoryList::iterator it = pDownloadQueue->GetHistory()->begin(); it != pDownloadQueue->GetHistory()->end(); it++) { HistoryInfo* pHistoryInfo = *it; if (pHistoryInfo->GetKind() != HistoryInfo::hkDup || bShowHidden) { iNrEntries++; } } bufsize += iNrEntries * sizeof(SNZBHistoryResponseEntry); for (HistoryList::iterator it = pDownloadQueue->GetHistory()->begin(); it != pDownloadQueue->GetHistory()->end(); it++) { HistoryInfo* pHistoryInfo = *it; if (pHistoryInfo->GetKind() != HistoryInfo::hkDup || bShowHidden) { char szNicename[1024]; pHistoryInfo->GetName(szNicename, sizeof(szNicename)); bufsize += strlen(szNicename) + 1; // align struct to 4-bytes, needed by ARM-processor (and may be others) bufsize += bufsize % 4 > 0 ? 4 - bufsize % 4 : 0; } } buf = (char*) malloc(bufsize); char* bufptr = buf; // write nzb entries for (HistoryList::iterator it = pDownloadQueue->GetHistory()->begin(); it != pDownloadQueue->GetHistory()->end(); it++) { HistoryInfo* pHistoryInfo = *it; if (pHistoryInfo->GetKind() != HistoryInfo::hkDup || bShowHidden) { SNZBHistoryResponseEntry* pListAnswer = (SNZBHistoryResponseEntry*) bufptr; pListAnswer->m_iID = htonl(pHistoryInfo->GetID()); pListAnswer->m_iKind = htonl((int)pHistoryInfo->GetKind()); pListAnswer->m_tTime = htonl((int)pHistoryInfo->GetTime()); char szNicename[1024]; pHistoryInfo->GetName(szNicename, sizeof(szNicename)); pListAnswer->m_iNicenameLen = htonl(strlen(szNicename) + 1); if (pHistoryInfo->GetKind() == HistoryInfo::hkNzb) { NZBInfo* pNZBInfo = pHistoryInfo->GetNZBInfo(); unsigned long iSizeHi, iSizeLo; Util::SplitInt64(pNZBInfo->GetSize(), &iSizeHi, &iSizeLo); pListAnswer->m_iSizeLo = htonl(iSizeLo); pListAnswer->m_iSizeHi = htonl(iSizeHi); pListAnswer->m_iFileCount = htonl(pNZBInfo->GetFileCount()); pListAnswer->m_iParStatus = htonl(pNZBInfo->GetParStatus()); pListAnswer->m_iScriptStatus = htonl(pNZBInfo->GetScriptStatuses()->CalcTotalStatus()); } else if (pHistoryInfo->GetKind() == HistoryInfo::hkDup && bShowHidden) { DupInfo* pDupInfo = pHistoryInfo->GetDupInfo(); unsigned long iSizeHi, iSizeLo; Util::SplitInt64(pDupInfo->GetSize(), &iSizeHi, &iSizeLo); pListAnswer->m_iSizeLo = htonl(iSizeLo); pListAnswer->m_iSizeHi = htonl(iSizeHi); } else if (pHistoryInfo->GetKind() == HistoryInfo::hkUrl) { NZBInfo* pNZBInfo = pHistoryInfo->GetNZBInfo(); pListAnswer->m_iUrlStatus = htonl(pNZBInfo->GetUrlStatus()); } bufptr += sizeof(SNZBHistoryResponseEntry); strcpy(bufptr, szNicename); bufptr += ntohl(pListAnswer->m_iNicenameLen); // align struct to 4-bytes, needed by ARM-processor (and may be others) if ((size_t)bufptr % 4 > 0) { pListAnswer->m_iNicenameLen = htonl(ntohl(pListAnswer->m_iNicenameLen) + 4 - (size_t)bufptr % 4); memset(bufptr, 0, 4 - (size_t)bufptr % 4); //suppress valgrind warning "uninitialized data" bufptr += 4 - (size_t)bufptr % 4; } } } DownloadQueue::Unlock(); HistoryResponse.m_iNrTrailingEntries = htonl(iNrEntries); HistoryResponse.m_iTrailingDataLength = htonl(bufsize); // Send the request answer m_pConnection->Send((char*) &HistoryResponse, sizeof(HistoryResponse)); // Send the data if (bufsize > 0) { m_pConnection->Send(buf, bufsize); } free(buf); } nzbget-16.4/daemon/remote/RemoteClient.cpp0000644000175000017500000011173512630544544020424 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2005 Bo Cordes Petersen * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #include #ifdef WIN32 #include #else #include #include #include #endif #include #include "nzbget.h" #include "RemoteClient.h" #include "DownloadInfo.h" #include "Options.h" #include "Log.h" #include "Util.h" RemoteClient::RemoteClient() { m_pConnection = NULL; m_bVerbose = true; /* printf("sizeof(SNZBRequestBase)=%i\n", sizeof(SNZBRequestBase)); printf("sizeof(SNZBDownloadRequest)=%i\n", sizeof(SNZBDownloadRequest)); printf("sizeof(SNZBListRequest)=%i\n", sizeof(SNZBListRequest)); printf("sizeof(SNZBListResponse)=%i\n", sizeof(SNZBListResponse)); printf("sizeof(SNZBListResponseFileEntry)=%i\n", sizeof(SNZBListResponseFileEntry)); printf("sizeof(SNZBLogRequest)=%i\n", sizeof(SNZBLogRequest)); printf("sizeof(SNZBLogResponse)=%i\n", sizeof(SNZBLogResponse)); printf("sizeof(SNZBLogResponseEntry)=%i\n", sizeof(SNZBLogResponseEntry)); printf("sizeof(SNZBPauseUnpauseRequest)=%i\n", sizeof(SNZBPauseUnpauseRequest)); printf("sizeof(SNZBSetDownloadRateRequest)=%i\n", sizeof(SNZBSetDownloadRateRequest)); printf("sizeof(SNZBEditQueueRequest)=%i\n", sizeof(SNZBEditQueueRequest)); printf("sizeof(SNZBDumpDebugRequest)=%i\n", sizeof(SNZBDumpDebugRequest)); */ } RemoteClient::~RemoteClient() { delete m_pConnection; } void RemoteClient::printf(const char * msg,...) { if (m_bVerbose) { va_list ap; va_start(ap, msg); ::vprintf(msg, ap); va_end(ap); } } void RemoteClient::perror(const char * msg) { if (m_bVerbose) { ::perror(msg); } } bool RemoteClient::InitConnection() { const char* szControlIP = !strcmp(g_pOptions->GetControlIP(), "0.0.0.0") ? "127.0.0.1" : g_pOptions->GetControlIP(); // Create a connection to the server m_pConnection = new Connection(szControlIP, g_pOptions->GetControlPort(), false); bool OK = m_pConnection->Connect(); if (!OK) { printf("Unable to send request to nzbget-server at %s (port %i)\n", szControlIP, g_pOptions->GetControlPort()); } return OK; } void RemoteClient::InitMessageBase(SNZBRequestBase* pMessageBase, int iRequest, int iSize) { 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'; } bool RemoteClient::ReceiveBoolResponse() { printf("Request sent\n"); // all bool-responses have the same format of structure, we use SNZBDownloadResponse here SNZBDownloadResponse BoolResponse; memset(&BoolResponse, 0, sizeof(BoolResponse)); bool bRead = m_pConnection->Recv((char*)&BoolResponse, sizeof(BoolResponse)); if (!bRead || (int)ntohl(BoolResponse.m_MessageBase.m_iSignature) != (int)NZBMESSAGE_SIGNATURE || ntohl(BoolResponse.m_MessageBase.m_iStructSize) != sizeof(BoolResponse)) { printf("No response or invalid response (timeout, not nzbget-server or wrong nzbget-server version)\n"); return false; } int iTextLen = ntohl(BoolResponse.m_iTrailingDataLength); char* buf = (char*)malloc(iTextLen); bRead = m_pConnection->Recv(buf, iTextLen); if (!bRead) { printf("No response or invalid response (timeout, not nzbget-server or wrong nzbget-server version)\n"); free(buf); return false; } printf("server returned: %s\n", buf); free(buf); return ntohl(BoolResponse.m_bSuccess); } /* * Sends a message to the running nzbget process. */ bool RemoteClient::RequestServerDownload(const char* szNZBFilename, const char* szNZBContent, const char* szCategory, bool bAddFirst, bool bAddPaused, int iPriority, const char* szDupeKey, int iDupeMode, int iDupeScore) { // Read the file into the buffer char* szBuffer = NULL; int iLength = 0; bool bIsUrl = !strncasecmp(szNZBContent, "http://", 6) || !strncasecmp(szNZBContent, "https://", 7); if (bIsUrl) { iLength = strlen(szNZBContent) + 1; } else { if (!Util::LoadFileIntoBuffer(szNZBContent, &szBuffer, &iLength)) { printf("Could not load file %s\n", szNZBContent); return false; } iLength--; } bool OK = InitConnection(); if (OK) { SNZBDownloadRequest DownloadRequest; InitMessageBase(&DownloadRequest.m_MessageBase, eRemoteRequestDownload, sizeof(DownloadRequest)); DownloadRequest.m_bAddFirst = htonl(bAddFirst); DownloadRequest.m_bAddPaused = htonl(bAddPaused); DownloadRequest.m_iPriority = htonl(iPriority); DownloadRequest.m_iDupeMode = htonl(iDupeMode); DownloadRequest.m_iDupeScore = htonl(iDupeScore); DownloadRequest.m_iTrailingDataLength = htonl(iLength); DownloadRequest.m_szNZBFilename[0] = '\0'; if (!Util::EmptyStr(szNZBFilename)) { strncpy(DownloadRequest.m_szNZBFilename, szNZBFilename, NZBREQUESTFILENAMESIZE - 1); } else if (!bIsUrl) { strncpy(DownloadRequest.m_szNZBFilename, szNZBContent, NZBREQUESTFILENAMESIZE - 1); } DownloadRequest.m_szNZBFilename[NZBREQUESTFILENAMESIZE-1] = '\0'; DownloadRequest.m_szCategory[0] = '\0'; if (szCategory) { strncpy(DownloadRequest.m_szCategory, szCategory, NZBREQUESTFILENAMESIZE - 1); } DownloadRequest.m_szCategory[NZBREQUESTFILENAMESIZE-1] = '\0'; DownloadRequest.m_szDupeKey[0] = '\0'; if (!Util::EmptyStr(szDupeKey)) { strncpy(DownloadRequest.m_szDupeKey, szDupeKey, NZBREQUESTFILENAMESIZE - 1); } DownloadRequest.m_szDupeKey[NZBREQUESTFILENAMESIZE-1] = '\0'; if (!m_pConnection->Send((char*)(&DownloadRequest), sizeof(DownloadRequest))) { perror("m_pConnection->Send"); OK = false; } else { m_pConnection->Send(bIsUrl ? szNZBContent : szBuffer, iLength); OK = ReceiveBoolResponse(); m_pConnection->Disconnect(); } } // Cleanup free(szBuffer); return OK; } void RemoteClient::BuildFileList(SNZBListResponse* pListResponse, const char* pTrailingData, DownloadQueue* pDownloadQueue) { if (ntohl(pListResponse->m_iTrailingDataLength) > 0) { const char* pBufPtr = pTrailingData; // read nzb entries for (unsigned int i = 0; i < ntohl(pListResponse->m_iNrTrailingNZBEntries); i++) { SNZBListResponseNZBEntry* pListAnswer = (SNZBListResponseNZBEntry*) pBufPtr; const char* szFileName = pBufPtr + sizeof(SNZBListResponseNZBEntry); const char* szName = pBufPtr + sizeof(SNZBListResponseNZBEntry) + ntohl(pListAnswer->m_iFilenameLen); const char* szDestDir = pBufPtr + sizeof(SNZBListResponseNZBEntry) + ntohl(pListAnswer->m_iFilenameLen) + ntohl(pListAnswer->m_iNameLen); const char* szCategory = pBufPtr + sizeof(SNZBListResponseNZBEntry) + ntohl(pListAnswer->m_iFilenameLen) + ntohl(pListAnswer->m_iNameLen) + ntohl(pListAnswer->m_iDestDirLen); const char* m_szQueuedFilename = pBufPtr + sizeof(SNZBListResponseNZBEntry) + ntohl(pListAnswer->m_iFilenameLen) + ntohl(pListAnswer->m_iNameLen) + ntohl(pListAnswer->m_iDestDirLen) + ntohl(pListAnswer->m_iCategoryLen); MatchedNZBInfo* pNZBInfo = new MatchedNZBInfo(); pNZBInfo->SetID(ntohl(pListAnswer->m_iID)); pNZBInfo->SetKind((NZBInfo::EKind)ntohl(pListAnswer->m_iKind)); pNZBInfo->SetSize(Util::JoinInt64(ntohl(pListAnswer->m_iSizeHi), ntohl(pListAnswer->m_iSizeLo))); pNZBInfo->SetRemainingSize(Util::JoinInt64(ntohl(pListAnswer->m_iRemainingSizeHi), ntohl(pListAnswer->m_iRemainingSizeLo))); pNZBInfo->SetPausedSize(Util::JoinInt64(ntohl(pListAnswer->m_iPausedSizeHi), ntohl(pListAnswer->m_iPausedSizeLo))); pNZBInfo->SetPausedFileCount(ntohl(pListAnswer->m_iPausedCount)); pNZBInfo->SetRemainingParCount(ntohl(pListAnswer->m_iRemainingParCount)); pNZBInfo->SetFilename(szFileName); pNZBInfo->SetName(szName); pNZBInfo->SetDestDir(szDestDir); pNZBInfo->SetCategory(szCategory); pNZBInfo->SetQueuedFilename(m_szQueuedFilename); pNZBInfo->SetPriority(ntohl(pListAnswer->m_iPriority)); pNZBInfo->m_bMatch = ntohl(pListAnswer->m_bMatch); pDownloadQueue->GetQueue()->push_back(pNZBInfo); pBufPtr += sizeof(SNZBListResponseNZBEntry) + ntohl(pListAnswer->m_iFilenameLen) + ntohl(pListAnswer->m_iNameLen) + ntohl(pListAnswer->m_iDestDirLen) + ntohl(pListAnswer->m_iCategoryLen) + ntohl(pListAnswer->m_iQueuedFilenameLen); } //read ppp entries for (unsigned int i = 0; i < ntohl(pListResponse->m_iNrTrailingPPPEntries); i++) { SNZBListResponsePPPEntry* pListAnswer = (SNZBListResponsePPPEntry*) pBufPtr; const char* szName = pBufPtr + sizeof(SNZBListResponsePPPEntry); const char* szValue = pBufPtr + sizeof(SNZBListResponsePPPEntry) + ntohl(pListAnswer->m_iNameLen); NZBInfo* pNZBInfo = pDownloadQueue->GetQueue()->at(ntohl(pListAnswer->m_iNZBIndex) - 1); pNZBInfo->GetParameters()->SetParameter(szName, szValue); pBufPtr += sizeof(SNZBListResponsePPPEntry) + ntohl(pListAnswer->m_iNameLen) + ntohl(pListAnswer->m_iValueLen); } //read file entries for (unsigned int i = 0; i < ntohl(pListResponse->m_iNrTrailingFileEntries); i++) { SNZBListResponseFileEntry* pListAnswer = (SNZBListResponseFileEntry*) pBufPtr; const char* szSubject = pBufPtr + sizeof(SNZBListResponseFileEntry); const char* szFileName = pBufPtr + sizeof(SNZBListResponseFileEntry) + ntohl(pListAnswer->m_iSubjectLen); MatchedFileInfo* pFileInfo = new MatchedFileInfo(); pFileInfo->SetID(ntohl(pListAnswer->m_iID)); pFileInfo->SetSize(Util::JoinInt64(ntohl(pListAnswer->m_iFileSizeHi), ntohl(pListAnswer->m_iFileSizeLo))); pFileInfo->SetRemainingSize(Util::JoinInt64(ntohl(pListAnswer->m_iRemainingSizeHi), ntohl(pListAnswer->m_iRemainingSizeLo))); pFileInfo->SetPaused(ntohl(pListAnswer->m_bPaused)); pFileInfo->SetSubject(szSubject); pFileInfo->SetFilename(szFileName); pFileInfo->SetFilenameConfirmed(ntohl(pListAnswer->m_bFilenameConfirmed)); pFileInfo->SetActiveDownloads(ntohl(pListAnswer->m_iActiveDownloads)); pFileInfo->m_bMatch = ntohl(pListAnswer->m_bMatch); NZBInfo* pNZBInfo = pDownloadQueue->GetQueue()->at(ntohl(pListAnswer->m_iNZBIndex) - 1); pFileInfo->SetNZBInfo(pNZBInfo); pNZBInfo->GetFileList()->push_back(pFileInfo); pBufPtr += sizeof(SNZBListResponseFileEntry) + ntohl(pListAnswer->m_iSubjectLen) + ntohl(pListAnswer->m_iFilenameLen); } } } bool RemoteClient::RequestServerList(bool bFiles, bool bGroups, const char* szPattern) { if (!InitConnection()) return false; SNZBListRequest ListRequest; InitMessageBase(&ListRequest.m_MessageBase, eRemoteRequestList, sizeof(ListRequest)); ListRequest.m_bFileList = htonl(true); ListRequest.m_bServerState = htonl(true); ListRequest.m_iMatchMode = htonl(szPattern ? eRemoteMatchModeRegEx : eRemoteMatchModeID); ListRequest.m_bMatchGroup = htonl(bGroups); if (szPattern) { strncpy(ListRequest.m_szPattern, szPattern, NZBREQUESTFILENAMESIZE - 1); ListRequest.m_szPattern[NZBREQUESTFILENAMESIZE-1] = '\0'; } if (!m_pConnection->Send((char*)(&ListRequest), sizeof(ListRequest))) { perror("m_pConnection->Send"); return false; } printf("Request sent\n"); // Now listen for the returned list SNZBListResponse ListResponse; bool bRead = m_pConnection->Recv((char*) &ListResponse, sizeof(ListResponse)); if (!bRead || (int)ntohl(ListResponse.m_MessageBase.m_iSignature) != (int)NZBMESSAGE_SIGNATURE || ntohl(ListResponse.m_MessageBase.m_iStructSize) != sizeof(ListResponse)) { printf("No response or invalid response (timeout, not nzbget-server or wrong nzbget-server version)\n"); return false; } char* pBuf = NULL; if (ntohl(ListResponse.m_iTrailingDataLength) > 0) { pBuf = (char*)malloc(ntohl(ListResponse.m_iTrailingDataLength)); if (!m_pConnection->Recv(pBuf, ntohl(ListResponse.m_iTrailingDataLength))) { free(pBuf); return false; } } m_pConnection->Disconnect(); if (szPattern && !ListResponse.m_bRegExValid) { printf("Error in regular expression\n"); free(pBuf); return false; } if (bFiles) { if (ntohl(ListResponse.m_iTrailingDataLength) == 0) { printf("Server has no files queued for download\n"); } else { printf("Queue List\n"); printf("-----------------------------------\n"); DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); BuildFileList(&ListResponse, pBuf, pDownloadQueue); long long lRemaining = 0; long long lPaused = 0; int iMatches = 0; int iNrFileEntries = 0; for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; for (FileList::iterator it2 = pNZBInfo->GetFileList()->begin(); it2 != pNZBInfo->GetFileList()->end(); it2++) { FileInfo* pFileInfo = *it2; iNrFileEntries++; char szCompleted[100]; szCompleted[0] = '\0'; if (pFileInfo->GetRemainingSize() < pFileInfo->GetSize()) { sprintf(szCompleted, ", %i%s", (int)(100 - pFileInfo->GetRemainingSize() * 100 / pFileInfo->GetSize()), "%"); } char szThreads[100]; szThreads[0] = '\0'; if (pFileInfo->GetActiveDownloads() > 0) { sprintf(szThreads, ", %i thread%s", pFileInfo->GetActiveDownloads(), (pFileInfo->GetActiveDownloads() > 1 ? "s" : "")); } char szStatus[100]; if (pFileInfo->GetPaused()) { sprintf(szStatus, " (paused)"); lPaused += pFileInfo->GetRemainingSize(); } else { szStatus[0] = '\0'; lRemaining += pFileInfo->GetRemainingSize(); } if (!szPattern || ((MatchedFileInfo*)pFileInfo)->m_bMatch) { char szSize[20]; printf("[%i] %s/%s (%s%s%s)%s\n", pFileInfo->GetID(), pFileInfo->GetNZBInfo()->GetName(), pFileInfo->GetFilename(), Util::FormatSize(szSize, sizeof(szSize), pFileInfo->GetSize()), szCompleted, szThreads, szStatus); iMatches++; } } } DownloadQueue::Unlock(); if (iMatches == 0) { printf("No matches founds\n"); } printf("-----------------------------------\n"); printf("Files: %i\n", iNrFileEntries); if (szPattern) { printf("Matches: %i\n", iMatches); } if (lPaused > 0) { char szRemaining[20]; char szPausedSize[20]; printf("Remaining size: %s (+%s paused)\n", Util::FormatSize(szRemaining, sizeof(szRemaining), lRemaining), Util::FormatSize(szPausedSize, sizeof(szPausedSize), lPaused)); } else { char szRemaining[20]; printf("Remaining size: %s\n", Util::FormatSize(szRemaining, sizeof(szRemaining), lRemaining)); } } } if (bGroups) { if (ntohl(ListResponse.m_iTrailingDataLength) == 0) { printf("Server has no files queued for download\n"); } else { printf("Queue List\n"); printf("-----------------------------------\n"); DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); BuildFileList(&ListResponse, pBuf, pDownloadQueue); long long lRemaining = 0; long long lPaused = 0; int iMatches = 0; int iNrFileEntries = 0; for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; iNrFileEntries += pNZBInfo->GetFileList()->size(); long long lUnpausedRemainingSize = pNZBInfo->GetRemainingSize() - pNZBInfo->GetPausedSize(); lRemaining += lUnpausedRemainingSize; char szRemaining[20]; Util::FormatSize(szRemaining, sizeof(szRemaining), lUnpausedRemainingSize); char szPriority[100]; szPriority[0] = '\0'; if (pNZBInfo->GetPriority() != 0) { sprintf(szPriority, "[%+i] ", pNZBInfo->GetPriority()); } char szPaused[20]; szPaused[0] = '\0'; if (pNZBInfo->GetPausedSize() > 0) { char szPausedSize[20]; Util::FormatSize(szPausedSize, sizeof(szPausedSize), pNZBInfo->GetPausedSize()); sprintf(szPaused, " + %s paused", szPausedSize); lPaused += pNZBInfo->GetPausedSize(); } char szCategory[1024]; szCategory[0] = '\0'; if (pNZBInfo->GetCategory() && strlen(pNZBInfo->GetCategory()) > 0) { sprintf(szCategory, " (%s)", pNZBInfo->GetCategory()); } char szThreads[100]; szThreads[0] = '\0'; if (pNZBInfo->GetActiveDownloads() > 0) { sprintf(szThreads, ", %i thread%s", pNZBInfo->GetActiveDownloads(), (pNZBInfo->GetActiveDownloads() > 1 ? "s" : "")); } char szParameters[1024]; szParameters[0] = '\0'; for (NZBParameterList::iterator it = pNZBInfo->GetParameters()->begin(); it != pNZBInfo->GetParameters()->end(); it++) { if (szParameters[0] == '\0') { strncat(szParameters, " (", sizeof(szParameters) - strlen(szParameters) - 1); } else { strncat(szParameters, ", ", sizeof(szParameters) - strlen(szParameters) - 1); } NZBParameter* pNZBParameter = *it; 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, ")", sizeof(szParameters) - strlen(szParameters) - 1); } char szUrlOrFile[100]; if (pNZBInfo->GetKind() == NZBInfo::nkUrl) { strncpy(szUrlOrFile, "URL", sizeof(szUrlOrFile)); } else { snprintf(szUrlOrFile, sizeof(szUrlOrFile), "%i file%s", (int)pNZBInfo->GetFileList()->size(), pNZBInfo->GetFileList()->size() > 1 ? "s" : ""); szUrlOrFile[100-1] = '\0'; } if (!szPattern || ((MatchedNZBInfo*)pNZBInfo)->m_bMatch) { printf("[%i] %s%s (%s, %s%s%s)%s%s\n", pNZBInfo->GetID(), szPriority, pNZBInfo->GetName(), szUrlOrFile, szRemaining, szPaused, szThreads, szCategory, szParameters); iMatches++; } } if (iMatches == 0) { printf("No matches founds\n"); } printf("-----------------------------------\n"); printf("Groups: %i\n", pDownloadQueue->GetQueue()->size()); if (szPattern) { printf("Matches: %i\n", iMatches); } printf("Files: %i\n", iNrFileEntries); if (lPaused > 0) { char szRemaining[20]; char szPausedSize[20]; printf("Remaining size: %s (+%s paused)\n", Util::FormatSize(szRemaining, sizeof(szRemaining), lRemaining), Util::FormatSize(szPausedSize, sizeof(szPausedSize), lPaused)); } else { char szRemaining[20]; printf("Remaining size: %s\n", Util::FormatSize(szRemaining, sizeof(szRemaining), lRemaining)); } DownloadQueue::Unlock(); } } free(pBuf); long long lRemaining = Util::JoinInt64(ntohl(ListResponse.m_iRemainingSizeHi), ntohl(ListResponse.m_iRemainingSizeLo)); if (!bFiles && !bGroups) { char szRemaining[20]; printf("Remaining size: %s\n", Util::FormatSize(szRemaining, sizeof(szRemaining), lRemaining)); } if (ntohl(ListResponse.m_iDownloadRate) > 0 && !ntohl(ListResponse.m_bDownloadPaused) && !ntohl(ListResponse.m_bDownload2Paused) && !ntohl(ListResponse.m_bDownloadStandBy)) { long long remain_sec = (long long)(lRemaining / ntohl(ListResponse.m_iDownloadRate)); int h = (int)(remain_sec / 3600); int m = (int)((remain_sec % 3600) / 60); int s = (int)(remain_sec % 60); printf("Remaining time: %.2d:%.2d:%.2d\n", h, m, s); } char szSpeed[20]; printf("Current download rate: %s\n", Util::FormatSpeed(szSpeed, sizeof(szSpeed), ntohl(ListResponse.m_iDownloadRate))); long long iAllBytes = Util::JoinInt64(ntohl(ListResponse.m_iDownloadedBytesHi), ntohl(ListResponse.m_iDownloadedBytesLo)); int iAverageSpeed = (int)(ntohl(ListResponse.m_iDownloadTimeSec) > 0 ? iAllBytes / ntohl(ListResponse.m_iDownloadTimeSec) : 0); printf("Session download rate: %s\n", Util::FormatSpeed(szSpeed, sizeof(szSpeed), iAverageSpeed)); if (ntohl(ListResponse.m_iDownloadLimit) > 0) { printf("Speed limit: %s\n", Util::FormatSpeed(szSpeed, sizeof(szSpeed), ntohl(ListResponse.m_iDownloadLimit))); } int sec = ntohl(ListResponse.m_iUpTimeSec); int h = sec / 3600; int m = (sec % 3600) / 60; int s = sec % 60; printf("Up time: %.2d:%.2d:%.2d\n", h, m, s); sec = ntohl(ListResponse.m_iDownloadTimeSec); h = sec / 3600; m = (sec % 3600) / 60; s = sec % 60; printf("Download time: %.2d:%.2d:%.2d\n", h, m, s); char szSize[20]; printf("Downloaded: %s\n", Util::FormatSize(szSize, sizeof(szSize), iAllBytes)); printf("Threads running: %i\n", ntohl(ListResponse.m_iThreadCount)); if (ntohl(ListResponse.m_iPostJobCount) > 0) { printf("Post-jobs: %i\n", (int)ntohl(ListResponse.m_iPostJobCount)); } if (ntohl(ListResponse.m_bScanPaused)) { printf("Scan state: Paused\n"); } char szServerState[50]; if (ntohl(ListResponse.m_bDownloadPaused) || ntohl(ListResponse.m_bDownload2Paused)) { snprintf(szServerState, sizeof(szServerState), "%s%s", ntohl(ListResponse.m_bDownloadStandBy) ? "Paused" : "Pausing", ntohl(ListResponse.m_bDownloadPaused) && ntohl(ListResponse.m_bDownload2Paused) ? " (+2)" : ntohl(ListResponse.m_bDownload2Paused) ? " (2)" : ""); } else { snprintf(szServerState, sizeof(szServerState), "%s", ntohl(ListResponse.m_bDownloadStandBy) ? "" : "Downloading"); } if (ntohl(ListResponse.m_iPostJobCount) > 0 || ntohl(ListResponse.m_bPostPaused)) { strncat(szServerState, strlen(szServerState) > 0 ? ", Post-Processing" : "Post-Processing", sizeof(szServerState) - strlen(szServerState) - 1); if (ntohl(ListResponse.m_bPostPaused)) { strncat(szServerState, " paused", sizeof(szServerState) - strlen(szServerState) - 1); } } if (strlen(szServerState) == 0) { strncpy(szServerState, "Stand-By", sizeof(szServerState)); } printf("Server state: %s\n", szServerState); return true; } bool RemoteClient::RequestServerLog(int iLines) { if (!InitConnection()) return false; SNZBLogRequest LogRequest; InitMessageBase(&LogRequest.m_MessageBase, eRemoteRequestLog, sizeof(LogRequest)); LogRequest.m_iLines = htonl(iLines); LogRequest.m_iIDFrom = 0; if (!m_pConnection->Send((char*)(&LogRequest), sizeof(LogRequest))) { perror("m_pConnection->Send"); return false; } printf("Request sent\n"); // Now listen for the returned log SNZBLogResponse LogResponse; bool bRead = m_pConnection->Recv((char*) &LogResponse, sizeof(LogResponse)); if (!bRead || (int)ntohl(LogResponse.m_MessageBase.m_iSignature) != (int)NZBMESSAGE_SIGNATURE || ntohl(LogResponse.m_MessageBase.m_iStructSize) != sizeof(LogResponse)) { printf("No response or invalid response (timeout, not nzbget-server or wrong nzbget-server version)\n"); return false; } char* pBuf = NULL; if (ntohl(LogResponse.m_iTrailingDataLength) > 0) { pBuf = (char*)malloc(ntohl(LogResponse.m_iTrailingDataLength)); if (!m_pConnection->Recv(pBuf, ntohl(LogResponse.m_iTrailingDataLength))) { free(pBuf); return false; } } m_pConnection->Disconnect(); if (LogResponse.m_iTrailingDataLength == 0) { printf("Log is empty\n"); } else { printf("Log (last %i entries)\n", ntohl(LogResponse.m_iNrTrailingEntries)); printf("-----------------------------------\n"); char* pBufPtr = (char*)pBuf; for (unsigned int i = 0; i < ntohl(LogResponse.m_iNrTrailingEntries); i++) { SNZBLogResponseEntry* pLogAnswer = (SNZBLogResponseEntry*) pBufPtr; char* szText = pBufPtr + sizeof(SNZBLogResponseEntry); switch (ntohl(pLogAnswer->m_iKind)) { case Message::mkDebug: printf("[DEBUG] %s\n", szText); break; case Message::mkError: printf("[ERROR] %s\n", szText); break; case Message::mkWarning: printf("[WARNING] %s\n", szText); break; case Message::mkInfo: printf("[INFO] %s\n", szText); break; case Message::mkDetail: printf("[DETAIL] %s\n", szText); break; } pBufPtr += sizeof(SNZBLogResponseEntry) + ntohl(pLogAnswer->m_iTextLen); } printf("-----------------------------------\n"); free(pBuf); } return true; } bool RemoteClient::RequestServerPauseUnpause(bool bPause, eRemotePauseUnpauseAction iAction) { if (!InitConnection()) return false; SNZBPauseUnpauseRequest PauseUnpauseRequest; InitMessageBase(&PauseUnpauseRequest.m_MessageBase, eRemoteRequestPauseUnpause, sizeof(PauseUnpauseRequest)); PauseUnpauseRequest.m_bPause = htonl(bPause); PauseUnpauseRequest.m_iAction = htonl(iAction); if (!m_pConnection->Send((char*)(&PauseUnpauseRequest), sizeof(PauseUnpauseRequest))) { perror("m_pConnection->Send"); m_pConnection->Disconnect(); return false; } bool OK = ReceiveBoolResponse(); m_pConnection->Disconnect(); return OK; } bool RemoteClient::RequestServerSetDownloadRate(int iRate) { if (!InitConnection()) return false; SNZBSetDownloadRateRequest SetDownloadRateRequest; InitMessageBase(&SetDownloadRateRequest.m_MessageBase, eRemoteRequestSetDownloadRate, sizeof(SetDownloadRateRequest)); SetDownloadRateRequest.m_iDownloadRate = htonl(iRate); if (!m_pConnection->Send((char*)(&SetDownloadRateRequest), sizeof(SetDownloadRateRequest))) { perror("m_pConnection->Send"); m_pConnection->Disconnect(); return false; } bool OK = ReceiveBoolResponse(); m_pConnection->Disconnect(); return OK; } bool RemoteClient::RequestServerDumpDebug() { if (!InitConnection()) return false; SNZBDumpDebugRequest DumpDebugInfo; InitMessageBase(&DumpDebugInfo.m_MessageBase, eRemoteRequestDumpDebug, sizeof(DumpDebugInfo)); if (!m_pConnection->Send((char*)(&DumpDebugInfo), sizeof(DumpDebugInfo))) { perror("m_pConnection->Send"); m_pConnection->Disconnect(); return false; } bool OK = ReceiveBoolResponse(); m_pConnection->Disconnect(); return OK; } bool RemoteClient::RequestServerEditQueue(DownloadQueue::EEditAction eAction, int iOffset, const char* szText, int* pIDList, int iIDCount, NameList* pNameList, eRemoteMatchMode iMatchMode) { if ((iIDCount <= 0 || pIDList == NULL) && (pNameList == NULL || pNameList->size() == 0)) { printf("File(s) not specified\n"); return false; } if (!InitConnection()) return false; int iIDLength = sizeof(int32_t) * iIDCount; int iNameCount = 0; int iNameLength = 0; if (pNameList && pNameList->size() > 0) { for (NameList::iterator it = pNameList->begin(); it != pNameList->end(); it++) { const char *szName = *it; iNameLength += strlen(szName) + 1; iNameCount++; } // align size to 4-bytes, needed by ARM-processor (and may be others) iNameLength += iNameLength % 4 > 0 ? 4 - iNameLength % 4 : 0; } int iTextLen = szText ? strlen(szText) + 1 : 0; // align size to 4-bytes, needed by ARM-processor (and may be others) iTextLen += iTextLen % 4 > 0 ? 4 - iTextLen % 4 : 0; int iLength = iTextLen + iIDLength + iNameLength; SNZBEditQueueRequest EditQueueRequest; InitMessageBase(&EditQueueRequest.m_MessageBase, eRemoteRequestEditQueue, sizeof(EditQueueRequest)); EditQueueRequest.m_iAction = htonl(eAction); EditQueueRequest.m_iMatchMode = htonl(iMatchMode); EditQueueRequest.m_iOffset = htonl((int)iOffset); EditQueueRequest.m_iTextLen = htonl(iTextLen); EditQueueRequest.m_iNrTrailingIDEntries = htonl(iIDCount); EditQueueRequest.m_iNrTrailingNameEntries = htonl(iNameCount); EditQueueRequest.m_iTrailingNameEntriesLen = htonl(iNameLength); EditQueueRequest.m_iTrailingDataLength = htonl(iLength); char* pTrailingData = (char*)malloc(iLength); if (iTextLen > 0) { strcpy(pTrailingData, szText); } int32_t* pIDs = (int32_t*)(pTrailingData + iTextLen); for (int i = 0; i < iIDCount; i++) { pIDs[i] = htonl(pIDList[i]); } if (iNameCount > 0) { char *pNames = pTrailingData + iTextLen + iIDLength; for (NameList::iterator it = pNameList->begin(); it != pNameList->end(); it++) { const char *szName = *it; int iLen = strlen(szName); strncpy(pNames, szName, iLen + 1); pNames += iLen + 1; } } bool OK = false; if (!m_pConnection->Send((char*)(&EditQueueRequest), sizeof(EditQueueRequest))) { perror("m_pConnection->Send"); } else { m_pConnection->Send(pTrailingData, iLength); OK = ReceiveBoolResponse(); m_pConnection->Disconnect(); } free(pTrailingData); m_pConnection->Disconnect(); return OK; } bool RemoteClient::RequestServerShutdown() { if (!InitConnection()) return false; SNZBShutdownRequest ShutdownRequest; InitMessageBase(&ShutdownRequest.m_MessageBase, eRemoteRequestShutdown, sizeof(ShutdownRequest)); bool OK = m_pConnection->Send((char*)(&ShutdownRequest), sizeof(ShutdownRequest)); if (OK) { OK = ReceiveBoolResponse(); } else { perror("m_pConnection->Send"); } m_pConnection->Disconnect(); return OK; } bool RemoteClient::RequestServerReload() { if (!InitConnection()) return false; SNZBReloadRequest ReloadRequest; InitMessageBase(&ReloadRequest.m_MessageBase, eRemoteRequestReload, sizeof(ReloadRequest)); bool OK = m_pConnection->Send((char*)(&ReloadRequest), sizeof(ReloadRequest)); if (OK) { OK = ReceiveBoolResponse(); } else { perror("m_pConnection->Send"); } m_pConnection->Disconnect(); return OK; } bool RemoteClient::RequestServerVersion() { if (!InitConnection()) return false; SNZBVersionRequest VersionRequest; InitMessageBase(&VersionRequest.m_MessageBase, eRemoteRequestVersion, sizeof(VersionRequest)); bool OK = m_pConnection->Send((char*)(&VersionRequest), sizeof(VersionRequest)); if (OK) { OK = ReceiveBoolResponse(); } else { perror("m_pConnection->Send"); } m_pConnection->Disconnect(); return OK; } bool RemoteClient::RequestPostQueue() { if (!InitConnection()) return false; SNZBPostQueueRequest PostQueueRequest; InitMessageBase(&PostQueueRequest.m_MessageBase, eRemoteRequestPostQueue, sizeof(PostQueueRequest)); if (!m_pConnection->Send((char*)(&PostQueueRequest), sizeof(PostQueueRequest))) { perror("m_pConnection->Send"); return false; } printf("Request sent\n"); // Now listen for the returned list SNZBPostQueueResponse PostQueueResponse; bool bRead = m_pConnection->Recv((char*) &PostQueueResponse, sizeof(PostQueueResponse)); if (!bRead || (int)ntohl(PostQueueResponse.m_MessageBase.m_iSignature) != (int)NZBMESSAGE_SIGNATURE || ntohl(PostQueueResponse.m_MessageBase.m_iStructSize) != sizeof(PostQueueResponse)) { printf("No response or invalid response (timeout, not nzbget-server or wrong nzbget-server version)\n"); return false; } char* pBuf = NULL; if (ntohl(PostQueueResponse.m_iTrailingDataLength) > 0) { pBuf = (char*)malloc(ntohl(PostQueueResponse.m_iTrailingDataLength)); if (!m_pConnection->Recv(pBuf, ntohl(PostQueueResponse.m_iTrailingDataLength))) { free(pBuf); return false; } } m_pConnection->Disconnect(); if (ntohl(PostQueueResponse.m_iTrailingDataLength) == 0) { printf("Server has no jobs queued for post-processing\n"); } else { printf("Post-Processing List\n"); printf("-----------------------------------\n"); char* pBufPtr = (char*)pBuf; for (unsigned int i = 0; i < ntohl(PostQueueResponse.m_iNrTrailingEntries); i++) { SNZBPostQueueResponseEntry* pPostQueueAnswer = (SNZBPostQueueResponseEntry*) pBufPtr; int iStageProgress = ntohl(pPostQueueAnswer->m_iStageProgress); char szCompleted[100]; szCompleted[0] = '\0'; if (iStageProgress > 0 && (int)ntohl(pPostQueueAnswer->m_iStage) != (int)PostInfo::ptExecutingScript) { sprintf(szCompleted, ", %i%s", (int)(iStageProgress / 10), "%"); } 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); 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_iInfoNameLen) + ntohl(pPostQueueAnswer->m_iDestDirLen) + ntohl(pPostQueueAnswer->m_iProgressLabelLen); } free(pBuf); printf("-----------------------------------\n"); } return true; } bool RemoteClient::RequestWriteLog(int iKind, const char* szText) { if (!InitConnection()) return false; SNZBWriteLogRequest WriteLogRequest; InitMessageBase(&WriteLogRequest.m_MessageBase, eRemoteRequestWriteLog, sizeof(WriteLogRequest)); WriteLogRequest.m_iKind = htonl(iKind); int iLength = strlen(szText) + 1; WriteLogRequest.m_iTrailingDataLength = htonl(iLength); if (!m_pConnection->Send((char*)(&WriteLogRequest), sizeof(WriteLogRequest))) { perror("m_pConnection->Send"); return false; } m_pConnection->Send(szText, iLength); bool OK = ReceiveBoolResponse(); m_pConnection->Disconnect(); return OK; } bool RemoteClient::RequestScan(bool bSyncMode) { if (!InitConnection()) return false; SNZBScanRequest ScanRequest; InitMessageBase(&ScanRequest.m_MessageBase, eRemoteRequestScan, sizeof(ScanRequest)); ScanRequest.m_bSyncMode = htonl(bSyncMode); bool OK = m_pConnection->Send((char*)(&ScanRequest), sizeof(ScanRequest)); if (OK) { OK = ReceiveBoolResponse(); } else { perror("m_pConnection->Send"); } m_pConnection->Disconnect(); return OK; } bool RemoteClient::RequestHistory(bool bWithHidden) { if (!InitConnection()) return false; SNZBHistoryRequest HistoryRequest; InitMessageBase(&HistoryRequest.m_MessageBase, eRemoteRequestHistory, sizeof(HistoryRequest)); HistoryRequest.m_bHidden = htonl(bWithHidden); if (!m_pConnection->Send((char*)(&HistoryRequest), sizeof(HistoryRequest))) { perror("m_pConnection->Send"); return false; } printf("Request sent\n"); // Now listen for the returned list SNZBHistoryResponse HistoryResponse; bool bRead = m_pConnection->Recv((char*) &HistoryResponse, sizeof(HistoryResponse)); if (!bRead || (int)ntohl(HistoryResponse.m_MessageBase.m_iSignature) != (int)NZBMESSAGE_SIGNATURE || ntohl(HistoryResponse.m_MessageBase.m_iStructSize) != sizeof(HistoryResponse)) { printf("No response or invalid response (timeout, not nzbget-server or wrong nzbget-server version)\n"); return false; } char* pBuf = NULL; if (ntohl(HistoryResponse.m_iTrailingDataLength) > 0) { pBuf = (char*)malloc(ntohl(HistoryResponse.m_iTrailingDataLength)); if (!m_pConnection->Recv(pBuf, ntohl(HistoryResponse.m_iTrailingDataLength))) { free(pBuf); return false; } } m_pConnection->Disconnect(); if (ntohl(HistoryResponse.m_iTrailingDataLength) == 0) { printf("Server has no files in history\n"); } else { printf("History (most recent first)\n"); printf("-----------------------------------\n"); char* pBufPtr = (char*)pBuf; for (unsigned int i = 0; i < ntohl(HistoryResponse.m_iNrTrailingEntries); i++) { SNZBHistoryResponseEntry* pListAnswer = (SNZBHistoryResponseEntry*) pBufPtr; HistoryInfo::EKind eKind = (HistoryInfo::EKind)ntohl(pListAnswer->m_iKind); const char* szNicename = pBufPtr + sizeof(SNZBHistoryResponseEntry); if (eKind == HistoryInfo::hkNzb || eKind == HistoryInfo::hkDup) { char szFiles[20]; snprintf(szFiles, sizeof(szFiles), "%i files, ", ntohl(pListAnswer->m_iFileCount)); szFiles[20 - 1] = '\0'; long long lSize = Util::JoinInt64(ntohl(pListAnswer->m_iSizeHi), ntohl(pListAnswer->m_iSizeLo)); char szSize[20]; Util::FormatSize(szSize, sizeof(szSize), lSize); const char* szParStatusText[] = { "", "", ", Par failed", ", Par successful", ", Repair possible", ", Repair needed" }; const char* szScriptStatusText[] = { "", ", Script status unknown", ", Script failed", ", Script successful" }; int iParStatus = ntohl(pListAnswer->m_iParStatus); int iScriptStatus = ntohl(pListAnswer->m_iScriptStatus); printf("[%i] %s (%s%s%s%s%s)\n", ntohl(pListAnswer->m_iID), szNicename, (eKind == HistoryInfo::hkDup ? "Hidden, " : ""), (eKind == HistoryInfo::hkDup ? "" : szFiles), szSize, (eKind == HistoryInfo::hkDup ? "" : szParStatusText[iParStatus]), (eKind == HistoryInfo::hkDup ? "" : szScriptStatusText[iScriptStatus])); } else if (eKind == HistoryInfo::hkUrl) { const char* szUrlStatusText[] = { "", "", "Url download successful", "Url download failed", "", "Nzb scan skipped", "Nzb scan failed" }; printf("[%i] %s (URL, %s)\n", ntohl(pListAnswer->m_iID), szNicename, szUrlStatusText[ntohl(pListAnswer->m_iUrlStatus)]); } pBufPtr += sizeof(SNZBHistoryResponseEntry) + ntohl(pListAnswer->m_iNicenameLen); } printf("-----------------------------------\n"); printf("Items: %i\n", ntohl(HistoryResponse.m_iNrTrailingEntries)); } free(pBuf); return true; } nzbget-16.4/daemon/remote/XmlRpc.h0000644000175000017500000000735212630544544016703 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2014 Andrey Prygunkov * * 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 XMLRPC_H #define XMLRPC_H #include "Connection.h" #include "Util.h" class XmlCommand; class XmlRpcProcessor { public: enum ERpcProtocol { rpUndefined, rpXmlRpc, rpJsonRpc, rpJsonPRpc }; enum EHttpMethod { hmPost, hmGet }; enum EUserAccess { uaControl, uaRestricted, uaAdd }; private: char* m_szRequest; const char* m_szContentType; ERpcProtocol m_eProtocol; EHttpMethod m_eHttpMethod; EUserAccess m_eUserAccess; char* m_szUrl; StringBuilder m_cResponse; void Dispatch(); XmlCommand* CreateCommand(const char* szMethodName); void MutliCall(); void BuildResponse(const char* szResponse, const char* szCallbackFunc, bool bFault, const char* szRequestId); public: XmlRpcProcessor(); ~XmlRpcProcessor(); void Execute(); void SetHttpMethod(EHttpMethod eHttpMethod) { m_eHttpMethod = eHttpMethod; } void SetUserAccess(EUserAccess eUserAccess) { m_eUserAccess = eUserAccess; } void SetUrl(const char* szUrl); void SetRequest(char* szRequest) { m_szRequest = szRequest; } const char* GetResponse() { return m_cResponse.GetBuffer(); } const char* GetContentType() { return m_szContentType; } static bool IsRpcRequest(const char* szUrl); }; class XmlCommand { protected: char* m_szRequest; char* m_szRequestPtr; char* m_szCallbackFunc; StringBuilder m_StringBuilder; bool m_bFault; XmlRpcProcessor::ERpcProtocol m_eProtocol; XmlRpcProcessor::EHttpMethod m_eHttpMethod; XmlRpcProcessor::EUserAccess m_eUserAccess; void BuildErrorResponse(int iErrCode, const char* szErrText, ...); void BuildBoolResponse(bool bOK); void BuildIntResponse(int iValue); void AppendResponse(const char* szPart); void AppendFmtResponse(const char* szFormat, ...); void AppendCondResponse(const char* szPart, bool bCond); void OptimizeResponse(int iRecordCount); bool IsJson(); bool CheckSafeMethod(); bool NextParamAsInt(int* iValue); bool NextParamAsBool(bool* bValue); bool NextParamAsStr(char** szValueBuf); char* XmlNextValue(char* szXml, const char* szTag, int* pValueLength); const char* BoolToStr(bool bValue); char* EncodeStr(const char* szStr); void DecodeStr(char* szStr); public: XmlCommand(); virtual ~XmlCommand() {} virtual void Execute() = 0; void PrepareParams(); void SetRequest(char* szRequest) { m_szRequest = szRequest; m_szRequestPtr = m_szRequest; } void SetProtocol(XmlRpcProcessor::ERpcProtocol eProtocol) { m_eProtocol = eProtocol; } void SetHttpMethod(XmlRpcProcessor::EHttpMethod eHttpMethod) { m_eHttpMethod = eHttpMethod; } void SetUserAccess(XmlRpcProcessor::EUserAccess eUserAccess) { m_eUserAccess = eUserAccess; } const char* GetResponse() { return m_StringBuilder.GetBuffer(); } const char* GetCallbackFunc() { return m_szCallbackFunc; } bool GetFault() { return m_bFault; } }; #endif nzbget-16.4/daemon/remote/WebServer.cpp0000644000175000017500000003402312630544544017730 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2012-2015 Andrey Prygunkov * * 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 #include #include #ifndef WIN32 #include #endif #include "nzbget.h" #include "WebServer.h" #include "XmlRpc.h" #include "Log.h" #include "Options.h" #include "Util.h" static const char* ERR_HTTP_BAD_REQUEST = "400 Bad Request"; static const char* ERR_HTTP_NOT_FOUND = "404 Not Found"; static const char* ERR_HTTP_SERVICE_UNAVAILABLE = "503 Service Unavailable"; static const int MAX_UNCOMPRESSED_SIZE = 500; char WebProcessor::m_szServerAuthToken[3][49]; //***************************************************************** // WebProcessor void WebProcessor::Init() { if (m_szServerAuthToken[0][0] != 0) { // already initialized return; } for (int j = uaControl; j <= uaAdd; j++) { for (int i = 0; i < sizeof(m_szServerAuthToken[j]) - 1; i++) { int ch = rand() % (10 + 26 + 26); if (0 <= ch && ch < 10) { m_szServerAuthToken[j][i] = '0' + ch; } else if (10 <= ch && ch < 10 + 26) { m_szServerAuthToken[j][i] = 'a' + ch - 10; } else { m_szServerAuthToken[j][i] = 'A' + ch - 10 - 26; } } m_szServerAuthToken[j][sizeof(m_szServerAuthToken[j]) - 1] = '\0'; debug("X-Auth-Token[%i]: %s", j, m_szServerAuthToken[j]); } } WebProcessor::WebProcessor() { m_pConnection = NULL; m_szRequest = NULL; m_szUrl = NULL; m_szOrigin = NULL; } WebProcessor::~WebProcessor() { free(m_szRequest); free(m_szUrl); free(m_szOrigin); } void WebProcessor::SetUrl(const char* szUrl) { m_szUrl = strdup(szUrl); } void WebProcessor::Execute() { m_bGZip =false; m_eUserAccess = uaControl; m_szAuthInfo[0] = '\0'; m_szAuthToken[0] = '\0'; ParseHeaders(); if (m_eHttpMethod == hmPost && m_iContentLen <= 0) { error("Invalid-request: content length is 0"); return; } if (m_eHttpMethod == hmOptions) { SendOptionsResponse(); return; } ParseURL(); if (!CheckCredentials()) { SendAuthResponse(); return; } if (m_eHttpMethod == hmPost) { // reading http body (request content) m_szRequest = (char*)malloc(m_iContentLen + 1); m_szRequest[m_iContentLen] = '\0'; if (!m_pConnection->Recv(m_szRequest, m_iContentLen)) { error("Invalid-request: could not read data"); return; } debug("Request=%s", m_szRequest); } debug("request received from %s", m_pConnection->GetRemoteAddr()); Dispatch(); } void WebProcessor::ParseHeaders() { // reading http header char szBuffer[1024]; m_iContentLen = 0; while (char* p = m_pConnection->ReadLine(szBuffer, sizeof(szBuffer), NULL)) { if (char* pe = strrchr(p, '\r')) *pe = '\0'; debug("header=%s", p); if (!strncasecmp(p, "Content-Length: ", 16)) { m_iContentLen = atoi(p + 16); } if (!strncasecmp(p, "Authorization: Basic ", 21)) { char* szAuthInfo64 = p + 21; if (strlen(szAuthInfo64) > sizeof(m_szAuthInfo)) { error("Invalid-request: auth-info too big"); return; } m_szAuthInfo[WebUtil::DecodeBase64(szAuthInfo64, 0, m_szAuthInfo)] = '\0'; } if (!strncasecmp(p, "Accept-Encoding: ", 17)) { m_bGZip = strstr(p, "gzip"); } if (!strncasecmp(p, "Origin: ", 8)) { m_szOrigin = strdup(p + 8); } if (!strncasecmp(p, "X-Auth-Token: ", 14)) { strncpy(m_szAuthToken, p + 14, sizeof(m_szAuthToken)-1); m_szAuthToken[sizeof(m_szAuthToken)-1] = '\0'; } if (*p == '\0') { break; } } debug("URL=%s", m_szUrl); debug("Authorization=%s", m_szAuthInfo); debug("X-Auth-Token=%s", m_szAuthToken); } void WebProcessor::ParseURL() { // remove subfolder "nzbget" from the path (if exists) // http://localhost:6789/nzbget/username:password/jsonrpc -> http://localhost:6789/username:password/jsonrpc if (!strncmp(m_szUrl, "/nzbget/", 8)) { char* sz_OldUrl = m_szUrl; m_szUrl = strdup(m_szUrl + 7); free(sz_OldUrl); } // http://localhost:6789/nzbget -> http://localhost:6789 if (!strcmp(m_szUrl, "/nzbget")) { char szRedirectURL[1024]; snprintf(szRedirectURL, 1024, "%s/", m_szUrl); szRedirectURL[1024-1] = '\0'; SendRedirectResponse(szRedirectURL); return; } // authorization via URL in format: // http://localhost:6789/username:password/jsonrpc char* pauth1 = strchr(m_szUrl + 1, ':'); char* pauth2 = strchr(m_szUrl + 1, '/'); if (pauth1 && pauth1 < pauth2) { char* pstart = m_szUrl + 1; int iLen = 0; char* pend = strchr(pstart + 1, '/'); if (pend) { iLen = (int)(pend - pstart < (int)sizeof(m_szAuthInfo) - 1 ? pend - pstart : (int)sizeof(m_szAuthInfo) - 1); } else { iLen = strlen(pstart); } strncpy(m_szAuthInfo, pstart, iLen); m_szAuthInfo[iLen] = '\0'; char* sz_OldUrl = m_szUrl; m_szUrl = strdup(pend); free(sz_OldUrl); } debug("Final URL=%s", m_szUrl); } bool WebProcessor::CheckCredentials() { if (!Util::EmptyStr(g_pOptions->GetControlPassword()) && !(!Util::EmptyStr(g_pOptions->GetAuthorizedIP()) && IsAuthorizedIP(m_pConnection->GetRemoteAddr()))) { if (Util::EmptyStr(m_szAuthInfo)) { // Authorization via X-Auth-Token for (int j = uaControl; j <= uaAdd; j++) { if (!strcmp(m_szAuthToken, m_szServerAuthToken[j])) { m_eUserAccess = (EUserAccess)j; return true; } } return false; } // Authorization via username:password char* pw = strchr(m_szAuthInfo, ':'); if (pw) *pw++ = '\0'; if ((Util::EmptyStr(g_pOptions->GetControlUsername()) || !strcmp(m_szAuthInfo, g_pOptions->GetControlUsername())) && pw && !strcmp(pw, g_pOptions->GetControlPassword())) { m_eUserAccess = uaControl; } else if (!Util::EmptyStr(g_pOptions->GetRestrictedUsername()) && !strcmp(m_szAuthInfo, g_pOptions->GetRestrictedUsername()) && pw && !strcmp(pw, g_pOptions->GetRestrictedPassword())) { m_eUserAccess = uaRestricted; } else if (!Util::EmptyStr(g_pOptions->GetAddUsername()) && !strcmp(m_szAuthInfo, g_pOptions->GetAddUsername()) && pw && !strcmp(pw, g_pOptions->GetAddPassword())) { m_eUserAccess = uaAdd; } else { warn("Request received on port %i from %s, but username or password invalid (%s:%s)", g_pOptions->GetControlPort(), m_pConnection->GetRemoteAddr(), m_szAuthInfo, pw); return false; } } return true; } bool WebProcessor::IsAuthorizedIP(const char* szRemoteAddr) { const char* szRemoteIP = m_pConnection->GetRemoteAddr(); // split option AuthorizedIP into tokens and check each token bool bAuthorized = false; Tokenizer tok(g_pOptions->GetAuthorizedIP(), ",;"); while (const char* szIP = tok.Next()) { if (!strcmp(szIP, szRemoteIP)) { bAuthorized = true; break; } } return bAuthorized; } void WebProcessor::Dispatch() { if (*m_szUrl != '/') { SendErrorResponse(ERR_HTTP_BAD_REQUEST); return; } if (XmlRpcProcessor::IsRpcRequest(m_szUrl)) { XmlRpcProcessor processor; processor.SetRequest(m_szRequest); processor.SetHttpMethod(m_eHttpMethod == hmGet ? XmlRpcProcessor::hmGet : XmlRpcProcessor::hmPost); processor.SetUserAccess((XmlRpcProcessor::EUserAccess)m_eUserAccess); processor.SetUrl(m_szUrl); processor.Execute(); SendBodyResponse(processor.GetResponse(), strlen(processor.GetResponse()), processor.GetContentType()); return; } if (Util::EmptyStr(g_pOptions->GetWebDir())) { SendErrorResponse(ERR_HTTP_SERVICE_UNAVAILABLE); return; } if (m_eHttpMethod != hmGet) { SendErrorResponse(ERR_HTTP_BAD_REQUEST); return; } // for security reasons we allow only characters "0..9 A..Z a..z . - _ /" in the URLs // we also don't allow ".." in the URLs for (char *p = m_szUrl; *p; p++) { if (!((*p >= '0' && *p <= '9') || (*p >= 'A' && *p <= 'Z') || (*p >= 'a' && *p <= 'z') || *p == '.' || *p == '-' || *p == '_' || *p == '/') || (*p == '.' && p[1] == '.')) { SendErrorResponse(ERR_HTTP_NOT_FOUND); return; } } const char *szDefRes = ""; if (m_szUrl[strlen(m_szUrl)-1] == '/') { // default file in directory (if not specified) is "index.html" szDefRes = "index.html"; } char disk_filename[1024]; snprintf(disk_filename, sizeof(disk_filename), "%s%s%s", g_pOptions->GetWebDir(), m_szUrl + 1, szDefRes); disk_filename[sizeof(disk_filename)-1] = '\0'; SendFileResponse(disk_filename); } void WebProcessor::SendAuthResponse() { const char* AUTH_RESPONSE_HEADER = "HTTP/1.0 401 Unauthorized\r\n" "WWW-Authenticate: Basic realm=\"NZBGet\"\r\n" "Connection: close\r\n" "Content-Type: text/plain\r\n" "Server: nzbget-%s\r\n" "\r\n"; char szResponseHeader[1024]; snprintf(szResponseHeader, 1024, AUTH_RESPONSE_HEADER, Util::VersionRevision()); // Send the response answer debug("ResponseHeader=%s", szResponseHeader); m_pConnection->Send(szResponseHeader, strlen(szResponseHeader)); } void WebProcessor::SendOptionsResponse() { const char* OPTIONS_RESPONSE_HEADER = "HTTP/1.1 200 OK\r\n" "Connection: close\r\n" //"Content-Type: plain/text\r\n" "Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n" "Access-Control-Allow-Origin: %s\r\n" "Access-Control-Allow-Credentials: true\r\n" "Access-Control-Max-Age: 86400\r\n" "Access-Control-Allow-Headers: Content-Type, Authorization\r\n" "Server: nzbget-%s\r\n" "\r\n"; char szResponseHeader[1024]; snprintf(szResponseHeader, 1024, OPTIONS_RESPONSE_HEADER, m_szOrigin ? m_szOrigin : "", Util::VersionRevision()); // Send the response answer debug("ResponseHeader=%s", szResponseHeader); m_pConnection->Send(szResponseHeader, strlen(szResponseHeader)); } void WebProcessor::SendErrorResponse(const char* szErrCode) { const char* RESPONSE_HEADER = "HTTP/1.0 %s\r\n" "Connection: close\r\n" "Content-Length: %i\r\n" "Content-Type: text/html\r\n" "Server: nzbget-%s\r\n" "\r\n"; warn("Web-Server: %s, Resource: %s", szErrCode, m_szUrl); char szResponseBody[1024]; snprintf(szResponseBody, 1024, "%sError: %s", szErrCode, szErrCode); int iPageContentLen = strlen(szResponseBody); char szResponseHeader[1024]; snprintf(szResponseHeader, 1024, RESPONSE_HEADER, szErrCode, iPageContentLen, Util::VersionRevision()); // Send the response answer m_pConnection->Send(szResponseHeader, strlen(szResponseHeader)); m_pConnection->Send(szResponseBody, iPageContentLen); } void WebProcessor::SendRedirectResponse(const char* szURL) { const char* REDIRECT_RESPONSE_HEADER = "HTTP/1.0 301 Moved Permanently\r\n" "Location: %s\r\n" "Connection: close\r\n" "Server: nzbget-%s\r\n" "\r\n"; char szResponseHeader[1024]; snprintf(szResponseHeader, 1024, REDIRECT_RESPONSE_HEADER, szURL, Util::VersionRevision()); // Send the response answer debug("ResponseHeader=%s", szResponseHeader); m_pConnection->Send(szResponseHeader, strlen(szResponseHeader)); } void WebProcessor::SendBodyResponse(const char* szBody, int iBodyLen, const char* szContentType) { const char* RESPONSE_HEADER = "HTTP/1.1 200 OK\r\n" "Connection: close\r\n" "Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n" "Access-Control-Allow-Origin: %s\r\n" "Access-Control-Allow-Credentials: true\r\n" "Access-Control-Max-Age: 86400\r\n" "Access-Control-Allow-Headers: Content-Type, Authorization\r\n" "X-Auth-Token: %s\r\n" "Content-Length: %i\r\n" "%s" // Content-Type: xxx "%s" // Content-Encoding: gzip "Server: nzbget-%s\r\n" "\r\n"; #ifndef DISABLE_GZIP char *szGBuf = NULL; bool bGZip = m_bGZip && iBodyLen > MAX_UNCOMPRESSED_SIZE; if (bGZip) { unsigned int iOutLen = ZLib::GZipLen(iBodyLen); szGBuf = (char*)malloc(iOutLen); int iGZippedLen = ZLib::GZip(szBody, iBodyLen, szGBuf, iOutLen); if (iGZippedLen > 0 && iGZippedLen < iBodyLen) { szBody = szGBuf; iBodyLen = iGZippedLen; } else { free(szGBuf); szGBuf = NULL; bGZip = false; } } #else bool bGZip = false; #endif char szContentTypeHeader[1024]; if (szContentType) { snprintf(szContentTypeHeader, 1024, "Content-Type: %s\r\n", szContentType); } else { szContentTypeHeader[0] = '\0'; } char szResponseHeader[1024]; snprintf(szResponseHeader, 1024, RESPONSE_HEADER, m_szOrigin ? m_szOrigin : "", m_szServerAuthToken[m_eUserAccess], iBodyLen, szContentTypeHeader, bGZip ? "Content-Encoding: gzip\r\n" : "", Util::VersionRevision()); // Send the request answer m_pConnection->Send(szResponseHeader, strlen(szResponseHeader)); m_pConnection->Send(szBody, iBodyLen); #ifndef DISABLE_GZIP free(szGBuf); #endif } void WebProcessor::SendFileResponse(const char* szFilename) { debug("serving file: %s", szFilename); char *szBody; int iBodyLen; if (!Util::LoadFileIntoBuffer(szFilename, &szBody, &iBodyLen)) { SendErrorResponse(ERR_HTTP_NOT_FOUND); return; } // "LoadFileIntoBuffer" adds a trailing NULL, which we don't need here iBodyLen--; SendBodyResponse(szBody, iBodyLen, DetectContentType(szFilename)); free(szBody); } const char* WebProcessor::DetectContentType(const char* szFilename) { if (const char *szExt = strrchr(szFilename, '.')) { if (!strcasecmp(szExt, ".css")) { return "text/css"; } else if (!strcasecmp(szExt, ".html")) { return "text/html"; } else if (!strcasecmp(szExt, ".js")) { return "application/javascript"; } else if (!strcasecmp(szExt, ".png")) { return "image/png"; } else if (!strcasecmp(szExt, ".jpeg")) { return "image/jpeg"; } else if (!strcasecmp(szExt, ".gif")) { return "image/gif"; } } return NULL; } nzbget-16.4/daemon/extension/0000755000175000017500000000000012630544544016037 5ustar andreasandreasnzbget-16.4/daemon/extension/ScanScript.h0000644000175000017500000000347612630544544020273 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 SCANSCRIPT_H #define SCANSCRIPT_H #include "NzbScript.h" class ScanScriptController : public NZBScriptController { private: const char* m_szNZBFilename; const char* m_szUrl; const char* m_szDirectory; char** m_pNZBName; char** m_pCategory; int* m_iPriority; NZBParameterList* m_pParameters; bool* m_bAddTop; bool* m_bAddPaused; char** m_pDupeKey; int* m_iDupeScore; EDupeMode* m_eDupeMode; int m_iPrefixLen; void PrepareParams(const char* szScriptName); protected: virtual void ExecuteScript(ScriptConfig::Script* pScript); virtual void AddMessage(Message::EKind eKind, const char* szText); public: static void ExecuteScripts(const char* szNZBFilename, const char* szUrl, const char* szDirectory, char** pNZBName, char** pCategory, int* iPriority, NZBParameterList* pParameters, bool* bAddTop, bool* bAddPaused, char** pDupeKey, int* iDupeScore, EDupeMode* eDupeMode); }; #endif nzbget-16.4/daemon/extension/PostScript.cpp0000644000175000017500000002621412630544544020662 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #include #ifndef WIN32 #include #endif #include #include "nzbget.h" #include "PostScript.h" #include "Log.h" #include "Util.h" #include "Options.h" static const int POSTPROCESS_PARCHECK = 92; static const int POSTPROCESS_SUCCESS = 93; static const int POSTPROCESS_ERROR = 94; static const int POSTPROCESS_NONE = 95; void PostScriptController::StartJob(PostInfo* pPostInfo) { PostScriptController* pScriptController = new PostScriptController(); pScriptController->m_pPostInfo = pPostInfo; pScriptController->SetWorkingDir(g_pOptions->GetDestDir()); pScriptController->SetAutoDestroy(false); pScriptController->m_iPrefixLen = 0; pPostInfo->SetPostThread(pScriptController); pScriptController->Start(); } void PostScriptController::Run() { StringBuilder scriptCommaList; // the locking is needed for accessing the members of NZBInfo DownloadQueue::Lock(); for (NZBParameterList::iterator it = m_pPostInfo->GetNZBInfo()->GetParameters()->begin(); it != m_pPostInfo->GetNZBInfo()->GetParameters()->end(); it++) { NZBParameter* pParameter = *it; const char* szVarname = pParameter->GetName(); if (strlen(szVarname) > 0 && szVarname[0] != '*' && szVarname[strlen(szVarname)-1] == ':' && (!strcasecmp(pParameter->GetValue(), "yes") || !strcasecmp(pParameter->GetValue(), "on") || !strcasecmp(pParameter->GetValue(), "1"))) { char* szScriptName = strdup(szVarname); szScriptName[strlen(szScriptName)-1] = '\0'; // remove trailing ':' scriptCommaList.Append(szScriptName); scriptCommaList.Append(","); free(szScriptName); } } m_pPostInfo->GetNZBInfo()->GetScriptStatuses()->Clear(); DownloadQueue::Unlock(); ExecuteScriptList(scriptCommaList.GetBuffer()); m_pPostInfo->SetStage(PostInfo::ptFinished); m_pPostInfo->SetWorking(false); } void PostScriptController::ExecuteScript(ScriptConfig::Script* pScript) { // if any script has requested par-check, do not execute other scripts if (!pScript->GetPostScript() || m_pPostInfo->GetRequestParCheck()) { return; } PrintMessage(Message::mkInfo, "Executing post-process-script %s for %s", pScript->GetName(), m_pPostInfo->GetNZBInfo()->GetName()); char szProgressLabel[1024]; snprintf(szProgressLabel, 1024, "Executing post-process-script %s", pScript->GetName()); szProgressLabel[1024-1] = '\0'; DownloadQueue::Lock(); m_pPostInfo->SetProgressLabel(szProgressLabel); DownloadQueue::Unlock(); SetScript(pScript->GetLocation()); SetArgs(NULL, false); char szInfoName[1024]; snprintf(szInfoName, 1024, "post-process-script %s for %s", pScript->GetName(), m_pPostInfo->GetNZBInfo()->GetName()); szInfoName[1024-1] = '\0'; SetInfoName(szInfoName); m_pScript = pScript; SetLogPrefix(pScript->GetDisplayName()); m_iPrefixLen = strlen(pScript->GetDisplayName()) + 2; // 2 = strlen(": "); PrepareParams(pScript->GetName()); int iExitCode = Execute(); szInfoName[0] = 'P'; // uppercase SetLogPrefix(NULL); ScriptStatus::EStatus eStatus = AnalyseExitCode(iExitCode); // the locking is needed for accessing the members of NZBInfo DownloadQueue::Lock(); m_pPostInfo->GetNZBInfo()->GetScriptStatuses()->Add(pScript->GetName(), eStatus); DownloadQueue::Unlock(); } void PostScriptController::PrepareParams(const char* szScriptName) { // the locking is needed for accessing the members of NZBInfo DownloadQueue::Lock(); ResetEnv(); SetIntEnvVar("NZBPP_NZBID", m_pPostInfo->GetNZBInfo()->GetID()); SetEnvVar("NZBPP_NZBNAME", m_pPostInfo->GetNZBInfo()->GetName()); SetEnvVar("NZBPP_DIRECTORY", m_pPostInfo->GetNZBInfo()->GetDestDir()); SetEnvVar("NZBPP_NZBFILENAME", m_pPostInfo->GetNZBInfo()->GetFilename()); SetEnvVar("NZBPP_URL", m_pPostInfo->GetNZBInfo()->GetURL()); SetEnvVar("NZBPP_FINALDIR", m_pPostInfo->GetNZBInfo()->GetFinalDir()); SetEnvVar("NZBPP_CATEGORY", m_pPostInfo->GetNZBInfo()->GetCategory()); SetIntEnvVar("NZBPP_HEALTH", m_pPostInfo->GetNZBInfo()->CalcHealth()); SetIntEnvVar("NZBPP_CRITICALHEALTH", m_pPostInfo->GetNZBInfo()->CalcCriticalHealth(false)); SetEnvVar("NZBPP_DUPEKEY", m_pPostInfo->GetNZBInfo()->GetDupeKey()); SetIntEnvVar("NZBPP_DUPESCORE", m_pPostInfo->GetNZBInfo()->GetDupeScore()); const char* szDupeModeName[] = { "SCORE", "ALL", "FORCE" }; SetEnvVar("NZBPP_DUPEMODE", szDupeModeName[m_pPostInfo->GetNZBInfo()->GetDupeMode()]); char szStatus[256]; strncpy(szStatus, m_pPostInfo->GetNZBInfo()->MakeTextStatus(true), sizeof(szStatus)); szStatus[256-1] = '\0'; SetEnvVar("NZBPP_STATUS", szStatus); char* szDetail = strchr(szStatus, '/'); if (szDetail) *szDetail = '\0'; SetEnvVar("NZBPP_TOTALSTATUS", szStatus); const char* szScriptStatusName[] = { "NONE", "FAILURE", "SUCCESS" }; SetEnvVar("NZBPP_SCRIPTSTATUS", szScriptStatusName[m_pPostInfo->GetNZBInfo()->GetScriptStatuses()->CalcTotalStatus()]); // deprecated int iParStatus[] = { 0, 0, 1, 2, 3, 4 }; NZBInfo::EParStatus eParStatus = m_pPostInfo->GetNZBInfo()->GetParStatus(); // for downloads marked as bad and for deleted downloads pass par status "Failure" // for compatibility with older scripts which don't check "NZBPP_TOTALSTATUS" if (m_pPostInfo->GetNZBInfo()->GetDeleteStatus() != NZBInfo::dsNone || m_pPostInfo->GetNZBInfo()->GetMarkStatus() == NZBInfo::ksBad) { eParStatus = NZBInfo::psFailure; } SetIntEnvVar("NZBPP_PARSTATUS", iParStatus[eParStatus]); // deprecated int iUnpackStatus[] = { 0, 0, 1, 2, 3, 4 }; SetIntEnvVar("NZBPP_UNPACKSTATUS", iUnpackStatus[m_pPostInfo->GetNZBInfo()->GetUnpackStatus()]); // deprecated SetIntEnvVar("NZBPP_HEALTHDELETED", (int)m_pPostInfo->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsHealth); SetIntEnvVar("NZBPP_TOTALARTICLES", (int)m_pPostInfo->GetNZBInfo()->GetTotalArticles()); SetIntEnvVar("NZBPP_SUCCESSARTICLES", (int)m_pPostInfo->GetNZBInfo()->GetSuccessArticles()); SetIntEnvVar("NZBPP_FAILEDARTICLES", (int)m_pPostInfo->GetNZBInfo()->GetFailedArticles()); for (ServerStatList::iterator it = m_pPostInfo->GetNZBInfo()->GetServerStats()->begin(); it != m_pPostInfo->GetNZBInfo()->GetServerStats()->end(); it++) { ServerStat* pServerStat = *it; char szName[50]; snprintf(szName, 50, "NZBPP_SERVER%i_SUCCESSARTICLES", pServerStat->GetServerID()); szName[50-1] = '\0'; SetIntEnvVar(szName, pServerStat->GetSuccessArticles()); snprintf(szName, 50, "NZBPP_SERVER%i_FAILEDARTICLES", pServerStat->GetServerID()); szName[50-1] = '\0'; SetIntEnvVar(szName, pServerStat->GetFailedArticles()); } PrepareEnvScript(m_pPostInfo->GetNZBInfo()->GetParameters(), szScriptName); DownloadQueue::Unlock(); } ScriptStatus::EStatus PostScriptController::AnalyseExitCode(int iExitCode) { // The ScriptStatus is accumulated for all scripts: // If any script has failed the status is "failure", etc. switch (iExitCode) { case POSTPROCESS_SUCCESS: PrintMessage(Message::mkInfo, "%s successful", GetInfoName()); return ScriptStatus::srSuccess; case POSTPROCESS_ERROR: case -1: // Execute() returns -1 if the process could not be started (file not found or other problem) PrintMessage(Message::mkError, "%s failed", GetInfoName()); return ScriptStatus::srFailure; case POSTPROCESS_NONE: PrintMessage(Message::mkInfo, "%s skipped", GetInfoName()); return ScriptStatus::srNone; #ifndef DISABLE_PARCHECK case POSTPROCESS_PARCHECK: if (m_pPostInfo->GetNZBInfo()->GetParStatus() > NZBInfo::psSkipped) { PrintMessage(Message::mkError, "%s requested par-check/repair, but the collection was already checked", GetInfoName()); return ScriptStatus::srFailure; } else { PrintMessage(Message::mkInfo, "%s requested par-check/repair", GetInfoName()); m_pPostInfo->SetRequestParCheck(true); m_pPostInfo->SetForceRepair(true); return ScriptStatus::srSuccess; } break; #endif default: PrintMessage(Message::mkError, "%s failed (terminated with unknown status)", GetInfoName()); return ScriptStatus::srFailure; } } void PostScriptController::AddMessage(Message::EKind eKind, const char* szText) { const char* szMsgText = szText + m_iPrefixLen; if (!strncmp(szMsgText, "[NZB] ", 6)) { debug("Command %s detected", szMsgText + 6); if (!strncmp(szMsgText + 6, "FINALDIR=", 9)) { DownloadQueue::Lock(); m_pPostInfo->GetNZBInfo()->SetFinalDir(szMsgText + 6 + 9); DownloadQueue::Unlock(); } else if (!strncmp(szMsgText + 6, "DIRECTORY=", 10)) { DownloadQueue::Lock(); m_pPostInfo->GetNZBInfo()->SetDestDir(szMsgText + 6 + 10); DownloadQueue::Unlock(); } else if (!strncmp(szMsgText + 6, "NZBPR_", 6)) { char* szParam = strdup(szMsgText + 6 + 6); char* szValue = strchr(szParam, '='); if (szValue) { *szValue = '\0'; DownloadQueue::Lock(); m_pPostInfo->GetNZBInfo()->GetParameters()->SetParameter(szParam, szValue + 1); DownloadQueue::Unlock(); } else { m_pPostInfo->GetNZBInfo()->PrintMessage(Message::mkError, "Invalid command \"%s\" received from %s", szMsgText, GetInfoName()); } free(szParam); } else if (!strncmp(szMsgText + 6, "MARK=BAD", 8)) { SetLogPrefix(NULL); PrintMessage(Message::mkWarning, "Marking %s as bad", m_pPostInfo->GetNZBInfo()->GetName()); SetLogPrefix(m_pScript->GetDisplayName()); m_pPostInfo->GetNZBInfo()->SetMarkStatus(NZBInfo::ksBad); } else { m_pPostInfo->GetNZBInfo()->PrintMessage(Message::mkError, "Invalid command \"%s\" received from %s", szMsgText, GetInfoName()); } } else { m_pPostInfo->GetNZBInfo()->AddMessage(eKind, szText); DownloadQueue::Lock(); m_pPostInfo->SetProgressLabel(szText); DownloadQueue::Unlock(); } if (g_pOptions->GetPausePostProcess() && !m_pPostInfo->GetNZBInfo()->GetForcePriority()) { time_t tStageTime = m_pPostInfo->GetStageTime(); time_t tStartTime = m_pPostInfo->GetStartTime(); time_t tWaitTime = time(NULL); // wait until Post-processor is unpaused while (g_pOptions->GetPausePostProcess() && !m_pPostInfo->GetNZBInfo()->GetForcePriority() && !IsStopped()) { usleep(100 * 1000); // update time stamps time_t tDelta = time(NULL) - tWaitTime; if (tStageTime > 0) { m_pPostInfo->SetStageTime(tStageTime + tDelta); } if (tStartTime > 0) { m_pPostInfo->SetStartTime(tStartTime + tDelta); } } } } void PostScriptController::Stop() { debug("Stopping post-process-script"); Thread::Stop(); Terminate(); } nzbget-16.4/daemon/extension/FeedScript.cpp0000644000175000017500000000445012630544544020576 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2015 Andrey Prygunkov * * 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 #include #include #ifndef WIN32 #include #endif #include #include #include "nzbget.h" #include "FeedScript.h" #include "Options.h" #include "Log.h" #include "Util.h" void FeedScriptController::ExecuteScripts(const char* szFeedScript, const char* szFeedFile, int iFeedID) { FeedScriptController* pScriptController = new FeedScriptController(); pScriptController->m_szFeedFile = szFeedFile; pScriptController->m_iFeedID = iFeedID; pScriptController->ExecuteScriptList(szFeedScript); delete pScriptController; } void FeedScriptController::ExecuteScript(ScriptConfig::Script* pScript) { if (!pScript->GetFeedScript()) { return; } PrintMessage(Message::mkInfo, "Executing feed-script %s for Feed%i", pScript->GetName(), m_iFeedID); SetScript(pScript->GetLocation()); SetArgs(NULL, false); char szInfoName[1024]; snprintf(szInfoName, 1024, "feed-script %s for Feed%i", pScript->GetName(), m_iFeedID); szInfoName[1024-1] = '\0'; SetInfoName(szInfoName); SetLogPrefix(pScript->GetDisplayName()); PrepareParams(pScript->GetName()); Execute(); SetLogPrefix(NULL); } void FeedScriptController::PrepareParams(const char* szScriptName) { ResetEnv(); SetEnvVar("NZBFP_FILENAME", m_szFeedFile); SetIntEnvVar("NZBFP_FEEDID", m_iFeedID); PrepareEnvScript(NULL, szScriptName); } nzbget-16.4/daemon/extension/FeedScript.h0000644000175000017500000000242312630544544020241 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2015 Andrey Prygunkov * * 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 FEEDSCRIPT_H #define FEEDSCRIPT_H #include "NzbScript.h" class FeedScriptController : public NZBScriptController { private: const char* m_szFeedFile; int m_iFeedID; void PrepareParams(const char* szScriptName); protected: virtual void ExecuteScript(ScriptConfig::Script* pScript); public: static void ExecuteScripts(const char* szFeedScript, const char* szFeedFile, int iFeedID); }; #endif nzbget-16.4/daemon/extension/NzbScript.cpp0000644000175000017500000000631612630544544020467 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #include #ifndef WIN32 #include #endif #include #include #include #include "nzbget.h" #include "NzbScript.h" #include "Options.h" #include "Log.h" #include "Util.h" /** * If szStripPrefix is not NULL, only pp-parameters, whose names start with the prefix * are processed. The prefix is then stripped from the names. * If szStripPrefix is NULL, all pp-parameters are processed; without stripping. */ void NZBScriptController::PrepareEnvParameters(NZBParameterList* pParameters, const char* szStripPrefix) { int iPrefixLen = szStripPrefix ? strlen(szStripPrefix) : 0; for (NZBParameterList::iterator it = pParameters->begin(); it != pParameters->end(); it++) { NZBParameter* pParameter = *it; const char* szValue = pParameter->GetValue(); #ifdef WIN32 char* szAnsiValue = strdup(szValue); WebUtil::Utf8ToAnsi(szAnsiValue, strlen(szAnsiValue) + 1); szValue = szAnsiValue; #endif if (szStripPrefix && !strncmp(pParameter->GetName(), szStripPrefix, iPrefixLen) && (int)strlen(pParameter->GetName()) > iPrefixLen) { SetEnvVarSpecial("NZBPR", pParameter->GetName() + iPrefixLen, szValue); } else if (!szStripPrefix) { SetEnvVarSpecial("NZBPR", pParameter->GetName(), szValue); } #ifdef WIN32 free(szAnsiValue); #endif } } void NZBScriptController::PrepareEnvScript(NZBParameterList* pParameters, const char* szScriptName) { if (pParameters) { PrepareEnvParameters(pParameters, NULL); } char szParamPrefix[1024]; snprintf(szParamPrefix, 1024, "%s:", szScriptName); szParamPrefix[1024-1] = '\0'; if (pParameters) { PrepareEnvParameters(pParameters, szParamPrefix); } PrepareEnvOptions(szParamPrefix); } void NZBScriptController::ExecuteScriptList(const char* szScriptList) { for (ScriptConfig::Scripts::iterator it = g_pScriptConfig->GetScripts()->begin(); it != g_pScriptConfig->GetScripts()->end(); it++) { ScriptConfig::Script* pScript = *it; if (szScriptList && *szScriptList) { // split szScriptList into tokens Tokenizer tok(szScriptList, ",;"); while (const char* szScriptName = tok.Next()) { if (Util::SameFilename(szScriptName, pScript->GetName())) { ExecuteScript(pScript); break; } } } } } nzbget-16.4/daemon/extension/ScriptConfig.h0000644000175000017500000000726412630544544020613 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2013-2015 Andrey Prygunkov * * 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 SCRIPTCONFIG_H #define SCRIPTCONFIG_H #include #include #include #include "Options.h" class ScriptConfig { public: class Script { private: char* m_szName; char* m_szLocation; char* m_szDisplayName; bool m_bPostScript; bool m_bScanScript; bool m_bQueueScript; bool m_bSchedulerScript; bool m_bFeedScript; char* m_szQueueEvents; public: Script(const char* szName, const char* szLocation); ~Script(); const char* GetName() { return m_szName; } const char* GetLocation() { return m_szLocation; } void SetDisplayName(const char* szDisplayName); const char* GetDisplayName() { return m_szDisplayName; } bool GetPostScript() { return m_bPostScript; } void SetPostScript(bool bPostScript) { m_bPostScript = bPostScript; } bool GetScanScript() { return m_bScanScript; } void SetScanScript(bool bScanScript) { m_bScanScript = bScanScript; } bool GetQueueScript() { return m_bQueueScript; } void SetQueueScript(bool bQueueScript) { m_bQueueScript = bQueueScript; } bool GetSchedulerScript() { return m_bSchedulerScript; } void SetSchedulerScript(bool bSchedulerScript) { m_bSchedulerScript = bSchedulerScript; } bool GetFeedScript() { return m_bFeedScript; } void SetFeedScript(bool bFeedScript) { m_bFeedScript = bFeedScript; } void SetQueueEvents(const char* szQueueEvents); const char* GetQueueEvents() { return m_szQueueEvents; } }; typedef std::list ScriptsBase; class Scripts: public ScriptsBase { public: ~Scripts(); void Clear(); Script* Find(const char* szName); }; class ConfigTemplate { private: Script* m_pScript; char* m_szTemplate; friend class Options; public: ConfigTemplate(Script* pScript, const char* szTemplate); ~ConfigTemplate(); Script* GetScript() { return m_pScript; } const char* GetTemplate() { return m_szTemplate; } }; typedef std::vector ConfigTemplatesBase; class ConfigTemplates: public ConfigTemplatesBase { public: ~ConfigTemplates(); }; private: Scripts m_Scripts; ConfigTemplates m_ConfigTemplates; void InitScripts(); void InitConfigTemplates(); static bool CompareScripts(Script* pScript1, Script* pScript2); void LoadScriptDir(Scripts* pScripts, const char* szDirectory, bool bIsSubDir); void BuildScriptDisplayNames(Scripts* pScripts); void LoadScripts(Scripts* pScripts); public: ScriptConfig(); ~ScriptConfig(); Scripts* GetScripts() { return &m_Scripts; } bool LoadConfig(Options::OptEntries* pOptEntries); bool SaveConfig(Options::OptEntries* pOptEntries); bool LoadConfigTemplates(ConfigTemplates* pConfigTemplates); ConfigTemplates* GetConfigTemplates() { return &m_ConfigTemplates; } }; extern ScriptConfig* g_pScriptConfig; #endif nzbget-16.4/daemon/extension/QueueScript.h0000644000175000017500000000420712630544544020464 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 QUEUESCRIPT_H #define QUEUESCRIPT_H #include #include "DownloadInfo.h" #include "ScriptConfig.h" class QueueScriptCoordinator { public: enum EEvent { qeFileDownloaded, // lowest priority qeUrlCompleted, qeNzbAdded, qeNzbDownloaded, qeNzbDeleted // highest priority }; private: class QueueItem { private: int m_iNZBID; ScriptConfig::Script* m_pScript; EEvent m_eEvent; public: QueueItem(int iNZBID, ScriptConfig::Script* pScript, EEvent eEvent); int GetNZBID() { return m_iNZBID; } ScriptConfig::Script* GetScript() { return m_pScript; } EEvent GetEvent() { return m_eEvent; } }; typedef std::list Queue; Queue m_Queue; Mutex m_mutexQueue; QueueItem* m_pCurItem; bool m_bHasQueueScripts; bool m_bStopped; void StartScript(NZBInfo* pNZBInfo, QueueItem* pQueueItem); NZBInfo* FindNZBInfo(DownloadQueue* pDownloadQueue, int iNZBID); public: QueueScriptCoordinator(); ~QueueScriptCoordinator(); void Stop() { m_bStopped = true; } void InitOptions(); void EnqueueScript(NZBInfo* pNZBInfo, EEvent eEvent); void CheckQueue(); bool HasJob(int iNZBID, bool* pActive); int GetQueueSize(); }; extern QueueScriptCoordinator* g_pQueueScriptCoordinator; #endif nzbget-16.4/daemon/extension/SchedulerScript.cpp0000644000175000017500000000673412630544544021660 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #include #ifndef WIN32 #include #endif #include #include #include "nzbget.h" #include "SchedulerScript.h" #include "Options.h" #include "Log.h" #include "Util.h" SchedulerScriptController::~SchedulerScriptController() { free(m_szScript); } void SchedulerScriptController::StartScript(const char* szParam, bool bExternalProcess, int iTaskID) { char** argv = NULL; if (bExternalProcess && !Util::SplitCommandLine(szParam, &argv)) { error("Could not execute scheduled process-script, failed to parse command line: %s", szParam); return; } SchedulerScriptController* pScriptController = new SchedulerScriptController(); pScriptController->m_bExternalProcess = bExternalProcess; pScriptController->m_szScript = strdup(szParam); pScriptController->m_iTaskID = iTaskID; if (bExternalProcess) { pScriptController->SetScript(argv[0]); pScriptController->SetArgs((const char**)argv, true); } pScriptController->SetAutoDestroy(true); pScriptController->Start(); } void SchedulerScriptController::Run() { if (m_bExternalProcess) { ExecuteExternalProcess(); } else { ExecuteScriptList(m_szScript); } } void SchedulerScriptController::ExecuteScript(ScriptConfig::Script* pScript) { if (!pScript->GetSchedulerScript()) { return; } PrintMessage(Message::mkInfo, "Executing scheduler-script %s for Task%i", pScript->GetName(), m_iTaskID); SetScript(pScript->GetLocation()); SetArgs(NULL, false); char szInfoName[1024]; snprintf(szInfoName, 1024, "scheduler-script %s for Task%i", pScript->GetName(), m_iTaskID); szInfoName[1024-1] = '\0'; SetInfoName(szInfoName); SetLogPrefix(pScript->GetDisplayName()); PrepareParams(pScript->GetName()); Execute(); SetLogPrefix(NULL); } void SchedulerScriptController::PrepareParams(const char* szScriptName) { ResetEnv(); SetIntEnvVar("NZBSP_TASKID", m_iTaskID); PrepareEnvScript(NULL, szScriptName); } void SchedulerScriptController::ExecuteExternalProcess() { info("Executing scheduled process-script %s for Task%i", Util::BaseFileName(GetScript()), m_iTaskID); char szInfoName[1024]; snprintf(szInfoName, 1024, "scheduled process-script %s for Task%i", Util::BaseFileName(GetScript()), m_iTaskID); szInfoName[1024-1] = '\0'; SetInfoName(szInfoName); char szLogPrefix[1024]; strncpy(szLogPrefix, Util::BaseFileName(GetScript()), 1024); szLogPrefix[1024-1] = '\0'; if (char* ext = strrchr(szLogPrefix, '.')) *ext = '\0'; // strip file extension SetLogPrefix(szLogPrefix); Execute(); } nzbget-16.4/daemon/extension/QueueScript.cpp0000644000175000017500000003307712630544544021026 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #include #ifndef WIN32 #include #endif #include #include #include "nzbget.h" #include "QueueScript.h" #include "NzbScript.h" #include "Options.h" #include "Log.h" #include "Util.h" static const char* QUEUE_EVENT_NAMES[] = { "FILE_DOWNLOADED", "URL_COMPLETED", "NZB_ADDED", "NZB_DOWNLOADED", "NZB_DELETED" }; class QueueScriptController : public Thread, public NZBScriptController { private: char* m_szNZBName; char* m_szNZBFilename; char* m_szUrl; char* m_szCategory; char* m_szDestDir; int m_iID; int m_iPriority; char* m_szDupeKey; EDupeMode m_eDupeMode; int m_iDupeScore; NZBParameterList m_Parameters; int m_iPrefixLen; ScriptConfig::Script* m_pScript; QueueScriptCoordinator::EEvent m_eEvent; bool m_bMarkBad; NZBInfo::EDeleteStatus m_eDeleteStatus; NZBInfo::EUrlStatus m_eUrlStatus; void PrepareParams(const char* szScriptName); protected: virtual void ExecuteScript(ScriptConfig::Script* pScript); virtual void AddMessage(Message::EKind eKind, const char* szText); public: virtual ~QueueScriptController(); virtual void Run(); static void StartScript(NZBInfo* pNZBInfo, ScriptConfig::Script* pScript, QueueScriptCoordinator::EEvent eEvent); }; QueueScriptController::~QueueScriptController() { free(m_szNZBName); free(m_szNZBFilename); free(m_szUrl); free(m_szCategory); free(m_szDestDir); free(m_szDupeKey); } void QueueScriptController::StartScript(NZBInfo* pNZBInfo, ScriptConfig::Script* pScript, QueueScriptCoordinator::EEvent eEvent) { QueueScriptController* pScriptController = new QueueScriptController(); pScriptController->m_szNZBName = strdup(pNZBInfo->GetName()); pScriptController->m_szNZBFilename = strdup(pNZBInfo->GetFilename()); pScriptController->m_szUrl = strdup(pNZBInfo->GetURL()); pScriptController->m_szCategory = strdup(pNZBInfo->GetCategory()); pScriptController->m_szDestDir = strdup(pNZBInfo->GetDestDir()); pScriptController->m_iID = pNZBInfo->GetID(); pScriptController->m_iPriority = pNZBInfo->GetPriority(); pScriptController->m_szDupeKey = strdup(pNZBInfo->GetDupeKey()); pScriptController->m_eDupeMode = pNZBInfo->GetDupeMode(); pScriptController->m_iDupeScore = pNZBInfo->GetDupeScore(); pScriptController->m_Parameters.CopyFrom(pNZBInfo->GetParameters()); pScriptController->m_pScript = pScript; pScriptController->m_eEvent = eEvent; pScriptController->m_iPrefixLen = 0; pScriptController->m_bMarkBad = false; pScriptController->m_eDeleteStatus = pNZBInfo->GetDeleteStatus(); pScriptController->m_eUrlStatus = pNZBInfo->GetUrlStatus(); pScriptController->SetAutoDestroy(true); pScriptController->Start(); } void QueueScriptController::Run() { ExecuteScript(m_pScript); SetLogPrefix(NULL); if (m_bMarkBad) { DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); NZBInfo* pNZBInfo = pDownloadQueue->GetQueue()->Find(m_iID); if (pNZBInfo) { PrintMessage(Message::mkWarning, "Cancelling download and deleting %s", m_szNZBName); pNZBInfo->SetDeleteStatus(NZBInfo::dsBad); pDownloadQueue->EditEntry(m_iID, DownloadQueue::eaGroupDelete, 0, NULL); } DownloadQueue::Unlock(); } g_pQueueScriptCoordinator->CheckQueue(); } void QueueScriptController::ExecuteScript(ScriptConfig::Script* pScript) { PrintMessage(m_eEvent == QueueScriptCoordinator::qeFileDownloaded ? Message::mkDetail : Message::mkInfo, "Executing queue-script %s for %s", pScript->GetName(), Util::BaseFileName(m_szNZBName)); SetScript(pScript->GetLocation()); SetArgs(NULL, false); char szInfoName[1024]; snprintf(szInfoName, 1024, "queue-script %s for %s", pScript->GetName(), Util::BaseFileName(m_szNZBName)); szInfoName[1024-1] = '\0'; SetInfoName(szInfoName); SetLogPrefix(pScript->GetDisplayName()); m_iPrefixLen = strlen(pScript->GetDisplayName()) + 2; // 2 = strlen(": "); PrepareParams(pScript->GetName()); Execute(); SetLogPrefix(NULL); } void QueueScriptController::PrepareParams(const char* szScriptName) { ResetEnv(); SetEnvVar("NZBNA_NZBNAME", m_szNZBName); SetIntEnvVar("NZBNA_NZBID", m_iID); SetEnvVar("NZBNA_FILENAME", m_szNZBFilename); SetEnvVar("NZBNA_DIRECTORY", m_szDestDir); SetEnvVar("NZBNA_URL", m_szUrl); SetEnvVar("NZBNA_CATEGORY", m_szCategory); SetIntEnvVar("NZBNA_PRIORITY", m_iPriority); SetIntEnvVar("NZBNA_LASTID", m_iID); // deprecated SetEnvVar("NZBNA_DUPEKEY", m_szDupeKey); SetIntEnvVar("NZBNA_DUPESCORE", m_iDupeScore); const char* szDupeModeName[] = { "SCORE", "ALL", "FORCE" }; SetEnvVar("NZBNA_DUPEMODE", szDupeModeName[m_eDupeMode]); SetEnvVar("NZBNA_EVENT", QUEUE_EVENT_NAMES[m_eEvent]); const char* szDeleteStatusName[] = { "NONE", "MANUAL", "HEALTH", "DUPE", "BAD", "GOOD", "COPY", "SCAN" }; SetEnvVar("NZBNA_DELETESTATUS", szDeleteStatusName[m_eDeleteStatus]); const char* szUrlStatusName[] = { "NONE", "UNKNOWN", "SUCCESS", "FAILURE", "UNKNOWN", "SCAN_SKIPPED", "SCAN_FAILURE" }; SetEnvVar("NZBNA_URLSTATUS", szUrlStatusName[m_eUrlStatus]); PrepareEnvScript(&m_Parameters, szScriptName); } void QueueScriptController::AddMessage(Message::EKind eKind, const char* szText) { const char* szMsgText = szText + m_iPrefixLen; if (!strncmp(szMsgText, "[NZB] ", 6)) { debug("Command %s detected", szMsgText + 6); if (!strncmp(szMsgText + 6, "NZBPR_", 6)) { char* szParam = strdup(szMsgText + 6 + 6); char* szValue = strchr(szParam, '='); if (szValue) { *szValue = '\0'; DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); NZBInfo* pNZBInfo = pDownloadQueue->GetQueue()->Find(m_iID); if (pNZBInfo) { pNZBInfo->GetParameters()->SetParameter(szParam, szValue + 1); } DownloadQueue::Unlock(); } else { error("Invalid command \"%s\" received from %s", szMsgText, GetInfoName()); } free(szParam); } else if (!strncmp(szMsgText + 6, "MARK=BAD", 8)) { m_bMarkBad = true; DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); NZBInfo* pNZBInfo = pDownloadQueue->GetQueue()->Find(m_iID); if (pNZBInfo) { SetLogPrefix(NULL); PrintMessage(Message::mkWarning, "Marking %s as bad", m_szNZBName); SetLogPrefix(m_pScript->GetDisplayName()); pNZBInfo->SetMarkStatus(NZBInfo::ksBad); } DownloadQueue::Unlock(); } else { error("Invalid command \"%s\" received from %s", szMsgText, GetInfoName()); } } else { ScriptController::AddMessage(eKind, szText); } } QueueScriptCoordinator::QueueItem::QueueItem(int iNZBID, ScriptConfig::Script* pScript, EEvent eEvent) { m_iNZBID = iNZBID; m_pScript = pScript; m_eEvent = eEvent; } QueueScriptCoordinator::QueueScriptCoordinator() { m_pCurItem = NULL; m_bStopped = false; } QueueScriptCoordinator::~QueueScriptCoordinator() { delete m_pCurItem; for (Queue::iterator it = m_Queue.begin(); it != m_Queue.end(); it++ ) { delete *it; } } void QueueScriptCoordinator::InitOptions() { m_bHasQueueScripts = false; for (ScriptConfig::Scripts::iterator it = g_pScriptConfig->GetScripts()->begin(); it != g_pScriptConfig->GetScripts()->end(); it++) { ScriptConfig::Script* pScript = *it; if (pScript->GetQueueScript()) { m_bHasQueueScripts = true; break; } } } void QueueScriptCoordinator::EnqueueScript(NZBInfo* pNZBInfo, EEvent eEvent) { if (!m_bHasQueueScripts) { return; } m_mutexQueue.Lock(); if (eEvent == qeNzbDownloaded) { // delete all other queued scripts for this nzb for (Queue::iterator it = m_Queue.begin(); it != m_Queue.end(); ) { QueueItem* pQueueItem = *it; if (pQueueItem->GetNZBID() == pNZBInfo->GetID()) { delete pQueueItem; it = m_Queue.erase(it); continue; } it++; } } // respect option "EventInterval" time_t tCurTime = time(NULL); if (eEvent == qeFileDownloaded && (g_pOptions->GetEventInterval() == -1 || (g_pOptions->GetEventInterval() > 0 && tCurTime - pNZBInfo->GetQueueScriptTime() > 0 && (int)(tCurTime - pNZBInfo->GetQueueScriptTime()) < g_pOptions->GetEventInterval()))) { m_mutexQueue.Unlock(); return; } for (ScriptConfig::Scripts::iterator it = g_pScriptConfig->GetScripts()->begin(); it != g_pScriptConfig->GetScripts()->end(); it++) { ScriptConfig::Script* pScript = *it; if (!pScript->GetQueueScript()) { continue; } bool bUseScript = false; // check queue-scripts const char* szQueueScript = g_pOptions->GetQueueScript(); if (!Util::EmptyStr(szQueueScript)) { // split szQueueScript into tokens Tokenizer tok(szQueueScript, ",;"); while (const char* szScriptName = tok.Next()) { if (Util::SameFilename(szScriptName, pScript->GetName())) { bUseScript = true; break; } } } // check post-processing-scripts if (!bUseScript) { for (NZBParameterList::iterator it = pNZBInfo->GetParameters()->begin(); it != pNZBInfo->GetParameters()->end(); it++) { NZBParameter* pParameter = *it; const char* szVarname = pParameter->GetName(); if (strlen(szVarname) > 0 && szVarname[0] != '*' && szVarname[strlen(szVarname)-1] == ':' && (!strcasecmp(pParameter->GetValue(), "yes") || !strcasecmp(pParameter->GetValue(), "on") || !strcasecmp(pParameter->GetValue(), "1"))) { char szScriptName[1024]; strncpy(szScriptName, szVarname, 1024); szScriptName[1024-1] = '\0'; szScriptName[strlen(szScriptName)-1] = '\0'; // remove trailing ':' if (Util::SameFilename(szScriptName, pScript->GetName())) { bUseScript = true; break; } } } } bUseScript &= Util::EmptyStr(pScript->GetQueueEvents()) || strstr(pScript->GetQueueEvents(), QUEUE_EVENT_NAMES[eEvent]); if (bUseScript) { bool bAlreadyQueued = false; if (eEvent == qeFileDownloaded) { // check if this script is already queued for this nzb for (Queue::iterator it2 = m_Queue.begin(); it2 != m_Queue.end(); it2++) { QueueItem* pQueueItem = *it2; if (pQueueItem->GetNZBID() == pNZBInfo->GetID() && pQueueItem->GetScript() == pScript) { bAlreadyQueued = true; break; } } } if (!bAlreadyQueued) { QueueItem* pQueueItem = new QueueItem(pNZBInfo->GetID(), pScript, eEvent); if (m_pCurItem) { m_Queue.push_back(pQueueItem); } else { StartScript(pNZBInfo, pQueueItem); } } pNZBInfo->SetQueueScriptTime(time(NULL)); } } m_mutexQueue.Unlock(); } NZBInfo* QueueScriptCoordinator::FindNZBInfo(DownloadQueue* pDownloadQueue, int iNZBID) { NZBInfo* pNZBInfo = pDownloadQueue->GetQueue()->Find(iNZBID); if (!pNZBInfo) { for (HistoryList::iterator it = pDownloadQueue->GetHistory()->begin(); it != pDownloadQueue->GetHistory()->end(); it++) { HistoryInfo* pHistoryInfo = *it; if (pHistoryInfo->GetNZBInfo() && pHistoryInfo->GetNZBInfo()->GetID() == iNZBID) { pNZBInfo = pHistoryInfo->GetNZBInfo(); break; } } } return pNZBInfo; } void QueueScriptCoordinator::CheckQueue() { if (m_bStopped) { return; } DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); m_mutexQueue.Lock(); delete m_pCurItem; m_pCurItem = NULL; NZBInfo* pCurNZBInfo = NULL; Queue::iterator itCurItem = m_Queue.end(); for (Queue::iterator it = m_Queue.begin(); it != m_Queue.end(); ) { QueueItem* pQueueItem = *it; NZBInfo* pNZBInfo = FindNZBInfo(pDownloadQueue, pQueueItem->GetNZBID()); // in a case this nzb must not be processed further - delete queue script from queue if (!pNZBInfo || (pNZBInfo->GetDeleteStatus() != NZBInfo::dsNone && pQueueItem->GetEvent() != qeNzbDeleted) || pNZBInfo->GetMarkStatus() == NZBInfo::ksBad) { delete pQueueItem; it = m_Queue.erase(it); continue; } if (!m_pCurItem || pQueueItem->GetEvent() > m_pCurItem->GetEvent()) { m_pCurItem = pQueueItem; itCurItem = it; pCurNZBInfo = pNZBInfo; } it++; } if (m_pCurItem) { m_Queue.erase(itCurItem); StartScript(pCurNZBInfo, m_pCurItem); } m_mutexQueue.Unlock(); DownloadQueue::Unlock(); } void QueueScriptCoordinator::StartScript(NZBInfo* pNZBInfo, QueueItem* pQueueItem) { m_pCurItem = pQueueItem; QueueScriptController::StartScript(pNZBInfo, pQueueItem->GetScript(), pQueueItem->GetEvent()); } bool QueueScriptCoordinator::HasJob(int iNZBID, bool* pActive) { m_mutexQueue.Lock(); bool bWorking = m_pCurItem && m_pCurItem->GetNZBID() == iNZBID; if (pActive) { *pActive = bWorking; } if (!bWorking) { for (Queue::iterator it = m_Queue.begin(); it != m_Queue.end(); it++) { QueueItem* pQueueItem = *it; bWorking = pQueueItem->GetNZBID() == iNZBID; if (bWorking) { break; } } } m_mutexQueue.Unlock(); return bWorking; } int QueueScriptCoordinator::GetQueueSize() { m_mutexQueue.Lock(); int iQueuedCount = m_Queue.size(); if (m_pCurItem) { iQueuedCount++; } m_mutexQueue.Unlock(); return iQueuedCount; } nzbget-16.4/daemon/extension/PostScript.h0000644000175000017500000000272712630544544020332 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 POSTSCRIPT_H #define POSTSCRIPT_H #include "Thread.h" #include "NzbScript.h" class PostScriptController : public Thread, public NZBScriptController { private: PostInfo* m_pPostInfo; int m_iPrefixLen; ScriptConfig::Script* m_pScript; void PrepareParams(const char* szScriptName); ScriptStatus::EStatus AnalyseExitCode(int iExitCode); protected: virtual void ExecuteScript(ScriptConfig::Script* pScript); virtual void AddMessage(Message::EKind eKind, const char* szText); public: virtual void Run(); virtual void Stop(); static void StartJob(PostInfo* pPostInfo); }; #endif nzbget-16.4/daemon/extension/SchedulerScript.h0000644000175000017500000000264612630544544021323 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 SCHEDULERSCRIPT_H #define SCHEDULERSCRIPT_H #include "NzbScript.h" class SchedulerScriptController : public Thread, public NZBScriptController { private: char* m_szScript; bool m_bExternalProcess; int m_iTaskID; void PrepareParams(const char* szScriptName); void ExecuteExternalProcess(); protected: virtual void ExecuteScript(ScriptConfig::Script* pScript); public: virtual ~SchedulerScriptController(); virtual void Run(); static void StartScript(const char* szParam, bool bExternalProcess, int iTaskID); }; #endif nzbget-16.4/daemon/extension/ScanScript.cpp0000644000175000017500000001362012630544544020616 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #include #ifndef WIN32 #include #endif #include #include #include "nzbget.h" #include "ScanScript.h" #include "Scanner.h" #include "Options.h" #include "Log.h" #include "Util.h" void ScanScriptController::ExecuteScripts(const char* szNZBFilename, const char* szUrl, const char* szDirectory, char** pNZBName, char** pCategory, int* iPriority, NZBParameterList* pParameters, bool* bAddTop, bool* bAddPaused, char** pDupeKey, int* iDupeScore, EDupeMode* eDupeMode) { ScanScriptController* pScriptController = new ScanScriptController(); pScriptController->m_szNZBFilename = szNZBFilename; pScriptController->m_szUrl = szUrl; pScriptController->m_szDirectory = szDirectory; pScriptController->m_pNZBName = pNZBName; pScriptController->m_pCategory = pCategory; pScriptController->m_pParameters = pParameters; pScriptController->m_iPriority = iPriority; pScriptController->m_bAddTop = bAddTop; pScriptController->m_bAddPaused = bAddPaused; pScriptController->m_pDupeKey = pDupeKey; pScriptController->m_iDupeScore = iDupeScore; pScriptController->m_eDupeMode = eDupeMode; pScriptController->m_iPrefixLen = 0; pScriptController->ExecuteScriptList(g_pOptions->GetScanScript()); delete pScriptController; } void ScanScriptController::ExecuteScript(ScriptConfig::Script* pScript) { if (!pScript->GetScanScript() || !Util::FileExists(m_szNZBFilename)) { return; } PrintMessage(Message::mkInfo, "Executing scan-script %s for %s", pScript->GetName(), Util::BaseFileName(m_szNZBFilename)); SetScript(pScript->GetLocation()); SetArgs(NULL, false); char szInfoName[1024]; snprintf(szInfoName, 1024, "scan-script %s for %s", pScript->GetName(), Util::BaseFileName(m_szNZBFilename)); szInfoName[1024-1] = '\0'; SetInfoName(szInfoName); SetLogPrefix(pScript->GetDisplayName()); m_iPrefixLen = strlen(pScript->GetDisplayName()) + 2; // 2 = strlen(": "); PrepareParams(pScript->GetName()); Execute(); SetLogPrefix(NULL); } void ScanScriptController::PrepareParams(const char* szScriptName) { ResetEnv(); SetEnvVar("NZBNP_FILENAME", m_szNZBFilename); SetEnvVar("NZBNP_URL", m_szUrl); SetEnvVar("NZBNP_NZBNAME", strlen(*m_pNZBName) > 0 ? *m_pNZBName : Util::BaseFileName(m_szNZBFilename)); SetEnvVar("NZBNP_CATEGORY", *m_pCategory); SetIntEnvVar("NZBNP_PRIORITY", *m_iPriority); SetIntEnvVar("NZBNP_TOP", *m_bAddTop ? 1 : 0); SetIntEnvVar("NZBNP_PAUSED", *m_bAddPaused ? 1 : 0); SetEnvVar("NZBNP_DUPEKEY", *m_pDupeKey); SetIntEnvVar("NZBNP_DUPESCORE", *m_iDupeScore); const char* szDupeModeName[] = { "SCORE", "ALL", "FORCE" }; SetEnvVar("NZBNP_DUPEMODE", szDupeModeName[*m_eDupeMode]); // remove trailing slash char szDir[1024]; strncpy(szDir, m_szDirectory, 1024); szDir[1024-1] = '\0'; int iLen = strlen(szDir); if (szDir[iLen-1] == PATH_SEPARATOR) { szDir[iLen-1] = '\0'; } SetEnvVar("NZBNP_DIRECTORY", szDir); PrepareEnvScript(m_pParameters, szScriptName); } void ScanScriptController::AddMessage(Message::EKind eKind, const char* szText) { const char* szMsgText = szText + m_iPrefixLen; if (!strncmp(szMsgText, "[NZB] ", 6)) { debug("Command %s detected", szMsgText + 6); if (!strncmp(szMsgText + 6, "NZBNAME=", 8)) { free(*m_pNZBName); *m_pNZBName = strdup(szMsgText + 6 + 8); } else if (!strncmp(szMsgText + 6, "CATEGORY=", 9)) { free(*m_pCategory); *m_pCategory = strdup(szMsgText + 6 + 9); g_pScanner->InitPPParameters(*m_pCategory, m_pParameters, true); } else if (!strncmp(szMsgText + 6, "NZBPR_", 6)) { char* szParam = strdup(szMsgText + 6 + 6); char* szValue = strchr(szParam, '='); if (szValue) { *szValue = '\0'; m_pParameters->SetParameter(szParam, szValue + 1); } else { error("Invalid command \"%s\" received from %s", szMsgText, GetInfoName()); } free(szParam); } else if (!strncmp(szMsgText + 6, "PRIORITY=", 9)) { *m_iPriority = atoi(szMsgText + 6 + 9); } else if (!strncmp(szMsgText + 6, "TOP=", 4)) { *m_bAddTop = atoi(szMsgText + 6 + 4) != 0; } else if (!strncmp(szMsgText + 6, "PAUSED=", 7)) { *m_bAddPaused = atoi(szMsgText + 6 + 7) != 0; } else if (!strncmp(szMsgText + 6, "DUPEKEY=", 8)) { free(*m_pDupeKey); *m_pDupeKey = strdup(szMsgText + 6 + 8); } else if (!strncmp(szMsgText + 6, "DUPESCORE=", 10)) { *m_iDupeScore = atoi(szMsgText + 6 + 10); } else if (!strncmp(szMsgText + 6, "DUPEMODE=", 9)) { const char* szDupeMode = szMsgText + 6 + 9; if (strcasecmp(szDupeMode, "score") && strcasecmp(szDupeMode, "all") && strcasecmp(szDupeMode, "force")) { error("Invalid value \"%s\" for command \"DUPEMODE\" received from %s", szDupeMode, GetInfoName()); return; } *m_eDupeMode = !strcasecmp(szDupeMode, "all") ? dmAll : !strcasecmp(szDupeMode, "force") ? dmForce : dmScore; } else { error("Invalid command \"%s\" received from %s", szMsgText, GetInfoName()); } } else { ScriptController::AddMessage(eKind, szText); } } nzbget-16.4/daemon/extension/ScriptConfig.cpp0000644000175000017500000003304612630544544021143 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2013-2015 Andrey Prygunkov * * 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 #include #include #include #include #include #include "nzbget.h" #include "Util.h" #include "Options.h" #include "Log.h" #include "ScriptConfig.h" static const char* BEGIN_SCRIPT_SIGNATURE = "### NZBGET "; static const char* POST_SCRIPT_SIGNATURE = "POST-PROCESSING"; static const char* SCAN_SCRIPT_SIGNATURE = "SCAN"; static const char* QUEUE_SCRIPT_SIGNATURE = "QUEUE"; static const char* SCHEDULER_SCRIPT_SIGNATURE = "SCHEDULER"; static const char* FEED_SCRIPT_SIGNATURE = "FEED"; static const char* END_SCRIPT_SIGNATURE = " SCRIPT"; static const char* QUEUE_EVENTS_SIGNATURE = "### QUEUE EVENTS:"; ScriptConfig* g_pScriptConfig = NULL; ScriptConfig::ConfigTemplate::ConfigTemplate(Script* pScript, const char* szTemplate) { m_pScript = pScript; m_szTemplate = strdup(szTemplate ? szTemplate : ""); } ScriptConfig::ConfigTemplate::~ConfigTemplate() { delete m_pScript; free(m_szTemplate); } ScriptConfig::ConfigTemplates::~ConfigTemplates() { for (iterator it = begin(); it != end(); it++) { delete *it; } } ScriptConfig::Script::Script(const char* szName, const char* szLocation) { m_szName = strdup(szName); m_szLocation = strdup(szLocation); m_szDisplayName = strdup(szName); m_bPostScript = false; m_bScanScript = false; m_bQueueScript = false; m_bSchedulerScript = false; m_bFeedScript = false; m_szQueueEvents = NULL; } ScriptConfig::Script::~Script() { free(m_szName); free(m_szLocation); free(m_szDisplayName); free(m_szQueueEvents); } void ScriptConfig::Script::SetDisplayName(const char* szDisplayName) { free(m_szDisplayName); m_szDisplayName = strdup(szDisplayName); } void ScriptConfig::Script::SetQueueEvents(const char* szQueueEvents) { free(m_szQueueEvents); m_szQueueEvents = szQueueEvents ? strdup(szQueueEvents) : NULL; } ScriptConfig::Scripts::~Scripts() { Clear(); } void ScriptConfig::Scripts::Clear() { for (iterator it = begin(); it != end(); it++) { delete *it; } clear(); } ScriptConfig::Script* ScriptConfig::Scripts::Find(const char* szName) { for (iterator it = begin(); it != end(); it++) { Script* pScript = *it; if (!strcmp(pScript->GetName(), szName)) { return pScript; } } return NULL; } ScriptConfig::ScriptConfig() { InitScripts(); InitConfigTemplates(); } ScriptConfig::~ScriptConfig() { } bool ScriptConfig::LoadConfig(Options::OptEntries* pOptEntries) { // read config file FILE* infile = fopen(g_pOptions->GetConfigFilename(), FOPEN_RB); if (!infile) { return false; } int iBufLen = (int)Util::FileSize(g_pOptions->GetConfigFilename()) + 1; char* buf = (char*)malloc(iBufLen); while (fgets(buf, iBufLen - 1, infile)) { // remove trailing '\n' and '\r' and spaces Util::TrimRight(buf); // skip comments and empty lines if (buf[0] == 0 || buf[0] == '#' || strspn(buf, " ") == strlen(buf)) { continue; } char* optname; char* optvalue; if (g_pOptions->SplitOptionString(buf, &optname, &optvalue)) { Options::OptEntry* pOptEntry = new Options::OptEntry(); pOptEntry->SetName(optname); pOptEntry->SetValue(optvalue); pOptEntries->push_back(pOptEntry); free(optname); free(optvalue); } } fclose(infile); free(buf); return true; } bool ScriptConfig::SaveConfig(Options::OptEntries* pOptEntries) { // save to config file FILE* infile = fopen(g_pOptions->GetConfigFilename(), FOPEN_RBP); if (!infile) { return false; } std::vector config; std::set writtenOptions; // read config file into memory array int iBufLen = (int)Util::FileSize(g_pOptions->GetConfigFilename()) + 1; char* buf = (char*)malloc(iBufLen); while (fgets(buf, iBufLen - 1, infile)) { config.push_back(strdup(buf)); } free(buf); // write config file back to disk, replace old values of existing options with new values rewind(infile); for (std::vector::iterator it = config.begin(); it != config.end(); it++) { char* buf = *it; const char* eq = strchr(buf, '='); if (eq && buf[0] != '#') { // remove trailing '\n' and '\r' and spaces Util::TrimRight(buf); char* optname; char* optvalue; if (g_pOptions->SplitOptionString(buf, &optname, &optvalue)) { Options::OptEntry *pOptEntry = pOptEntries->FindOption(optname); if (pOptEntry) { fputs(pOptEntry->GetName(), infile); fputs("=", infile); fputs(pOptEntry->GetValue(), infile); fputs("\n", infile); writtenOptions.insert(pOptEntry); } free(optname); free(optvalue); } } else { fputs(buf, infile); } free(buf); } // write new options for (Options::OptEntries::iterator it = pOptEntries->begin(); it != pOptEntries->end(); it++) { Options::OptEntry* pOptEntry = *it; std::set::iterator fit = writtenOptions.find(pOptEntry); if (fit == writtenOptions.end()) { fputs(pOptEntry->GetName(), infile); fputs("=", infile); fputs(pOptEntry->GetValue(), infile); fputs("\n", infile); } } // close and truncate the file int pos = (int)ftell(infile); fclose(infile); Util::TruncateFile(g_pOptions->GetConfigFilename(), pos); return true; } bool ScriptConfig::LoadConfigTemplates(ConfigTemplates* pConfigTemplates) { char* szBuffer; int iLength; if (!Util::LoadFileIntoBuffer(g_pOptions->GetConfigTemplate(), &szBuffer, &iLength)) { return false; } ConfigTemplate* pConfigTemplate = new ConfigTemplate(NULL, szBuffer); pConfigTemplates->push_back(pConfigTemplate); free(szBuffer); if (!g_pOptions->GetScriptDir()) { return true; } Scripts scriptList; LoadScripts(&scriptList); const int iBeginSignatureLen = strlen(BEGIN_SCRIPT_SIGNATURE); const int iQueueEventsSignatureLen = strlen(QUEUE_EVENTS_SIGNATURE); for (Scripts::iterator it = scriptList.begin(); it != scriptList.end(); it++) { Script* pScript = *it; FILE* infile = fopen(pScript->GetLocation(), FOPEN_RB); if (!infile) { ConfigTemplate* pConfigTemplate = new ConfigTemplate(pScript, ""); pConfigTemplates->push_back(pConfigTemplate); continue; } StringBuilder stringBuilder; char buf[1024]; bool bInConfig = false; while (fgets(buf, sizeof(buf) - 1, infile)) { if (!strncmp(buf, BEGIN_SCRIPT_SIGNATURE, iBeginSignatureLen) && strstr(buf, END_SCRIPT_SIGNATURE) && (strstr(buf, POST_SCRIPT_SIGNATURE) || strstr(buf, SCAN_SCRIPT_SIGNATURE) || strstr(buf, QUEUE_SCRIPT_SIGNATURE) || strstr(buf, SCHEDULER_SCRIPT_SIGNATURE) || strstr(buf, FEED_SCRIPT_SIGNATURE))) { if (bInConfig) { break; } bInConfig = true; continue; } bool bSkip = !strncmp(buf, QUEUE_EVENTS_SIGNATURE, iQueueEventsSignatureLen); if (bInConfig && !bSkip) { stringBuilder.Append(buf); } } fclose(infile); ConfigTemplate* pConfigTemplate = new ConfigTemplate(pScript, stringBuilder.GetBuffer()); pConfigTemplates->push_back(pConfigTemplate); } // clearing the list without deleting of objects, which are in pConfigTemplates now scriptList.clear(); return true; } void ScriptConfig::InitConfigTemplates() { if (!LoadConfigTemplates(&m_ConfigTemplates)) { error("Could not read configuration templates"); } } void ScriptConfig::InitScripts() { LoadScripts(&m_Scripts); } void ScriptConfig::LoadScripts(Scripts* pScripts) { if (strlen(g_pOptions->GetScriptDir()) == 0) { return; } Scripts tmpScripts; LoadScriptDir(&tmpScripts, g_pOptions->GetScriptDir(), false); tmpScripts.sort(CompareScripts); // first add all scripts from m_szScriptOrder Tokenizer tok(g_pOptions->GetScriptOrder(), ",;"); while (const char* szScriptName = tok.Next()) { Script* pScript = tmpScripts.Find(szScriptName); if (pScript) { tmpScripts.remove(pScript); pScripts->push_back(pScript); } } // second add all other scripts from scripts directory for (Scripts::iterator it = tmpScripts.begin(); it != tmpScripts.end(); it++) { Script* pScript = *it; if (!pScripts->Find(pScript->GetName())) { pScripts->push_back(pScript); } } tmpScripts.clear(); BuildScriptDisplayNames(pScripts); } void ScriptConfig::LoadScriptDir(Scripts* pScripts, const char* szDirectory, bool bIsSubDir) { int iBufSize = 1024*10; char* szBuffer = (char*)malloc(iBufSize+1); const int iBeginSignatureLen = strlen(BEGIN_SCRIPT_SIGNATURE); const int iQueueEventsSignatureLen = strlen(QUEUE_EVENTS_SIGNATURE); DirBrowser dir(szDirectory); while (const char* szFilename = dir.Next()) { if (szFilename[0] != '.' && szFilename[0] != '_') { char szFullFilename[1024]; snprintf(szFullFilename, 1024, "%s%s", szDirectory, szFilename); szFullFilename[1024-1] = '\0'; if (!Util::DirectoryExists(szFullFilename)) { // check if the file contains pp-script-signature FILE* infile = fopen(szFullFilename, FOPEN_RB); if (infile) { // read first 10KB of the file and look for signature int iReadBytes = fread(szBuffer, 1, iBufSize, infile); fclose(infile); szBuffer[iReadBytes] = 0; // split buffer into lines Tokenizer tok(szBuffer, "\n\r", true); while (char* szLine = tok.Next()) { if (!strncmp(szLine, BEGIN_SCRIPT_SIGNATURE, iBeginSignatureLen) && strstr(szLine, END_SCRIPT_SIGNATURE)) { bool bPostScript = strstr(szLine, POST_SCRIPT_SIGNATURE); bool bScanScript = strstr(szLine, SCAN_SCRIPT_SIGNATURE); bool bQueueScript = strstr(szLine, QUEUE_SCRIPT_SIGNATURE); bool bSchedulerScript = strstr(szLine, SCHEDULER_SCRIPT_SIGNATURE); bool bFeedScript = strstr(szLine, FEED_SCRIPT_SIGNATURE); if (bPostScript || bScanScript || bQueueScript || bSchedulerScript || bFeedScript) { char szScriptName[1024]; if (bIsSubDir) { char szDirectory2[1024]; snprintf(szDirectory2, 1024, "%s", szDirectory); szDirectory2[1024-1] = '\0'; int iLen = strlen(szDirectory2); if (szDirectory2[iLen-1] == PATH_SEPARATOR || szDirectory2[iLen-1] == ALT_PATH_SEPARATOR) { // trim last path-separator szDirectory2[iLen-1] = '\0'; } snprintf(szScriptName, 1024, "%s%c%s", Util::BaseFileName(szDirectory2), PATH_SEPARATOR, szFilename); } else { snprintf(szScriptName, 1024, "%s", szFilename); } szScriptName[1024-1] = '\0'; char* szQueueEvents = NULL; if (bQueueScript) { while (char* szLine = tok.Next()) { if (!strncmp(szLine, QUEUE_EVENTS_SIGNATURE, iQueueEventsSignatureLen)) { szQueueEvents = szLine + iQueueEventsSignatureLen; break; } } } Script* pScript = new Script(szScriptName, szFullFilename); pScript->SetPostScript(bPostScript); pScript->SetScanScript(bScanScript); pScript->SetQueueScript(bQueueScript); pScript->SetSchedulerScript(bSchedulerScript); pScript->SetFeedScript(bFeedScript); pScript->SetQueueEvents(szQueueEvents); pScripts->push_back(pScript); break; } } } } } else if (!bIsSubDir) { snprintf(szFullFilename, 1024, "%s%s%c", szDirectory, szFilename, PATH_SEPARATOR); szFullFilename[1024-1] = '\0'; LoadScriptDir(pScripts, szFullFilename, true); } } } free(szBuffer); } bool ScriptConfig::CompareScripts(Script* pScript1, Script* pScript2) { return strcmp(pScript1->GetName(), pScript2->GetName()) < 0; } void ScriptConfig::BuildScriptDisplayNames(Scripts* pScripts) { // trying to use short name without path and extension. // if there are other scripts with the same short name - using a longer name instead (with ot without extension) for (Scripts::iterator it = pScripts->begin(); it != pScripts->end(); it++) { Script* pScript = *it; char szShortName[256]; strncpy(szShortName, pScript->GetName(), 256); szShortName[256-1] = '\0'; if (char* ext = strrchr(szShortName, '.')) *ext = '\0'; // strip file extension const char* szDisplayName = Util::BaseFileName(szShortName); for (Scripts::iterator it2 = pScripts->begin(); it2 != pScripts->end(); it2++) { Script* pScript2 = *it2; char szShortName2[256]; strncpy(szShortName2, pScript2->GetName(), 256); szShortName2[256-1] = '\0'; if (char* ext = strrchr(szShortName2, '.')) *ext = '\0'; // strip file extension const char* szDisplayName2 = Util::BaseFileName(szShortName2); if (!strcmp(szDisplayName, szDisplayName2) && pScript->GetName() != pScript2->GetName()) { if (!strcmp(szShortName, szShortName2)) { szDisplayName = pScript->GetName(); } else { szDisplayName = szShortName; } break; } } pScript->SetDisplayName(szDisplayName); } } nzbget-16.4/daemon/extension/NzbScript.h0000644000175000017500000000252512630544544020132 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 NZBSCRIPT_H #define NZBSCRIPT_H #include "Script.h" #include "DownloadInfo.h" #include "ScriptConfig.h" class NZBScriptController : public ScriptController { protected: void PrepareEnvParameters(NZBParameterList* pParameters, const char* szStripPrefix); void PrepareEnvScript(NZBParameterList* pParameters, const char* szScriptName); void ExecuteScriptList(const char* szScriptList); virtual void ExecuteScript(ScriptConfig::Script* pScript) = 0; }; #endif nzbget-16.4/daemon/feed/0000755000175000017500000000000012630544544014726 5ustar andreasandreasnzbget-16.4/daemon/feed/FeedFilter.h0000644000175000017500000001225212630544544017112 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2013 Andrey Prygunkov * * 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 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 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 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 nzbget-16.4/daemon/feed/FeedInfo.h0000644000175000017500000002007112630544544016556 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2013-2015 Andrey Prygunkov * * 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 #include #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; char* m_szFeedScript; int m_iPriority; time_t m_tLastUpdate; bool m_bPreview; EStatus m_eStatus; char* m_szOutputFilename; bool m_bFetch; bool m_bForce; bool m_bBacklog; public: FeedInfo(int iID, const char* szName, const char* szUrl, bool bBacklog, int iInterval, const char* szFilter, bool bPauseNzb, const char* szCategory, int iPriority, const char* szFeedScript); ~FeedInfo(); int GetID() { return m_iID; } const char* GetName() { return m_szName; } const char* GetUrl() { return m_szUrl; } int GetInterval() { return m_iInterval; } const char* GetFilter() { return m_szFilter; } unsigned int GetFilterHash() { return m_iFilterHash; } bool GetPauseNzb() { return m_bPauseNzb; } const char* GetCategory() { return m_szCategory; } int GetPriority() { return m_iPriority; } const char* GetFeedScript() { return m_szFeedScript; } time_t GetLastUpdate() { return m_tLastUpdate; } void SetLastUpdate(time_t tLastUpdate) { m_tLastUpdate = tLastUpdate; } bool GetPreview() { return m_bPreview; } void SetPreview(bool bPreview) { m_bPreview = bPreview; } EStatus GetStatus() { return m_eStatus; } void SetStatus(EStatus Status) { m_eStatus = Status; } const char* GetOutputFilename() { return m_szOutputFilename; } void SetOutputFilename(const char* szOutputFilename); bool GetFetch() { return m_bFetch; } void SetFetch(bool bFetch) { m_bFetch = bFetch; } bool GetForce() { return m_bForce; } void SetForce(bool bForce) { m_bForce = bForce; } bool GetBacklog() { return m_bBacklog; } void SetBacklog(bool bBacklog) { m_bBacklog = bBacklog; } }; typedef std::deque Feeds; class FeedFilterHelper { public: virtual RegEx** GetSeasonEpisodeRegEx() = 0; virtual void CalcDupeStatus(const char* szTitle, const char* szDupeKey, char* szStatusBuf, int iBufLen) = 0; }; 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 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; char* m_szDupeStatus; FeedFilterHelper* m_pFeedFilterHelper; Attributes m_Attributes; int ParsePrefixedInt(const char *szValue); void ParseSeasonEpisode(); public: FeedItemInfo(); ~FeedItemInfo(); void SetFeedFilterHelper(FeedFilterHelper* pFeedFilterHelper) { m_pFeedFilterHelper = pFeedFilterHelper; } const char* GetTitle() { return m_szTitle; } void SetTitle(const char* szTitle); const char* GetFilename() { return m_szFilename; } void SetFilename(const char* szFilename); const char* GetUrl() { return m_szUrl; } void SetUrl(const char* szUrl); long long GetSize() { return m_lSize; } void SetSize(long long lSize) { m_lSize = lSize; } const char* GetCategory() { return m_szCategory; } void SetCategory(const char* szCategory); int GetImdbId() { return m_iImdbId; } void SetImdbId(int iImdbId) { m_iImdbId = iImdbId; } int GetRageId() { return m_iRageId; } void SetRageId(int iRageId) { m_iRageId = iRageId; } const char* GetDescription() { return m_szDescription; } void SetDescription(const char* szDescription); const char* GetSeason() { return m_szSeason; } void SetSeason(const char* szSeason); const char* GetEpisode() { return m_szEpisode; } void SetEpisode(const char* szEpisode); int GetSeasonNum(); int GetEpisodeNum(); const char* GetAddCategory() { return m_szAddCategory; } void SetAddCategory(const char* szAddCategory); bool GetPauseNzb() { return m_bPauseNzb; } void SetPauseNzb(bool bPauseNzb) { m_bPauseNzb = bPauseNzb; } int GetPriority() { return m_iPriority; } void SetPriority(int iPriority) { m_iPriority = iPriority; } time_t GetTime() { return m_tTime; } void SetTime(time_t tTime) { m_tTime = tTime; } EStatus GetStatus() { return m_eStatus; } void SetStatus(EStatus eStatus) { m_eStatus = eStatus; } EMatchStatus GetMatchStatus() { return m_eMatchStatus; } void SetMatchStatus(EMatchStatus eMatchStatus) { m_eMatchStatus = eMatchStatus; } int GetMatchRule() { return m_iMatchRule; } void SetMatchRule(int iMatchRule) { m_iMatchRule = iMatchRule; } const char* GetDupeKey() { return m_szDupeKey; } void SetDupeKey(const char* szDupeKey); void AppendDupeKey(const char* szExtraDupeKey); void BuildDupeKey(const char* szRageId, const char* szSeries); int GetDupeScore() { return m_iDupeScore; } void SetDupeScore(int iDupeScore) { m_iDupeScore = iDupeScore; } EDupeMode GetDupeMode() { return m_eDupeMode; } void SetDupeMode(EDupeMode eDupeMode) { m_eDupeMode = eDupeMode; } const char* GetDupeStatus(); Attributes* GetAttributes() { return &m_Attributes; } }; typedef std::deque FeedItemInfosBase; class FeedItemInfos : public FeedItemInfosBase { private: int m_iRefCount; 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 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 nzbget-16.4/daemon/feed/FeedCoordinator.cpp0000644000175000017500000005102712630544544020506 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2013-2015 Andrey Prygunkov * * 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 #include #include #include #ifndef WIN32 #include #include #endif #include "nzbget.h" #include "FeedCoordinator.h" #include "Options.h" #include "WebDownloader.h" #include "Util.h" #include "FeedFile.h" #include "FeedFilter.h" #include "FeedScript.h" #include "DiskState.h" #include "DupeCoordinator.h" 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::FilterHelper::FilterHelper() { m_pSeasonEpisodeRegEx = NULL; } FeedCoordinator::FilterHelper::~FilterHelper() { delete m_pSeasonEpisodeRegEx; } void FeedCoordinator::FilterHelper::CalcDupeStatus(const char* szTitle, const char* szDupeKey, char* szStatusBuf, int iBufLen) { const char* szDupeStatusName[] = { "", "QUEUED", "DOWNLOADING", "3", "SUCCESS", "5", "6", "7", "WARNING", "9", "10", "11", "12", "13", "14", "15", "FAILURE" }; char szStatuses[200]; szStatuses[0] = '\0'; DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); DupeCoordinator::EDupeStatus eDupeStatus = g_pDupeCoordinator->GetDupeStatus(pDownloadQueue, szTitle, szDupeKey); DownloadQueue::Unlock(); for (int i = 1; i <= (int)DupeCoordinator::dsFailure; i = i << 1) { if (eDupeStatus & i) { if (*szStatuses) { strcat(szStatuses, ","); } strcat(szStatuses, szDupeStatusName[i]); } } strncpy(szStatusBuf, szStatuses, iBufLen); } FeedCoordinator::FeedCoordinator() { debug("Creating FeedCoordinator"); m_bForce = false; m_bSave = false; g_pLog->RegisterDebuggable(this); m_DownloadQueueObserver.m_pOwner = this; DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); pDownloadQueue->Attach(&m_DownloadQueueObserver); DownloadQueue::Unlock(); } FeedCoordinator::~FeedCoordinator() { debug("Destroying FeedCoordinator"); // Cleanup g_pLog->UnregisterDebuggable(this); 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"); while (!DownloadQueue::IsLoaded()) { usleep(20 * 1000); } if (g_pOptions->GetServerMode() && g_pOptions->GetSaveQueue() && g_pOptions->GetReloadQueue()) { m_mutexDownloads.Lock(); 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() || 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() { info(" ---------- FeedCoordinator"); m_mutexDownloads.Lock(); info(" 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 %s", 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]; NZBInfo::MakeNiceUrlName(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()) { FeedScriptController::ExecuteScripts( !Util::EmptyStr(pFeedInfo->GetFeedScript()) ? pFeedInfo->GetFeedScript(): g_pOptions->GetFeedScript(), pFeedInfo->GetOutputFilename(), pFeedInfo->GetID()); FeedFile* pFeedFile = FeedFile::Create(pFeedInfo->GetOutputFilename()); remove(pFeedInfo->GetOutputFilename()); NZBList addedNZBs; m_mutexDownloads.Lock(); if (pFeedFile) { ProcessFeed(pFeedInfo, pFeedFile->GetFeedItemInfos(), &addedNZBs); delete pFeedFile; } pFeedInfo->SetLastUpdate(time(NULL)); pFeedInfo->SetForce(false); m_bSave = true; m_mutexDownloads.Unlock(); DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); for (NZBList::iterator it = addedNZBs.begin(); it != addedNZBs.end(); it++) { NZBInfo* pNZBInfo = *it; pDownloadQueue->GetQueue()->Add(pNZBInfo, false); } pDownloadQueue->Save(); DownloadQueue::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->SetFeedFilterHelper(&m_FilterHelper); pFeedItemInfo->BuildDupeKey(NULL, NULL); if (pFeedFilter) { pFeedFilter->Match(pFeedItemInfo); } } delete pFeedFilter; } void FeedCoordinator::ProcessFeed(FeedInfo* pFeedInfo, FeedItemInfos* pFeedItemInfos, NZBList* pAddedNZBs) { 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 && pFeedInfo->GetBacklog()) { eStatus = FeedHistoryInfo::hsBacklog; } else if (!pFeedHistoryInfo) { NZBInfo* pNZBInfo = CreateNZBInfo(pFeedInfo, pFeedItemInfo); pAddedNZBs->Add(pNZBInfo, false); 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()); } } NZBInfo* FeedCoordinator::CreateNZBInfo(FeedInfo* pFeedInfo, FeedItemInfo* pFeedItemInfo) { debug("Download %s from %s", pFeedItemInfo->GetUrl(), pFeedInfo->GetName()); NZBInfo* pNZBInfo = new NZBInfo(); pNZBInfo->SetKind(NZBInfo::nkUrl); pNZBInfo->SetFeedID(pFeedInfo->GetID()); pNZBInfo->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) { pNZBInfo->SetFilename(szNZBName2); } pNZBInfo->SetCategory(pFeedItemInfo->GetAddCategory()); pNZBInfo->SetPriority(pFeedItemInfo->GetPriority()); pNZBInfo->SetAddUrlPaused(pFeedItemInfo->GetPauseNzb()); pNZBInfo->SetDupeKey(pFeedItemInfo->GetDupeKey()); pNZBInfo->SetDupeScore(pFeedItemInfo->GetDupeScore()); pNZBInfo->SetDupeMode(pFeedItemInfo->GetDupeMode()); return pNZBInfo; } 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->GetID(), pFeedInfo->GetName(), pFeedInfo->GetUrl(), pFeedInfo->GetFilter(), pFeedInfo->GetBacklog(), pFeedInfo->GetPauseNzb(), pFeedInfo->GetCategory(), pFeedInfo->GetPriority(), pFeedInfo->GetInterval(), pFeedInfo->GetFeedScript(), 0, NULL, ppFeedItemInfos); } bool FeedCoordinator::PreviewFeed(int iID, const char* szName, const char* szUrl, const char* szFilter, bool bBacklog, bool bPauseNzb, const char* szCategory, int iPriority, int iInterval, const char* szFeedScript, int iCacheTimeSec, const char* szCacheId, FeedItemInfos** ppFeedItemInfos) { debug("Preview feed %s", szName); FeedInfo* pFeedInfo = new FeedInfo(iID, szName, szUrl, bBacklog, iInterval, szFilter, bPauseNzb, szCategory, iPriority, szFeedScript); 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) { FeedScriptController::ExecuteScripts( !Util::EmptyStr(pFeedInfo->GetFeedScript()) ? pFeedInfo->GetFeedScript(): g_pOptions->GetFeedScript(), pFeedInfo->GetOutputFilename(), pFeedInfo->GetID()); 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 && pFeedInfo->GetBacklog() ? 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::DownloadQueueUpdate(Subject* pCaller, void* pAspect) { debug("Notification from URL-Coordinator received"); DownloadQueue::Aspect* pQueueAspect = (DownloadQueue::Aspect*)pAspect; if (pQueueAspect->eAction == DownloadQueue::eaUrlCompleted) { m_mutexDownloads.Lock(); FeedHistoryInfo* pFeedHistoryInfo = m_FeedHistory.Find(pQueueAspect->pNZBInfo->GetURL()); if (pFeedHistoryInfo) { pFeedHistoryInfo->SetStatus(FeedHistoryInfo::hsFetched); } else { m_FeedHistory.Add(pQueueAspect->pNZBInfo->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() * 60*60*24; 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(); } nzbget-16.4/daemon/feed/FeedFile.cpp0000644000175000017500000003407712630544544017110 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2013-2015 Andrey Prygunkov * * 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 #include #ifdef WIN32 #include #import named_guids using namespace MSXML; #else #include #include #include #include #endif #include "nzbget.h" #include "FeedFile.h" #include "Log.h" #include "DownloadInfo.h" #include "Options.h" #include "Util.h" 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() { info(" 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; // Debian 6 tag = node->selectSingleNode("title"); if (!tag) { // bad rss feed return false; } _bstr_t title(tag->Gettext()); pFeedItemInfo->SetTitle(title); ParseSubject(pFeedItemInfo); // Wed, 26 Jun 2013 00:02:54 -0600 tag = node->selectSingleNode("pubDate"); if (tag) { _bstr_t time(tag->Gettext()); time_t unixtime = WebUtil::ParseRfc822DateTime(time); if (unixtime > 0) { pFeedItemInfo->SetTime(unixtime); } } // Movies > HD tag = node->selectSingleNode("category"); if (tag) { _bstr_t category(tag->Gettext()); pFeedItemInfo->SetCategory(category); } // long text tag = node->selectSingleNode("description"); if (tag) { _bstr_t description(tag->Gettext()); // cleanup CDATA char* szDescription = strdup((const char*)description); WebUtil::XmlStripTags(szDescription); WebUtil::XmlDecode(szDescription); WebUtil::XmlRemoveEntities(szDescription); pFeedItemInfo->SetDescription(szDescription); free(szDescription); } // 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()) { // https://nzb.org/fetch/334534ce/4364564564 tag = node->selectSingleNode("link"); if (!tag) { // bad rss feed return false; } _bstr_t link(tag->Gettext()); pFeedItemInfo->SetUrl(link); } // newznab special // 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); } } } // 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); } } // 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); } } // // tag = node->selectSingleNode("newznab:attr[@name='episode']"); if (tag) { attr = tag->Getattributes()->getNamedItem("value"); if (attr) { _bstr_t val(attr->Gettext()); pFeedItemInfo->SetEpisode(val); } } // // 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(SAX_StartElement); SAX_handler.endElement = reinterpret_cast(SAX_EndElement); SAX_handler.characters = reinterpret_cast(SAX_characters); SAX_handler.error = reinterpret_cast(SAX_error); SAX_handler.getEntity = reinterpret_cast(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) { // 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]); // if (m_pFeedItemInfo->GetSize() == 0 && !strcmp("size", atts[1])) { long long lSize = atoll(atts[3]); m_pFeedItemInfo->SetSize(lSize); } // else if (!strcmp("imdb", atts[1])) { m_pFeedItemInfo->SetImdbId(atoi(atts[3])); } // else if (!strcmp("rageid", atts[1])) { m_pFeedItemInfo->SetRageId(atoi(atts[3])); } // // else if (!strcmp("episode", atts[1])) { m_pFeedItemInfo->SetEpisode(atts[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) { // cleanup CDATA char* szDescription = strdup(m_szTagContent); WebUtil::XmlStripTags(szDescription); WebUtil::XmlDecode(szDescription); WebUtil::XmlRemoveEntities(szDescription); m_pFeedItemInfo->SetDescription(szDescription); free(szDescription); 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 nzbget-16.4/daemon/feed/FeedFile.h0000644000175000017500000000416512630544544016550 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2013 Andrey Prygunkov * * 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 #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 nzbget-16.4/daemon/feed/FeedFilter.cpp0000644000175000017500000006161012630544544017447 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2013-2014 Andrey Prygunkov * * 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 #include #include #include #include "nzbget.h" #include "Log.h" #include "DownloadInfo.h" #include "Util.h" #include "FeedFilter.h" FeedFilter::Term::Term() { m_szField = NULL; m_szParam = NULL; m_bFloat = false; m_iIntParam = 0; m_fFloatParam = 0.0; m_pRegEx = NULL; m_pRefValues = NULL; } FeedFilter::Term::~Term() { free(m_szField); free(m_szParam); delete m_pRegEx; } bool FeedFilter::Term::Match(FeedItemInfo* pFeedItemInfo) { const char* szStrValue = NULL; long long iIntValue = 0; if (!GetFieldData(m_szField, pFeedItemInfo, &szStrValue, &iIntValue)) { return false; } bool bMatch = MatchValue(szStrValue, iIntValue); if (m_bPositive != bMatch) { return false; } return true; } bool FeedFilter::Term::MatchValue(const char* szStrValue, long long iIntValue) { double fFloatValue = (double)iIntValue; char szIntBuf[100]; if (m_eCommand < fcEqual && !szStrValue) { snprintf(szIntBuf, 100, "%lld", iIntValue); szIntBuf[100-1] = '\0'; szStrValue = szIntBuf; } else if (m_eCommand >= fcEqual && szStrValue) { fFloatValue = atof(szStrValue); iIntValue = (long long)fFloatValue; } switch (m_eCommand) { case fcText: return MatchText(szStrValue); case fcRegex: return MatchRegex(szStrValue); case fcEqual: return m_bFloat ? fFloatValue == m_fFloatParam : iIntValue == m_iIntParam; case fcLess: return m_bFloat ? fFloatValue < m_fFloatParam : iIntValue < m_iIntParam; case fcLessEqual: return m_bFloat ? fFloatValue <= m_fFloatParam : iIntValue <= m_iIntParam; case fcGreater: return m_bFloat ? fFloatValue > m_fFloatParam : iIntValue > m_iIntParam; case fcGreaterEqual: return m_bFloat ? fFloatValue >= m_fFloatParam : iIntValue >= m_iIntParam; default: return false; } } bool FeedFilter::Term::MatchText(const char* szStrValue) { const char* WORD_SEPARATORS = " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; // first check if we should make word-search or substring-search int iParamLen = strlen(m_szParam); bool bSubstr = iParamLen >= 2 && m_szParam[0] == '*' && m_szParam[iParamLen-1] == '*'; if (!bSubstr) { for (const char* p = m_szParam; *p; p++) { char ch = *p; if (strchr(WORD_SEPARATORS, ch) && ch != '*' && ch != '?' && ch != '#') { bSubstr = true; break; } } } bool bMatch = false; if (!bSubstr) { // Word-search // split szStrValue into tokens Tokenizer tok(szStrValue, WORD_SEPARATORS); while (const char* szWord = tok.Next()) { WildMask mask(m_szParam, m_pRefValues != NULL); bMatch = mask.Match(szWord); if (bMatch) { FillWildMaskRefValues(szWord, &mask, 0); break; } } } else { // Substring-search int iRefOffset = 1; const char* szFormat = "*%s*"; if (iParamLen >= 2 && m_szParam[0] == '*' && m_szParam[iParamLen-1] == '*') { szFormat = "%s"; iRefOffset = 0; } else if (iParamLen >= 1 && m_szParam[0] == '*') { szFormat = "%s*"; iRefOffset = 0; } else if (iParamLen >= 1 && m_szParam[iParamLen-1] == '*') { szFormat = "*%s"; } int iMaskLen = strlen(m_szParam) + 2 + 1; char* szMask = (char*)malloc(iMaskLen); snprintf(szMask, iMaskLen, szFormat, m_szParam); szMask[iMaskLen-1] = '\0'; WildMask mask(szMask, m_pRefValues != NULL); bMatch = mask.Match(szStrValue); if (bMatch) { FillWildMaskRefValues(szStrValue, &mask, iRefOffset); } free(szMask); } return bMatch; } bool FeedFilter::Term::MatchRegex(const char* szStrValue) { if (!m_pRegEx) { m_pRegEx = new RegEx(m_szParam, m_pRefValues == NULL ? 0 : 100); } bool bFound = m_pRegEx->Match(szStrValue); if (bFound) { FillRegExRefValues(szStrValue, m_pRegEx); } return bFound; } bool FeedFilter::Term::Compile(char* szToken) { debug("Token: %s", szToken); char ch = szToken[0]; m_bPositive = ch != '-'; if (ch == '-' || ch == '+') { szToken++; ch = szToken[0]; } char ch2= szToken[1]; if ((ch == '(' || ch == ')' || ch == '|') && (ch2 == ' ' || ch2 == '\0')) { switch (ch) { case '(': m_eCommand = fcOpeningBrace; return true; case ')': m_eCommand = fcClosingBrace; return true; case '|': m_eCommand = fcOrOperator; return true; } } char *szField = NULL; m_eCommand = fcText; char* szColon = NULL; if (ch != '@' && ch != '$' && ch != '<' && ch != '>' && ch != '=') { szColon = strchr(szToken, ':'); } if (szColon) { szField = szToken; szColon[0] = '\0'; szToken = szColon + 1; ch = szToken[0]; } if (ch == '\0') { return false; } ch2= szToken[1]; if (ch == '@') { m_eCommand = fcText; szToken++; } else if (ch == '$') { m_eCommand = fcRegex; szToken++; } else if (ch == '=') { m_eCommand = fcEqual; szToken++; } else if (ch == '<' && ch2 == '=') { m_eCommand = fcLessEqual; szToken += 2; } else if (ch == '>' && ch2 == '=') { m_eCommand = fcGreaterEqual; szToken += 2; } else if (ch == '<') { m_eCommand = fcLess; szToken++; } else if (ch == '>') { m_eCommand = fcGreater; szToken++; } debug("%s, Field: %s, Command: %i, Param: %s", (m_bPositive ? "Positive" : "Negative"), szField, m_eCommand, szToken); const char* szStrValue; long long iIntValue; if (!GetFieldData(szField, NULL, &szStrValue, &iIntValue)) { return false; } if (szField && !ParseParam(szField, szToken)) { return false; } m_szField = szField ? strdup(szField) : NULL; m_szParam = strdup(szToken); return true; } /* * If pFeedItemInfo is NULL, only field name is validated */ bool FeedFilter::Term::GetFieldData(const char* szField, FeedItemInfo* pFeedItemInfo, const char** StrValue, long long* IntValue) { *StrValue = NULL; *IntValue = 0; if (!szField || !strcasecmp(szField, "title")) { *StrValue = pFeedItemInfo ? pFeedItemInfo->GetTitle() : NULL; return true; } else if (!strcasecmp(szField, "filename")) { *StrValue = pFeedItemInfo ? pFeedItemInfo->GetFilename() : NULL; return true; } else if (!strcasecmp(szField, "category")) { *StrValue = pFeedItemInfo ? pFeedItemInfo->GetCategory() : NULL; return true; } else if (!strcasecmp(szField, "link") || !strcasecmp(szField, "url")) { *StrValue = pFeedItemInfo ? pFeedItemInfo->GetUrl() : NULL; return true; } else if (!strcasecmp(szField, "size")) { *IntValue = pFeedItemInfo ? pFeedItemInfo->GetSize() : 0; return true; } else if (!strcasecmp(szField, "age")) { *IntValue = pFeedItemInfo ? time(NULL) - pFeedItemInfo->GetTime() : 0; return true; } else if (!strcasecmp(szField, "imdbid")) { *IntValue = pFeedItemInfo ? pFeedItemInfo->GetImdbId() : 0; return true; } else if (!strcasecmp(szField, "rageid")) { *IntValue = pFeedItemInfo ? pFeedItemInfo->GetRageId() : 0; return true; } else if (!strcasecmp(szField, "description")) { *StrValue = pFeedItemInfo ? pFeedItemInfo->GetDescription() : NULL; return true; } else if (!strcasecmp(szField, "season")) { *IntValue = pFeedItemInfo ? pFeedItemInfo->GetSeasonNum() : 0; return true; } else if (!strcasecmp(szField, "episode")) { *IntValue = pFeedItemInfo ? pFeedItemInfo->GetEpisodeNum() : 0; return true; } else if (!strcasecmp(szField, "priority")) { *IntValue = pFeedItemInfo ? pFeedItemInfo->GetPriority() : 0; return true; } else if (!strcasecmp(szField, "dupekey")) { *StrValue = pFeedItemInfo ? pFeedItemInfo->GetDupeKey() : NULL; return true; } else if (!strcasecmp(szField, "dupescore")) { *IntValue = pFeedItemInfo ? pFeedItemInfo->GetDupeScore() : 0; return true; } else if (!strcasecmp(szField, "dupestatus")) { *StrValue = pFeedItemInfo ? pFeedItemInfo->GetDupeStatus() : NULL; return true; } else if (!strncasecmp(szField, "attr-", 5)) { if (pFeedItemInfo) { FeedItemInfo::Attr* pAttr = pFeedItemInfo->GetAttributes()->Find(szField + 5); *StrValue = pAttr ? pAttr->GetValue() : NULL; } return true; } return false; } bool FeedFilter::Term::ParseParam(const char* szField, const char* szParam) { if (!strcasecmp(szField, "size")) { return ParseSizeParam(szParam); } else if (!strcasecmp(szField, "age")) { return ParseAgeParam(szParam); } else if (m_eCommand >= fcEqual) { return ParseNumericParam(szParam); } return true; } bool FeedFilter::Term::ParseSizeParam(const char* szParam) { double fParam = atof(szParam); const char* p; for (p = szParam; *p && ((*p >= '0' && *p <='9') || *p == '.'); p++) ; if (*p) { if (!strcasecmp(p, "K") || !strcasecmp(p, "KB")) { m_iIntParam = (long long)(fParam*1024); } else if (!strcasecmp(p, "M") || !strcasecmp(p, "MB")) { m_iIntParam = (long long)(fParam*1024*1024); } else if (!strcasecmp(p, "G") || !strcasecmp(p, "GB")) { m_iIntParam = (long long)(fParam*1024*1024*1024); } else { return false; } } else { m_iIntParam = (long long)fParam; } return true; } bool FeedFilter::Term::ParseAgeParam(const char* szParam) { double fParam = atof(szParam); const char* p; for (p = szParam; *p && ((*p >= '0' && *p <='9') || *p == '.'); p++) ; if (*p) { if (!strcasecmp(p, "m")) { // minutes m_iIntParam = (long long)(fParam*60); } else if (!strcasecmp(p, "h")) { // hours m_iIntParam = (long long)(fParam*60*60); } else if (!strcasecmp(p, "d")) { // days m_iIntParam = (long long)(fParam*60*60*24); } else { return false; } } else { // days by default m_iIntParam = (long long)(fParam*60*60*24); } return true; } bool FeedFilter::Term::ParseNumericParam(const char* szParam) { m_fFloatParam = atof(szParam); m_iIntParam = (long long)m_fFloatParam; m_bFloat = strchr(szParam, '.'); const char* p; for (p = szParam; *p && ((*p >= '0' && *p <='9') || *p == '.' || *p == '-') ; p++) ; if (*p) { return false; } return true; } void FeedFilter::Term::FillWildMaskRefValues(const char* szStrValue, WildMask* pMask, int iRefOffset) { if (!m_pRefValues) { return; } for (int i = iRefOffset; i < pMask->GetMatchCount(); i++) { int iLen = pMask->GetMatchLen(i); char* szValue = (char*)malloc(iLen + 1); strncpy(szValue, szStrValue + pMask->GetMatchStart(i), iLen); szValue[iLen] = '\0'; m_pRefValues->push_back(szValue); } } void FeedFilter::Term::FillRegExRefValues(const char* szStrValue, RegEx* pRegEx) { if (!m_pRefValues) { return; } for (int i = 1; i < pRegEx->GetMatchCount(); i++) { int iLen = pRegEx->GetMatchLen(i); char* szValue = (char*)malloc(iLen + 1); strncpy(szValue, szStrValue + pRegEx->GetMatchStart(i), iLen); szValue[iLen] = '\0'; m_pRefValues->push_back(szValue); } } FeedFilter::Rule::Rule() { m_eCommand = frAccept; m_bIsValid = false; m_szCategory = NULL; m_iPriority = 0; m_iAddPriority = 0; m_bPause = false; m_szDupeKey = NULL; m_szAddDupeKey = NULL; m_iDupeScore = 0; m_iAddDupeScore = 0; m_eDupeMode = dmScore; m_szRageId = NULL; m_szSeries = NULL; m_bHasCategory = false; m_bHasPriority = false; m_bHasAddPriority = false; m_bHasPause = false; m_bHasDupeScore = false; m_bHasAddDupeScore = false; m_bHasDupeKey = false; m_bHasAddDupeKey = false; m_bHasDupeMode = false; m_bHasRageId = false; m_bHasSeries = false; m_bPatCategory = false; m_bPatDupeKey = false; m_bPatAddDupeKey = false; m_szPatCategory = NULL; m_szPatDupeKey = NULL; m_szPatAddDupeKey = NULL; } FeedFilter::Rule::~Rule() { free(m_szCategory); free(m_szDupeKey); free(m_szAddDupeKey); free(m_szRageId); free(m_szSeries); free(m_szPatCategory); free(m_szPatDupeKey); free(m_szPatAddDupeKey); for (TermList::iterator it = m_Terms.begin(); it != m_Terms.end(); it++) { delete *it; } for (RefValues::iterator it = m_RefValues.begin(); it != m_RefValues.end(); it++) { delete *it; } } void FeedFilter::Rule::Compile(char* szRule) { debug("Compiling rule: %s", szRule); m_bIsValid = true; char* szFilter3 = Util::Trim(szRule); char* szTerm = CompileCommand(szFilter3); if (!szTerm) { m_bIsValid = false; return; } if (m_eCommand == frComment) { return; } szTerm = Util::Trim(szTerm); for (char* p = szTerm; *p && m_bIsValid; p++) { char ch = *p; if (ch == ' ') { *p = '\0'; m_bIsValid = CompileTerm(szTerm); szTerm = p + 1; while (*szTerm == ' ') szTerm++; p = szTerm; } } m_bIsValid = m_bIsValid && CompileTerm(szTerm); if (m_bIsValid && m_bPatCategory) { m_szPatCategory = m_szCategory; m_szCategory = NULL; } if (m_bIsValid && m_bPatDupeKey) { m_szPatDupeKey = m_szDupeKey; m_szDupeKey = NULL; } if (m_bIsValid && m_bPatAddDupeKey) { m_szPatAddDupeKey = m_szAddDupeKey; m_szAddDupeKey = NULL; } } /* Checks if the rule starts with command and compiles it. * Returns a pointer to the next (first) term or NULL in a case of compilation error. */ char* FeedFilter::Rule::CompileCommand(char* szRule) { if (!strncasecmp(szRule, "A:", 2) || !strncasecmp(szRule, "Accept:", 7) || !strncasecmp(szRule, "A(", 2) || !strncasecmp(szRule, "Accept(", 7)) { m_eCommand = frAccept; szRule += szRule[1] == ':' || szRule[1] == '(' ? 2 : 7; } else if (!strncasecmp(szRule, "O(", 2) || !strncasecmp(szRule, "Options(", 8)) { m_eCommand = frOptions; szRule += szRule[1] == ':' || szRule[1] == '(' ? 2 : 8; } else if (!strncasecmp(szRule, "R:", 2) || !strncasecmp(szRule, "Reject:", 7)) { m_eCommand = frReject; szRule += szRule[1] == ':' || szRule[1] == '(' ? 2 : 7; } else if (!strncasecmp(szRule, "Q:", 2) || !strncasecmp(szRule, "Require:", 8)) { m_eCommand = frRequire; szRule += szRule[1] == ':' || szRule[1] == '(' ? 2 : 8; } else if (*szRule == '#') { m_eCommand = frComment; return szRule; } else { // not a command return szRule; } if ((m_eCommand == frAccept || m_eCommand == frOptions) && szRule[-1] == '(') { szRule = CompileOptions(szRule); } return szRule; } char* FeedFilter::Rule::CompileOptions(char* szRule) { char* p = strchr(szRule, ')'); if (!p) { // error return NULL; } // split command into tokens *p = '\0'; Tokenizer tok(szRule, ",", true); while (char* szOption = tok.Next()) { const char* szValue = ""; char* szColon = strchr(szOption, ':'); if (szColon) { *szColon = '\0'; szValue = Util::Trim(szColon + 1); } if (!strcasecmp(szOption, "category") || !strcasecmp(szOption, "cat") || !strcasecmp(szOption, "c")) { m_bHasCategory = true; free(m_szCategory); m_szCategory = strdup(szValue); m_bPatCategory = strstr(szValue, "${"); } else if (!strcasecmp(szOption, "pause") || !strcasecmp(szOption, "p")) { m_bHasPause = true; m_bPause = !*szValue || !strcasecmp(szValue, "yes") || !strcasecmp(szValue, "y"); if (!m_bPause && !(!strcasecmp(szValue, "no") || !strcasecmp(szValue, "n"))) { // error return NULL; } } else if (!strcasecmp(szOption, "priority") || !strcasecmp(szOption, "pr") || !strcasecmp(szOption, "r")) { if (!strchr("0123456789-+", *szValue)) { // error return NULL; } m_bHasPriority = true; m_iPriority = atoi(szValue); } else if (!strcasecmp(szOption, "priority+") || !strcasecmp(szOption, "pr+") || !strcasecmp(szOption, "r+")) { if (!strchr("0123456789-+", *szValue)) { // error return NULL; } m_bHasAddPriority = true; m_iAddPriority = atoi(szValue); } else if (!strcasecmp(szOption, "dupescore") || !strcasecmp(szOption, "ds") || !strcasecmp(szOption, "s")) { if (!strchr("0123456789-+", *szValue)) { // error return NULL; } m_bHasDupeScore = true; m_iDupeScore = atoi(szValue); } else if (!strcasecmp(szOption, "dupescore+") || !strcasecmp(szOption, "ds+") || !strcasecmp(szOption, "s+")) { if (!strchr("0123456789-+", *szValue)) { // error return NULL; } m_bHasAddDupeScore = true; m_iAddDupeScore = atoi(szValue); } else if (!strcasecmp(szOption, "dupekey") || !strcasecmp(szOption, "dk") || !strcasecmp(szOption, "k")) { m_bHasDupeKey = true; free(m_szDupeKey); m_szDupeKey = strdup(szValue); m_bPatDupeKey = strstr(szValue, "${"); } else if (!strcasecmp(szOption, "dupekey+") || !strcasecmp(szOption, "dk+") || !strcasecmp(szOption, "k+")) { m_bHasAddDupeKey = true; free(m_szAddDupeKey); m_szAddDupeKey = strdup(szValue); m_bPatAddDupeKey = strstr(szValue, "${"); } else if (!strcasecmp(szOption, "dupemode") || !strcasecmp(szOption, "dm") || !strcasecmp(szOption, "m")) { m_bHasDupeMode = true; if (!strcasecmp(szValue, "score") || !strcasecmp(szValue, "s")) { m_eDupeMode = dmScore; } else if (!strcasecmp(szValue, "all") || !strcasecmp(szValue, "a")) { m_eDupeMode = dmAll; } else if (!strcasecmp(szValue, "force") || !strcasecmp(szValue, "f")) { m_eDupeMode = dmForce; } else { // error return NULL; } } else if (!strcasecmp(szOption, "rageid")) { m_bHasRageId = true; free(m_szRageId); m_szRageId = strdup(szValue); } else if (!strcasecmp(szOption, "series")) { m_bHasSeries = true; free(m_szSeries); m_szSeries = strdup(szValue); } // for compatibility with older version we support old commands too else if (!strcasecmp(szOption, "paused") || !strcasecmp(szOption, "unpaused")) { m_bHasPause = true; m_bPause = !strcasecmp(szOption, "paused"); } else if (strchr("0123456789-+", *szOption)) { m_bHasPriority = true; m_iPriority = atoi(szOption); } else { m_bHasCategory = true; free(m_szCategory); m_szCategory = strdup(szOption); } } szRule = p + 1; if (*szRule == ':') { szRule++; } return szRule; } bool FeedFilter::Rule::CompileTerm(char* szTerm) { Term* pTerm = new Term(); pTerm->SetRefValues(m_bPatCategory || m_bPatDupeKey || m_bPatAddDupeKey ? &m_RefValues : NULL); if (pTerm->Compile(szTerm)) { m_Terms.push_back(pTerm); return true; } else { delete pTerm; return false; } } bool FeedFilter::Rule::Match(FeedItemInfo* pFeedItemInfo) { for (RefValues::iterator it = m_RefValues.begin(); it != m_RefValues.end(); it++) { delete *it; } m_RefValues.clear(); if (!MatchExpression(pFeedItemInfo)) { return false; } if (m_bPatCategory) { ExpandRefValues(pFeedItemInfo, &m_szCategory, m_szPatCategory); } if (m_bPatDupeKey) { ExpandRefValues(pFeedItemInfo, &m_szDupeKey, m_szPatDupeKey); } if (m_bPatAddDupeKey) { ExpandRefValues(pFeedItemInfo, &m_szAddDupeKey, m_szPatAddDupeKey); } return true; } bool FeedFilter::Rule::MatchExpression(FeedItemInfo* pFeedItemInfo) { char* expr = (char*)malloc(m_Terms.size() + 1); int index = 0; for (TermList::iterator it = m_Terms.begin(); it != m_Terms.end(); it++, index++) { Term* pTerm = *it; switch (pTerm->GetCommand()) { case fcOpeningBrace: expr[index] = '('; break; case fcClosingBrace: expr[index] = ')'; break; case fcOrOperator: expr[index] = '|'; break; default: expr[index] = pTerm->Match(pFeedItemInfo) ? 'T' : 'F'; break; } } expr[index] = '\0'; // reduce result tree to one element (may be longer if expression has syntax errors) for (int iOldLen = 0, iNewLen = strlen(expr); iNewLen != iOldLen; iOldLen = iNewLen, iNewLen = strlen(expr)) { // NOTE: there are no operator priorities. // the order of operators "OR" and "AND" is not defined, they can be checked in any order. // "OR" and "AND" should not be mixed in one group; instead braces should be used to define priorities. Util::ReduceStr(expr, "TT", "T"); Util::ReduceStr(expr, "TF", "F"); Util::ReduceStr(expr, "FT", "F"); Util::ReduceStr(expr, "FF", "F"); Util::ReduceStr(expr, "||", "|"); Util::ReduceStr(expr, "(|", "("); Util::ReduceStr(expr, "|)", ")"); Util::ReduceStr(expr, "T|T", "T"); Util::ReduceStr(expr, "T|F", "T"); Util::ReduceStr(expr, "F|T", "T"); Util::ReduceStr(expr, "F|F", "F"); Util::ReduceStr(expr, "(T)", "T"); Util::ReduceStr(expr, "(F)", "F"); } bool bMatch = *expr && *expr == 'T' && expr[1] == '\0'; free(expr); return bMatch; } void FeedFilter::Rule::ExpandRefValues(FeedItemInfo* pFeedItemInfo, char** pDestStr, char* pPatStr) { free(*pDestStr); *pDestStr = strdup(pPatStr); char* curvalue = *pDestStr; int iAttempts = 0; while (char* dollar = strstr(curvalue, "${")) { iAttempts++; if (iAttempts > 100) { break; // error } char* end = strchr(dollar, '}'); if (!end) { break; // error } int varlen = (int)(end - dollar - 2); char variable[101]; int maxlen = varlen < 100 ? varlen : 100; strncpy(variable, dollar + 2, maxlen); variable[maxlen] = '\0'; const char* varvalue = GetRefValue(pFeedItemInfo, variable); if (!varvalue) { break; // error } int newlen = strlen(varvalue); char* newvalue = (char*)malloc(strlen(curvalue) - varlen - 3 + newlen + 1); strncpy(newvalue, curvalue, dollar - curvalue); strncpy(newvalue + (dollar - curvalue), varvalue, newlen); strcpy(newvalue + (dollar - curvalue) + newlen, end + 1); free(curvalue); curvalue = newvalue; *pDestStr = curvalue; } } const char* FeedFilter::Rule::GetRefValue(FeedItemInfo* pFeedItemInfo, const char* szVarName) { if (!strcasecmp(szVarName, "season")) { pFeedItemInfo->GetSeasonNum(); // needed to parse title return pFeedItemInfo->GetSeason() ? pFeedItemInfo->GetSeason() : ""; } else if (!strcasecmp(szVarName, "episode")) { pFeedItemInfo->GetEpisodeNum(); // needed to parse title return pFeedItemInfo->GetEpisode() ? pFeedItemInfo->GetEpisode() : ""; } int iIndex = atoi(szVarName) - 1; if (iIndex >= 0 && iIndex < (int)m_RefValues.size()) { return m_RefValues[iIndex]; } return NULL; } FeedFilter::FeedFilter(const char* szFilter) { Compile(szFilter); } FeedFilter::~FeedFilter() { for (RuleList::iterator it = m_Rules.begin(); it != m_Rules.end(); it++) { delete *it; } } void FeedFilter::Compile(const char* szFilter) { debug("Compiling filter: %s", szFilter); char* szFilter2 = strdup(szFilter); char* szRule = szFilter2; for (char* p = szRule; *p; p++) { char ch = *p; if (ch == '%') { *p = '\0'; CompileRule(szRule); szRule = p + 1; } } CompileRule(szRule); free(szFilter2); } void FeedFilter::CompileRule(char* szRule) { Rule* pRule = new Rule(); m_Rules.push_back(pRule); pRule->Compile(szRule); } void FeedFilter::Match(FeedItemInfo* pFeedItemInfo) { int index = 0; for (RuleList::iterator it = m_Rules.begin(); it != m_Rules.end(); it++) { Rule* pRule = *it; index++; if (pRule->IsValid()) { bool bMatch = pRule->Match(pFeedItemInfo); switch (pRule->GetCommand()) { case frAccept: case frOptions: if (bMatch) { pFeedItemInfo->SetMatchStatus(FeedItemInfo::msAccepted); pFeedItemInfo->SetMatchRule(index); ApplyOptions(pRule, pFeedItemInfo); if (pRule->GetCommand() == frAccept) { return; } } break; case frReject: if (bMatch) { pFeedItemInfo->SetMatchStatus(FeedItemInfo::msRejected); pFeedItemInfo->SetMatchRule(index); return; } break; case frRequire: if (!bMatch) { pFeedItemInfo->SetMatchStatus(FeedItemInfo::msRejected); pFeedItemInfo->SetMatchRule(index); return; } break; case frComment: break; } } } pFeedItemInfo->SetMatchStatus(FeedItemInfo::msIgnored); pFeedItemInfo->SetMatchRule(0); } void FeedFilter::ApplyOptions(Rule* pRule, FeedItemInfo* pFeedItemInfo) { if (pRule->HasPause()) { pFeedItemInfo->SetPauseNzb(pRule->GetPause()); } if (pRule->HasCategory()) { pFeedItemInfo->SetAddCategory(pRule->GetCategory()); } if (pRule->HasPriority()) { pFeedItemInfo->SetPriority(pRule->GetPriority()); } if (pRule->HasAddPriority()) { pFeedItemInfo->SetPriority(pFeedItemInfo->GetPriority() + pRule->GetAddPriority()); } if (pRule->HasDupeScore()) { pFeedItemInfo->SetDupeScore(pRule->GetDupeScore()); } if (pRule->HasAddDupeScore()) { pFeedItemInfo->SetDupeScore(pFeedItemInfo->GetDupeScore() + pRule->GetAddDupeScore()); } if (pRule->HasRageId() || pRule->HasSeries()) { pFeedItemInfo->BuildDupeKey(pRule->GetRageId(), pRule->GetSeries()); } if (pRule->HasDupeKey()) { pFeedItemInfo->SetDupeKey(pRule->GetDupeKey()); } if (pRule->HasAddDupeKey()) { pFeedItemInfo->AppendDupeKey(pRule->GetAddDupeKey()); } if (pRule->HasDupeMode()) { pFeedItemInfo->SetDupeMode(pRule->GetDupeMode()); } } nzbget-16.4/daemon/feed/FeedInfo.cpp0000644000175000017500000002235612630544544017121 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2013-2015 Andrey Prygunkov * * 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 #include #include #include #include #include "nzbget.h" #include "FeedInfo.h" #include "Util.h" FeedInfo::FeedInfo(int iID, const char* szName, const char* szUrl, bool bBacklog, int iInterval, const char* szFilter, bool bPauseNzb, const char* szCategory, int iPriority, const char* szFeedScript) { m_iID = iID; m_szName = strdup(szName ? szName : ""); m_szUrl = strdup(szUrl ? szUrl : ""); m_szFilter = strdup(szFilter ? szFilter : ""); m_bBacklog = bBacklog; m_iFilterHash = Util::HashBJ96(m_szFilter, strlen(m_szFilter), 0); m_szCategory = strdup(szCategory ? szCategory : ""); m_iInterval = iInterval; m_szFeedScript = strdup(szFeedScript ? szFeedScript : ""); m_bPauseNzb = bPauseNzb; m_iPriority = iPriority; m_tLastUpdate = 0; m_bPreview = false; m_eStatus = fsUndefined; m_szOutputFilename = NULL; m_bFetch = false; m_bForce = false; } FeedInfo::~FeedInfo() { free(m_szName); free(m_szUrl); free(m_szFilter); free(m_szCategory); free(m_szOutputFilename); free(m_szFeedScript); } void FeedInfo::SetOutputFilename(const char* szOutputFilename) { free(m_szOutputFilename); m_szOutputFilename = strdup(szOutputFilename); } FeedItemInfo::Attr::Attr(const char* szName, const char* szValue) { m_szName = strdup(szName ? szName : ""); m_szValue = strdup(szValue ? szValue : ""); } FeedItemInfo::Attr::~Attr() { free(m_szName); free(m_szValue); } FeedItemInfo::Attributes::~Attributes() { for (iterator it = begin(); it != end(); it++) { 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_pFeedFilterHelper = NULL; m_szTitle = NULL; m_szFilename = NULL; m_szUrl = NULL; m_szCategory = strdup(""); m_lSize = 0; m_tTime = 0; m_iImdbId = 0; m_iRageId = 0; m_szDescription = strdup(""); m_szSeason = NULL; m_szEpisode = NULL; m_iSeasonNum = 0; m_iEpisodeNum = 0; m_bSeasonEpisodeParsed = false; m_szAddCategory = strdup(""); m_bPauseNzb = false; m_iPriority = 0; m_eStatus = isUnknown; m_eMatchStatus = msIgnored; m_iMatchRule = 0; m_szDupeKey = NULL; m_iDupeScore = 0; m_eDupeMode = dmScore; m_szDupeStatus = NULL; } 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); free(m_szDupeStatus); } 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** ppRegEx = m_pFeedFilterHelper->GetSeasonEpisodeRegEx(); if (!*ppRegEx) { *ppRegEx = new RegEx("[^[:alnum:]]s?([0-9]+)[ex]([0-9]+(-?e[0-9]+)?)[^[:alnum:]]", 10); } if ((*ppRegEx)->Match(m_szTitle)) { char szRegValue[100]; char szValue[100]; snprintf(szValue, 100, "S%02d", atoi(m_szTitle + (*ppRegEx)->GetMatchStart(1))); szValue[100-1] = '\0'; SetSeason(szValue); int iLen = (*ppRegEx)->GetMatchLen(2); iLen = iLen < 99 ? iLen : 99; strncpy(szRegValue, m_szTitle + (*ppRegEx)->GetMatchStart(2), (*ppRegEx)->GetMatchLen(2)); szRegValue[iLen] = '\0'; snprintf(szValue, 100, "E%s", szRegValue); szValue[100-1] = '\0'; Util::ReduceStr(szValue, "-", ""); for (char* p = szValue; *p; p++) *p = toupper(*p); // convert string to uppercase e02 -> E02 SetEpisode(szValue); } } const char* FeedItemInfo::GetDupeStatus() { if (!m_szDupeStatus) { char szStatuses[200]; szStatuses[0] = '\0'; m_pFeedFilterHelper->CalcDupeStatus(m_szTitle, m_szDupeKey, szStatuses, sizeof(szStatuses)); m_szDupeStatus = strdup(szStatuses); } return m_szDupeStatus; } 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); } nzbget-16.4/daemon/feed/FeedCoordinator.h0000644000175000017500000001051712630544544020152 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2013-2015 Andrey Prygunkov * * 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 #include #include #include "Log.h" #include "Thread.h" #include "WebDownloader.h" #include "DownloadInfo.h" #include "FeedInfo.h" #include "Observer.h" #include "Util.h" class FeedDownloader; class FeedCoordinator : public Thread, public Observer, public Subject, public Debuggable { private: class DownloadQueueObserver: public Observer { public: FeedCoordinator* m_pOwner; virtual void Update(Subject* pCaller, void* pAspect) { m_pOwner->DownloadQueueUpdate(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; } }; class FilterHelper : public FeedFilterHelper { private: RegEx* m_pSeasonEpisodeRegEx; public: FilterHelper(); ~FilterHelper(); virtual RegEx** GetSeasonEpisodeRegEx() { return &m_pSeasonEpisodeRegEx; }; virtual void CalcDupeStatus(const char* szTitle, const char* szDupeKey, char* szStatusBuf, int iBufLen); }; typedef std::deque FeedCache; typedef std::list ActiveDownloads; private: Feeds m_Feeds; ActiveDownloads m_ActiveDownloads; FeedHistory m_FeedHistory; Mutex m_mutexDownloads; DownloadQueueObserver m_DownloadQueueObserver; bool m_bForce; bool m_bSave; FeedCache m_FeedCache; FilterHelper m_FilterHelper; void StartFeedDownload(FeedInfo* pFeedInfo, bool bForce); void FeedCompleted(FeedDownloader* pFeedDownloader); void FilterFeed(FeedInfo* pFeedInfo, FeedItemInfos* pFeedItemInfos); void ProcessFeed(FeedInfo* pFeedInfo, FeedItemInfos* pFeedItemInfos, NZBList* pAddedNZBs); NZBInfo* CreateNZBInfo(FeedInfo* pFeedInfo, FeedItemInfo* pFeedItemInfo); void ResetHangingDownloads(); void DownloadQueueUpdate(Subject* pCaller, void* pAspect); void CleanupHistory(); void CleanupCache(); void CheckSaveFeeds(); protected: virtual void LogDebugInfo(); public: FeedCoordinator(); virtual ~FeedCoordinator(); virtual void Run(); virtual void Stop(); void Update(Subject* pCaller, void* pAspect); void AddFeed(FeedInfo* pFeedInfo); bool PreviewFeed(int iID, const char* szName, const char* szUrl, const char* szFilter, bool bBacklog, bool bPauseNzb, const char* szCategory, int iPriority, int iInterval, const char* szFeedScript, int iCacheTimeSec, const char* szCacheId, FeedItemInfos** ppFeedItemInfos); bool ViewFeed(int iID, FeedItemInfos** ppFeedItemInfos); void FetchFeed(int iID); bool HasActiveDownloads(); Feeds* GetFeeds() { return &m_Feeds; } }; extern FeedCoordinator* g_pFeedCoordinator; class FeedDownloader : public WebDownloader { private: FeedInfo* m_pFeedInfo; public: void SetFeedInfo(FeedInfo* pFeedInfo) { m_pFeedInfo = pFeedInfo; } FeedInfo* GetFeedInfo() { return m_pFeedInfo; } }; #endif nzbget-16.4/daemon/queue/0000755000175000017500000000000012630544544015147 5ustar andreasandreasnzbget-16.4/daemon/queue/NZBFile.h0000644000175000017500000000512512630544544016554 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 NZBFILE_H #define NZBFILE_H #include #include "DownloadInfo.h" class NZBFile { public: typedef std::list TempFileList; private: NZBInfo* m_pNZBInfo; char* m_szFileName; char* m_szPassword; void AddArticle(FileInfo* pFileInfo, ArticleInfo* pArticleInfo); void AddFileInfo(FileInfo* pFileInfo); void ParseSubject(FileInfo* pFileInfo, bool TryQuotes); void BuildFilenames(); void ProcessFiles(); void CalcHashes(); bool HasDuplicateFilenames(); void ReadPassword(); #ifdef WIN32 bool ParseNZB(IUnknown* nzb); static void EncodeURL(const char* szFilename, char* szURL); #else FileInfo* m_pFileInfo; ArticleInfo* m_pArticle; 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); static void SAX_characters(NZBFile* pFile, const char * xmlstr, int len); static void* SAX_getEntity(NZBFile* pFile, const char * name); static void SAX_error(NZBFile* 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); #endif public: NZBFile(const char* szFileName, const char* szCategory); ~NZBFile(); bool Parse(); const char* GetFileName() const { return m_szFileName; } NZBInfo* GetNZBInfo() { return m_pNZBInfo; } const char* GetPassword() { return m_szPassword; } void DetachNZBInfo() { m_pNZBInfo = NULL; } void LogDebugInfo(); }; #endif nzbget-16.4/daemon/queue/HistoryCoordinator.cpp0000644000175000017500000005352512630544544021532 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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: 951 $ * $Date$ * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef WIN32 #include "win32.h" #endif #include #include #include #ifdef WIN32 #include #else #include #endif #include #include #include "nzbget.h" #include "HistoryCoordinator.h" #include "Options.h" #include "Log.h" #include "QueueCoordinator.h" #include "DiskState.h" #include "Util.h" #include "NZBFile.h" #include "DupeCoordinator.h" #include "ParParser.h" #include "PrePostProcessor.h" #include "DupeCoordinator.h" HistoryCoordinator::HistoryCoordinator() { debug("Creating HistoryCoordinator"); } HistoryCoordinator::~HistoryCoordinator() { debug("Destroying HistoryCoordinator"); } /** * Removes old entries from (recent) history */ void HistoryCoordinator::ServiceWork() { DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); time_t tMinTime = time(NULL) - g_pOptions->GetKeepHistory() * 60*60*24; 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->GetHistory()->rbegin(); it != pDownloadQueue->GetHistory()->rend(); ) { HistoryInfo* pHistoryInfo = *it; if (pHistoryInfo->GetKind() != HistoryInfo::hkDup && pHistoryInfo->GetTime() < tMinTime) { if (g_pOptions->GetDupeCheck() && pHistoryInfo->GetKind() == HistoryInfo::hkNzb) { // replace history element HistoryHide(pDownloadQueue, pHistoryInfo, index); index++; } else { char szNiceName[1024]; pHistoryInfo->GetName(szNiceName, 1024); pDownloadQueue->GetHistory()->erase(pDownloadQueue->GetHistory()->end() - 1 - index); if (pHistoryInfo->GetKind() == HistoryInfo::hkNzb) { DeleteDiskFiles(pHistoryInfo->GetNZBInfo()); } info("Collection %s removed from history", szNiceName); delete pHistoryInfo; } it = pDownloadQueue->GetHistory()->rbegin() + index; bChanged = true; } else { it++; index++; } } if (bChanged) { pDownloadQueue->Save(); } DownloadQueue::Unlock(); } void HistoryCoordinator::DeleteDiskFiles(NZBInfo* pNZBInfo) { if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { // delete parked files g_pDiskState->DiscardFiles(pNZBInfo); } pNZBInfo->GetFileList()->Clear(); // delete nzb-file if (!g_pOptions->GetNzbCleanupDisk()) { return; } // QueuedFile may contain one filename or several filenames separated // with "|"-character (for merged groups) char* szFilename = strdup(pNZBInfo->GetQueuedFilename()); char* szEnd = szFilename - 1; while (szEnd) { char* szName1 = szEnd + 1; szEnd = strchr(szName1, '|'); if (szEnd) *szEnd = '\0'; if (Util::FileExists(szName1)) { info("Deleting file %s", szName1); remove(szName1); } } free(szFilename); } void HistoryCoordinator::AddToHistory(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo) { //remove old item for the same NZB for (HistoryList::iterator it = pDownloadQueue->GetHistory()->begin(); it != pDownloadQueue->GetHistory()->end(); it++) { HistoryInfo* pHistoryInfo = *it; if (pHistoryInfo->GetNZBInfo() == pNZBInfo) { delete pHistoryInfo; pDownloadQueue->GetHistory()->erase(it); break; } } HistoryInfo* pHistoryInfo = new HistoryInfo(pNZBInfo); pHistoryInfo->SetTime(time(NULL)); pDownloadQueue->GetHistory()->push_front(pHistoryInfo); pDownloadQueue->GetQueue()->Remove(pNZBInfo); if (pNZBInfo->GetDeleteStatus() == NZBInfo::dsNone) { // park files and delete files marked for deletion int iParkedFiles = 0; for (FileList::iterator it = pNZBInfo->GetFileList()->begin(); it != pNZBInfo->GetFileList()->end(); ) { FileInfo* pFileInfo = *it; if (!pFileInfo->GetDeleted()) { detail("Parking file %s", pFileInfo->GetFilename()); g_pQueueCoordinator->DiscardDiskFile(pFileInfo); iParkedFiles++; it++; } else { // since we removed pNZBInfo from queue we need to take care of removing file infos marked for deletion pNZBInfo->GetFileList()->erase(it); delete pFileInfo; it = pNZBInfo->GetFileList()->begin() + iParkedFiles; } } pNZBInfo->SetParkedFileCount(iParkedFiles); } else { pNZBInfo->GetFileList()->Clear(); } pNZBInfo->PrintMessage(Message::mkInfo, "Collection %s added to history", pNZBInfo->GetName()); } void HistoryCoordinator::HistoryHide(DownloadQueue* pDownloadQueue, HistoryInfo* pHistoryInfo, int rindex) { char szNiceName[1024]; pHistoryInfo->GetName(szNiceName, 1024); // replace history element DupInfo* pDupInfo = new DupInfo(); pDupInfo->SetID(pHistoryInfo->GetNZBInfo()->GetID()); 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()->GetMarkStatus() == NZBInfo::ksSuccess ? DupInfo::dsSuccess : pHistoryInfo->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsDupe ? DupInfo::dsDupe : pHistoryInfo->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsManual || pHistoryInfo->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsGood || pHistoryInfo->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsCopy ? DupInfo::dsDeleted : pHistoryInfo->GetNZBInfo()->IsDupeSuccess() ? DupInfo::dsSuccess : DupInfo::dsFailed); HistoryInfo* pNewHistoryInfo = new HistoryInfo(pDupInfo); pNewHistoryInfo->SetTime(pHistoryInfo->GetTime()); (*pDownloadQueue->GetHistory())[pDownloadQueue->GetHistory()->size() - 1 - rindex] = pNewHistoryInfo; DeleteDiskFiles(pHistoryInfo->GetNZBInfo()); delete pHistoryInfo; info("Collection %s removed from history", szNiceName); } void HistoryCoordinator::PrepareEdit(DownloadQueue* pDownloadQueue, IDList* pIDList, DownloadQueue::EEditAction eAction) { // First pass: when marking multiple items - mark them bad without performing the mark-logic, // this will later (on second step) avoid moving other items to download queue, if they are marked bad too. if (eAction == DownloadQueue::eaHistoryMarkBad) { for (IDList::iterator itID = pIDList->begin(); itID != pIDList->end(); itID++) { int iID = *itID; HistoryInfo* pHistoryInfo = pDownloadQueue->GetHistory()->Find(iID); if (pHistoryInfo && pHistoryInfo->GetKind() == HistoryInfo::hkNzb) { pHistoryInfo->GetNZBInfo()->SetMarkStatus(NZBInfo::ksBad); } } } } bool HistoryCoordinator::EditList(DownloadQueue* pDownloadQueue, IDList* pIDList, DownloadQueue::EEditAction eAction, int iOffset, const char* szText) { bool bOK = false; PrepareEdit(pDownloadQueue, pIDList, eAction); for (IDList::iterator itID = pIDList->begin(); itID != pIDList->end(); itID++) { int iID = *itID; for (HistoryList::iterator itHistory = pDownloadQueue->GetHistory()->begin(); itHistory != pDownloadQueue->GetHistory()->end(); itHistory++) { HistoryInfo* pHistoryInfo = *itHistory; if (pHistoryInfo->GetID() == iID) { bOK = true; switch (eAction) { case DownloadQueue::eaHistoryDelete: case DownloadQueue::eaHistoryFinalDelete: HistoryDelete(pDownloadQueue, itHistory, pHistoryInfo, eAction == DownloadQueue::eaHistoryFinalDelete); break; case DownloadQueue::eaHistoryReturn: case DownloadQueue::eaHistoryProcess: HistoryReturn(pDownloadQueue, itHistory, pHistoryInfo, eAction == DownloadQueue::eaHistoryProcess); break; case DownloadQueue::eaHistoryRedownload: HistoryRedownload(pDownloadQueue, itHistory, pHistoryInfo, false); break; case DownloadQueue::eaHistorySetParameter: bOK = HistorySetParameter(pHistoryInfo, szText); break; case DownloadQueue::eaHistorySetCategory: bOK = HistorySetCategory(pHistoryInfo, szText); break; case DownloadQueue::eaHistorySetName: bOK = HistorySetName(pHistoryInfo, szText); break; case DownloadQueue::eaHistorySetDupeKey: case DownloadQueue::eaHistorySetDupeScore: case DownloadQueue::eaHistorySetDupeMode: case DownloadQueue::eaHistorySetDupeBackup: HistorySetDupeParam(pHistoryInfo, eAction, szText); break; case DownloadQueue::eaHistoryMarkBad: g_pDupeCoordinator->HistoryMark(pDownloadQueue, pHistoryInfo, NZBInfo::ksBad); break; case DownloadQueue::eaHistoryMarkGood: g_pDupeCoordinator->HistoryMark(pDownloadQueue, pHistoryInfo, NZBInfo::ksGood); break; case DownloadQueue::eaHistoryMarkSuccess: g_pDupeCoordinator->HistoryMark(pDownloadQueue, pHistoryInfo, NZBInfo::ksSuccess); break; default: // nothing, just to avoid compiler warning break; } break; } } } if (bOK) { pDownloadQueue->Save(); } return bOK; } void HistoryCoordinator::HistoryDelete(DownloadQueue* pDownloadQueue, HistoryList::iterator itHistory, HistoryInfo* pHistoryInfo, bool bFinal) { char szNiceName[1024]; pHistoryInfo->GetName(szNiceName, 1024); info("Deleting %s from history", szNiceName); if (pHistoryInfo->GetKind() == HistoryInfo::hkNzb) { DeleteDiskFiles(pHistoryInfo->GetNZBInfo()); } if (pHistoryInfo->GetKind() == HistoryInfo::hkNzb && g_pOptions->GetDeleteCleanupDisk() && (pHistoryInfo->GetNZBInfo()->GetDeleteStatus() != NZBInfo::dsNone || pHistoryInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psFailure || pHistoryInfo->GetNZBInfo()->GetUnpackStatus() == NZBInfo::usFailure || pHistoryInfo->GetNZBInfo()->GetUnpackStatus() == NZBInfo::usPassword) && Util::DirectoryExists(pHistoryInfo->GetNZBInfo()->GetDestDir())) { info("Deleting %s", pHistoryInfo->GetNZBInfo()->GetDestDir()); char szErrBuf[256]; if (!Util::DeleteDirectoryWithContent(pHistoryInfo->GetNZBInfo()->GetDestDir(), szErrBuf, sizeof(szErrBuf))) { error("Could not delete directory %s: %s", pHistoryInfo->GetNZBInfo()->GetDestDir(), szErrBuf); } } if (bFinal || !g_pOptions->GetDupeCheck() || pHistoryInfo->GetKind() == HistoryInfo::hkUrl) { pDownloadQueue->GetHistory()->erase(itHistory); delete pHistoryInfo; } else { if (pHistoryInfo->GetKind() == HistoryInfo::hkNzb) { // replace history element int rindex = pDownloadQueue->GetHistory()->size() - 1 - (itHistory - pDownloadQueue->GetHistory()->begin()); HistoryHide(pDownloadQueue, pHistoryInfo, rindex); } } } void HistoryCoordinator::HistoryReturn(DownloadQueue* pDownloadQueue, HistoryList::iterator itHistory, HistoryInfo* pHistoryInfo, bool bReprocess) { char szNiceName[1024]; pHistoryInfo->GetName(szNiceName, 1024); debug("Returning %s from history back to download queue", szNiceName); NZBInfo* pNZBInfo = NULL; if (bReprocess && pHistoryInfo->GetKind() != HistoryInfo::hkNzb) { error("Could not restart postprocessing for %s: history item has wrong type", szNiceName); return; } if (pHistoryInfo->GetKind() == HistoryInfo::hkNzb) { pNZBInfo = pHistoryInfo->GetNZBInfo(); // unpark files bool bUnparked = false; for (FileList::iterator it = pNZBInfo->GetFileList()->begin(); it != pNZBInfo->GetFileList()->end(); it++) { FileInfo* pFileInfo = *it; detail("Unpark file %s", pFileInfo->GetFilename()); bUnparked = true; } if (!(bUnparked || bReprocess)) { warn("Could not return %s back from history to download queue: history item does not have any files left for download", szNiceName); return; } pDownloadQueue->GetQueue()->push_front(pNZBInfo); pHistoryInfo->DiscardNZBInfo(); // reset postprocessing status variables pNZBInfo->SetParCleanup(false); if (!pNZBInfo->GetUnpackCleanedUpDisk()) { pNZBInfo->SetUnpackStatus(NZBInfo::usNone); pNZBInfo->SetCleanupStatus(NZBInfo::csNone); pNZBInfo->SetRenameStatus(NZBInfo::rsNone); pNZBInfo->SetPostTotalSec(pNZBInfo->GetPostTotalSec() - pNZBInfo->GetUnpackSec()); pNZBInfo->SetUnpackSec(0); if (ParParser::FindMainPars(pNZBInfo->GetDestDir(), NULL)) { pNZBInfo->SetParStatus(NZBInfo::psNone); pNZBInfo->SetPostTotalSec(pNZBInfo->GetPostTotalSec() - pNZBInfo->GetParSec()); pNZBInfo->SetParSec(0); pNZBInfo->SetRepairSec(0); pNZBInfo->SetParFull(false); } } pNZBInfo->SetDeleteStatus(NZBInfo::dsNone); pNZBInfo->SetDeletePaused(false); pNZBInfo->SetMarkStatus(NZBInfo::ksNone); pNZBInfo->GetScriptStatuses()->Clear(); pNZBInfo->SetParkedFileCount(0); if (pNZBInfo->GetMoveStatus() == NZBInfo::msFailure) { pNZBInfo->SetMoveStatus(NZBInfo::msNone); } pNZBInfo->SetReprocess(bReprocess); } if (pHistoryInfo->GetKind() == HistoryInfo::hkUrl) { pNZBInfo = pHistoryInfo->GetNZBInfo(); pHistoryInfo->DiscardNZBInfo(); pNZBInfo->SetUrlStatus(NZBInfo::lsNone); pNZBInfo->SetDeleteStatus(NZBInfo::dsNone); pDownloadQueue->GetQueue()->push_front(pNZBInfo); } pDownloadQueue->GetHistory()->erase(itHistory); // the object "pHistoryInfo" is released few lines later, after the call to "NZBDownloaded" pNZBInfo->PrintMessage(Message::mkInfo, "%s returned from history back to download queue", szNiceName); if (bReprocess) { // start postprocessing debug("Restarting postprocessing for %s", szNiceName); g_pPrePostProcessor->NZBDownloaded(pDownloadQueue, pNZBInfo); } delete pHistoryInfo; } void HistoryCoordinator::HistoryRedownload(DownloadQueue* pDownloadQueue, HistoryList::iterator itHistory, HistoryInfo* pHistoryInfo, bool bRestorePauseState) { if (pHistoryInfo->GetKind() == HistoryInfo::hkUrl) { HistoryReturn(pDownloadQueue, itHistory, pHistoryInfo, false); return; } if (pHistoryInfo->GetKind() != HistoryInfo::hkNzb) { char szNiceName[1024]; pHistoryInfo->GetName(szNiceName, 1024); error("Could not return %s from history back to queue: history item has wrong type", szNiceName); return; } NZBInfo* pNZBInfo = pHistoryInfo->GetNZBInfo(); bool bPaused = bRestorePauseState && pNZBInfo->GetDeletePaused(); if (!Util::FileExists(pNZBInfo->GetQueuedFilename())) { error("Could not return %s from history back to queue: could not find source nzb-file %s", pNZBInfo->GetName(), pNZBInfo->GetQueuedFilename()); return; } NZBFile* pNZBFile = new NZBFile(pNZBInfo->GetQueuedFilename(), ""); if (!pNZBFile->Parse()) { error("Could not return %s from history back to queue: could not parse nzb-file", pNZBInfo->GetName()); delete pNZBFile; return; } info("Returning %s from history back to queue", pNZBInfo->GetName()); for (FileList::iterator it = pNZBFile->GetNZBInfo()->GetFileList()->begin(); it != pNZBFile->GetNZBInfo()->GetFileList()->end(); it++) { FileInfo* pFileInfo = *it; pFileInfo->SetPaused(bPaused); } if (Util::DirectoryExists(pNZBInfo->GetDestDir())) { detail("Deleting %s", pNZBInfo->GetDestDir()); char szErrBuf[256]; if (!Util::DeleteDirectoryWithContent(pNZBInfo->GetDestDir(), szErrBuf, sizeof(szErrBuf))) { error("Could not delete directory %s: %s", pNZBInfo->GetDestDir(), szErrBuf); } } pNZBInfo->BuildDestDirName(); if (Util::DirectoryExists(pNZBInfo->GetDestDir())) { detail("Deleting %s", pNZBInfo->GetDestDir()); char szErrBuf[256]; if (!Util::DeleteDirectoryWithContent(pNZBInfo->GetDestDir(), szErrBuf, sizeof(szErrBuf))) { error("Could not delete directory %s: %s", pNZBInfo->GetDestDir(), szErrBuf); } } g_pDiskState->DiscardFiles(pNZBInfo); // reset status fields (which are not reset by "HistoryReturn") pNZBInfo->SetMoveStatus(NZBInfo::msNone); pNZBInfo->SetUnpackCleanedUpDisk(false); pNZBInfo->SetParStatus(NZBInfo::psNone); pNZBInfo->SetRenameStatus(NZBInfo::rsNone); pNZBInfo->SetDownloadedSize(0); pNZBInfo->SetDownloadSec(0); pNZBInfo->SetPostTotalSec(0); pNZBInfo->SetParSec(0); pNZBInfo->SetRepairSec(0); pNZBInfo->SetUnpackSec(0); pNZBInfo->SetExtraParBlocks(0); pNZBInfo->ClearCompletedFiles(); pNZBInfo->GetServerStats()->Clear(); pNZBInfo->GetCurrentServerStats()->Clear(); pNZBInfo->CopyFileList(pNZBFile->GetNZBInfo()); g_pQueueCoordinator->CheckDupeFileInfos(pNZBInfo); delete pNZBFile; HistoryReturn(pDownloadQueue, itHistory, pHistoryInfo, false); g_pPrePostProcessor->NZBAdded(pDownloadQueue, pNZBInfo); } bool HistoryCoordinator::HistorySetParameter(HistoryInfo* pHistoryInfo, const char* szText) { char szNiceName[1024]; pHistoryInfo->GetName(szNiceName, 1024); debug("Setting post-process-parameter '%s' for '%s'", szText, szNiceName); if (!(pHistoryInfo->GetKind() == HistoryInfo::hkNzb || pHistoryInfo->GetKind() == HistoryInfo::hkUrl)) { error("Could not set post-process-parameter for %s: history item has wrong type", szNiceName); return false; } char* szStr = strdup(szText); char* szValue = strchr(szStr, '='); if (szValue) { *szValue = '\0'; szValue++; pHistoryInfo->GetNZBInfo()->GetParameters()->SetParameter(szStr, szValue); } else { error("Could not set post-process-parameter for %s: invalid argument: %s", pHistoryInfo->GetNZBInfo()->GetName(), szText); } free(szStr); return true; } bool HistoryCoordinator::HistorySetCategory(HistoryInfo* pHistoryInfo, const char* szText) { char szNiceName[1024]; pHistoryInfo->GetName(szNiceName, 1024); debug("Setting category '%s' for '%s'", szText, szNiceName); if (!(pHistoryInfo->GetKind() == HistoryInfo::hkNzb || pHistoryInfo->GetKind() == HistoryInfo::hkUrl)) { error("Could not set category for %s: history item has wrong type", szNiceName); return false; } pHistoryInfo->GetNZBInfo()->SetCategory(szText); return true; } bool HistoryCoordinator::HistorySetName(HistoryInfo* pHistoryInfo, const char* szText) { char szNiceName[1024]; pHistoryInfo->GetName(szNiceName, 1024); debug("Setting name '%s' for '%s'", szText, szNiceName); if (Util::EmptyStr(szText)) { error("Could not rename %s. The new name cannot be empty", szNiceName); return false; } if (pHistoryInfo->GetKind() == HistoryInfo::hkNzb || pHistoryInfo->GetKind() == HistoryInfo::hkUrl) { pHistoryInfo->GetNZBInfo()->SetName(szText); } else if (pHistoryInfo->GetKind() == HistoryInfo::hkDup) { pHistoryInfo->GetDupInfo()->SetName(szText); } return true; } void HistoryCoordinator::HistorySetDupeParam(HistoryInfo* pHistoryInfo, DownloadQueue::EEditAction eAction, const char* szText) { char szNiceName[1024]; pHistoryInfo->GetName(szNiceName, 1024); debug("Setting dupe-parameter '%i'='%s' for '%s'", (int)eAction, szText, szNiceName); EDupeMode eMode = dmScore; if (eAction == DownloadQueue::eaHistorySetDupeMode) { 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)", szNiceName, szText); return; } } if (pHistoryInfo->GetKind() == HistoryInfo::hkNzb || pHistoryInfo->GetKind() == HistoryInfo::hkUrl) { switch (eAction) { case DownloadQueue::eaHistorySetDupeKey: pHistoryInfo->GetNZBInfo()->SetDupeKey(szText); break; case DownloadQueue::eaHistorySetDupeScore: pHistoryInfo->GetNZBInfo()->SetDupeScore(atoi(szText)); break; case DownloadQueue::eaHistorySetDupeMode: pHistoryInfo->GetNZBInfo()->SetDupeMode(eMode); break; case DownloadQueue::eaHistorySetDupeBackup: if (pHistoryInfo->GetKind() == HistoryInfo::hkUrl) { error("Could not set duplicate parameter for %s: history item has wrong type", szNiceName); return; } else if (pHistoryInfo->GetNZBInfo()->GetDeleteStatus() != NZBInfo::dsDupe && pHistoryInfo->GetNZBInfo()->GetDeleteStatus() != NZBInfo::dsManual) { error("Could not set duplicate parameter for %s: history item has wrong delete status", szNiceName); return; } pHistoryInfo->GetNZBInfo()->SetDeleteStatus(!strcasecmp(szText, "YES") || !strcasecmp(szText, "TRUE") || !strcasecmp(szText, "1") ? NZBInfo::dsDupe : NZBInfo::dsManual); break; default: // suppress compiler warning break; } } else if (pHistoryInfo->GetKind() == HistoryInfo::hkDup) { switch (eAction) { case DownloadQueue::eaHistorySetDupeKey: pHistoryInfo->GetDupInfo()->SetDupeKey(szText); break; case DownloadQueue::eaHistorySetDupeScore: pHistoryInfo->GetDupInfo()->SetDupeScore(atoi(szText)); break; case DownloadQueue::eaHistorySetDupeMode: pHistoryInfo->GetDupInfo()->SetDupeMode(eMode); break; case DownloadQueue::eaHistorySetDupeBackup: error("Could not set duplicate parameter for %s: history item has wrong type", szNiceName); return; default: // suppress compiler warning break; } } } void HistoryCoordinator::Redownload(DownloadQueue* pDownloadQueue, HistoryInfo* pHistoryInfo) { HistoryList::iterator it = std::find(pDownloadQueue->GetHistory()->begin(), pDownloadQueue->GetHistory()->end(), pHistoryInfo); HistoryRedownload(pDownloadQueue, it, pHistoryInfo, true); } nzbget-16.4/daemon/queue/DupeCoordinator.cpp0000644000175000017500000005452312630544544020765 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #include #ifdef WIN32 #include #else #include #endif #include #include #include "nzbget.h" #include "Options.h" #include "Log.h" #include "Util.h" #include "NZBFile.h" #include "HistoryCoordinator.h" #include "DupeCoordinator.h" 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()); // find duplicates in download queue with exactly same content for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pQueuedNZBInfo = *it; bool bSameContent = (pNZBInfo->GetFullContentHash() > 0 && pNZBInfo->GetFullContentHash() == pQueuedNZBInfo->GetFullContentHash()) || (pNZBInfo->GetFilteredContentHash() > 0 && pNZBInfo->GetFilteredContentHash() == pQueuedNZBInfo->GetFilteredContentHash()); // if there is a duplicate with exactly same content (via hash-check) // in queue - the new item is skipped if (pQueuedNZBInfo != pNZBInfo && bSameContent && pNZBInfo->GetKind() == NZBInfo::nkNzb) { char szMessage[1024]; if (!strcmp(pNZBInfo->GetName(), pQueuedNZBInfo->GetName())) { snprintf(szMessage, 1024, "Skipping duplicate %s, already queued", pNZBInfo->GetName()); } else { snprintf(szMessage, 1024, "Skipping duplicate %s, already queued as %s", pNZBInfo->GetName(), pQueuedNZBInfo->GetName()); } szMessage[1024-1] = '\0'; if (pNZBInfo->GetFeedID()) { warn("%s", szMessage); // Flag saying QueueCoordinator to skip nzb-file pNZBInfo->SetDeleteStatus(NZBInfo::dsManual); g_pHistoryCoordinator->DeleteDiskFiles(pNZBInfo); } else { pNZBInfo->SetDeleteStatus(NZBInfo::dsCopy); pNZBInfo->AddMessage(Message::mkWarning, szMessage); } return; } } // if download has empty dupekey and empty dupescore - check if download queue // or history have an item with the same name and non empty dupekey or dupescore and // take these properties from this item if (Util::EmptyStr(pNZBInfo->GetDupeKey()) && pNZBInfo->GetDupeScore() == 0) { for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pQueuedNZBInfo = *it; if (!strcmp(pQueuedNZBInfo->GetName(), pNZBInfo->GetName()) && (!Util::EmptyStr(pQueuedNZBInfo->GetDupeKey()) || pQueuedNZBInfo->GetDupeScore() != 0)) { pNZBInfo->SetDupeKey(pQueuedNZBInfo->GetDupeKey()); pNZBInfo->SetDupeScore(pQueuedNZBInfo->GetDupeScore()); info("Assigning dupekey %s and dupescore %i to %s from existing queue item with the same name", pNZBInfo->GetDupeKey(), pNZBInfo->GetDupeScore(), pNZBInfo->GetName()); break; } } } if (Util::EmptyStr(pNZBInfo->GetDupeKey()) && pNZBInfo->GetDupeScore() == 0) { for (HistoryList::iterator it = pDownloadQueue->GetHistory()->begin(); it != pDownloadQueue->GetHistory()->end(); it++) { HistoryInfo* pHistoryInfo = *it; if (pHistoryInfo->GetKind() == HistoryInfo::hkNzb && !strcmp(pHistoryInfo->GetNZBInfo()->GetName(), pNZBInfo->GetName()) && (!Util::EmptyStr(pHistoryInfo->GetNZBInfo()->GetDupeKey()) || pHistoryInfo->GetNZBInfo()->GetDupeScore() != 0)) { pNZBInfo->SetDupeKey(pHistoryInfo->GetNZBInfo()->GetDupeKey()); pNZBInfo->SetDupeScore(pHistoryInfo->GetNZBInfo()->GetDupeScore()); info("Assigning dupekey %s and dupescore %i to %s from existing history item with the same name", pNZBInfo->GetDupeKey(), pNZBInfo->GetDupeScore(), pNZBInfo->GetName()); break; } if (pHistoryInfo->GetKind() == HistoryInfo::hkDup && !strcmp(pHistoryInfo->GetDupInfo()->GetName(), pNZBInfo->GetName()) && (!Util::EmptyStr(pHistoryInfo->GetDupInfo()->GetDupeKey()) || pHistoryInfo->GetDupInfo()->GetDupeScore() != 0)) { pNZBInfo->SetDupeKey(pHistoryInfo->GetDupInfo()->GetDupeKey()); pNZBInfo->SetDupeScore(pHistoryInfo->GetDupInfo()->GetDupeScore()); info("Assigning dupekey %s and dupescore %i to %s from existing history item with the same name", pNZBInfo->GetDupeKey(), pNZBInfo->GetDupeScore(), pNZBInfo->GetName()); break; } } } // find duplicates in history bool bSkip = false; bool bGood = false; bool bSameContent = false; const char* szDupeName = NULL; // find duplicates in history 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 not having duplicates in recent history are skipped for (HistoryList::iterator it = pDownloadQueue->GetHistory()->begin(); it != pDownloadQueue->GetHistory()->end(); it++) { HistoryInfo* pHistoryInfo = *it; if (pHistoryInfo->GetKind() == HistoryInfo::hkNzb && ((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::hkDup && ((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::hkNzb && 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::hkDup && 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->GetHistory()->begin(); it != pDownloadQueue->GetHistory()->end(); it++) { HistoryInfo* pHistoryInfo = *it; if (pHistoryInfo->GetKind() == HistoryInfo::hkNzb && pHistoryInfo->GetNZBInfo()->GetDupeMode() != dmForce && SameNameOrKey(pHistoryInfo->GetNZBInfo()->GetName(), pHistoryInfo->GetNZBInfo()->GetDupeKey(), pNZBInfo->GetName(), pNZBInfo->GetDupeKey()) && pNZBInfo->GetDupeScore() <= pHistoryInfo->GetNZBInfo()->GetDupeScore() && pHistoryInfo->GetNZBInfo()->IsDupeSuccess()) { // Flag saying QueueCoordinator to skip nzb-file pNZBInfo->SetDeleteStatus(NZBInfo::dsDupe); info("Collection %s is a duplicate to %s", pNZBInfo->GetName(), pHistoryInfo->GetNZBInfo()->GetName()); return; } } } if (bSkip) { char szMessage[1024]; if (!strcmp(pNZBInfo->GetName(), szDupeName)) { snprintf(szMessage, 1024, "Skipping duplicate %s, found in history with %s", pNZBInfo->GetName(), bSameContent ? "exactly same content" : bGood ? "good status" : "success status"); } else { snprintf(szMessage, 1024, "Skipping duplicate %s, found in history %s with %s", pNZBInfo->GetName(), szDupeName, bSameContent ? "exactly same content" : bGood ? "good status" : "success status"); } szMessage[1024-1] = '\0'; if (pNZBInfo->GetFeedID()) { warn("%s", szMessage); // Flag saying QueueCoordinator to skip nzb-file pNZBInfo->SetDeleteStatus(NZBInfo::dsManual); g_pHistoryCoordinator->DeleteDiskFiles(pNZBInfo); } else { pNZBInfo->SetDeleteStatus(bSameContent ? NZBInfo::dsCopy : NZBInfo::dsGood); pNZBInfo->AddMessage(Message::mkWarning, szMessage); } 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 int index = 0; for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); index++) { NZBInfo* pQueuedNZBInfo = *it++; if (pQueuedNZBInfo != pNZBInfo && pQueuedNZBInfo->GetKind() == NZBInfo::nkNzb && pQueuedNZBInfo->GetDupeMode() != dmForce && SameNameOrKey(pQueuedNZBInfo->GetName(), pQueuedNZBInfo->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() <= pQueuedNZBInfo->GetDupeScore()) { // Flag saying QueueCoordinator to skip nzb-file pNZBInfo->SetDeleteStatus(NZBInfo::dsDupe); info("Collection %s is a duplicate to %s", pNZBInfo->GetName(), pQueuedNZBInfo->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 (unless it is in post-processing stage) if (!pQueuedNZBInfo->GetPostInfo()) { // the existing queue item is moved to history as dupe-backup info("Moving collection %s with lower duplicate score to history", pQueuedNZBInfo->GetName()); pQueuedNZBInfo->SetDeleteStatus(NZBInfo::dsDupe); pDownloadQueue->EditEntry(pQueuedNZBInfo->GetID(), DownloadQueue::eaGroupDelete, 0, NULL); it = pDownloadQueue->GetQueue()->begin() + index; } } } } } /** - if download of an item fails and there are duplicates in history - return the best duplicate from history to 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 && !pNZBInfo->IsDupeSuccess()) { 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->GetHistory()->begin(); it != pDownloadQueue->GetHistory()->end(); it++) { HistoryInfo* pHistoryInfo = *it; bool bGoodDupe = false; if (pHistoryInfo->GetKind() == HistoryInfo::hkNzb && pHistoryInfo->GetNZBInfo()->GetDupeMode() != dmForce && pHistoryInfo->GetNZBInfo()->IsDupeSuccess() && 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::hkDup && 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 download queue bool bQueueDupe = false; int iQueueScore = 0; for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pQueuedNZBInfo = *it; if (pQueuedNZBInfo != pNZBInfo && pQueuedNZBInfo->GetKind() == NZBInfo::nkNzb && pQueuedNZBInfo->GetDupeMode() != dmForce && SameNameOrKey(pQueuedNZBInfo->GetName(), pQueuedNZBInfo->GetDupeKey(), szNZBName, szDupeKey) && (!bQueueDupe || pQueuedNZBInfo->GetDupeScore() > iQueueScore)) { iQueueScore = pQueuedNZBInfo->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->GetHistory()->begin(); it != pDownloadQueue->GetHistory()->end(); it++) { HistoryInfo* pHistoryInfo = *it; if (pHistoryInfo->GetKind() == HistoryInfo::hkNzb && pHistoryInfo->GetNZBInfo()->GetDupeMode() != dmForce && pHistoryInfo->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsDupe && pHistoryInfo->GetNZBInfo()->CalcHealth() >= pHistoryInfo->GetNZBInfo()->CalcCriticalHealth(true) && pHistoryInfo->GetNZBInfo()->GetMarkStatus() != NZBInfo::ksBad && (!bHistoryDupe || pHistoryInfo->GetNZBInfo()->GetDupeScore() > iHistoryScore) && (!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); g_pHistoryCoordinator->Redownload(pDownloadQueue, pHistoryDupe); } } void DupeCoordinator::HistoryMark(DownloadQueue* pDownloadQueue, HistoryInfo* pHistoryInfo, NZBInfo::EMarkStatus eMarkStatus) { char szNZBName[1024]; pHistoryInfo->GetName(szNZBName, 1024); const char* szMarkStatusName[] = { "NONE", "bad", "good", "success" }; info("Marking %s as %s", szNZBName, szMarkStatusName[eMarkStatus]); if (pHistoryInfo->GetKind() == HistoryInfo::hkNzb) { pHistoryInfo->GetNZBInfo()->SetMarkStatus(eMarkStatus); } else if (pHistoryInfo->GetKind() == HistoryInfo::hkDup) { pHistoryInfo->GetDupInfo()->SetStatus( eMarkStatus == NZBInfo::ksGood ? DupInfo::dsGood : eMarkStatus == NZBInfo::ksSuccess ? DupInfo::dsSuccess : DupInfo::dsBad); } else { error("Could not mark %s as bad: history item has wrong type", szNZBName); return; } if (!g_pOptions->GetDupeCheck() || (pHistoryInfo->GetKind() == HistoryInfo::hkNzb && pHistoryInfo->GetNZBInfo()->GetDupeMode() == dmForce) || (pHistoryInfo->GetKind() == HistoryInfo::hkDup && pHistoryInfo->GetDupInfo()->GetDupeMode() == dmForce)) { return; } if (eMarkStatus == NZBInfo::ksGood) { // mark as good // moving all duplicates from history to dup-history HistoryCleanup(pDownloadQueue, pHistoryInfo); } else if (eMarkStatus == NZBInfo::ksBad) { // mark as bad const char* szDupeKey = pHistoryInfo->GetKind() == HistoryInfo::hkNzb ? pHistoryInfo->GetNZBInfo()->GetDupeKey() : pHistoryInfo->GetKind() == HistoryInfo::hkDup ? pHistoryInfo->GetDupInfo()->GetDupeKey() : NULL; ReturnBestDupe(pDownloadQueue, NULL, szNZBName, szDupeKey); } } void DupeCoordinator::HistoryCleanup(DownloadQueue* pDownloadQueue, HistoryInfo* pMarkHistoryInfo) { const char* szDupeKey = pMarkHistoryInfo->GetKind() == HistoryInfo::hkNzb ? pMarkHistoryInfo->GetNZBInfo()->GetDupeKey() : pMarkHistoryInfo->GetKind() == HistoryInfo::hkDup ? pMarkHistoryInfo->GetDupInfo()->GetDupeKey() : NULL; const char* szNZBName = pMarkHistoryInfo->GetKind() == HistoryInfo::hkNzb ? pMarkHistoryInfo->GetNZBInfo()->GetName() : pMarkHistoryInfo->GetKind() == HistoryInfo::hkDup ? 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->GetHistory()->rbegin(); it != pDownloadQueue->GetHistory()->rend(); ) { HistoryInfo* pHistoryInfo = *it; if (pHistoryInfo->GetKind() == HistoryInfo::hkNzb && pHistoryInfo->GetNZBInfo()->GetDupeMode() != dmForce && pHistoryInfo->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsDupe && pHistoryInfo != pMarkHistoryInfo && SameNameOrKey(pHistoryInfo->GetNZBInfo()->GetName(), pHistoryInfo->GetNZBInfo()->GetDupeKey(), szNZBName, szDupeKey)) { g_pHistoryCoordinator->HistoryHide(pDownloadQueue, pHistoryInfo, index); index++; it = pDownloadQueue->GetHistory()->rbegin() + index; bChanged = true; } else { it++; index++; } } if (bChanged) { pDownloadQueue->Save(); } } DupeCoordinator::EDupeStatus DupeCoordinator::GetDupeStatus(DownloadQueue* pDownloadQueue, const char* szName, const char* szDupeKey) { EDupeStatus eStatuses = dsNone; // find duplicates in download queue for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; if (SameNameOrKey(szName, szDupeKey, pNZBInfo->GetName(), pNZBInfo->GetDupeKey())) { if (pNZBInfo->GetSuccessArticles() + pNZBInfo->GetFailedArticles() > 0) { eStatuses = (EDupeStatus)(eStatuses | dsDownloading); } else { eStatuses = (EDupeStatus)(eStatuses | dsQueued); } } } // find duplicates in history for (HistoryList::iterator it = pDownloadQueue->GetHistory()->begin(); it != pDownloadQueue->GetHistory()->end(); it++) { HistoryInfo* pHistoryInfo = *it; if (pHistoryInfo->GetKind() == HistoryInfo::hkNzb && SameNameOrKey(szName, szDupeKey, pHistoryInfo->GetNZBInfo()->GetName(), pHistoryInfo->GetNZBInfo()->GetDupeKey())) { const char* szTextStatus = pHistoryInfo->GetNZBInfo()->MakeTextStatus(true); if (!strncasecmp(szTextStatus, "SUCCESS", 7)) { eStatuses = (EDupeStatus)(eStatuses | dsSuccess); } else if (!strncasecmp(szTextStatus, "FAILURE", 7)) { eStatuses = (EDupeStatus)(eStatuses | dsFailure); } else if (!strncasecmp(szTextStatus, "WARNING", 7)) { eStatuses = (EDupeStatus)(eStatuses | dsWarning); } } if (pHistoryInfo->GetKind() == HistoryInfo::hkDup && SameNameOrKey(szName, szDupeKey, pHistoryInfo->GetDupInfo()->GetName(), pHistoryInfo->GetDupInfo()->GetDupeKey())) { if (pHistoryInfo->GetDupInfo()->GetStatus() == DupInfo::dsSuccess || pHistoryInfo->GetDupInfo()->GetStatus() == DupInfo::dsGood) { eStatuses = (EDupeStatus)(eStatuses | dsSuccess); } else if (pHistoryInfo->GetDupInfo()->GetStatus() == DupInfo::dsFailed || pHistoryInfo->GetDupInfo()->GetStatus() == DupInfo::dsBad) { eStatuses = (EDupeStatus)(eStatuses | dsFailure); } } } return eStatuses; } void DupeCoordinator::ListHistoryDupes(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, NZBList* pDupeList) { if (pNZBInfo->GetDupeMode() == dmForce) { return; } // find duplicates in history for (HistoryList::iterator it = pDownloadQueue->GetHistory()->begin(); it != pDownloadQueue->GetHistory()->end(); it++) { HistoryInfo* pHistoryInfo = *it; if (pHistoryInfo->GetKind() == HistoryInfo::hkNzb && pHistoryInfo->GetNZBInfo()->GetDupeMode() != dmForce && SameNameOrKey(pHistoryInfo->GetNZBInfo()->GetName(), pHistoryInfo->GetNZBInfo()->GetDupeKey(), pNZBInfo->GetName(), pNZBInfo->GetDupeKey())) { pDupeList->push_back(pHistoryInfo->GetNZBInfo()); } } } nzbget-16.4/daemon/queue/DownloadInfo.cpp0000644000175000017500000007337712630544544020257 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #include #include #include #include #include #include "nzbget.h" #include "DownloadInfo.h" #include "ArticleWriter.h" #include "DiskState.h" #include "Options.h" #include "Util.h" int FileInfo::m_iIDGen = 0; int FileInfo::m_iIDMax = 0; int NZBInfo::m_iIDGen = 0; int NZBInfo::m_iIDMax = 0; DownloadQueue* DownloadQueue::g_pDownloadQueue = NULL; bool DownloadQueue::g_bLoaded = false; NZBParameter::NZBParameter(const char* szName) { m_szName = strdup(szName); m_szValue = NULL; } NZBParameter::~NZBParameter() { free(m_szName); free(m_szValue); } void NZBParameter::SetValue(const char* szValue) { free(m_szValue); m_szValue = strdup(szValue); } NZBParameterList::~NZBParameterList() { Clear(); } void NZBParameterList::Clear() { for (iterator it = begin(); it != end(); it++) { delete *it; } clear(); } void NZBParameterList::SetParameter(const char* szName, const char* szValue) { NZBParameter* pParameter = NULL; bool bDelete = !szValue || !*szValue; for (iterator it = begin(); it != end(); it++) { NZBParameter* pLookupParameter = *it; if (!strcmp(pLookupParameter->GetName(), szName)) { if (bDelete) { delete pLookupParameter; erase(it); return; } pParameter = pLookupParameter; break; } } if (bDelete) { return; } if (!pParameter) { pParameter = new NZBParameter(szName); push_back(pParameter); } pParameter->SetValue(szValue); } NZBParameter* NZBParameterList::Find(const char* szName, bool bCaseSensitive) { for (iterator it = begin(); it != end(); it++) { NZBParameter* pParameter = *it; if ((bCaseSensitive && !strcmp(pParameter->GetName(), szName)) || (!bCaseSensitive && !strcasecmp(pParameter->GetName(), szName))) { return pParameter; } } return NULL; } void NZBParameterList::CopyFrom(NZBParameterList* pSourceParameters) { for (iterator it = pSourceParameters->begin(); it != pSourceParameters->end(); it++) { NZBParameter* pParameter = *it; SetParameter(pParameter->GetName(), pParameter->GetValue()); } } ScriptStatus::ScriptStatus(const char* szName, EStatus eStatus) { m_szName = strdup(szName); m_eStatus = eStatus; } ScriptStatus::~ScriptStatus() { free(m_szName); } ScriptStatusList::~ScriptStatusList() { Clear(); } void ScriptStatusList::Clear() { for (iterator it = begin(); it != end(); it++) { delete *it; } clear(); } void ScriptStatusList::Add(const char* szScriptName, ScriptStatus::EStatus eStatus) { push_back(new ScriptStatus(szScriptName, eStatus)); } ScriptStatus::EStatus ScriptStatusList::CalcTotalStatus() { ScriptStatus::EStatus eStatus = ScriptStatus::srNone; for (iterator it = begin(); it != end(); it++) { ScriptStatus* pScriptStatus = *it; // Failure-Status overrides Success-Status if ((pScriptStatus->GetStatus() == ScriptStatus::srSuccess && eStatus == ScriptStatus::srNone) || (pScriptStatus->GetStatus() == ScriptStatus::srFailure)) { eStatus = pScriptStatus->GetStatus(); } } return eStatus; } ServerStat::ServerStat(int iServerID) { m_iServerID = iServerID; m_iSuccessArticles = 0; m_iFailedArticles = 0; } ServerStatList::~ServerStatList() { Clear(); } void ServerStatList::Clear() { for (iterator it = begin(); it != end(); it++) { delete *it; } clear(); } void ServerStatList::StatOp(int iServerID, int iSuccessArticles, int iFailedArticles, EStatOperation eStatOperation) { ServerStat* pServerStat = NULL; for (iterator it = begin(); it != end(); it++) { ServerStat* pServerStat1 = *it; if (pServerStat1->GetServerID() == iServerID) { pServerStat = pServerStat1; break; } } if (!pServerStat) { pServerStat = new ServerStat(iServerID); push_back(pServerStat); } switch (eStatOperation) { case soSet: pServerStat->SetSuccessArticles(iSuccessArticles); pServerStat->SetFailedArticles(iFailedArticles); break; case soAdd: pServerStat->SetSuccessArticles(pServerStat->GetSuccessArticles() + iSuccessArticles); pServerStat->SetFailedArticles(pServerStat->GetFailedArticles() + iFailedArticles); break; case soSubtract: pServerStat->SetSuccessArticles(pServerStat->GetSuccessArticles() - iSuccessArticles); pServerStat->SetFailedArticles(pServerStat->GetFailedArticles() - iFailedArticles); break; } } void ServerStatList::ListOp(ServerStatList* pServerStats, EStatOperation eStatOperation) { for (iterator it = pServerStats->begin(); it != pServerStats->end(); it++) { ServerStat* pServerStat = *it; StatOp(pServerStat->GetServerID(), pServerStat->GetSuccessArticles(), pServerStat->GetFailedArticles(), eStatOperation); } } NZBInfo::NZBInfo() : m_FileList(true) { debug("Creating NZBInfo"); m_eKind = nkNzb; m_szURL = strdup(""); m_szFilename = strdup(""); m_szDestDir = strdup(""); m_szFinalDir = strdup(""); m_szCategory = strdup(""); m_szName = NULL; m_iFileCount = 0; m_iParkedFileCount = 0; m_lSize = 0; m_lSuccessSize = 0; m_lFailedSize = 0; m_lCurrentSuccessSize = 0; m_lCurrentFailedSize = 0; m_lParSize = 0; m_lParSuccessSize = 0; m_lParFailedSize = 0; m_lParCurrentSuccessSize = 0; m_lParCurrentFailedSize = 0; m_iTotalArticles = 0; m_iSuccessArticles = 0; m_iFailedArticles = 0; m_iCurrentSuccessArticles = 0; m_iCurrentFailedArticles = 0; m_eRenameStatus = rsNone; m_eParStatus = psNone; m_eUnpackStatus = usNone; m_eCleanupStatus = csNone; m_eMoveStatus = msNone; m_eDeleteStatus = dsNone; m_eMarkStatus = ksNone; m_eUrlStatus = lsNone; m_iExtraParBlocks = 0; m_bAddUrlPaused = false; m_bDeleting = false; m_bDeletePaused = false; m_bManyDupeFiles = false; m_bAvoidHistory = false; m_bHealthPaused = false; m_bParCleanup = false; m_bCleanupDisk = false; m_bUnpackCleanedUpDisk = false; m_szQueuedFilename = strdup(""); m_szDupeKey = strdup(""); m_iDupeScore = 0; m_eDupeMode = dmScore; m_iFullContentHash = 0; m_iFilteredContentHash = 0; m_iPausedFileCount = 0; m_lRemainingSize = 0; m_lPausedSize = 0; m_iRemainingParCount = 0; m_tMinTime = 0; m_tMaxTime = 0; m_iPriority = 0; m_iActiveDownloads = 0; m_Messages.clear(); m_pPostInfo = NULL; m_iIDMessageGen = 0; m_iID = ++m_iIDGen; m_lDownloadedSize = 0; m_iDownloadSec = 0; m_iPostTotalSec = 0; m_iParSec = 0; m_iRepairSec = 0; m_iUnpackSec = 0; m_tDownloadStartTime = 0; m_bReprocess = false; m_tQueueScriptTime = 0; m_bParFull = false; m_iMessageCount = 0; m_iCachedMessageCount = 0; m_iFeedID = 0; } NZBInfo::~NZBInfo() { debug("Destroying NZBInfo"); free(m_szURL); free(m_szFilename); free(m_szDestDir); free(m_szFinalDir); free(m_szCategory); free(m_szName); free(m_szQueuedFilename); free(m_szDupeKey); delete m_pPostInfo; ClearCompletedFiles(); m_FileList.Clear(); } void NZBInfo::SetID(int iID) { m_iID = iID; if (m_iIDMax < m_iID) { m_iIDMax = m_iID; } } void NZBInfo::ResetGenID(bool bMax) { if (bMax) { m_iIDGen = m_iIDMax; } else { m_iIDGen = 0; m_iIDMax = 0; } } int NZBInfo::GenerateID() { return ++m_iIDGen; } void NZBInfo::ClearCompletedFiles() { for (CompletedFiles::iterator it = m_completedFiles.begin(); it != m_completedFiles.end(); it++) { delete *it; } m_completedFiles.clear(); } void NZBInfo::SetDestDir(const char* szDestDir) { free(m_szDestDir); m_szDestDir = strdup(szDestDir); } void NZBInfo::SetFinalDir(const char* szFinalDir) { free(m_szFinalDir); m_szFinalDir = strdup(szFinalDir); } void NZBInfo::SetURL(const char* szURL) { free(m_szURL); m_szURL = strdup(szURL); if (!m_szName) { char szNZBNicename[1024]; MakeNiceUrlName(szURL, m_szFilename, szNZBNicename, sizeof(szNZBNicename)); szNZBNicename[1024-1] = '\0'; #ifdef WIN32 WebUtil::AnsiToUtf8(szNZBNicename, 1024); #endif SetName(szNZBNicename); } } void NZBInfo::SetFilename(const char* szFilename) { bool bHadFilename = !Util::EmptyStr(m_szFilename); free(m_szFilename); m_szFilename = strdup(szFilename); if ((!m_szName || !bHadFilename) && !Util::EmptyStr(szFilename)) { char szNZBNicename[1024]; MakeNiceNZBName(m_szFilename, szNZBNicename, sizeof(szNZBNicename), true); szNZBNicename[1024-1] = '\0'; #ifdef WIN32 WebUtil::AnsiToUtf8(szNZBNicename, 1024); #endif SetName(szNZBNicename); } } void NZBInfo::SetName(const char* szName) { free(m_szName); m_szName = szName ? strdup(szName) : NULL; } void NZBInfo::SetCategory(const char* szCategory) { free(m_szCategory); m_szCategory = strdup(szCategory); } void NZBInfo::SetQueuedFilename(const char * szQueuedFilename) { free(m_szQueuedFilename); m_szQueuedFilename = strdup(szQueuedFilename); } void NZBInfo::SetDupeKey(const char* szDupeKey) { free(m_szDupeKey); m_szDupeKey = strdup(szDupeKey ? szDupeKey : ""); } void NZBInfo::MakeNiceNZBName(const char * szNZBFilename, char * szBuffer, int iSize, bool bRemoveExt) { char postname[1024]; const char* szBaseName = Util::BaseFileName(szNZBFilename); strncpy(postname, szBaseName, 1024); postname[1024-1] = '\0'; if (bRemoveExt) { // wipe out ".nzb" char* p = strrchr(postname, '.'); if (p && !strcasecmp(p, ".nzb")) *p = '\0'; } Util::MakeValidFilename(postname, '_', false); strncpy(szBuffer, postname, iSize); szBuffer[iSize-1] = '\0'; } void NZBInfo::MakeNiceUrlName(const char* szURL, const char* szNZBFilename, char* szBuffer, int iSize) { URL url(szURL); if (!Util::EmptyStr(szNZBFilename)) { char szNZBNicename[1024]; MakeNiceNZBName(szNZBFilename, szNZBNicename, sizeof(szNZBNicename), true); snprintf(szBuffer, iSize, "%s @ %s", szNZBNicename, url.GetHost()); } else if (url.IsValid()) { snprintf(szBuffer, iSize, "%s%s", url.GetHost(), url.GetResource()); } else { snprintf(szBuffer, iSize, "%s", szURL); } szBuffer[iSize-1] = '\0'; } void NZBInfo::BuildDestDirName() { char szDestDir[1024]; if (Util::EmptyStr(g_pOptions->GetInterDir())) { BuildFinalDirName(szDestDir, 1024); } else { snprintf(szDestDir, 1024, "%s%s.#%i", g_pOptions->GetInterDir(), GetName(), GetID()); szDestDir[1024-1] = '\0'; } #ifdef WIN32 WebUtil::Utf8ToAnsi(szDestDir, 1024); #endif SetDestDir(szDestDir); } void NZBInfo::BuildFinalDirName(char* szFinalDirBuf, int iBufSize) { char szBuffer[1024]; bool bUseCategory = m_szCategory && m_szCategory[0] != '\0'; snprintf(szFinalDirBuf, iBufSize, "%s", g_pOptions->GetDestDir()); szFinalDirBuf[iBufSize-1] = '\0'; if (bUseCategory) { Options::Category *pCategory = g_pOptions->FindCategory(m_szCategory, false); if (pCategory && pCategory->GetDestDir() && pCategory->GetDestDir()[0] != '\0') { snprintf(szFinalDirBuf, iBufSize, "%s", pCategory->GetDestDir()); szFinalDirBuf[iBufSize-1] = '\0'; bUseCategory = false; } } if (g_pOptions->GetAppendCategoryDir() && bUseCategory) { char szCategoryDir[1024]; strncpy(szCategoryDir, m_szCategory, 1024); szCategoryDir[1024 - 1] = '\0'; Util::MakeValidFilename(szCategoryDir, '_', true); snprintf(szBuffer, 1024, "%s%s%c", szFinalDirBuf, szCategoryDir, PATH_SEPARATOR); szBuffer[1024-1] = '\0'; strncpy(szFinalDirBuf, szBuffer, iBufSize); } snprintf(szBuffer, 1024, "%s%s", szFinalDirBuf, GetName()); szBuffer[1024-1] = '\0'; strncpy(szFinalDirBuf, szBuffer, iBufSize); #ifdef WIN32 WebUtil::Utf8ToAnsi(szFinalDirBuf, iBufSize); #endif } int NZBInfo::CalcHealth() { if (m_lCurrentFailedSize == 0 || m_lSize == m_lParSize) { return 1000; } int iHealth = (int)((m_lSize - m_lParSize - (m_lCurrentFailedSize - m_lParCurrentFailedSize)) * 1000 / (m_lSize - m_lParSize)); if (iHealth == 1000 && m_lCurrentFailedSize - m_lParCurrentFailedSize > 0) { iHealth = 999; } return iHealth; } int NZBInfo::CalcCriticalHealth(bool bAllowEstimation) { if (m_lSize == 0) { return 1000; } if (m_lSize == m_lParSize) { return 0; } long long lGoodParSize = m_lParSize - m_lParCurrentFailedSize; int iCriticalHealth = (int)((m_lSize - lGoodParSize*2) * 1000 / (m_lSize - lGoodParSize)); if (lGoodParSize*2 > m_lSize) { iCriticalHealth = 0; } else if (iCriticalHealth == 1000 && m_lParSize > 0) { iCriticalHealth = 999; } if (iCriticalHealth == 1000 && bAllowEstimation) { // using empirical critical health 85%, to avoid false alarms for downloads with renamed par-files iCriticalHealth = 850; } return iCriticalHealth; } void NZBInfo::UpdateMinMaxTime() { m_tMinTime = 0; m_tMaxTime = 0; bool bFirst = true; for (FileList::iterator it = m_FileList.begin(); it != m_FileList.end(); it++) { FileInfo* pFileInfo = *it; if (bFirst) { m_tMinTime = pFileInfo->GetTime(); m_tMaxTime = pFileInfo->GetTime(); bFirst = false; } if (pFileInfo->GetTime() > 0) { if (pFileInfo->GetTime() < m_tMinTime) { m_tMinTime = pFileInfo->GetTime(); } if (pFileInfo->GetTime() > m_tMaxTime) { m_tMaxTime = pFileInfo->GetTime(); } } } } MessageList* NZBInfo::LockCachedMessages() { m_mutexLog.Lock(); return &m_Messages; } void NZBInfo::UnlockCachedMessages() { m_mutexLog.Unlock(); } void NZBInfo::AddMessage(Message::EKind eKind, const char * 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; } m_mutexLog.Lock(); Message* pMessage = new Message(++m_iIDMessageGen, eKind, time(NULL), szText); m_Messages.push_back(pMessage); if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode() && g_pOptions->GetNzbLog()) { g_pDiskState->AppendNZBMessage(m_iID, eKind, szText); m_iMessageCount++; } while (m_Messages.size() > (unsigned int)g_pOptions->GetLogBufferSize()) { Message* pMessage = m_Messages.front(); delete pMessage; m_Messages.pop_front(); } m_iCachedMessageCount = m_Messages.size(); m_mutexLog.Unlock(); } void NZBInfo::PrintMessage(Message::EKind eKind, const char* szFormat, ...) { char tmp2[1024]; va_list ap; va_start(ap, szFormat); vsnprintf(tmp2, 1024, szFormat, ap); tmp2[1024-1] = '\0'; va_end(ap); AddMessage(eKind, tmp2); } void NZBInfo::ClearMessages() { m_mutexLog.Lock(); m_Messages.Clear(); m_iCachedMessageCount = 0; m_mutexLog.Unlock(); } void NZBInfo::CopyFileList(NZBInfo* pSrcNZBInfo) { m_FileList.Clear(); for (FileList::iterator it = pSrcNZBInfo->GetFileList()->begin(); it != pSrcNZBInfo->GetFileList()->end(); it++) { FileInfo* pFileInfo = *it; pFileInfo->SetNZBInfo(this); m_FileList.push_back(pFileInfo); } pSrcNZBInfo->GetFileList()->clear(); // only remove references SetFullContentHash(pSrcNZBInfo->GetFullContentHash()); SetFilteredContentHash(pSrcNZBInfo->GetFilteredContentHash()); SetFileCount(pSrcNZBInfo->GetFileCount()); SetPausedFileCount(pSrcNZBInfo->GetPausedFileCount()); SetRemainingParCount(pSrcNZBInfo->GetRemainingParCount()); SetSize(pSrcNZBInfo->GetSize()); SetRemainingSize(pSrcNZBInfo->GetRemainingSize()); SetPausedSize(pSrcNZBInfo->GetPausedSize()); SetSuccessSize(pSrcNZBInfo->GetSuccessSize()); SetCurrentSuccessSize(pSrcNZBInfo->GetCurrentSuccessSize()); SetFailedSize(pSrcNZBInfo->GetFailedSize()); SetCurrentFailedSize(pSrcNZBInfo->GetCurrentFailedSize()); SetParSize(pSrcNZBInfo->GetParSize()); SetParSuccessSize(pSrcNZBInfo->GetParSuccessSize()); SetParCurrentSuccessSize(pSrcNZBInfo->GetParCurrentSuccessSize()); SetParFailedSize(pSrcNZBInfo->GetParFailedSize()); SetParCurrentFailedSize(pSrcNZBInfo->GetParCurrentFailedSize()); SetTotalArticles(pSrcNZBInfo->GetTotalArticles()); SetSuccessArticles(pSrcNZBInfo->GetSuccessArticles()); SetFailedArticles(pSrcNZBInfo->GetFailedArticles()); SetCurrentSuccessArticles(pSrcNZBInfo->GetSuccessArticles()); SetCurrentFailedArticles(pSrcNZBInfo->GetFailedArticles()); SetMinTime(pSrcNZBInfo->GetMinTime()); SetMaxTime(pSrcNZBInfo->GetMaxTime()); } void NZBInfo::EnterPostProcess() { m_pPostInfo = new PostInfo(); m_pPostInfo->SetNZBInfo(this); } void NZBInfo::LeavePostProcess() { delete m_pPostInfo; m_pPostInfo = NULL; ClearMessages(); } void NZBInfo::SetActiveDownloads(int iActiveDownloads) { if (((m_iActiveDownloads == 0 && iActiveDownloads > 0) || (m_iActiveDownloads > 0 && iActiveDownloads == 0)) && m_eKind == NZBInfo::nkNzb) { if (iActiveDownloads > 0) { m_tDownloadStartTime = time(NULL); } else { m_iDownloadSec += time(NULL) - m_tDownloadStartTime; m_tDownloadStartTime = 0; } } m_iActiveDownloads = iActiveDownloads; } bool NZBInfo::IsDupeSuccess() { bool bFailure = m_eMarkStatus != NZBInfo::ksSuccess && m_eMarkStatus != NZBInfo::ksGood && (m_eDeleteStatus != NZBInfo::dsNone || m_eMarkStatus == NZBInfo::ksBad || m_eParStatus == NZBInfo::psFailure || m_eUnpackStatus == NZBInfo::usFailure || m_eUnpackStatus == NZBInfo::usPassword || (m_eParStatus == NZBInfo::psSkipped && m_eUnpackStatus == NZBInfo::usSkipped && CalcHealth() < CalcCriticalHealth(true))); return !bFailure; } const char* NZBInfo::MakeTextStatus(bool bIgnoreScriptStatus) { const char* szStatus = "FAILURE/INTERNAL_ERROR"; if (m_eKind == NZBInfo::nkNzb) { int iHealth = CalcHealth(); int iCriticalHealth = CalcCriticalHealth(false); ScriptStatus::EStatus eScriptStatus = bIgnoreScriptStatus ? ScriptStatus::srSuccess : m_scriptStatuses.CalcTotalStatus(); if (m_eMarkStatus == NZBInfo::ksBad) { szStatus = "FAILURE/BAD"; } else if (m_eMarkStatus == NZBInfo::ksGood) { szStatus = "SUCCESS/GOOD"; } else if (m_eMarkStatus == NZBInfo::ksSuccess) { szStatus = "SUCCESS/MARK"; } else if (m_eDeleteStatus == NZBInfo::dsHealth) { szStatus = "FAILURE/HEALTH"; } else if (m_eDeleteStatus == NZBInfo::dsManual) { szStatus = "DELETED/MANUAL"; } else if (m_eDeleteStatus == NZBInfo::dsDupe) { szStatus = "DELETED/DUPE"; } else if (m_eDeleteStatus == NZBInfo::dsBad) { szStatus = "FAILURE/BAD"; } else if (m_eDeleteStatus == NZBInfo::dsGood) { szStatus = "DELETED/GOOD"; } else if (m_eDeleteStatus == NZBInfo::dsCopy) { szStatus = "DELETED/COPY"; } else if (m_eDeleteStatus == NZBInfo::dsScan) { szStatus = "FAILURE/SCAN"; } else if (m_eParStatus == NZBInfo::psFailure) { szStatus = "FAILURE/PAR"; } else if (m_eUnpackStatus == NZBInfo::usFailure) { szStatus = "FAILURE/UNPACK"; } else if (m_eMoveStatus == NZBInfo::msFailure) { szStatus = "FAILURE/MOVE"; } else if (m_eParStatus == NZBInfo::psManual) { szStatus = "WARNING/DAMAGED"; } else if (m_eParStatus == NZBInfo::psRepairPossible) { szStatus = "WARNING/REPAIRABLE"; } else if ((m_eParStatus == NZBInfo::psNone || m_eParStatus == NZBInfo::psSkipped) && (m_eUnpackStatus == NZBInfo::usNone || m_eUnpackStatus == NZBInfo::usSkipped) && iHealth < iCriticalHealth) { szStatus = "FAILURE/HEALTH"; } else if ((m_eParStatus == NZBInfo::psNone || m_eParStatus == NZBInfo::psSkipped) && (m_eUnpackStatus == NZBInfo::usNone || m_eUnpackStatus == NZBInfo::usSkipped) && iHealth < 1000 && iHealth >= iCriticalHealth) { szStatus = "WARNING/HEALTH"; } else if ((m_eParStatus == NZBInfo::psNone || m_eParStatus == NZBInfo::psSkipped) && (m_eUnpackStatus == NZBInfo::usNone || m_eUnpackStatus == NZBInfo::usSkipped) && eScriptStatus != ScriptStatus::srFailure && iHealth == 1000) { szStatus = "SUCCESS/HEALTH"; } else if (m_eUnpackStatus == NZBInfo::usSpace) { szStatus = "WARNING/SPACE"; } else if (m_eUnpackStatus == NZBInfo::usPassword) { szStatus = "WARNING/PASSWORD"; } else if ((m_eUnpackStatus == NZBInfo::usSuccess || ((m_eUnpackStatus == NZBInfo::usNone || m_eUnpackStatus == NZBInfo::usSkipped) && m_eParStatus == NZBInfo::psSuccess)) && eScriptStatus == ScriptStatus::srSuccess) { szStatus = "SUCCESS/ALL"; } else if (m_eUnpackStatus == NZBInfo::usSuccess && eScriptStatus == ScriptStatus::srNone) { szStatus = "SUCCESS/UNPACK"; } else if (m_eParStatus == NZBInfo::psSuccess && eScriptStatus == ScriptStatus::srNone) { szStatus = "SUCCESS/PAR"; } else if (eScriptStatus == ScriptStatus::srFailure) { szStatus = "WARNING/SCRIPT"; } } else if (m_eKind == NZBInfo::nkUrl) { if (m_eDeleteStatus == NZBInfo::dsManual) { szStatus = "DELETED/MANUAL"; } else if (m_eDeleteStatus == NZBInfo::dsDupe) { szStatus = "DELETED/DUPE"; } else { const char* szUrlStatusName[] = { "FAILURE/INTERNAL_ERROR", "FAILURE/INTERNAL_ERROR", "FAILURE/INTERNAL_ERROR", "FAILURE/FETCH", "FAILURE/INTERNAL_ERROR", "WARNING/SKIPPED", "FAILURE/SCAN" }; szStatus = szUrlStatusName[m_eUrlStatus]; } } return szStatus; } NZBList::~NZBList() { if (m_bOwnObjects) { Clear(); } } void NZBList::Clear() { for (iterator it = begin(); it != end(); it++) { delete *it; } clear(); } void NZBList::Add(NZBInfo* pNZBInfo, bool bAddTop) { if (bAddTop) { push_front(pNZBInfo); } else { push_back(pNZBInfo); } } void NZBList::Remove(NZBInfo* pNZBInfo) { iterator it = std::find(begin(), end(), pNZBInfo); if (it != end()) { erase(it); } } NZBInfo* NZBList::Find(int iID) { for (iterator it = begin(); it != end(); it++) { NZBInfo* pNZBInfo = *it; if (pNZBInfo->GetID() == iID) { return pNZBInfo; } } return NULL; } ArticleInfo::ArticleInfo() { //debug("Creating ArticleInfo"); m_szMessageID = NULL; m_iSize = 0; m_pSegmentContent = NULL; m_iSegmentOffset = 0; m_iSegmentSize = 0; m_eStatus = aiUndefined; m_szResultFilename = NULL; m_lCrc = 0; } ArticleInfo::~ ArticleInfo() { //debug("Destroying ArticleInfo"); DiscardSegment(); free(m_szMessageID); free(m_szResultFilename); } void ArticleInfo::SetMessageID(const char * szMessageID) { free(m_szMessageID); m_szMessageID = strdup(szMessageID); } void ArticleInfo::SetResultFilename(const char * v) { free(m_szResultFilename); m_szResultFilename = strdup(v); } void ArticleInfo::AttachSegment(char* pContent, long long iOffset, int iSize) { DiscardSegment(); m_pSegmentContent = pContent; m_iSegmentOffset = iOffset; m_iSegmentSize = iSize; } void ArticleInfo::DiscardSegment() { if (m_pSegmentContent) { free(m_pSegmentContent); m_pSegmentContent = NULL; g_pArticleCache->Free(m_iSegmentSize); } } FileInfo::FileInfo(int iID) { debug("Creating FileInfo"); m_Articles.clear(); m_Groups.clear(); m_szSubject = NULL; m_szFilename = NULL; m_szOutputFilename = NULL; m_pMutexOutputFile = NULL; m_bFilenameConfirmed = false; m_lSize = 0; m_lRemainingSize = 0; m_lMissedSize = 0; m_lSuccessSize = 0; m_lFailedSize = 0; m_iTotalArticles = 0; m_iMissedArticles = 0; m_iFailedArticles = 0; m_iSuccessArticles = 0; m_tTime = 0; m_bPaused = false; m_bDeleted = false; m_iCompletedArticles = 0; m_bParFile = false; m_bOutputInitialized = false; m_pNZBInfo = NULL; m_bExtraPriority = false; m_iActiveDownloads = 0; m_bAutoDeleted = false; m_iCachedArticles = 0; m_bPartialChanged = false; m_iID = iID ? iID : ++m_iIDGen; } FileInfo::~ FileInfo() { debug("Destroying FileInfo"); free(m_szSubject); free(m_szFilename); free(m_szOutputFilename); delete m_pMutexOutputFile; for (Groups::iterator it = m_Groups.begin(); it != m_Groups.end() ;it++) { free(*it); } m_Groups.clear(); ClearArticles(); } void FileInfo::ClearArticles() { for (Articles::iterator it = m_Articles.begin(); it != m_Articles.end() ;it++) { delete *it; } m_Articles.clear(); } void FileInfo::SetID(int iID) { m_iID = iID; if (m_iIDMax < m_iID) { m_iIDMax = m_iID; } } void FileInfo::ResetGenID(bool bMax) { if (bMax) { m_iIDGen = m_iIDMax; } else { m_iIDGen = 0; m_iIDMax = 0; } } void FileInfo::SetPaused(bool bPaused) { if (m_bPaused != bPaused && m_pNZBInfo) { m_pNZBInfo->SetPausedFileCount(m_pNZBInfo->GetPausedFileCount() + (bPaused ? 1 : -1)); m_pNZBInfo->SetPausedSize(m_pNZBInfo->GetPausedSize() + (bPaused ? m_lRemainingSize : - m_lRemainingSize)); } m_bPaused = bPaused; } void FileInfo::SetSubject(const char* szSubject) { m_szSubject = strdup(szSubject); } void FileInfo::SetFilename(const char* szFilename) { free(m_szFilename); m_szFilename = strdup(szFilename); } void FileInfo::MakeValidFilename() { Util::MakeValidFilename(m_szFilename, '_', false); } void FileInfo::LockOutputFile() { m_pMutexOutputFile->Lock(); } void FileInfo::UnlockOutputFile() { m_pMutexOutputFile->Unlock(); } void FileInfo::SetOutputFilename(const char* szOutputFilename) { free(m_szOutputFilename); m_szOutputFilename = strdup(szOutputFilename); } void FileInfo::SetActiveDownloads(int iActiveDownloads) { m_iActiveDownloads = iActiveDownloads; if (m_iActiveDownloads > 0 && !m_pMutexOutputFile) { m_pMutexOutputFile = new Mutex(); } else if (m_iActiveDownloads == 0 && m_pMutexOutputFile) { delete m_pMutexOutputFile; m_pMutexOutputFile = NULL; } } FileList::~FileList() { if (m_bOwnObjects) { Clear(); } } void FileList::Clear() { for (iterator it = begin(); it != end(); it++) { delete *it; } clear(); } void FileList::Remove(FileInfo* pFileInfo) { erase(std::find(begin(), end(), pFileInfo)); } CompletedFile::CompletedFile(int iID, const char* szFileName, EStatus eStatus, unsigned long lCrc) { m_iID = iID; if (FileInfo::m_iIDMax < m_iID) { FileInfo::m_iIDMax = m_iID; } m_szFileName = strdup(szFileName); m_eStatus = eStatus; m_lCrc = lCrc; } void CompletedFile::SetFileName(const char* szFileName) { free(m_szFileName); m_szFileName = strdup(szFileName); } CompletedFile::~CompletedFile() { free(m_szFileName); } PostInfo::PostInfo() { debug("Creating PostInfo"); m_pNZBInfo = NULL; m_bWorking = false; m_bDeleted = false; m_bRequestParCheck = false; m_bForceParFull = false; m_bForceRepair = false; m_bParRepaired = false; m_bUnpackTried = false; m_bPassListTried = false; m_eLastUnpackStatus = 0; m_szProgressLabel = strdup(""); m_iFileProgress = 0; m_iStageProgress = 0; m_tStartTime = 0; m_tStageTime = 0; m_eStage = ptQueued; m_pPostThread = NULL; } PostInfo::~ PostInfo() { debug("Destroying PostInfo"); free(m_szProgressLabel); for (ParredFiles::iterator it = m_ParredFiles.begin(); it != m_ParredFiles.end(); it++) { free(*it); } } void PostInfo::SetProgressLabel(const char* szProgressLabel) { free(m_szProgressLabel); m_szProgressLabel = strdup(szProgressLabel); } DupInfo::DupInfo() { m_iID = 0; m_szName = NULL; m_szDupeKey = NULL; m_iDupeScore = 0; m_eDupeMode = dmScore; m_lSize = 0; m_iFullContentHash = 0; m_iFilteredContentHash = 0; m_eStatus = dsUndefined; } DupInfo::~DupInfo() { free(m_szName); free(m_szDupeKey); } void DupInfo::SetID(int iID) { m_iID = iID; if (NZBInfo::m_iIDMax < m_iID) { NZBInfo::m_iIDMax = m_iID; } } void DupInfo::SetName(const char* szName) { free(m_szName); m_szName = strdup(szName); } void DupInfo::SetDupeKey(const char* szDupeKey) { free(m_szDupeKey); m_szDupeKey = strdup(szDupeKey); } HistoryInfo::HistoryInfo(NZBInfo* pNZBInfo) { m_eKind = pNZBInfo->GetKind() == NZBInfo::nkNzb ? hkNzb : hkUrl; m_pInfo = pNZBInfo; m_tTime = 0; } HistoryInfo::HistoryInfo(DupInfo* pDupInfo) { m_eKind = hkDup; m_pInfo = pDupInfo; m_tTime = 0; } HistoryInfo::~HistoryInfo() { if ((m_eKind == hkNzb || m_eKind == hkUrl) && m_pInfo) { delete (NZBInfo*)m_pInfo; } else if (m_eKind == hkDup && m_pInfo) { delete (DupInfo*)m_pInfo; } } int HistoryInfo::GetID() { if ((m_eKind == hkNzb || m_eKind == hkUrl)) { return ((NZBInfo*)m_pInfo)->GetID(); } else // if (m_eKind == hkDup) { return ((DupInfo*)m_pInfo)->GetID(); } } void HistoryInfo::GetName(char* szBuffer, int iSize) { if (m_eKind == hkNzb || m_eKind == hkUrl) { strncpy(szBuffer, GetNZBInfo()->GetName(), iSize); szBuffer[iSize-1] = '\0'; } else if (m_eKind == hkDup) { strncpy(szBuffer, GetDupInfo()->GetName(), iSize); szBuffer[iSize-1] = '\0'; } else { strncpy(szBuffer, "", iSize); } } HistoryList::~HistoryList() { for (iterator it = begin(); it != end(); it++) { delete *it; } } HistoryInfo* HistoryList::Find(int iID) { for (iterator it = begin(); it != end(); it++) { HistoryInfo* pHistoryInfo = *it; if (pHistoryInfo->GetID() == iID) { return pHistoryInfo; } } return NULL; } DownloadQueue* DownloadQueue::Lock() { g_pDownloadQueue->m_LockMutex.Lock(); return g_pDownloadQueue; } void DownloadQueue::Unlock() { g_pDownloadQueue->m_LockMutex.Unlock(); } void DownloadQueue::CalcRemainingSize(long long* pRemaining, long long* pRemainingForced) { long long lRemainingSize = 0; long long lRemainingForced = 0; for (NZBList::iterator it = m_Queue.begin(); it != m_Queue.end(); it++) { NZBInfo* pNZBInfo = *it; for (FileList::iterator it2 = pNZBInfo->GetFileList()->begin(); it2 != pNZBInfo->GetFileList()->end(); it2++) { FileInfo* pFileInfo = *it2; if (!pFileInfo->GetPaused() && !pFileInfo->GetDeleted()) { lRemainingSize += pFileInfo->GetRemainingSize(); if (pNZBInfo->GetForcePriority()) { lRemainingForced += pFileInfo->GetRemainingSize(); } } } } *pRemaining = lRemainingSize; if (pRemainingForced) { *pRemainingForced = lRemainingForced; } } nzbget-16.4/daemon/queue/DupeCoordinator.h0000644000175000017500000000367012630544544020427 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 "DownloadInfo.h" class DupeCoordinator { public: enum EDupeStatus { dsNone = 0, dsQueued = 1, dsDownloading = 2, dsSuccess = 4, dsWarning = 8, dsFailure = 16 }; private: void ReturnBestDupe(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, const char* szNZBName, const char* szDupeKey); void HistoryCleanup(DownloadQueue* pDownloadQueue, HistoryInfo* pMarkHistoryInfo); bool SameNameOrKey(const char* szName1, const char* szDupeKey1, const char* szName2, const char* szDupeKey2); public: void NZBCompleted(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo); void NZBFound(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo); void HistoryMark(DownloadQueue* pDownloadQueue, HistoryInfo* pHistoryInfo, NZBInfo::EMarkStatus eMarkStatus); EDupeStatus GetDupeStatus(DownloadQueue* pDownloadQueue, const char* szName, const char* szDupeKey); void ListHistoryDupes(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, NZBList* pDupeList); }; extern DupeCoordinator* g_pDupeCoordinator; #endif nzbget-16.4/daemon/queue/DiskState.h0000644000175000017500000001220712630544544017215 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 DISKSTATE_H #define DISKSTATE_H #include "DownloadInfo.h" #include "FeedInfo.h" #include "NewsServer.h" #include "StatMeter.h" #include "Log.h" class DiskState { private: int fscanf(FILE* infile, const char* Format, ...); bool SaveFileInfo(FileInfo* pFileInfo, const char* szFilename); bool LoadFileInfo(FileInfo* pFileInfo, const char* szFilename, bool bFileSummary, bool bArticles); void SaveNZBQueue(DownloadQueue* pDownloadQueue, FILE* outfile); bool LoadNZBList(NZBList* pNZBList, Servers* pServers, FILE* infile, int iFormatVersion); void SaveNZBInfo(NZBInfo* pNZBInfo, FILE* outfile); bool LoadNZBInfo(NZBInfo* pNZBInfo, Servers* pServers, FILE* infile, int iFormatVersion); void SavePostQueue(DownloadQueue* pDownloadQueue, FILE* outfile); void SaveDupInfo(DupInfo* pDupInfo, FILE* outfile); bool LoadDupInfo(DupInfo* pDupInfo, FILE* infile, int iFormatVersion); void SaveHistory(DownloadQueue* pDownloadQueue, FILE* outfile); bool LoadHistory(DownloadQueue* pDownloadQueue, NZBList* pNZBList, Servers* pServers, FILE* infile, int iFormatVersion); NZBInfo* FindNZBInfo(DownloadQueue* pDownloadQueue, int iID); 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); bool SaveServerInfo(Servers* pServers, FILE* outfile); bool LoadServerInfo(Servers* pServers, FILE* infile, int iFormatVersion, bool* pPerfectMatch); bool SaveVolumeStat(ServerVolumes* pServerVolumes, FILE* outfile); bool LoadVolumeStat(Servers* pServers, ServerVolumes* pServerVolumes, FILE* infile, int iFormatVersion); void CalcFileStats(DownloadQueue* pDownloadQueue, int iFormatVersion); void CalcNZBFileStats(NZBInfo* pNZBInfo, int iFormatVersion); bool LoadAllFileStates(DownloadQueue* pDownloadQueue, Servers* pServers); void SaveServerStats(ServerStatList* pServerStatList, FILE* outfile); bool LoadServerStats(ServerStatList* pServerStatList, Servers* pServers, FILE* infile); bool FinishWriteTransaction(const char* szNewFileName, const char* szDestFileName); // backward compatibility functions (conversions from older formats) bool LoadPostQueue12(DownloadQueue* pDownloadQueue, NZBList* pNZBList, FILE* infile, int iFormatVersion); bool LoadPostQueue5(DownloadQueue* pDownloadQueue, NZBList* pNZBList); bool LoadUrlQueue12(DownloadQueue* pDownloadQueue, FILE* infile, int iFormatVersion); bool LoadUrlInfo12(NZBInfo* pNZBInfo, FILE* infile, int iFormatVersion); int FindNZBInfoIndex(NZBList* pNZBList, NZBInfo* pNZBInfo); void ConvertDupeKey(char* buf, int bufsize); bool LoadFileQueue12(NZBList* pNZBList, NZBList* pSortList, FILE* infile, int iFormatVersion); void CompleteNZBList12(DownloadQueue* pDownloadQueue, NZBList* pNZBList, int iFormatVersion); void CompleteDupList12(DownloadQueue* pDownloadQueue, int iFormatVersion); void CalcCriticalHealth(NZBList* pNZBList); public: bool DownloadQueueExists(); bool SaveDownloadQueue(DownloadQueue* pDownloadQueue); bool LoadDownloadQueue(DownloadQueue* pDownloadQueue, Servers* pServers); bool SaveFile(FileInfo* pFileInfo); bool SaveFileState(FileInfo* pFileInfo, bool bCompleted); bool LoadFileState(FileInfo* pFileInfo, Servers* pServers, bool bCompleted); bool LoadArticles(FileInfo* pFileInfo); void DiscardDownloadQueue(); void DiscardFile(FileInfo* pFileInfo, bool bDeleteData, bool bDeletePartialState, bool bDeleteCompletedState); void DiscardFiles(NZBInfo* pNZBInfo); bool SaveFeeds(Feeds* pFeeds, FeedHistory* pFeedHistory); bool LoadFeeds(Feeds* pFeeds, FeedHistory* pFeedHistory); bool SaveStats(Servers* pServers, ServerVolumes* pServerVolumes); bool LoadStats(Servers* pServers, ServerVolumes* pServerVolumes, bool* pPerfectMatch); void CleanupTempDir(DownloadQueue* pDownloadQueue); void WriteCacheFlag(); void DeleteCacheFlag(); void AppendNZBMessage(int iNZBID, Message::EKind eKind, const char* szText); void LoadNZBMessages(int iNZBID, MessageList* pMessages); }; extern DiskState* g_pDiskState; #endif nzbget-16.4/daemon/queue/QueueCoordinator.h0000644000175000017500000000740712630544544020620 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 QUEUECOORDINATOR_H #define QUEUECOORDINATOR_H #include #include #include "Log.h" #include "Thread.h" #include "NZBFile.h" #include "ArticleDownloader.h" #include "DownloadInfo.h" #include "Observer.h" #include "QueueEditor.h" #include "NNTPConnection.h" class QueueCoordinator : public Thread, public Observer, public Debuggable { public: typedef std::list ActiveDownloads; private: class CoordinatorDownloadQueue : public DownloadQueue { private: QueueCoordinator* m_pOwner; bool m_bMassEdit; bool m_bWantSave; friend class QueueCoordinator; public: CoordinatorDownloadQueue(): m_bMassEdit(false), m_bWantSave(false) {} virtual bool EditEntry(int ID, EEditAction eAction, int iOffset, const char* szText); virtual bool EditList(IDList* pIDList, NameList* pNameList, EMatchMode eMatchMode, EEditAction eAction, int iOffset, const char* szText); virtual void Save(); }; private: CoordinatorDownloadQueue m_DownloadQueue; ActiveDownloads m_ActiveDownloads; QueueEditor m_QueueEditor; bool m_bHasMoreJobs; int m_iDownloadsLimit; int m_iServerConfigGeneration; bool GetNextArticle(DownloadQueue* pDownloadQueue, FileInfo* &pFileInfo, ArticleInfo* &pArticleInfo); void StartArticleDownload(FileInfo* pFileInfo, ArticleInfo* pArticleInfo, NNTPConnection* pConnection); void ArticleCompleted(ArticleDownloader* pArticleDownloader); void DeleteFileInfo(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo, bool bCompleted); void StatFileInfo(FileInfo* pFileInfo, bool bCompleted); void CheckHealth(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo); void ResetHangingDownloads(); void AdjustDownloadsLimit(); void Load(); void SavePartialState(); protected: virtual void LogDebugInfo(); public: QueueCoordinator(); virtual ~QueueCoordinator(); virtual void Run(); virtual void Stop(); void Update(Subject* Caller, void* Aspect); // editing queue void AddNZBFileToQueue(NZBFile* pNZBFile, NZBInfo* pUrlInfo, bool bAddFirst); void CheckDupeFileInfos(NZBInfo* pNZBInfo); bool HasMoreJobs() { return m_bHasMoreJobs; } void DiscardDiskFile(FileInfo* pFileInfo); bool DeleteQueueEntry(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo); bool SetQueueEntryCategory(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, const char* szCategory); bool SetQueueEntryName(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, const char* szName); bool MergeQueueEntries(DownloadQueue* pDownloadQueue, NZBInfo* pDestNZBInfo, NZBInfo* pSrcNZBInfo); bool SplitQueueEntries(DownloadQueue* pDownloadQueue, FileList* pFileList, const char* szName, NZBInfo** pNewNZBInfo); }; extern QueueCoordinator* g_pQueueCoordinator; #endif nzbget-16.4/daemon/queue/QueueEditor.h0000644000175000017500000000632612630544544017562 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 QUEUEEDITOR_H #define QUEUEEDITOR_H #include #include "DownloadInfo.h" class QueueEditor { public: class EditItem { public: int m_iOffset; FileInfo* m_pFileInfo; NZBInfo* m_pNZBInfo; EditItem(FileInfo* pFileInfo, NZBInfo* pNZBInfo, int iOffset); }; typedef std::vector ItemList; private: DownloadQueue* m_pDownloadQueue; private: FileInfo* FindFileInfo(int iID); bool InternEditList(ItemList* pItemList, IDList* pIDList, DownloadQueue::EEditAction eAction, int iOffset, const char* szText); void PrepareList(ItemList* pItemList, IDList* pIDList, DownloadQueue::EEditAction eAction, int iOffset); bool BuildIDListFromNameList(IDList* pIDList, NameList* pNameList, DownloadQueue::EMatchMode eMatchMode, DownloadQueue::EEditAction eAction); bool EditGroup(NZBInfo* pNZBInfo, DownloadQueue::EEditAction eAction, int iOffset, const char* szText); void PauseParsInGroups(ItemList* pItemList, bool bExtraParsOnly); void PausePars(FileList* pFileList, bool bExtraParsOnly); void SetNZBPriority(NZBInfo* pNZBInfo, const char* szPriority); void SetNZBCategory(NZBInfo* pNZBInfo, const char* szCategory, bool bApplyParams); void SetNZBName(NZBInfo* pNZBInfo, const char* szName); bool CanCleanupDisk(NZBInfo* pNZBInfo); bool MergeGroups(ItemList* pItemList); bool SortGroups(ItemList* pItemList, const char* szSort); bool SplitGroup(ItemList* pItemList, const char* szName); bool DeleteUrl(NZBInfo* pNZBInfo, DownloadQueue::EEditAction eAction); void ReorderFiles(ItemList* pItemList); void SetNZBParameter(NZBInfo* pNZBInfo, const char* szParamString); void SetNZBDupeParam(NZBInfo* pNZBInfo, DownloadQueue::EEditAction eAction, const char* szText); void PauseUnpauseEntry(FileInfo* pFileInfo, bool bPause); void DeleteEntry(FileInfo* pFileInfo); void MoveEntry(FileInfo* pFileInfo, int iOffset); void MoveGroup(NZBInfo* pNZBInfo, int iOffset); public: QueueEditor(); ~QueueEditor(); bool EditEntry(DownloadQueue* pDownloadQueue, int ID, DownloadQueue::EEditAction eAction, int iOffset, const char* szText); bool EditList(DownloadQueue* pDownloadQueue, IDList* pIDList, NameList* pNameList, DownloadQueue::EMatchMode eMatchMode, DownloadQueue::EEditAction eAction, int iOffset, const char* szText); }; #endif nzbget-16.4/daemon/queue/HistoryCoordinator.h0000644000175000017500000000521712630544544021172 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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: 951 $ * $Date$ * */ #ifndef HISTORYCOORDINATOR_H #define HISTORYCOORDINATOR_H #include "DownloadInfo.h" #include "Service.h" class HistoryCoordinator : public Service { private: 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); bool HistorySetParameter(HistoryInfo* pHistoryInfo, const char* szText); void HistorySetDupeParam(HistoryInfo* pHistoryInfo, DownloadQueue::EEditAction eAction, const char* szText); bool HistorySetCategory(HistoryInfo* pHistoryInfo, const char* szText); bool HistorySetName(HistoryInfo* pHistoryInfo, const char* szText); void HistoryTransformToDup(DownloadQueue* pDownloadQueue, HistoryInfo* pHistoryInfo, int rindex); void SaveQueue(DownloadQueue* pDownloadQueue); void PrepareEdit(DownloadQueue* pDownloadQueue, IDList* pIDList, DownloadQueue::EEditAction eAction); protected: virtual int ServiceInterval() { return 600000; } virtual void ServiceWork(); public: HistoryCoordinator(); virtual ~HistoryCoordinator(); void AddToHistory(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo); bool EditList(DownloadQueue* pDownloadQueue, IDList* pIDList, DownloadQueue::EEditAction eAction, int iOffset, const char* szText); void DeleteDiskFiles(NZBInfo* pNZBInfo); void HistoryHide(DownloadQueue* pDownloadQueue, HistoryInfo* pHistoryInfo, int rindex); void Redownload(DownloadQueue* pDownloadQueue, HistoryInfo* pHistoryInfo); }; extern HistoryCoordinator* g_pHistoryCoordinator; #endif nzbget-16.4/daemon/queue/UrlCoordinator.cpp0000644000175000017500000003076312630544544020632 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2012-2015 Andrey Prygunkov * * 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 #include #include #include #ifndef WIN32 #include #include #endif #include "nzbget.h" #include "UrlCoordinator.h" #include "Options.h" #include "WebDownloader.h" #include "Util.h" #include "NZBFile.h" #include "Scanner.h" #include "DiskState.h" #include "QueueScript.h" UrlDownloader::UrlDownloader() : WebDownloader() { m_szCategory = NULL; } UrlDownloader::~UrlDownloader() { free(m_szCategory); } void UrlDownloader::ProcessHeader(const char* szLine) { WebDownloader::ProcessHeader(szLine); if (!strncmp(szLine, "X-DNZB-Category:", 16)) { 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 = '\0'; 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_pNZBInfo->GetParameters()->SetParameter(szParamName, szVal); free(szVal); } free(szModLine); } } UrlCoordinator::UrlCoordinator() { debug("Creating UrlCoordinator"); m_bHasMoreJobs = true; g_pLog->RegisterDebuggable(this); } UrlCoordinator::~UrlCoordinator() { debug("Destroying UrlCoordinator"); // Cleanup g_pLog->UnregisterDebuggable(this); debug("Deleting UrlDownloaders"); for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++) { delete *it; } m_ActiveDownloads.clear(); debug("UrlCoordinator destroyed"); } void UrlCoordinator::Run() { debug("Entering UrlCoordinator-loop"); while (!DownloadQueue::IsLoaded()) { usleep(20 * 1000); } int iResetCounter = 0; while (!IsStopped()) { bool bDownloadStarted = false; if (!g_pOptions->GetPauseDownload() || g_pOptions->GetUrlForce()) { // start download for next URL DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); if ((int)m_ActiveDownloads.size() < g_pOptions->GetUrlConnections()) { NZBInfo* pNZBInfo = GetNextUrl(pDownloadQueue); bool bHasMoreUrls = pNZBInfo != NULL; bool bUrlDownloadsRunning = !m_ActiveDownloads.empty(); m_bHasMoreJobs = bHasMoreUrls || bUrlDownloadsRunning; if (bHasMoreUrls && !IsStopped()) { StartUrlDownload(pNZBInfo); bDownloadStarted = true; } } DownloadQueue::Unlock(); } int iSleepInterval = bDownloadStarted ? 0 : 100; usleep(iSleepInterval * 1000); iResetCounter += iSleepInterval; if (iResetCounter >= 1000) { // this code should not be called too often, once per second is OK ResetHangingDownloads(); iResetCounter = 0; } } // waiting for downloads debug("UrlCoordinator: waiting for Downloads to complete"); bool completed = false; while (!completed) { DownloadQueue::Lock(); completed = m_ActiveDownloads.size() == 0; DownloadQueue::Unlock(); usleep(100 * 1000); ResetHangingDownloads(); } debug("UrlCoordinator: Downloads are completed"); debug("Exiting UrlCoordinator-loop"); } void UrlCoordinator::Stop() { Thread::Stop(); debug("Stopping UrlDownloads"); DownloadQueue::Lock(); for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++) { (*it)->Stop(); } DownloadQueue::Unlock(); debug("UrlDownloads are notified"); } void UrlCoordinator::ResetHangingDownloads() { const int TimeOut = g_pOptions->GetTerminateTimeout(); if (TimeOut == 0) { return; } DownloadQueue::Lock(); time_t tm = time(NULL); for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end();) { UrlDownloader* pUrlDownloader = *it; if (tm - pUrlDownloader->GetLastUpdateTime() > TimeOut && pUrlDownloader->GetStatus() == UrlDownloader::adRunning) { NZBInfo* pNZBInfo = pUrlDownloader->GetNZBInfo(); debug("Terminating hanging download %s", pUrlDownloader->GetInfoName()); if (pUrlDownloader->Terminate()) { error("Terminated hanging download %s", pUrlDownloader->GetInfoName()); pNZBInfo->SetUrlStatus(NZBInfo::lsNone); } else { error("Could not terminate hanging download %s", pUrlDownloader->GetInfoName()); } m_ActiveDownloads.erase(it); // it's not safe to destroy pUrlDownloader, because the state of object is unknown delete pUrlDownloader; it = m_ActiveDownloads.begin(); continue; } it++; } DownloadQueue::Unlock(); } void UrlCoordinator::LogDebugInfo() { info(" ---------- UrlCoordinator"); DownloadQueue::Lock(); info(" Active Downloads: %i", m_ActiveDownloads.size()); for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++) { UrlDownloader* pUrlDownloader = *it; pUrlDownloader->LogDebugInfo(); } DownloadQueue::Unlock(); } void UrlCoordinator::AddUrlToQueue(NZBInfo* pNZBInfo, bool bAddTop) { debug("Adding NZB-URL to queue"); DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); if (bAddTop) { pDownloadQueue->GetQueue()->push_front(pNZBInfo); } else { pDownloadQueue->GetQueue()->push_back(pNZBInfo); } pDownloadQueue->Save(); DownloadQueue::Unlock(); } /* * Returns next URL for download. */ NZBInfo* UrlCoordinator::GetNextUrl(DownloadQueue* pDownloadQueue) { bool bPauseDownload = g_pOptions->GetPauseDownload(); NZBInfo* pNZBInfo = NULL; for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo1 = *it; if (pNZBInfo1->GetKind() == NZBInfo::nkUrl && pNZBInfo1->GetUrlStatus() == NZBInfo::lsNone && pNZBInfo1->GetDeleteStatus() == NZBInfo::dsNone && (!bPauseDownload || g_pOptions->GetUrlForce()) && (!pNZBInfo || pNZBInfo1->GetPriority() > pNZBInfo->GetPriority())) { pNZBInfo = pNZBInfo1; } } return pNZBInfo; } void UrlCoordinator::StartUrlDownload(NZBInfo* pNZBInfo) { debug("Starting new UrlDownloader"); UrlDownloader* pUrlDownloader = new UrlDownloader(); pUrlDownloader->SetAutoDestroy(true); pUrlDownloader->Attach(this); pUrlDownloader->SetNZBInfo(pNZBInfo); pUrlDownloader->SetURL(pNZBInfo->GetURL()); pUrlDownloader->SetForce(g_pOptions->GetUrlForce()); pNZBInfo->SetActiveDownloads(1); char tmp[1024]; pNZBInfo->MakeNiceUrlName(pNZBInfo->GetURL(), pNZBInfo->GetFilename(), tmp, 1024); pUrlDownloader->SetInfoName(tmp); snprintf(tmp, 1024, "%surl-%i.tmp", g_pOptions->GetTempDir(), pNZBInfo->GetID()); tmp[1024-1] = '\0'; pUrlDownloader->SetOutputFilename(tmp); pNZBInfo->SetUrlStatus(NZBInfo::lsRunning); m_ActiveDownloads.push_back(pUrlDownloader); pUrlDownloader->Start(); } void UrlCoordinator::Update(Subject* pCaller, void* pAspect) { debug("Notification from UrlDownloader received"); UrlDownloader* pUrlDownloader = (UrlDownloader*) pCaller; if ((pUrlDownloader->GetStatus() == WebDownloader::adFinished) || (pUrlDownloader->GetStatus() == WebDownloader::adFailed) || (pUrlDownloader->GetStatus() == WebDownloader::adRetry)) { UrlCompleted(pUrlDownloader); } } void UrlCoordinator::UrlCompleted(UrlDownloader* pUrlDownloader) { debug("URL downloaded"); NZBInfo* pNZBInfo = pUrlDownloader->GetNZBInfo(); char filename[1024]; if (pUrlDownloader->GetOriginalFilename()) { strncpy(filename, pUrlDownloader->GetOriginalFilename(), 1024); filename[1024-1] = '\0'; } else { strncpy(filename, Util::BaseFileName(pNZBInfo->GetURL()), 1024); filename[1024-1] = '\0'; // TODO: decode URL escaping } Util::MakeValidFilename(filename, '_', false); debug("Filename: [%s]", filename); DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); // delete Download from active jobs for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++) { UrlDownloader* pa = *it; if (pa == pUrlDownloader) { m_ActiveDownloads.erase(it); break; } } pNZBInfo->SetActiveDownloads(0); bool bRetry = pUrlDownloader->GetStatus() == WebDownloader::adRetry && !pNZBInfo->GetDeleting(); if (pNZBInfo->GetDeleting()) { pNZBInfo->SetDeleteStatus(NZBInfo::dsManual); pNZBInfo->SetUrlStatus(NZBInfo::lsNone); pNZBInfo->SetDeleting(false); } else if (pUrlDownloader->GetStatus() == WebDownloader::adFinished) { pNZBInfo->SetUrlStatus(NZBInfo::lsFinished); } else if (pUrlDownloader->GetStatus() == WebDownloader::adFailed) { pNZBInfo->SetUrlStatus(NZBInfo::lsFailed); } else if (pUrlDownloader->GetStatus() == WebDownloader::adRetry) { pNZBInfo->SetUrlStatus(NZBInfo::lsNone); } if (!bRetry) { DownloadQueue::Aspect aspect = { DownloadQueue::eaUrlCompleted, pDownloadQueue, pNZBInfo, NULL }; pDownloadQueue->Notify(&aspect); } DownloadQueue::Unlock(); if (bRetry) { return; } if (pNZBInfo->GetUrlStatus() == NZBInfo::lsFinished) { // add nzb-file to download queue Scanner::EAddStatus eAddStatus = g_pScanner->AddExternalFile( !Util::EmptyStr(pNZBInfo->GetFilename()) ? pNZBInfo->GetFilename() : filename, !Util::EmptyStr(pNZBInfo->GetCategory()) ? pNZBInfo->GetCategory() : pUrlDownloader->GetCategory(), pNZBInfo->GetPriority(), pNZBInfo->GetDupeKey(), pNZBInfo->GetDupeScore(), pNZBInfo->GetDupeMode(), pNZBInfo->GetParameters(), false, pNZBInfo->GetAddUrlPaused(), pNZBInfo, pUrlDownloader->GetOutputFilename(), NULL, 0, NULL); if (eAddStatus == Scanner::asSuccess) { // if scanner has successfully added nzb-file to queue, our pNZBInfo is // already removed from queue and destroyed return; } pNZBInfo->SetUrlStatus(eAddStatus == Scanner::asFailed ? NZBInfo::lsScanFailed : NZBInfo::lsScanSkipped); } // the rest of function is only for failed URLs or for failed scans g_pQueueScriptCoordinator->EnqueueScript(pNZBInfo, QueueScriptCoordinator::qeUrlCompleted); pDownloadQueue = DownloadQueue::Lock(); // delete URL from queue pDownloadQueue->GetQueue()->Remove(pNZBInfo); bool bDeleteObj = true; // add failed URL to history if (g_pOptions->GetKeepHistory() > 0 && pNZBInfo->GetUrlStatus() != NZBInfo::lsFinished && !pNZBInfo->GetAvoidHistory()) { HistoryInfo* pHistoryInfo = new HistoryInfo(pNZBInfo); pHistoryInfo->SetTime(time(NULL)); pDownloadQueue->GetHistory()->push_front(pHistoryInfo); bDeleteObj = false; } pDownloadQueue->Save(); DownloadQueue::Unlock(); if (bDeleteObj) { g_pDiskState->DiscardFiles(pNZBInfo); delete pNZBInfo; } } bool UrlCoordinator::DeleteQueueEntry(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, bool bAvoidHistory) { if (pNZBInfo->GetActiveDownloads() > 0) { info("Deleting active URL %s", pNZBInfo->GetName()); pNZBInfo->SetDeleting(true); pNZBInfo->SetAvoidHistory(bAvoidHistory); for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++) { UrlDownloader* pUrlDownloader = *it; if (pUrlDownloader->GetNZBInfo() == pNZBInfo) { pUrlDownloader->Stop(); return true; } } } info("Deleting URL %s", pNZBInfo->GetName()); pNZBInfo->SetDeleteStatus(NZBInfo::dsManual); pNZBInfo->SetUrlStatus(NZBInfo::lsNone); pDownloadQueue->GetQueue()->Remove(pNZBInfo); if (g_pOptions->GetKeepHistory() > 0 && !bAvoidHistory) { HistoryInfo* pHistoryInfo = new HistoryInfo(pNZBInfo); pHistoryInfo->SetTime(time(NULL)); pDownloadQueue->GetHistory()->push_front(pHistoryInfo); } else { g_pDiskState->DiscardFiles(pNZBInfo); delete pNZBInfo; } return true; } nzbget-16.4/daemon/queue/UrlCoordinator.h0000644000175000017500000000462612630544544020276 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2012-2015 Andrey Prygunkov * * 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 URLCOORDINATOR_H #define URLCOORDINATOR_H #include #include #include #include "Log.h" #include "Thread.h" #include "WebDownloader.h" #include "DownloadInfo.h" #include "Observer.h" class UrlDownloader; class UrlCoordinator : public Thread, public Observer, public Debuggable { private: typedef std::list ActiveDownloads; private: ActiveDownloads m_ActiveDownloads; bool m_bHasMoreJobs; bool m_bForce; NZBInfo* GetNextUrl(DownloadQueue* pDownloadQueue); void StartUrlDownload(NZBInfo* pNZBInfo); void UrlCompleted(UrlDownloader* pUrlDownloader); void ResetHangingDownloads(); protected: virtual void LogDebugInfo(); public: UrlCoordinator(); virtual ~UrlCoordinator(); virtual void Run(); virtual void Stop(); void Update(Subject* pCaller, void* pAspect); // Editing the queue void AddUrlToQueue(NZBInfo* pNZBInfo, bool bAddTop); bool HasMoreJobs() { return m_bHasMoreJobs; } bool DeleteQueueEntry(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, bool bAvoidHistory); }; extern UrlCoordinator* g_pUrlCoordinator; class UrlDownloader : public WebDownloader { private: NZBInfo* m_pNZBInfo; char* m_szCategory; protected: virtual void ProcessHeader(const char* szLine); public: UrlDownloader(); ~UrlDownloader(); void SetNZBInfo(NZBInfo* pNZBInfo) { m_pNZBInfo = pNZBInfo; } NZBInfo* GetNZBInfo() { return m_pNZBInfo; } const char* GetCategory() { return m_szCategory; } }; #endif nzbget-16.4/daemon/queue/DiskState.cpp0000644000175000017500000024475012630544544017562 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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" #else #include #include #endif #include #include #include #include #include #include #include #include #include #include "nzbget.h" #include "DiskState.h" #include "Options.h" #include "Log.h" #include "Util.h" static const char* FORMATVERSION_SIGNATURE = "nzbget diskstate file version "; #if (defined(WIN32) && _MSC_VER < 1800) // Visual Studio prior 2013 doesn't have standard "vsscanf" // Hack from http://stackoverflow.com/questions/2457331/replacement-for-vsscanf-on-msvc int vsscanf(const char *s, const char *fmt, va_list ap) { // up to max 10 arguments void *a[10]; for (int i = 0; i < sizeof(a)/sizeof(a[0]); i++) { a[i] = va_arg(ap, void*); } return sscanf(s, fmt, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9]); } #endif /* Parse signature and return format version number */ int ParseFormatVersion(const char* szFormatSignature) { if (strncmp(szFormatSignature, FORMATVERSION_SIGNATURE, strlen(FORMATVERSION_SIGNATURE))) { return 0; } return atoi(szFormatSignature + strlen(FORMATVERSION_SIGNATURE)); } class StateFile { private: char m_szDestFilename[1024]; char m_szTempFilename[1024]; int m_iFormatVersion; int m_iFileVersion; FILE* m_pFile; public: StateFile(const char* szFilename, int iFormatVersion); ~StateFile(); void Discard(); bool FileExists(); FILE* BeginWriteTransaction(); bool FinishWriteTransaction(); FILE* BeginReadTransaction(); int GetFileVersion() { return m_iFileVersion; } const char* GetDestFilename() { return m_szDestFilename; } }; StateFile::StateFile(const char* szFilename, int iFormatVersion) { m_pFile = NULL; m_iFormatVersion = iFormatVersion; snprintf(m_szDestFilename, 1024, "%s%s", g_pOptions->GetQueueDir(), szFilename); m_szDestFilename[1024-1] = '\0'; snprintf(m_szTempFilename, 1024, "%s%s.new", g_pOptions->GetQueueDir(), szFilename); m_szTempFilename[1024-1] = '\0'; } StateFile::~StateFile() { if (m_pFile) { fclose(m_pFile); } } void StateFile::Discard() { remove(m_szDestFilename); } bool StateFile::FileExists() { return Util::FileExists(m_szDestFilename) || Util::FileExists(m_szTempFilename); } FILE* StateFile::BeginWriteTransaction() { m_pFile = fopen(m_szTempFilename, FOPEN_WB); if (!m_pFile) { char szErrBuf[256]; Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf)); error("Error saving diskstate: Could not create file %s: %s", m_szTempFilename, szErrBuf); return NULL; } fprintf(m_pFile, "%s%i\n", FORMATVERSION_SIGNATURE, m_iFormatVersion); return m_pFile; } bool StateFile::FinishWriteTransaction() { char szErrBuf[256]; // flush file content before renaming if (g_pOptions->GetFlushQueue()) { debug("Flushing data for file %s", Util::BaseFileName(m_szTempFilename)); fflush(m_pFile); if (!Util::FlushFileBuffers(fileno(m_pFile), szErrBuf, sizeof(szErrBuf))) { warn("Could not flush file %s into disk: %s", m_szTempFilename, szErrBuf); } } fclose(m_pFile); m_pFile = NULL; // now rename to dest file name remove(m_szDestFilename); if (rename(m_szTempFilename, m_szDestFilename)) { Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf)); error("Error saving diskstate: Could not rename file %s to %s: %s", m_szTempFilename, m_szDestFilename, szErrBuf); return false; } // flush directory buffer after renaming if (g_pOptions->GetFlushQueue()) { debug("Flushing directory for file %s", Util::BaseFileName(m_szDestFilename)); if (!Util::FlushDirBuffers(m_szDestFilename, szErrBuf, sizeof(szErrBuf))) { warn("Could not flush directory buffers for file %s into disk: %s", m_szDestFilename, szErrBuf); } } return true; } FILE* StateFile::BeginReadTransaction() { if (!Util::FileExists(m_szDestFilename) && Util::FileExists(m_szTempFilename)) { // disaster recovery: temp-file exists but the dest-file doesn't warn("Restoring diskstate file %s from %s", Util::BaseFileName(m_szDestFilename), Util::BaseFileName(m_szTempFilename)); if (rename(m_szTempFilename, m_szDestFilename)) { char szErrBuf[256]; Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf)); error("Error restoring diskstate: Could not rename file %s to %s: %s", m_szTempFilename, m_szDestFilename, szErrBuf); return NULL; } } m_pFile = fopen(m_szDestFilename, FOPEN_RB); if (!m_pFile) { char szErrBuf[256]; Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf)); error("Error reading diskstate: could not open file %s: %s", m_szDestFilename, szErrBuf); return NULL; } char FileSignatur[128]; fgets(FileSignatur, sizeof(FileSignatur), m_pFile); m_iFileVersion = ParseFormatVersion(FileSignatur); if (m_iFileVersion > m_iFormatVersion) { error("Could not load diskstate due to file version mismatch"); fclose(m_pFile); m_pFile = NULL; return NULL; } return m_pFile; } /* * Standard fscanf scans beoynd current line if the next line is empty. * This wrapper fixes that. */ int DiskState::fscanf(FILE* infile, const char* Format, ...) { char szLine[1024]; if (!fgets(szLine, sizeof(szLine), infile)) { return 0; } va_list ap; va_start(ap, Format); int res = vsscanf(szLine, Format, ap); va_end(ap); return res; } /* Save Download Queue to Disk. * The Disk State consists of file "queue", which contains the order of files, * and of one diskstate-file for each file in download queue. * This function saves file "queue" and files with NZB-info. It does not * save file-infos. * * For safety: * - first save to temp-file (queue.new) * - then delete queue * - then rename queue.new to queue */ bool DiskState::SaveDownloadQueue(DownloadQueue* pDownloadQueue) { debug("Saving queue to disk"); StateFile stateFile("queue", 55); if (pDownloadQueue->GetQueue()->empty() && pDownloadQueue->GetHistory()->empty()) { stateFile.Discard(); return true; } FILE* outfile = stateFile.BeginWriteTransaction(); if (!outfile) { return false; } // save nzb-infos SaveNZBQueue(pDownloadQueue, outfile); // save history SaveHistory(pDownloadQueue, outfile); // now rename to dest file name return stateFile.FinishWriteTransaction(); } bool DiskState::LoadDownloadQueue(DownloadQueue* pDownloadQueue, Servers* pServers) { debug("Loading queue from disk"); StateFile stateFile("queue", 55); FILE* infile = stateFile.BeginReadTransaction(); if (!infile) { return false; } bool bOK = false; int iFormatVersion = stateFile.GetFileVersion(); NZBList nzbList(false); NZBList sortList(false); if (iFormatVersion < 43) { // load nzb-infos if (!LoadNZBList(&nzbList, pServers, infile, iFormatVersion)) goto error; // load file-infos if (!LoadFileQueue12(&nzbList, &sortList, infile, iFormatVersion)) goto error; } else { if (!LoadNZBList(pDownloadQueue->GetQueue(), pServers, infile, iFormatVersion)) goto error; } if (iFormatVersion >= 7 && iFormatVersion < 45) { // load post-queue from v12 if (!LoadPostQueue12(pDownloadQueue, &nzbList, infile, iFormatVersion)) goto error; } else if (iFormatVersion < 7) { // load post-queue from v5 LoadPostQueue5(pDownloadQueue, &nzbList); } if (iFormatVersion >= 15 && iFormatVersion < 46) { // load url-queue if (!LoadUrlQueue12(pDownloadQueue, infile, iFormatVersion)) goto error; } if (iFormatVersion >= 9) { // load history if (!LoadHistory(pDownloadQueue, &nzbList, pServers, infile, iFormatVersion)) goto error; } if (iFormatVersion >= 9 && iFormatVersion < 43) { // load parked file-infos if (!LoadFileQueue12(&nzbList, NULL, infile, iFormatVersion)) goto error; } if (iFormatVersion < 29) { CalcCriticalHealth(&nzbList); } if (iFormatVersion < 43) { // finalize queue reading CompleteNZBList12(pDownloadQueue, &sortList, iFormatVersion); } if (iFormatVersion < 47) { CompleteDupList12(pDownloadQueue, iFormatVersion); } if (!LoadAllFileStates(pDownloadQueue, pServers)) goto error; bOK = true; error: if (!bOK) { error("Error reading diskstate for download queue and history"); } NZBInfo::ResetGenID(true); FileInfo::ResetGenID(true); if (iFormatVersion > 0) { CalcFileStats(pDownloadQueue, iFormatVersion); } return bOK; } void DiskState::CompleteNZBList12(DownloadQueue* pDownloadQueue, NZBList* pNZBList, int iFormatVersion) { // put all NZBs referenced from file queue into pDownloadQueue->GetQueue() for (NZBList::iterator it = pNZBList->begin(); it != pNZBList->end(); it++) { NZBInfo* pNZBInfo = *it; pDownloadQueue->GetQueue()->push_back(pNZBInfo); } if (31 <= iFormatVersion && iFormatVersion < 42) { // due to a bug in r811 (v12-testing) new NZBIDs were generated on each refresh of web-ui // this resulted in very high numbers for NZBIDs // here we renumber NZBIDs in order to keep them low. NZBInfo::ResetGenID(false); int iID = 1; for (NZBList::iterator it = pNZBList->begin(); it != pNZBList->end(); it++) { NZBInfo* pNZBInfo = *it; pNZBInfo->SetID(iID++); } } } void DiskState::CompleteDupList12(DownloadQueue* pDownloadQueue, int iFormatVersion) { NZBInfo::ResetGenID(true); for (HistoryList::iterator it = pDownloadQueue->GetHistory()->begin(); it != pDownloadQueue->GetHistory()->end(); it++) { HistoryInfo* pHistoryInfo = *it; if (pHistoryInfo->GetKind() == HistoryInfo::hkDup) { pHistoryInfo->GetDupInfo()->SetID(NZBInfo::GenerateID()); } } } void DiskState::SaveNZBQueue(DownloadQueue* pDownloadQueue, FILE* outfile) { debug("Saving nzb list to disk"); fprintf(outfile, "%i\n", (int)pDownloadQueue->GetQueue()->size()); for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; SaveNZBInfo(pNZBInfo, outfile); } } bool DiskState::LoadNZBList(NZBList* pNZBList, Servers* pServers, FILE* infile, int iFormatVersion) { debug("Loading nzb list from disk"); // load nzb-infos int size; if (fscanf(infile, "%i\n", &size) != 1) goto error; for (int i = 0; i < size; i++) { NZBInfo* pNZBInfo = new NZBInfo(); pNZBList->push_back(pNZBInfo); if (!LoadNZBInfo(pNZBInfo, pServers, infile, iFormatVersion)) goto error; } return true; error: error("Error reading nzb list from disk"); return false; } void DiskState::SaveNZBInfo(NZBInfo* pNZBInfo, FILE* outfile) { fprintf(outfile, "%i\n", pNZBInfo->GetID()); fprintf(outfile, "%i\n", (int)pNZBInfo->GetKind()); fprintf(outfile, "%s\n", pNZBInfo->GetURL()); fprintf(outfile, "%s\n", pNZBInfo->GetFilename()); fprintf(outfile, "%s\n", pNZBInfo->GetDestDir()); fprintf(outfile, "%s\n", pNZBInfo->GetFinalDir()); fprintf(outfile, "%s\n", pNZBInfo->GetQueuedFilename()); fprintf(outfile, "%s\n", pNZBInfo->GetName()); fprintf(outfile, "%s\n", pNZBInfo->GetCategory()); fprintf(outfile, "%i,%i,%i,%i,%i\n", (int)pNZBInfo->GetPriority(), pNZBInfo->GetPostInfo() ? (int)pNZBInfo->GetPostInfo()->GetStage() + 1 : 0, (int)pNZBInfo->GetDeletePaused(), (int)pNZBInfo->GetManyDupeFiles(), pNZBInfo->GetFeedID()); fprintf(outfile, "%i,%i,%i,%i,%i,%i,%i\n", (int)pNZBInfo->GetParStatus(), (int)pNZBInfo->GetUnpackStatus(), (int)pNZBInfo->GetMoveStatus(), (int)pNZBInfo->GetRenameStatus(), (int)pNZBInfo->GetDeleteStatus(), (int)pNZBInfo->GetMarkStatus(), (int)pNZBInfo->GetUrlStatus()); fprintf(outfile, "%i,%i,%i\n", (int)pNZBInfo->GetUnpackCleanedUpDisk(), (int)pNZBInfo->GetHealthPaused(), (int)pNZBInfo->GetAddUrlPaused()); fprintf(outfile, "%i,%i,%i\n", pNZBInfo->GetFileCount(), pNZBInfo->GetParkedFileCount(), pNZBInfo->GetMessageCount()); fprintf(outfile, "%i,%i\n", (int)pNZBInfo->GetMinTime(), (int)pNZBInfo->GetMaxTime()); fprintf(outfile, "%i,%i,%i,%i\n", (int)pNZBInfo->GetParFull(), pNZBInfo->GetPostInfo() ? (int)pNZBInfo->GetPostInfo()->GetForceParFull() : 0, pNZBInfo->GetPostInfo() ? (int)pNZBInfo->GetPostInfo()->GetForceRepair() : 0, pNZBInfo->GetExtraParBlocks()); fprintf(outfile, "%u,%u\n", pNZBInfo->GetFullContentHash(), pNZBInfo->GetFilteredContentHash()); unsigned long High1, Low1, High2, Low2, High3, Low3; Util::SplitInt64(pNZBInfo->GetSize(), &High1, &Low1); Util::SplitInt64(pNZBInfo->GetSuccessSize(), &High2, &Low2); Util::SplitInt64(pNZBInfo->GetFailedSize(), &High3, &Low3); fprintf(outfile, "%lu,%lu,%lu,%lu,%lu,%lu\n", High1, Low1, High2, Low2, High3, Low3); Util::SplitInt64(pNZBInfo->GetParSize(), &High1, &Low1); Util::SplitInt64(pNZBInfo->GetParSuccessSize(), &High2, &Low2); Util::SplitInt64(pNZBInfo->GetParFailedSize(), &High3, &Low3); fprintf(outfile, "%lu,%lu,%lu,%lu,%lu,%lu\n", High1, Low1, High2, Low2, High3, Low3); fprintf(outfile, "%i,%i,%i\n", pNZBInfo->GetTotalArticles(), pNZBInfo->GetSuccessArticles(), pNZBInfo->GetFailedArticles()); fprintf(outfile, "%s\n", pNZBInfo->GetDupeKey()); fprintf(outfile, "%i,%i\n", (int)pNZBInfo->GetDupeMode(), pNZBInfo->GetDupeScore()); Util::SplitInt64(pNZBInfo->GetDownloadedSize(), &High1, &Low1); fprintf(outfile, "%lu,%lu,%i,%i,%i,%i,%i\n", High1, Low1, pNZBInfo->GetDownloadSec(), pNZBInfo->GetPostTotalSec(), pNZBInfo->GetParSec(), pNZBInfo->GetRepairSec(), pNZBInfo->GetUnpackSec()); fprintf(outfile, "%i\n", (int)pNZBInfo->GetCompletedFiles()->size()); for (CompletedFiles::iterator it = pNZBInfo->GetCompletedFiles()->begin(); it != pNZBInfo->GetCompletedFiles()->end(); it++) { CompletedFile* pCompletedFile = *it; fprintf(outfile, "%i,%i,%lu,%s\n", pCompletedFile->GetID(), (int)pCompletedFile->GetStatus(), pCompletedFile->GetCrc(), pCompletedFile->GetFileName()); } fprintf(outfile, "%i\n", (int)pNZBInfo->GetParameters()->size()); for (NZBParameterList::iterator it = pNZBInfo->GetParameters()->begin(); it != pNZBInfo->GetParameters()->end(); it++) { NZBParameter* pParameter = *it; fprintf(outfile, "%s=%s\n", pParameter->GetName(), pParameter->GetValue()); } fprintf(outfile, "%i\n", (int)pNZBInfo->GetScriptStatuses()->size()); for (ScriptStatusList::iterator it = pNZBInfo->GetScriptStatuses()->begin(); it != pNZBInfo->GetScriptStatuses()->end(); it++) { ScriptStatus* pScriptStatus = *it; fprintf(outfile, "%i,%s\n", pScriptStatus->GetStatus(), pScriptStatus->GetName()); } SaveServerStats(pNZBInfo->GetServerStats(), outfile); // save file-infos int iSize = 0; for (FileList::iterator it = pNZBInfo->GetFileList()->begin(); it != pNZBInfo->GetFileList()->end(); it++) { FileInfo* pFileInfo = *it; if (!pFileInfo->GetDeleted()) { iSize++; } } fprintf(outfile, "%i\n", iSize); for (FileList::iterator it = pNZBInfo->GetFileList()->begin(); it != pNZBInfo->GetFileList()->end(); it++) { FileInfo* pFileInfo = *it; if (!pFileInfo->GetDeleted()) { fprintf(outfile, "%i,%i,%i,%i\n", pFileInfo->GetID(), (int)pFileInfo->GetPaused(), (int)pFileInfo->GetTime(), (int)pFileInfo->GetExtraPriority()); } } } bool DiskState::LoadNZBInfo(NZBInfo* pNZBInfo, Servers* pServers, FILE* infile, int iFormatVersion) { char buf[10240]; if (iFormatVersion >= 24) { int iID; if (fscanf(infile, "%i\n", &iID) != 1) goto error; pNZBInfo->SetID(iID); } if (iFormatVersion >= 46) { int iKind; if (fscanf(infile, "%i\n", &iKind) != 1) goto error; pNZBInfo->SetKind((NZBInfo::EKind)iKind); if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' pNZBInfo->SetURL(buf); } if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' pNZBInfo->SetFilename(buf); if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' pNZBInfo->SetDestDir(buf); if (iFormatVersion >= 27) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' pNZBInfo->SetFinalDir(buf); } if (iFormatVersion >= 5) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' pNZBInfo->SetQueuedFilename(buf); } if (iFormatVersion >= 13) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' if (strlen(buf) > 0) { pNZBInfo->SetName(buf); } } if (iFormatVersion >= 4) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' pNZBInfo->SetCategory(buf); } if (true) // clang requires a block for goto to work { int iPriority = 0, iPostProcess = 0, iPostStage = 0, iDeletePaused = 0, iManyDupeFiles = 0, iFeedID = 0; if (iFormatVersion >= 54) { if (fscanf(infile, "%i,%i,%i,%i,%i\n", &iPriority, &iPostStage, &iDeletePaused, &iManyDupeFiles, &iFeedID) != 5) goto error; } else if (iFormatVersion >= 45) { if (fscanf(infile, "%i,%i,%i,%i\n", &iPriority, &iPostStage, &iDeletePaused, &iManyDupeFiles) != 4) goto error; } else if (iFormatVersion >= 44) { if (fscanf(infile, "%i,%i,%i,%i\n", &iPriority, &iPostProcess, &iDeletePaused, &iManyDupeFiles) != 4) goto error; } else if (iFormatVersion >= 41) { if (fscanf(infile, "%i,%i,%i\n", &iPostProcess, &iDeletePaused, &iManyDupeFiles) != 3) goto error; } else if (iFormatVersion >= 40) { if (fscanf(infile, "%i,%i\n", &iPostProcess, &iDeletePaused) != 2) goto error; } else if (iFormatVersion >= 4) { if (fscanf(infile, "%i\n", &iPostProcess) != 1) goto error; } pNZBInfo->SetPriority(iPriority); pNZBInfo->SetDeletePaused((bool)iDeletePaused); pNZBInfo->SetManyDupeFiles((bool)iManyDupeFiles); if (iPostStage > 0) { pNZBInfo->EnterPostProcess(); pNZBInfo->GetPostInfo()->SetStage((PostInfo::EStage)iPostStage); } pNZBInfo->SetFeedID(iFeedID); } if (iFormatVersion >= 8 && iFormatVersion < 18) { int iParStatus; if (fscanf(infile, "%i\n", &iParStatus) != 1) goto error; pNZBInfo->SetParStatus((NZBInfo::EParStatus)iParStatus); } if (iFormatVersion >= 9 && iFormatVersion < 18) { int iScriptStatus; if (fscanf(infile, "%i\n", &iScriptStatus) != 1) goto error; if (iScriptStatus > 1) iScriptStatus--; pNZBInfo->GetScriptStatuses()->Add("SCRIPT", (ScriptStatus::EStatus)iScriptStatus); } if (iFormatVersion >= 18) { int iParStatus, iUnpackStatus, iScriptStatus, iMoveStatus = 0, iRenameStatus = 0, iDeleteStatus = 0, iMarkStatus = 0, iUrlStatus = 0; if (iFormatVersion >= 46) { if (fscanf(infile, "%i,%i,%i,%i,%i,%i,%i\n", &iParStatus, &iUnpackStatus, &iMoveStatus, &iRenameStatus, &iDeleteStatus, &iMarkStatus, &iUrlStatus) != 7) goto error; } else if (iFormatVersion >= 37) { if (fscanf(infile, "%i,%i,%i,%i,%i,%i\n", &iParStatus, &iUnpackStatus, &iMoveStatus, &iRenameStatus, &iDeleteStatus, &iMarkStatus) != 6) goto error; } else if (iFormatVersion >= 35) { if (fscanf(infile, "%i,%i,%i,%i,%i\n", &iParStatus, &iUnpackStatus, &iMoveStatus, &iRenameStatus, &iDeleteStatus) != 5) goto error; } else if (iFormatVersion >= 23) { if (fscanf(infile, "%i,%i,%i,%i\n", &iParStatus, &iUnpackStatus, &iMoveStatus, &iRenameStatus) != 4) goto error; } else if (iFormatVersion >= 21) { if (fscanf(infile, "%i,%i,%i,%i,%i\n", &iParStatus, &iUnpackStatus, &iScriptStatus, &iMoveStatus, &iRenameStatus) != 5) goto error; } else if (iFormatVersion >= 20) { if (fscanf(infile, "%i,%i,%i,%i\n", &iParStatus, &iUnpackStatus, &iScriptStatus, &iMoveStatus) != 4) goto error; } else { if (fscanf(infile, "%i,%i,%i\n", &iParStatus, &iUnpackStatus, &iScriptStatus) != 3) goto error; } pNZBInfo->SetParStatus((NZBInfo::EParStatus)iParStatus); pNZBInfo->SetUnpackStatus((NZBInfo::EUnpackStatus)iUnpackStatus); pNZBInfo->SetMoveStatus((NZBInfo::EMoveStatus)iMoveStatus); pNZBInfo->SetRenameStatus((NZBInfo::ERenameStatus)iRenameStatus); pNZBInfo->SetDeleteStatus((NZBInfo::EDeleteStatus)iDeleteStatus); pNZBInfo->SetMarkStatus((NZBInfo::EMarkStatus)iMarkStatus); if (pNZBInfo->GetKind() == NZBInfo::nkNzb || (NZBInfo::EUrlStatus)iUrlStatus >= NZBInfo::lsFailed || (NZBInfo::EUrlStatus)iUrlStatus >= NZBInfo::lsScanSkipped) { pNZBInfo->SetUrlStatus((NZBInfo::EUrlStatus)iUrlStatus); } if (iFormatVersion < 23) { if (iScriptStatus > 1) iScriptStatus--; pNZBInfo->GetScriptStatuses()->Add("SCRIPT", (ScriptStatus::EStatus)iScriptStatus); } } if (iFormatVersion >= 35) { int iUnpackCleanedUpDisk, iHealthPaused, iAddUrlPaused = 0; if (iFormatVersion >= 46) { if (fscanf(infile, "%i,%i,%i\n", &iUnpackCleanedUpDisk, &iHealthPaused, &iAddUrlPaused) != 3) goto error; } else { if (fscanf(infile, "%i,%i\n", &iUnpackCleanedUpDisk, &iHealthPaused) != 2) goto error; } pNZBInfo->SetUnpackCleanedUpDisk((bool)iUnpackCleanedUpDisk); pNZBInfo->SetHealthPaused((bool)iHealthPaused); pNZBInfo->SetAddUrlPaused((bool)iAddUrlPaused); } else if (iFormatVersion >= 28) { int iDeleted, iUnpackCleanedUpDisk, iHealthPaused, iHealthDeleted; if (fscanf(infile, "%i,%i,%i,%i\n", &iDeleted, &iUnpackCleanedUpDisk, &iHealthPaused, &iHealthDeleted) != 4) goto error; pNZBInfo->SetUnpackCleanedUpDisk((bool)iUnpackCleanedUpDisk); pNZBInfo->SetHealthPaused((bool)iHealthPaused); pNZBInfo->SetDeleteStatus(iHealthDeleted ? NZBInfo::dsHealth : iDeleted ? NZBInfo::dsManual : NZBInfo::dsNone); } if (iFormatVersion >= 28) { int iFileCount, iParkedFileCount, iMessageCount = 0; if (iFormatVersion >= 52) { if (fscanf(infile, "%i,%i,%i\n", &iFileCount, &iParkedFileCount, &iMessageCount) != 3) goto error; } else { if (fscanf(infile, "%i,%i\n", &iFileCount, &iParkedFileCount) != 2) goto error; } pNZBInfo->SetFileCount(iFileCount); pNZBInfo->SetParkedFileCount(iParkedFileCount); pNZBInfo->SetMessageCount(iMessageCount); } else { if (iFormatVersion >= 19) { int iUnpackCleanedUpDisk; if (fscanf(infile, "%i\n", &iUnpackCleanedUpDisk) != 1) goto error; pNZBInfo->SetUnpackCleanedUpDisk((bool)iUnpackCleanedUpDisk); } int iFileCount; if (fscanf(infile, "%i\n", &iFileCount) != 1) goto error; pNZBInfo->SetFileCount(iFileCount); if (iFormatVersion >= 10) { if (fscanf(infile, "%i\n", &iFileCount) != 1) goto error; pNZBInfo->SetParkedFileCount(iFileCount); } } if (iFormatVersion >= 44) { int iMinTime, iMaxTime; if (fscanf(infile, "%i,%i\n", &iMinTime, &iMaxTime) != 2) goto error; pNZBInfo->SetMinTime((time_t)iMinTime); pNZBInfo->SetMaxTime((time_t)iMaxTime); } if (iFormatVersion >= 51) { int iParFull, iForceParFull, iForceRepair, iExtraParBlocks = 0; if (iFormatVersion >= 55) { if (fscanf(infile, "%i,%i,%i,%i\n", &iParFull, &iForceParFull, &iForceRepair, &iExtraParBlocks) != 4) goto error; } else { if (fscanf(infile, "%i,%i,%i\n", &iParFull, &iForceParFull, &iForceRepair) != 3) goto error; } pNZBInfo->SetParFull((bool)iParFull); pNZBInfo->SetExtraParBlocks(iExtraParBlocks); if (pNZBInfo->GetPostInfo()) { pNZBInfo->GetPostInfo()->SetForceParFull((bool)iForceParFull); pNZBInfo->GetPostInfo()->SetForceRepair((bool)iForceRepair); } } if (true) // clang requires a block for goto to work { unsigned int iFullContentHash = 0, iFilteredContentHash = 0; if (iFormatVersion >= 34) { if (fscanf(infile, "%u,%u\n", &iFullContentHash, &iFilteredContentHash) != 2) goto error; } else if (iFormatVersion >= 32) { if (fscanf(infile, "%u\n", &iFullContentHash) != 1) goto error; } pNZBInfo->SetFullContentHash(iFullContentHash); pNZBInfo->SetFilteredContentHash(iFilteredContentHash); } if (iFormatVersion >= 28) { unsigned long High1, Low1, High2, Low2, High3, Low3; if (fscanf(infile, "%lu,%lu,%lu,%lu,%lu,%lu\n", &High1, &Low1, &High2, &Low2, &High3, &Low3) != 6) goto error; pNZBInfo->SetSize(Util::JoinInt64(High1, Low1)); pNZBInfo->SetSuccessSize(Util::JoinInt64(High2, Low2)); pNZBInfo->SetFailedSize(Util::JoinInt64(High3, Low3)); pNZBInfo->SetCurrentSuccessSize(pNZBInfo->GetSuccessSize()); pNZBInfo->SetCurrentFailedSize(pNZBInfo->GetFailedSize()); if (fscanf(infile, "%lu,%lu,%lu,%lu,%lu,%lu\n", &High1, &Low1, &High2, &Low2, &High3, &Low3) != 6) goto error; pNZBInfo->SetParSize(Util::JoinInt64(High1, Low1)); pNZBInfo->SetParSuccessSize(Util::JoinInt64(High2, Low2)); pNZBInfo->SetParFailedSize(Util::JoinInt64(High3, Low3)); pNZBInfo->SetParCurrentSuccessSize(pNZBInfo->GetParSuccessSize()); pNZBInfo->SetParCurrentFailedSize(pNZBInfo->GetParFailedSize()); } else { unsigned long High, Low; if (fscanf(infile, "%lu,%lu\n", &High, &Low) != 2) goto error; pNZBInfo->SetSize(Util::JoinInt64(High, Low)); } if (iFormatVersion >= 30) { int iTotalArticles, iSuccessArticles, iFailedArticles; if (fscanf(infile, "%i,%i,%i\n", &iTotalArticles, &iSuccessArticles, &iFailedArticles) != 3) goto error; pNZBInfo->SetTotalArticles(iTotalArticles); pNZBInfo->SetSuccessArticles(iSuccessArticles); pNZBInfo->SetFailedArticles(iFailedArticles); pNZBInfo->SetCurrentSuccessArticles(iSuccessArticles); pNZBInfo->SetCurrentFailedArticles(iFailedArticles); } if (iFormatVersion >= 31) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' if (iFormatVersion < 36) ConvertDupeKey(buf, sizeof(buf)); pNZBInfo->SetDupeKey(buf); } if (true) // clang requires a block for goto to work { int iDupeMode = 0, iDupeScore = 0; if (iFormatVersion >= 39) { if (fscanf(infile, "%i,%i\n", &iDupeMode, &iDupeScore) != 2) goto error; } else if (iFormatVersion >= 31) { int iDupe; if (fscanf(infile, "%i,%i,%i\n", &iDupe, &iDupeMode, &iDupeScore) != 3) goto error; } pNZBInfo->SetDupeMode((EDupeMode)iDupeMode); pNZBInfo->SetDupeScore(iDupeScore); } if (iFormatVersion >= 48) { unsigned long High1, Low1, iDownloadSec, iPostTotalSec, iParSec, iRepairSec, iUnpackSec; if (fscanf(infile, "%lu,%lu,%i,%i,%i,%i,%i\n", &High1, &Low1, &iDownloadSec, &iPostTotalSec, &iParSec, &iRepairSec, &iUnpackSec) != 7) goto error; pNZBInfo->SetDownloadedSize(Util::JoinInt64(High1, Low1)); pNZBInfo->SetDownloadSec(iDownloadSec); pNZBInfo->SetPostTotalSec(iPostTotalSec); pNZBInfo->SetParSec(iParSec); pNZBInfo->SetRepairSec(iRepairSec); pNZBInfo->SetUnpackSec(iUnpackSec); } if (iFormatVersion >= 4) { int iFileCount; if (fscanf(infile, "%i\n", &iFileCount) != 1) goto error; for (int i = 0; i < iFileCount; i++) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' int iID = 0; char* szFileName = buf; int iStatus = 0; unsigned long lCrc = 0; if (iFormatVersion >= 49) { if (iFormatVersion >= 50) { if (sscanf(buf, "%i,%i,%lu", &iID, &iStatus, &lCrc) != 3) goto error; szFileName = strchr(buf, ','); if (szFileName) szFileName = strchr(szFileName+1, ','); if (szFileName) szFileName = strchr(szFileName+1, ','); } else { if (sscanf(buf, "%i,%lu", &iStatus, &lCrc) != 2) goto error; szFileName = strchr(buf + 2, ','); } if (szFileName) { szFileName++; } } pNZBInfo->GetCompletedFiles()->push_back(new CompletedFile(iID, szFileName, (CompletedFile::EStatus)iStatus, lCrc)); } } if (iFormatVersion >= 6) { int iParameterCount; if (fscanf(infile, "%i\n", &iParameterCount) != 1) goto error; for (int i = 0; i < iParameterCount; i++) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' char* szValue = strchr(buf, '='); if (szValue) { *szValue = '\0'; szValue++; pNZBInfo->GetParameters()->SetParameter(buf, szValue); } } } if (iFormatVersion >= 23) { int iScriptCount; if (fscanf(infile, "%i\n", &iScriptCount) != 1) goto error; for (int i = 0; i < iScriptCount; i++) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' char* szScriptName = strchr(buf, ','); if (szScriptName) { szScriptName++; int iStatus = atoi(buf); if (iStatus > 1 && iFormatVersion < 25) iStatus--; pNZBInfo->GetScriptStatuses()->Add(szScriptName, (ScriptStatus::EStatus)iStatus); } } } if (iFormatVersion >= 30) { if (!LoadServerStats(pNZBInfo->GetServerStats(), pServers, infile)) goto error; pNZBInfo->GetCurrentServerStats()->ListOp(pNZBInfo->GetServerStats(), ServerStatList::soSet); } if (iFormatVersion >= 11 && iFormatVersion < 52) { int iLogCount; if (fscanf(infile, "%i\n", &iLogCount) != 1) goto error; for (int i = 0; i < iLogCount; i++) { if (!fgets(buf, sizeof(buf), infile)) goto error; } } if (iFormatVersion < 26) { NZBParameter* pUnpackParameter = pNZBInfo->GetParameters()->Find("*Unpack:", false); if (!pUnpackParameter) { pNZBInfo->GetParameters()->SetParameter("*Unpack:", g_pOptions->GetUnpack() ? "yes" : "no"); } } if (iFormatVersion >= 43) { int iFileCount; if (fscanf(infile, "%i\n", &iFileCount) != 1) goto error; for (int i = 0; i < iFileCount; i++) { unsigned int id, paused, iTime = 0; int iPriority = 0, iExtraPriority = 0; if (iFormatVersion >= 44) { if (fscanf(infile, "%i,%i,%i,%i\n", &id, &paused, &iTime, &iExtraPriority) != 4) goto error; } else { if (fscanf(infile, "%i,%i,%i,%i,%i\n", &id, &paused, &iTime, &iPriority, &iExtraPriority) != 5) goto error; pNZBInfo->SetPriority(iPriority); } char fileName[1024]; snprintf(fileName, 1024, "%s%i", g_pOptions->GetQueueDir(), id); fileName[1024-1] = '\0'; FileInfo* pFileInfo = new FileInfo(); bool res = LoadFileInfo(pFileInfo, fileName, true, false); if (res) { pFileInfo->SetID(id); pFileInfo->SetPaused(paused); pFileInfo->SetTime(iTime); pFileInfo->SetExtraPriority(iExtraPriority != 0); pFileInfo->SetNZBInfo(pNZBInfo); if (iFormatVersion < 30) { pNZBInfo->SetTotalArticles(pNZBInfo->GetTotalArticles() + pFileInfo->GetTotalArticles()); } pNZBInfo->GetFileList()->push_back(pFileInfo); } else { delete pFileInfo; } } } return true; error: error("Error reading nzb info from disk"); return false; } bool DiskState::LoadFileQueue12(NZBList* pNZBList, NZBList* pSortList, FILE* infile, int iFormatVersion) { debug("Loading file queue from disk"); int size; if (fscanf(infile, "%i\n", &size) != 1) goto error; for (int i = 0; i < size; i++) { unsigned int id, iNZBIndex, paused; unsigned int iTime = 0; int iPriority = 0, iExtraPriority = 0; if (iFormatVersion >= 17) { if (fscanf(infile, "%i,%i,%i,%i,%i,%i\n", &id, &iNZBIndex, &paused, &iTime, &iPriority, &iExtraPriority) != 6) goto error; } else if (iFormatVersion >= 14) { if (fscanf(infile, "%i,%i,%i,%i,%i\n", &id, &iNZBIndex, &paused, &iTime, &iPriority) != 5) goto error; } else if (iFormatVersion >= 12) { if (fscanf(infile, "%i,%i,%i,%i\n", &id, &iNZBIndex, &paused, &iTime) != 4) goto error; } else { if (fscanf(infile, "%i,%i,%i\n", &id, &iNZBIndex, &paused) != 3) goto error; } if (iNZBIndex > pNZBList->size()) goto error; char fileName[1024]; snprintf(fileName, 1024, "%s%i", g_pOptions->GetQueueDir(), id); fileName[1024-1] = '\0'; FileInfo* pFileInfo = new FileInfo(); bool res = LoadFileInfo(pFileInfo, fileName, true, false); if (res) { NZBInfo* pNZBInfo = pNZBList->at(iNZBIndex - 1); pNZBInfo->SetPriority(iPriority); pFileInfo->SetID(id); pFileInfo->SetPaused(paused); pFileInfo->SetTime(iTime); pFileInfo->SetExtraPriority(iExtraPriority != 0); pFileInfo->SetNZBInfo(pNZBInfo); if (iFormatVersion < 30) { pNZBInfo->SetTotalArticles(pNZBInfo->GetTotalArticles() + pFileInfo->GetTotalArticles()); } pNZBInfo->GetFileList()->push_back(pFileInfo); if (pSortList && std::find(pSortList->begin(), pSortList->end(), pNZBInfo) == pSortList->end()) { pSortList->push_back(pNZBInfo); } } else { delete pFileInfo; } } return true; error: error("Error reading file queue from disk"); return false; } void DiskState::SaveServerStats(ServerStatList* pServerStatList, FILE* outfile) { fprintf(outfile, "%i\n", (int)pServerStatList->size()); for (ServerStatList::iterator it = pServerStatList->begin(); it != pServerStatList->end(); it++) { ServerStat* pServerStat = *it; fprintf(outfile, "%i,%i,%i\n", pServerStat->GetServerID(), pServerStat->GetSuccessArticles(), pServerStat->GetFailedArticles()); } } bool DiskState::LoadServerStats(ServerStatList* pServerStatList, Servers* pServers, FILE* infile) { int iStatCount; if (fscanf(infile, "%i\n", &iStatCount) != 1) goto error; for (int i = 0; i < iStatCount; i++) { int iServerID, iSuccessArticles, iFailedArticles; if (fscanf(infile, "%i,%i,%i\n", &iServerID, &iSuccessArticles, &iFailedArticles) != 3) goto error; if (pServers) { // find server (id could change if config file was edited) for (Servers::iterator it = pServers->begin(); it != pServers->end(); it++) { NewsServer* pNewsServer = *it; if (pNewsServer->GetStateID() == iServerID) { pServerStatList->StatOp(pNewsServer->GetID(), iSuccessArticles, iFailedArticles, ServerStatList::soSet); } } } } return true; error: error("Error reading server stats from disk"); return false; } bool DiskState::SaveFile(FileInfo* pFileInfo) { char fileName[1024]; snprintf(fileName, 1024, "%s%i", g_pOptions->GetQueueDir(), pFileInfo->GetID()); fileName[1024-1] = '\0'; return SaveFileInfo(pFileInfo, fileName); } bool DiskState::SaveFileInfo(FileInfo* pFileInfo, const char* szFilename) { debug("Saving FileInfo to disk"); FILE* outfile = fopen(szFilename, FOPEN_WB); if (!outfile) { error("Error saving diskstate: could not create file %s", szFilename); return false; } fprintf(outfile, "%s%i\n", FORMATVERSION_SIGNATURE, 3); fprintf(outfile, "%s\n", pFileInfo->GetSubject()); fprintf(outfile, "%s\n", pFileInfo->GetFilename()); unsigned long High, Low; Util::SplitInt64(pFileInfo->GetSize(), &High, &Low); fprintf(outfile, "%lu,%lu\n", High, Low); Util::SplitInt64(pFileInfo->GetMissedSize(), &High, &Low); fprintf(outfile, "%lu,%lu\n", High, Low); fprintf(outfile, "%i\n", (int)pFileInfo->GetParFile()); fprintf(outfile, "%i,%i\n", pFileInfo->GetTotalArticles(), pFileInfo->GetMissedArticles()); fprintf(outfile, "%i\n", (int)pFileInfo->GetGroups()->size()); for (FileInfo::Groups::iterator it = pFileInfo->GetGroups()->begin(); it != pFileInfo->GetGroups()->end(); it++) { fprintf(outfile, "%s\n", *it); } fprintf(outfile, "%i\n", (int)pFileInfo->GetArticles()->size()); for (FileInfo::Articles::iterator it = pFileInfo->GetArticles()->begin(); it != pFileInfo->GetArticles()->end(); it++) { ArticleInfo* pArticleInfo = *it; fprintf(outfile, "%i,%i\n", pArticleInfo->GetPartNumber(), pArticleInfo->GetSize()); fprintf(outfile, "%s\n", pArticleInfo->GetMessageID()); } fclose(outfile); return true; } bool DiskState::LoadArticles(FileInfo* pFileInfo) { char fileName[1024]; snprintf(fileName, 1024, "%s%i", g_pOptions->GetQueueDir(), pFileInfo->GetID()); fileName[1024-1] = '\0'; return LoadFileInfo(pFileInfo, fileName, false, true); } bool DiskState::LoadFileInfo(FileInfo* pFileInfo, const char * szFilename, bool bFileSummary, bool bArticles) { debug("Loading FileInfo from disk"); FILE* infile = fopen(szFilename, FOPEN_RB); if (!infile) { error("Error reading diskstate: could not open file %s", szFilename); return false; } char buf[1024]; int iFormatVersion = 0; if (fgets(buf, sizeof(buf), infile)) { if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' iFormatVersion = ParseFormatVersion(buf); if (iFormatVersion > 3) { error("Could not load diskstate due to file version mismatch"); goto error; } } else { goto error; } if (iFormatVersion >= 2) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' } if (bFileSummary) pFileInfo->SetSubject(buf); if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' if (bFileSummary) pFileInfo->SetFilename(buf); if (iFormatVersion < 2) { int iFilenameConfirmed; if (fscanf(infile, "%i\n", &iFilenameConfirmed) != 1) goto error; if (bFileSummary) pFileInfo->SetFilenameConfirmed(iFilenameConfirmed); } unsigned long High, Low; if (fscanf(infile, "%lu,%lu\n", &High, &Low) != 2) goto error; if (bFileSummary) pFileInfo->SetSize(Util::JoinInt64(High, Low)); if (bFileSummary) pFileInfo->SetRemainingSize(pFileInfo->GetSize()); if (iFormatVersion >= 2) { if (fscanf(infile, "%lu,%lu\n", &High, &Low) != 2) goto error; if (bFileSummary) pFileInfo->SetMissedSize(Util::JoinInt64(High, Low)); if (bFileSummary) pFileInfo->SetRemainingSize(pFileInfo->GetSize() - pFileInfo->GetMissedSize()); int iParFile; if (fscanf(infile, "%i\n", &iParFile) != 1) goto error; if (bFileSummary) pFileInfo->SetParFile((bool)iParFile); } if (iFormatVersion >= 3) { int iTotalArticles, iMissedArticles; if (fscanf(infile, "%i,%i\n", &iTotalArticles, &iMissedArticles) != 2) goto error; if (bFileSummary) pFileInfo->SetTotalArticles(iTotalArticles); if (bFileSummary) pFileInfo->SetMissedArticles(iMissedArticles); } int size; if (fscanf(infile, "%i\n", &size) != 1) goto error; for (int i = 0; i < size; i++) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' if (bFileSummary) pFileInfo->GetGroups()->push_back(strdup(buf)); } if (fscanf(infile, "%i\n", &size) != 1) goto error; if (iFormatVersion < 3 && bFileSummary) { pFileInfo->SetTotalArticles(size); } if (bArticles) { for (int i = 0; i < size; i++) { int PartNumber, PartSize; if (fscanf(infile, "%i,%i\n", &PartNumber, &PartSize) != 2) goto error; if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' ArticleInfo* pArticleInfo = new ArticleInfo(); pArticleInfo->SetPartNumber(PartNumber); pArticleInfo->SetSize(PartSize); pArticleInfo->SetMessageID(buf); pFileInfo->GetArticles()->push_back(pArticleInfo); } } fclose(infile); return true; error: fclose(infile); error("Error reading diskstate for file %s", szFilename); return false; } bool DiskState::SaveFileState(FileInfo* pFileInfo, bool bCompleted) { debug("Saving FileState to disk"); char szFilename[1024]; snprintf(szFilename, 1024, "%s%i%s", g_pOptions->GetQueueDir(), pFileInfo->GetID(), bCompleted ? "c" : "s"); szFilename[1024-1] = '\0'; FILE* outfile = fopen(szFilename, FOPEN_WB); if (!outfile) { error("Error saving diskstate: could not create file %s", szFilename); return false; } fprintf(outfile, "%s%i\n", FORMATVERSION_SIGNATURE, 2); fprintf(outfile, "%i,%i\n", pFileInfo->GetSuccessArticles(), pFileInfo->GetFailedArticles()); unsigned long High1, Low1, High2, Low2, High3, Low3; Util::SplitInt64(pFileInfo->GetRemainingSize(), &High1, &Low1); Util::SplitInt64(pFileInfo->GetSuccessSize(), &High2, &Low2); Util::SplitInt64(pFileInfo->GetFailedSize(), &High3, &Low3); fprintf(outfile, "%lu,%lu,%lu,%lu,%lu,%lu\n", High1, Low1, High2, Low2, High3, Low3); SaveServerStats(pFileInfo->GetServerStats(), outfile); fprintf(outfile, "%i\n", (int)pFileInfo->GetArticles()->size()); for (FileInfo::Articles::iterator it = pFileInfo->GetArticles()->begin(); it != pFileInfo->GetArticles()->end(); it++) { ArticleInfo* pArticleInfo = *it; fprintf(outfile, "%i,%lu,%i,%lu\n", (int)pArticleInfo->GetStatus(), (unsigned long)pArticleInfo->GetSegmentOffset(), pArticleInfo->GetSegmentSize(), (unsigned long)pArticleInfo->GetCrc()); } fclose(outfile); return true; } bool DiskState::LoadFileState(FileInfo* pFileInfo, Servers* pServers, bool bCompleted) { char szFilename[1024]; snprintf(szFilename, 1024, "%s%i%s", g_pOptions->GetQueueDir(), pFileInfo->GetID(), bCompleted ? "c" : "s"); szFilename[1024-1] = '\0'; bool bHasArticles = !pFileInfo->GetArticles()->empty(); FILE* infile = fopen(szFilename, FOPEN_RB); if (!infile) { error("Error reading diskstate: could not open file %s", szFilename); return false; } char buf[1024]; int iFormatVersion = 0; if (fgets(buf, sizeof(buf), infile)) { if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' iFormatVersion = ParseFormatVersion(buf); if (iFormatVersion > 2) { error("Could not load diskstate due to file version mismatch"); goto error; } } else { goto error; } int iSuccessArticles, iFailedArticles; if (fscanf(infile, "%i,%i\n", &iSuccessArticles, &iFailedArticles) != 2) goto error; pFileInfo->SetSuccessArticles(iSuccessArticles); pFileInfo->SetFailedArticles(iFailedArticles); unsigned long High1, Low1, High2, Low2, High3, Low3; if (fscanf(infile, "%lu,%lu,%lu,%lu,%lu,%lu\n", &High1, &Low1, &High2, &Low2, &High3, &Low3) != 6) goto error; pFileInfo->SetRemainingSize(Util::JoinInt64(High1, Low1)); pFileInfo->SetSuccessSize(Util::JoinInt64(High2, Low3)); pFileInfo->SetFailedSize(Util::JoinInt64(High3, Low3)); if (!LoadServerStats(pFileInfo->GetServerStats(), pServers, infile)) goto error; int iCompletedArticles; iCompletedArticles = 0; //clang requires initialization in a separate line (due to goto statements) int size; if (fscanf(infile, "%i\n", &size) != 1) goto error; for (int i = 0; i < size; i++) { if (!bHasArticles) { pFileInfo->GetArticles()->push_back(new ArticleInfo()); } ArticleInfo* pa = pFileInfo->GetArticles()->at(i); int iStatus; if (iFormatVersion >= 2) { unsigned long iSegmentOffset, iCrc; int iSegmentSize; if (fscanf(infile, "%i,%lu,%i,%lu\n", &iStatus, &iSegmentOffset, &iSegmentSize, &iCrc) != 4) goto error; pa->SetSegmentOffset(iSegmentOffset); pa->SetSegmentSize(iSegmentSize); pa->SetCrc(iCrc); } else { if (fscanf(infile, "%i\n", &iStatus) != 1) goto error; } ArticleInfo::EStatus eStatus = (ArticleInfo::EStatus)iStatus; if (eStatus == ArticleInfo::aiRunning) { eStatus = ArticleInfo::aiUndefined; } // don't allow all articles be completed or the file will stuck. // such states should never be saved on disk but just in case. if (iCompletedArticles == size - 1 && !bCompleted) { eStatus = ArticleInfo::aiUndefined; } if (eStatus != ArticleInfo::aiUndefined) { iCompletedArticles++; } pa->SetStatus(eStatus); } pFileInfo->SetCompletedArticles(iCompletedArticles); fclose(infile); return true; error: fclose(infile); error("Error reading diskstate for file %s", szFilename); return false; } void DiskState::DiscardFiles(NZBInfo* pNZBInfo) { for (FileList::iterator it = pNZBInfo->GetFileList()->begin(); it != pNZBInfo->GetFileList()->end(); it++) { FileInfo* pFileInfo = *it; DiscardFile(pFileInfo, true, true, true); } char szFilename[1024]; for (CompletedFiles::iterator it = pNZBInfo->GetCompletedFiles()->begin(); it != pNZBInfo->GetCompletedFiles()->end(); it++) { CompletedFile* pCompletedFile = *it; if (pCompletedFile->GetStatus() != CompletedFile::cfSuccess && pCompletedFile->GetID() > 0) { snprintf(szFilename, 1024, "%s%i", g_pOptions->GetQueueDir(), pCompletedFile->GetID()); szFilename[1024-1] = '\0'; remove(szFilename); snprintf(szFilename, 1024, "%s%is", g_pOptions->GetQueueDir(), pCompletedFile->GetID()); szFilename[1024-1] = '\0'; remove(szFilename); snprintf(szFilename, 1024, "%s%ic", g_pOptions->GetQueueDir(), pCompletedFile->GetID()); szFilename[1024-1] = '\0'; remove(szFilename); } } snprintf(szFilename, 1024, "%sn%i.log", g_pOptions->GetQueueDir(), pNZBInfo->GetID()); szFilename[1024-1] = '\0'; remove(szFilename); } bool DiskState::LoadPostQueue12(DownloadQueue* pDownloadQueue, NZBList* pNZBList, FILE* infile, int iFormatVersion) { debug("Loading post-queue from disk"); int size; char buf[10240]; // load post-infos if (fscanf(infile, "%i\n", &size) != 1) goto error; for (int i = 0; i < size; i++) { PostInfo* pPostInfo = NULL; int iNZBID = 0; unsigned int iNZBIndex = 0, iStage, iDummy; if (iFormatVersion < 19) { if (fscanf(infile, "%i,%i,%i,%i\n", &iNZBIndex, &iDummy, &iDummy, &iStage) != 4) goto error; } else if (iFormatVersion < 22) { if (fscanf(infile, "%i,%i,%i,%i\n", &iNZBIndex, &iDummy, &iDummy, &iStage) != 4) goto error; } else if (iFormatVersion < 43) { if (fscanf(infile, "%i,%i\n", &iNZBIndex, &iStage) != 2) goto error; } else { if (fscanf(infile, "%i,%i\n", &iNZBID, &iStage) != 2) goto error; } if (iFormatVersion < 18 && iStage > (int)PostInfo::ptVerifyingRepaired) iStage++; if (iFormatVersion < 21 && iStage > (int)PostInfo::ptVerifyingRepaired) iStage++; if (iFormatVersion < 20 && iStage > (int)PostInfo::ptUnpacking) iStage++; NZBInfo* pNZBInfo = NULL; if (iFormatVersion < 43) { pNZBInfo = pNZBList->at(iNZBIndex - 1); if (!pNZBInfo) goto error; } else { pNZBInfo = FindNZBInfo(pDownloadQueue, iNZBID); if (!pNZBInfo) goto error; } pNZBInfo->EnterPostProcess(); pPostInfo = pNZBInfo->GetPostInfo(); pPostInfo->SetStage((PostInfo::EStage)iStage); // InfoName, ignore if (!fgets(buf, sizeof(buf), infile)) goto error; if (iFormatVersion < 22) { // ParFilename, ignore if (!fgets(buf, sizeof(buf), infile)) goto error; } } return true; error: error("Error reading diskstate for post-processor queue"); return false; } /* * Loads post-queue created with older versions of nzbget. * Returns true if successful, false if not */ bool DiskState::LoadPostQueue5(DownloadQueue* pDownloadQueue, NZBList* pNZBList) { debug("Loading post-queue from disk"); char fileName[1024]; snprintf(fileName, 1024, "%s%s", g_pOptions->GetQueueDir(), "postq"); fileName[1024-1] = '\0'; if (!Util::FileExists(fileName)) { return true; } FILE* infile = fopen(fileName, FOPEN_RB); if (!infile) { error("Error reading diskstate: could not open file %s", fileName); return false; } char FileSignatur[128]; fgets(FileSignatur, sizeof(FileSignatur), infile); int iFormatVersion = ParseFormatVersion(FileSignatur); if (iFormatVersion < 3 || iFormatVersion > 7) { error("Could not load diskstate due to file version mismatch"); fclose(infile); return false; } int size; char buf[10240]; int iIntValue; // load file-infos if (fscanf(infile, "%i\n", &size) != 1) goto error; for (int i = 0; i < size; i++) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' // find NZBInfo based on NZBFilename NZBInfo* pNZBInfo = NULL; for (NZBList::iterator it = pNZBList->begin(); it != pNZBList->end(); it++) { NZBInfo* pNZBInfo2 = *it; if (!strcmp(pNZBInfo2->GetFilename(), buf)) { pNZBInfo = pNZBInfo2; break; } } bool bNewNZBInfo = !pNZBInfo; if (bNewNZBInfo) { pNZBInfo = new NZBInfo(); pNZBList->push_front(pNZBInfo); pNZBInfo->SetFilename(buf); } pNZBInfo->EnterPostProcess(); PostInfo* pPostInfo = pNZBInfo->GetPostInfo(); if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' if (bNewNZBInfo) { pNZBInfo->SetDestDir(buf); } // ParFilename, ignore if (!fgets(buf, sizeof(buf), infile)) goto error; // InfoName, ignore if (!fgets(buf, sizeof(buf), infile)) goto error; if (iFormatVersion >= 4) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' if (bNewNZBInfo) { pNZBInfo->SetCategory(buf); } } else { if (bNewNZBInfo) { pNZBInfo->SetCategory(""); } } if (iFormatVersion >= 5) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' if (bNewNZBInfo) { pNZBInfo->SetQueuedFilename(buf); } } else { if (bNewNZBInfo) { pNZBInfo->SetQueuedFilename(""); } } int iParCheck; if (fscanf(infile, "%i\n", &iParCheck) != 1) goto error; // ParCheck if (fscanf(infile, "%i\n", &iIntValue) != 1) goto error; pNZBInfo->SetParStatus(iParCheck ? (NZBInfo::EParStatus)iIntValue : NZBInfo::psSkipped); if (iFormatVersion < 7) { // skip old field ParFailed, not used anymore if (fscanf(infile, "%i\n", &iIntValue) != 1) goto error; } if (fscanf(infile, "%i\n", &iIntValue) != 1) goto error; pPostInfo->SetStage((PostInfo::EStage)iIntValue); if (iFormatVersion >= 6) { int iParameterCount; if (fscanf(infile, "%i\n", &iParameterCount) != 1) goto error; for (int i = 0; i < iParameterCount; i++) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' char* szValue = strchr(buf, '='); if (szValue) { *szValue = '\0'; szValue++; if (bNewNZBInfo) { pNZBInfo->GetParameters()->SetParameter(buf, szValue); } } } } } fclose(infile); return true; error: fclose(infile); error("Error reading diskstate for file %s", fileName); return false; } bool DiskState::LoadUrlQueue12(DownloadQueue* pDownloadQueue, FILE* infile, int iFormatVersion) { debug("Loading url-queue from disk"); int size; // load url-infos if (fscanf(infile, "%i\n", &size) != 1) goto error; for (int i = 0; i < size; i++) { NZBInfo* pNZBInfo = new NZBInfo(); if (!LoadUrlInfo12(pNZBInfo, infile, iFormatVersion)) goto error; pDownloadQueue->GetQueue()->push_back(pNZBInfo); } return true; error: error("Error reading diskstate for url-queue"); return false; } bool DiskState::LoadUrlInfo12(NZBInfo* pNZBInfo, FILE* infile, int iFormatVersion) { char buf[10240]; if (iFormatVersion >= 24) { int iID; if (fscanf(infile, "%i\n", &iID) != 1) goto error; pNZBInfo->SetID(iID); } int iStatusDummy, iPriority; if (fscanf(infile, "%i,%i\n", &iStatusDummy, &iPriority) != 2) goto error; pNZBInfo->SetPriority(iPriority); if (iFormatVersion >= 16) { int iAddTopDummy, iAddPaused; if (fscanf(infile, "%i,%i\n", &iAddTopDummy, &iAddPaused) != 2) goto error; pNZBInfo->SetAddUrlPaused(iAddPaused); } if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' pNZBInfo->SetURL(buf); if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' pNZBInfo->SetFilename(buf); if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' pNZBInfo->SetCategory(buf); if (iFormatVersion >= 31) { if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' if (iFormatVersion < 36) ConvertDupeKey(buf, sizeof(buf)); pNZBInfo->SetDupeKey(buf); int iDupeMode, iDupeScore; if (fscanf(infile, "%i,%i\n", &iDupeMode, &iDupeScore) != 2) goto error; pNZBInfo->SetDupeMode((EDupeMode)iDupeMode); pNZBInfo->SetDupeScore(iDupeScore); } return true; error: return false; } void DiskState::SaveDupInfo(DupInfo* pDupInfo, FILE* outfile) { unsigned long High, Low; Util::SplitInt64(pDupInfo->GetSize(), &High, &Low); fprintf(outfile, "%i,%lu,%lu,%u,%u,%i,%i\n", (int)pDupInfo->GetStatus(), High, Low, pDupInfo->GetFullContentHash(), pDupInfo->GetFilteredContentHash(), pDupInfo->GetDupeScore(), (int)pDupInfo->GetDupeMode()); fprintf(outfile, "%s\n", pDupInfo->GetName()); fprintf(outfile, "%s\n", pDupInfo->GetDupeKey()); } bool DiskState::LoadDupInfo(DupInfo* pDupInfo, FILE* infile, int iFormatVersion) { char buf[1024]; int iStatus; unsigned long High, Low; unsigned int iFullContentHash, iFilteredContentHash = 0; int iDupeScore, iDupe = 0, iDupeMode = 0; if (iFormatVersion >= 39) { if (fscanf(infile, "%i,%lu,%lu,%u,%u,%i,%i\n", &iStatus, &High, &Low, &iFullContentHash, &iFilteredContentHash, &iDupeScore, &iDupeMode) != 7) goto error; } else if (iFormatVersion >= 38) { if (fscanf(infile, "%i,%lu,%lu,%u,%u,%i,%i,%i\n", &iStatus, &High, &Low, &iFullContentHash, &iFilteredContentHash, &iDupeScore, &iDupe, &iDupeMode) != 8) goto error; } else if (iFormatVersion >= 37) { if (fscanf(infile, "%i,%lu,%lu,%u,%u,%i,%i\n", &iStatus, &High, &Low, &iFullContentHash, &iFilteredContentHash, &iDupeScore, &iDupe) != 7) goto error; } else if (iFormatVersion >= 34) { if (fscanf(infile, "%i,%lu,%lu,%u,%u,%i\n", &iStatus, &High, &Low, &iFullContentHash, &iFilteredContentHash, &iDupeScore) != 6) goto error; } else { if (fscanf(infile, "%i,%lu,%lu,%u,%i\n", &iStatus, &High, &Low, &iFullContentHash, &iDupeScore) != 5) goto error; } pDupInfo->SetStatus((DupInfo::EStatus)iStatus); pDupInfo->SetFullContentHash(iFullContentHash); pDupInfo->SetFilteredContentHash(iFilteredContentHash); pDupInfo->SetSize(Util::JoinInt64(High, Low)); pDupInfo->SetDupeScore(iDupeScore); pDupInfo->SetDupeMode((EDupeMode)iDupeMode); if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' pDupInfo->SetName(buf); if (!fgets(buf, sizeof(buf), infile)) goto error; if (buf[0] != 0) buf[strlen(buf)-1] = 0; // remove traling '\n' if (iFormatVersion < 36) ConvertDupeKey(buf, sizeof(buf)); pDupInfo->SetDupeKey(buf); return true; error: return false; } void DiskState::SaveHistory(DownloadQueue* pDownloadQueue, FILE* outfile) { debug("Saving history to disk"); fprintf(outfile, "%i\n", (int)pDownloadQueue->GetHistory()->size()); for (HistoryList::iterator it = pDownloadQueue->GetHistory()->begin(); it != pDownloadQueue->GetHistory()->end(); it++) { HistoryInfo* pHistoryInfo = *it; fprintf(outfile, "%i,%i,%i\n", pHistoryInfo->GetID(), (int)pHistoryInfo->GetKind(), (int)pHistoryInfo->GetTime()); if (pHistoryInfo->GetKind() == HistoryInfo::hkNzb || pHistoryInfo->GetKind() == HistoryInfo::hkUrl) { SaveNZBInfo(pHistoryInfo->GetNZBInfo(), outfile); } else if (pHistoryInfo->GetKind() == HistoryInfo::hkDup) { SaveDupInfo(pHistoryInfo->GetDupInfo(), outfile); } } } bool DiskState::LoadHistory(DownloadQueue* pDownloadQueue, NZBList* pNZBList, Servers* pServers, FILE* infile, int iFormatVersion) { debug("Loading history from disk"); int size; if (fscanf(infile, "%i\n", &size) != 1) goto error; for (int i = 0; i < size; i++) { HistoryInfo* pHistoryInfo = NULL; HistoryInfo::EKind eKind = HistoryInfo::hkNzb; int iID = 0; int iTime; if (iFormatVersion >= 33) { int iKind = 0; if (fscanf(infile, "%i,%i,%i\n", &iID, &iKind, &iTime) != 3) goto error; eKind = (HistoryInfo::EKind)iKind; } else { if (iFormatVersion >= 24) { if (fscanf(infile, "%i\n", &iID) != 1) goto error; } if (iFormatVersion >= 15) { int iKind = 0; if (fscanf(infile, "%i\n", &iKind) != 1) goto error; eKind = (HistoryInfo::EKind)iKind; } } if (eKind == HistoryInfo::hkNzb) { NZBInfo* pNZBInfo = NULL; if (iFormatVersion < 43) { unsigned int iNZBIndex; if (fscanf(infile, "%i\n", &iNZBIndex) != 1) goto error; pNZBInfo = pNZBList->at(iNZBIndex - 1); } else { pNZBInfo = new NZBInfo(); if (!LoadNZBInfo(pNZBInfo, pServers, infile, iFormatVersion)) goto error; pNZBInfo->LeavePostProcess(); } pHistoryInfo = new HistoryInfo(pNZBInfo); if (iFormatVersion < 28 && pNZBInfo->GetParStatus() == 0 && pNZBInfo->GetUnpackStatus() == 0 && pNZBInfo->GetMoveStatus() == 0) { pNZBInfo->SetDeleteStatus(NZBInfo::dsManual); } } else if (eKind == HistoryInfo::hkUrl) { NZBInfo* pNZBInfo = new NZBInfo(); if (iFormatVersion >= 46) { if (!LoadNZBInfo(pNZBInfo, pServers, infile, iFormatVersion)) goto error; } else { if (!LoadUrlInfo12(pNZBInfo, infile, iFormatVersion)) goto error; } pHistoryInfo = new HistoryInfo(pNZBInfo); } else if (eKind == HistoryInfo::hkDup) { DupInfo* pDupInfo = new DupInfo(); if (!LoadDupInfo(pDupInfo, infile, iFormatVersion)) goto error; if (iFormatVersion >= 47) { pDupInfo->SetID(iID); } pHistoryInfo = new HistoryInfo(pDupInfo); } if (iFormatVersion < 33) { if (fscanf(infile, "%i\n", &iTime) != 1) goto error; } pHistoryInfo->SetTime((time_t)iTime); pDownloadQueue->GetHistory()->push_back(pHistoryInfo); } return true; error: error("Error reading diskstate for history"); return false; } /* * Find index of nzb-info. */ int DiskState::FindNZBInfoIndex(NZBList* pNZBList, NZBInfo* pNZBInfo) { int iNZBIndex = 0; for (NZBList::iterator it = pNZBList->begin(); it != pNZBList->end(); it++) { NZBInfo* pNZBInfo2 = *it; iNZBIndex++; if (pNZBInfo2 == pNZBInfo) { break; } } return iNZBIndex; } /* * Find nzb-info by id. */ NZBInfo* DiskState::FindNZBInfo(DownloadQueue* pDownloadQueue, int iID) { for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; if (pNZBInfo->GetID() == iID) { return pNZBInfo; } } return NULL; } /* * Deletes whole download queue including history. */ void DiskState::DiscardDownloadQueue() { debug("Discarding queue"); char szFullFilename[1024]; snprintf(szFullFilename, 1024, "%s%s", g_pOptions->GetQueueDir(), "queue"); szFullFilename[1024-1] = '\0'; remove(szFullFilename); DirBrowser dir(g_pOptions->GetQueueDir()); while (const char* filename = dir.Next()) { // delete all files whose names have only characters '0'..'9' bool bOnlyNums = true; for (const char* p = filename; *p != '\0'; p++) { if (!('0' <= *p && *p <= '9')) { bOnlyNums = false; break; } } if (bOnlyNums) { snprintf(szFullFilename, 1024, "%s%s", g_pOptions->GetQueueDir(), filename); szFullFilename[1024-1] = '\0'; remove(szFullFilename); // delete file state file snprintf(szFullFilename, 1024, "%s%ss", g_pOptions->GetQueueDir(), filename); szFullFilename[1024-1] = '\0'; remove(szFullFilename); // delete failed info file snprintf(szFullFilename, 1024, "%s%sc", g_pOptions->GetQueueDir(), filename); szFullFilename[1024-1] = '\0'; remove(szFullFilename); } } } bool DiskState::DownloadQueueExists() { debug("Checking if a saved queue exists on disk"); char fileName[1024]; snprintf(fileName, 1024, "%s%s", g_pOptions->GetQueueDir(), "queue"); fileName[1024-1] = '\0'; return Util::FileExists(fileName); } void DiskState::DiscardFile(FileInfo* pFileInfo, bool bDeleteData, bool bDeletePartialState, bool bDeleteCompletedState) { char fileName[1024]; // info and articles file if (bDeleteData) { snprintf(fileName, 1024, "%s%i", g_pOptions->GetQueueDir(), pFileInfo->GetID()); fileName[1024-1] = '\0'; remove(fileName); } // partial state file if (bDeletePartialState) { snprintf(fileName, 1024, "%s%is", g_pOptions->GetQueueDir(), pFileInfo->GetID()); fileName[1024-1] = '\0'; remove(fileName); } // completed state file if (bDeleteCompletedState) { snprintf(fileName, 1024, "%s%ic", g_pOptions->GetQueueDir(), pFileInfo->GetID()); fileName[1024-1] = '\0'; remove(fileName); } } void DiskState::CleanupTempDir(DownloadQueue* pDownloadQueue) { DirBrowser dir(g_pOptions->GetTempDir()); while (const char* filename = dir.Next()) { int id, part; if (strstr(filename, ".tmp") || strstr(filename, ".dec") || (sscanf(filename, "%i.%i", &id, &part) == 2)) { char szFullFilename[1024]; snprintf(szFullFilename, 1024, "%s%s", g_pOptions->GetTempDir(), filename); szFullFilename[1024-1] = '\0'; remove(szFullFilename); } } } /* For safety: * - first save to temp-file (feeds.new) * - then delete feeds * - then rename feeds.new to feeds */ bool DiskState::SaveFeeds(Feeds* pFeeds, FeedHistory* pFeedHistory) { debug("Saving feeds state to disk"); StateFile stateFile("feeds", 3); if (pFeeds->empty() && pFeedHistory->empty()) { stateFile.Discard(); return true; } FILE* outfile = stateFile.BeginWriteTransaction(); if (!outfile) { return false; } // save status SaveFeedStatus(pFeeds, outfile); // save history SaveFeedHistory(pFeedHistory, outfile); // now rename to dest file name return stateFile.FinishWriteTransaction(); } bool DiskState::LoadFeeds(Feeds* pFeeds, FeedHistory* pFeedHistory) { debug("Loading feeds state from disk"); StateFile stateFile("feeds", 3); if (!stateFile.FileExists()) { return true; } FILE* infile = stateFile.BeginReadTransaction(); if (!infile) { return false; } bool bOK = false; int iFormatVersion = stateFile.GetFileVersion(); // load feed status if (!LoadFeedStatus(pFeeds, infile, iFormatVersion)) goto error; // load feed history if (!LoadFeedHistory(pFeedHistory, infile, iFormatVersion)) goto error; bOK = true; error: if (!bOK) { error("Error reading diskstate for feeds"); } return bOK; } bool DiskState::SaveFeedStatus(Feeds* pFeeds, FILE* outfile) { debug("Saving feed status to disk"); fprintf(outfile, "%i\n", (int)pFeeds->size()); for (Feeds::iterator it = pFeeds->begin(); it != pFeeds->end(); it++) { FeedInfo* pFeedInfo = *it; fprintf(outfile, "%s\n", pFeedInfo->GetUrl()); fprintf(outfile, "%u\n", pFeedInfo->GetFilterHash()); fprintf(outfile, "%i\n", (int)pFeedInfo->GetLastUpdate()); } return true; } bool DiskState::LoadFeedStatus(Feeds* pFeeds, FILE* infile, int iFormatVersion) { debug("Loading feed status from disk"); int size; if (fscanf(infile, "%i\n", &size) != 1) goto error; for (int i = 0; i < size; i++) { char szUrl[1024]; if (!fgets(szUrl, sizeof(szUrl), infile)) goto error; if (szUrl[0] != 0) szUrl[strlen(szUrl)-1] = 0; // remove traling '\n' char szFilter[1024]; if (iFormatVersion == 2) { if (!fgets(szFilter, sizeof(szFilter), infile)) goto error; if (szFilter[0] != 0) szFilter[strlen(szFilter)-1] = 0; // remove traling '\n' } unsigned int iFilterHash = 0; if (iFormatVersion >= 3) { if (fscanf(infile, "%u\n", &iFilterHash) != 1) goto error; } int iLastUpdate = 0; if (fscanf(infile, "%i\n", &iLastUpdate) != 1) goto error; for (Feeds::iterator it = pFeeds->begin(); it != pFeeds->end(); it++) { FeedInfo* pFeedInfo = *it; if (!strcmp(pFeedInfo->GetUrl(), szUrl) && ((iFormatVersion == 1) || (iFormatVersion == 2 && !strcmp(pFeedInfo->GetFilter(), szFilter)) || (iFormatVersion >= 3 && pFeedInfo->GetFilterHash() == iFilterHash))) { pFeedInfo->SetLastUpdate((time_t)iLastUpdate); } } } return true; error: error("Error reading feed status from disk"); return false; } bool DiskState::SaveFeedHistory(FeedHistory* pFeedHistory, FILE* outfile) { debug("Saving feed history to disk"); fprintf(outfile, "%i\n", (int)pFeedHistory->size()); for (FeedHistory::iterator it = pFeedHistory->begin(); it != pFeedHistory->end(); it++) { FeedHistoryInfo* pFeedHistoryInfo = *it; fprintf(outfile, "%i,%i\n", (int)pFeedHistoryInfo->GetStatus(), (int)pFeedHistoryInfo->GetLastSeen()); fprintf(outfile, "%s\n", pFeedHistoryInfo->GetUrl()); } return true; } bool DiskState::LoadFeedHistory(FeedHistory* pFeedHistory, FILE* infile, int iFormatVersion) { debug("Loading feed history from disk"); int size; if (fscanf(infile, "%i\n", &size) != 1) goto error; for (int i = 0; i < size; i++) { int iStatus = 0; int iLastSeen = 0; int r = fscanf(infile, "%i,%i\n", &iStatus, &iLastSeen); if (r != 2) goto error; char szUrl[1024]; if (!fgets(szUrl, sizeof(szUrl), infile)) goto error; if (szUrl[0] != 0) szUrl[strlen(szUrl)-1] = 0; // remove traling '\n' pFeedHistory->Add(szUrl, (FeedHistoryInfo::EStatus)(iStatus), (time_t)(iLastSeen)); } return true; error: error("Error reading feed history from disk"); return false; } // calculate critical health for old NZBs void DiskState::CalcCriticalHealth(NZBList* pNZBList) { // build list of old NZBs which do not have critical health calculated for (NZBList::iterator it = pNZBList->begin(); it != pNZBList->end(); it++) { NZBInfo* pNZBInfo = *it; if (pNZBInfo->CalcCriticalHealth(false) == 1000) { debug("Calculating critical health for %s", pNZBInfo->GetName()); for (FileList::iterator it = pNZBInfo->GetFileList()->begin(); it != pNZBInfo->GetFileList()->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 bool bParFile = strstr(szLoFileName, ".par2"); pFileInfo->SetParFile(bParFile); if (bParFile) { pNZBInfo->SetParSize(pNZBInfo->GetParSize() + pFileInfo->GetSize()); } } } } } void DiskState::CalcFileStats(DownloadQueue* pDownloadQueue, int iFormatVersion) { for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; CalcNZBFileStats(pNZBInfo, iFormatVersion); } for (HistoryList::iterator it = pDownloadQueue->GetHistory()->begin(); it != pDownloadQueue->GetHistory()->end(); it++) { HistoryInfo* pHistoryInfo = *it; if (pHistoryInfo->GetKind() == HistoryInfo::hkNzb) { CalcNZBFileStats(pHistoryInfo->GetNZBInfo(), iFormatVersion); } } } void DiskState::CalcNZBFileStats(NZBInfo* pNZBInfo, int iFormatVersion) { int iPausedFileCount = 0; int iRemainingParCount = 0; int iSuccessArticles = 0; int iFailedArticles = 0; long long lRemainingSize = 0; long long lPausedSize = 0; long long lSuccessSize = 0; long long lFailedSize = 0; long long lParSuccessSize = 0; long long lParFailedSize = 0; for (FileList::iterator it2 = pNZBInfo->GetFileList()->begin(); it2 != pNZBInfo->GetFileList()->end(); it2++) { FileInfo* pFileInfo = *it2; lRemainingSize += pFileInfo->GetRemainingSize(); iSuccessArticles += pFileInfo->GetSuccessArticles(); iFailedArticles += pFileInfo->GetFailedArticles(); lSuccessSize += pFileInfo->GetSuccessSize(); lFailedSize += pFileInfo->GetFailedSize(); if (pFileInfo->GetPaused()) { lPausedSize += pFileInfo->GetRemainingSize(); iPausedFileCount++; } if (pFileInfo->GetParFile()) { iRemainingParCount++; lParSuccessSize += pFileInfo->GetSuccessSize(); lParFailedSize += pFileInfo->GetFailedSize(); } pNZBInfo->GetCurrentServerStats()->ListOp(pFileInfo->GetServerStats(), ServerStatList::soAdd); } pNZBInfo->SetRemainingSize(lRemainingSize); pNZBInfo->SetPausedSize(lPausedSize); pNZBInfo->SetPausedFileCount(iPausedFileCount); pNZBInfo->SetRemainingParCount(iRemainingParCount); pNZBInfo->SetCurrentSuccessArticles(pNZBInfo->GetSuccessArticles() + iSuccessArticles); pNZBInfo->SetCurrentFailedArticles(pNZBInfo->GetFailedArticles() + iFailedArticles); pNZBInfo->SetCurrentSuccessSize(pNZBInfo->GetSuccessSize() + lSuccessSize); pNZBInfo->SetCurrentFailedSize(pNZBInfo->GetFailedSize() + lFailedSize); pNZBInfo->SetParCurrentSuccessSize(pNZBInfo->GetParSuccessSize() + lParSuccessSize); pNZBInfo->SetParCurrentFailedSize(pNZBInfo->GetParFailedSize() + lParFailedSize); if (iFormatVersion < 44) { pNZBInfo->UpdateMinMaxTime(); } } bool DiskState::LoadAllFileStates(DownloadQueue* pDownloadQueue, Servers* pServers) { char szCacheFlagFilename[1024]; snprintf(szCacheFlagFilename, 1024, "%s%s", g_pOptions->GetQueueDir(), "acache"); szCacheFlagFilename[1024-1] = '\0'; bool bCacheWasActive = Util::FileExists(szCacheFlagFilename); DirBrowser dir(g_pOptions->GetQueueDir()); while (const char* filename = dir.Next()) { int id; char suffix; if (sscanf(filename, "%i%c", &id, &suffix) == 2 && suffix == 's') { if (g_pOptions->GetContinuePartial() && !bCacheWasActive) { for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; for (FileList::iterator it2 = pNZBInfo->GetFileList()->begin(); it2 != pNZBInfo->GetFileList()->end(); it2++) { FileInfo* pFileInfo = *it2; if (pFileInfo->GetID() == id) { if (!LoadArticles(pFileInfo)) goto error; if (!LoadFileState(pFileInfo, pServers, false)) goto error; } } } } else { char szFullFilename[1024]; snprintf(szFullFilename, 1024, "%s%s", g_pOptions->GetQueueDir(), filename); szFullFilename[1024-1] = '\0'; remove(szFullFilename); } } } return true; error: return false; } void DiskState::ConvertDupeKey(char* buf, int bufsize) { if (strncmp(buf, "rageid=", 7)) { return; } int iRageId = atoi(buf + 7); int iSeason = 0; int iEpisode = 0; char* p = strchr(buf + 7, ','); if (p) { iSeason = atoi(p + 1); p = strchr(p + 1, ','); if (p) { iEpisode = atoi(p + 1); } } if (iRageId != 0 && iSeason != 0 && iEpisode != 0) { snprintf(buf, bufsize, "rageid=%i-S%02i-E%02i", iRageId, iSeason, iEpisode); } } bool DiskState::SaveStats(Servers* pServers, ServerVolumes* pServerVolumes) { debug("Saving stats to disk"); StateFile stateFile("stats", 3); if (pServers->empty()) { stateFile.Discard(); return true; } FILE* outfile = stateFile.BeginWriteTransaction(); if (!outfile) { return false; } // save server names SaveServerInfo(pServers, outfile); // save stat SaveVolumeStat(pServerVolumes, outfile); // now rename to dest file name return stateFile.FinishWriteTransaction(); } bool DiskState::LoadStats(Servers* pServers, ServerVolumes* pServerVolumes, bool* pPerfectMatch) { debug("Loading stats from disk"); StateFile stateFile("stats", 3); if (!stateFile.FileExists()) { return true; } FILE* infile = stateFile.BeginReadTransaction(); if (!infile) { return false; } bool bOK = false; int iFormatVersion = stateFile.GetFileVersion(); if (!LoadServerInfo(pServers, infile, iFormatVersion, pPerfectMatch)) goto error; if (iFormatVersion >=2) { if (!LoadVolumeStat(pServers, pServerVolumes, infile, iFormatVersion)) goto error; } bOK = true; error: if (!bOK) { error("Error reading diskstate for statistics"); } return bOK; } bool DiskState::SaveServerInfo(Servers* pServers, FILE* outfile) { debug("Saving server info to disk"); fprintf(outfile, "%i\n", (int)pServers->size()); for (Servers::iterator it = pServers->begin(); it != pServers->end(); it++) { NewsServer* pNewsServer = *it; fprintf(outfile, "%s\n", pNewsServer->GetName()); fprintf(outfile, "%s\n", pNewsServer->GetHost()); fprintf(outfile, "%i\n", pNewsServer->GetPort()); fprintf(outfile, "%s\n", pNewsServer->GetUser()); } return true; } /* *************************************************************************************** * Server matching */ class ServerRef { public: int m_iStateID; char* m_szName; char* m_szHost; int m_iPort; char* m_szUser; bool m_bMatched; bool m_bPerfect; ~ServerRef(); int GetStateID() { return m_iStateID; } const char* GetName() { return m_szName; } const char* GetHost() { return m_szHost; } int GetPort() { return m_iPort; } const char* GetUser() { return m_szUser; } bool GetMatched() { return m_bMatched; } void SetMatched(bool bMatched) { m_bMatched = bMatched; } bool GetPerfect() { return m_bPerfect; } void SetPerfect(bool bPerfect) { m_bPerfect = bPerfect; } }; typedef std::deque ServerRefList; ServerRef::~ServerRef() { free(m_szName); free(m_szHost); free(m_szUser); } enum ECriteria { eName, eHost, ePort, eUser }; void FindCandidates(NewsServer* pNewsServer, ServerRefList* pRefs, ECriteria eCriteria, bool bKeepIfNothing) { ServerRefList originalRefs; originalRefs.insert(originalRefs.begin(), pRefs->begin(), pRefs->end()); int index = 0; for (ServerRefList::iterator it = pRefs->begin(); it != pRefs->end(); ) { ServerRef* pRef = *it; bool bMatch = false; switch(eCriteria) { case eName: bMatch = !strcasecmp(pNewsServer->GetName(), pRef->GetName()); break; case eHost: bMatch = !strcasecmp(pNewsServer->GetHost(), pRef->GetHost()); break; case ePort: bMatch = pNewsServer->GetPort() == pRef->GetPort(); break; case eUser: bMatch = !strcasecmp(pNewsServer->GetUser(), pRef->GetUser()); break; } if (bMatch && !pRef->GetMatched()) { it++; index++; } else { pRefs->erase(it); it = pRefs->begin() + index; } } if (pRefs->size() == 0 && bKeepIfNothing) { pRefs->insert(pRefs->begin(), originalRefs.begin(), originalRefs.end()); } } void MatchServers(Servers* pServers, ServerRefList* pServerRefs) { // Step 1: trying perfect match for (Servers::iterator it = pServers->begin(); it != pServers->end(); it++) { NewsServer* pNewsServer = *it; ServerRefList matchedRefs; matchedRefs.insert(matchedRefs.begin(), pServerRefs->begin(), pServerRefs->end()); FindCandidates(pNewsServer, &matchedRefs, eName, false); FindCandidates(pNewsServer, &matchedRefs, eHost, false); FindCandidates(pNewsServer, &matchedRefs, ePort, false); FindCandidates(pNewsServer, &matchedRefs, eUser, false); if (matchedRefs.size() == 1) { ServerRef* pRef = matchedRefs.front(); pNewsServer->SetStateID(pRef->GetStateID()); pRef->SetMatched(true); pRef->SetPerfect(true); } } // Step 2: matching host, port, username and server-name for (Servers::iterator it = pServers->begin(); it != pServers->end(); it++) { NewsServer* pNewsServer = *it; if (!pNewsServer->GetStateID()) { ServerRefList matchedRefs; matchedRefs.insert(matchedRefs.begin(), pServerRefs->begin(), pServerRefs->end()); FindCandidates(pNewsServer, &matchedRefs, eHost, false); if (matchedRefs.size() > 1) { FindCandidates(pNewsServer, &matchedRefs, eName, true); } if (matchedRefs.size() > 1) { FindCandidates(pNewsServer, &matchedRefs, eUser, true); } if (matchedRefs.size() > 1) { FindCandidates(pNewsServer, &matchedRefs, ePort, true); } if (!matchedRefs.empty()) { ServerRef* pRef = matchedRefs.front(); pNewsServer->SetStateID(pRef->GetStateID()); pRef->SetMatched(true); } } } } /* * END: Server matching *************************************************************************************** */ bool DiskState::LoadServerInfo(Servers* pServers, FILE* infile, int iFormatVersion, bool* pPerfectMatch) { debug("Loading server info from disk"); ServerRefList serverRefs; *pPerfectMatch = true; int size; if (fscanf(infile, "%i\n", &size) != 1) goto error; for (int i = 0; i < size; i++) { char szName[1024]; if (!fgets(szName, sizeof(szName), infile)) goto error; if (szName[0] != 0) szName[strlen(szName)-1] = 0; // remove traling '\n' char szHost[200]; if (!fgets(szHost, sizeof(szHost), infile)) goto error; if (szHost[0] != 0) szHost[strlen(szHost)-1] = 0; // remove traling '\n' int iPort; if (fscanf(infile, "%i\n", &iPort) != 1) goto error; char szUser[100]; if (!fgets(szUser, sizeof(szUser), infile)) goto error; if (szUser[0] != 0) szUser[strlen(szUser)-1] = 0; // remove traling '\n' ServerRef* pRef = new ServerRef(); pRef->m_iStateID = i + 1; pRef->m_szName = strdup(szName); pRef->m_szHost = strdup(szHost); pRef->m_iPort = iPort; pRef->m_szUser = strdup(szUser); pRef->m_bMatched = false; pRef->m_bPerfect = false; serverRefs.push_back(pRef); } MatchServers(pServers, &serverRefs); for (ServerRefList::iterator it = serverRefs.begin(); it != serverRefs.end(); it++) { ServerRef* pRef = *it; *pPerfectMatch = *pPerfectMatch && pRef->GetPerfect(); delete *it; } debug("******** MATCHING NEWS-SERVERS **********"); for (Servers::iterator it = pServers->begin(); it != pServers->end(); it++) { NewsServer* pNewsServer = *it; *pPerfectMatch = *pPerfectMatch && pNewsServer->GetStateID(); debug("Server %i -> %i", pNewsServer->GetID(), pNewsServer->GetStateID()); debug("Server %i.Name: %s", pNewsServer->GetID(), pNewsServer->GetName()); debug("Server %i.Host: %s:%i", pNewsServer->GetID(), pNewsServer->GetHost(), pNewsServer->GetPort()); } debug("All servers perfectly matched: %s", *pPerfectMatch ? "yes" : "no"); return true; error: error("Error reading server info from disk"); for (ServerRefList::iterator it = serverRefs.begin(); it != serverRefs.end(); it++) { delete *it; } return false; } bool DiskState::SaveVolumeStat(ServerVolumes* pServerVolumes, FILE* outfile) { debug("Saving volume stats to disk"); fprintf(outfile, "%i\n", (int)pServerVolumes->size()); for (ServerVolumes::iterator it = pServerVolumes->begin(); it != pServerVolumes->end(); it++) { ServerVolume* pServerVolume = *it; fprintf(outfile, "%i,%i,%i\n", pServerVolume->GetFirstDay(), (int)pServerVolume->GetDataTime(), (int)pServerVolume->GetCustomTime()); unsigned long High1, Low1, High2, Low2; Util::SplitInt64(pServerVolume->GetTotalBytes(), &High1, &Low1); Util::SplitInt64(pServerVolume->GetCustomBytes(), &High2, &Low2); fprintf(outfile, "%lu,%lu,%lu,%lu\n", High1, Low1, High2, Low2); ServerVolume::VolumeArray* VolumeArrays[] = { pServerVolume->BytesPerSeconds(), pServerVolume->BytesPerMinutes(), pServerVolume->BytesPerHours(), pServerVolume->BytesPerDays() }; for (int i=0; i < 4; i++) { ServerVolume::VolumeArray* pVolumeArray = VolumeArrays[i]; fprintf(outfile, "%i\n", (int)pVolumeArray->size()); for (ServerVolume::VolumeArray::iterator it2 = pVolumeArray->begin(); it2 != pVolumeArray->end(); it2++) { long long lBytes = *it2; Util::SplitInt64(lBytes, &High1, &Low1); fprintf(outfile, "%lu,%lu\n", High1, Low1); } } } return true; } bool DiskState::LoadVolumeStat(Servers* pServers, ServerVolumes* pServerVolumes, FILE* infile, int iFormatVersion) { debug("Loading volume stats from disk"); int size; if (fscanf(infile, "%i\n", &size) != 1) goto error; for (int i = 0; i < size; i++) { ServerVolume* pServerVolume = NULL; if (i == 0) { pServerVolume = pServerVolumes->at(0); } else { for (Servers::iterator it = pServers->begin(); it != pServers->end(); it++) { NewsServer* pNewsServer = *it; if (pNewsServer->GetStateID() == i) { pServerVolume = pServerVolumes->at(pNewsServer->GetID()); } } } int iFirstDay, iDataTime, iCustomTime; unsigned long High1, Low1, High2 = 0, Low2 = 0; if (iFormatVersion >= 3) { if (fscanf(infile, "%i,%i,%i\n", &iFirstDay, &iDataTime,&iCustomTime) != 3) goto error; if (fscanf(infile, "%lu,%lu,%lu,%lu\n", &High1, &Low1, &High2, &Low2) != 4) goto error; if (pServerVolume) pServerVolume->SetCustomTime((time_t)iCustomTime); } else { if (fscanf(infile, "%i,%i\n", &iFirstDay, &iDataTime) != 2) goto error; if (fscanf(infile, "%lu,%lu\n", &High1, &Low1) != 2) goto error; } if (pServerVolume) pServerVolume->SetFirstDay(iFirstDay); if (pServerVolume) pServerVolume->SetDataTime((time_t)iDataTime); if (pServerVolume) pServerVolume->SetTotalBytes(Util::JoinInt64(High1, Low1)); if (pServerVolume) pServerVolume->SetCustomBytes(Util::JoinInt64(High2, Low2)); ServerVolume::VolumeArray* VolumeArrays[] = { pServerVolume ? pServerVolume->BytesPerSeconds() : NULL, pServerVolume ? pServerVolume->BytesPerMinutes() : NULL, pServerVolume ? pServerVolume->BytesPerHours() : NULL, pServerVolume ? pServerVolume->BytesPerDays() : NULL }; for (int k=0; k < 4; k++) { ServerVolume::VolumeArray* pVolumeArray = VolumeArrays[k]; int iArrSize; if (fscanf(infile, "%i\n", &iArrSize) != 1) goto error; if (pVolumeArray) pVolumeArray->resize(iArrSize); for (int j = 0; j < iArrSize; j++) { if (fscanf(infile, "%lu,%lu\n", &High1, &Low1) != 2) goto error; if (pVolumeArray) (*pVolumeArray)[j] = Util::JoinInt64(High1, Low1); } } } return true; error: error("Error reading volume stats from disk"); return false; } void DiskState::WriteCacheFlag() { char szFlagFilename[1024]; snprintf(szFlagFilename, 1024, "%s%s", g_pOptions->GetQueueDir(), "acache"); szFlagFilename[1024-1] = '\0'; FILE* outfile = fopen(szFlagFilename, FOPEN_WB); if (!outfile) { error("Error saving diskstate: Could not create file %s", szFlagFilename); return; } fclose(outfile); } void DiskState::DeleteCacheFlag() { char szFlagFilename[1024]; snprintf(szFlagFilename, 1024, "%s%s", g_pOptions->GetQueueDir(), "acache"); szFlagFilename[1024-1] = '\0'; remove(szFlagFilename); } void DiskState::AppendNZBMessage(int iNZBID, Message::EKind eKind, const char* szText) { char szLogFilename[1024]; snprintf(szLogFilename, 1024, "%sn%i.log", g_pOptions->GetQueueDir(), iNZBID); szLogFilename[1024-1] = '\0'; FILE* outfile = fopen(szLogFilename, FOPEN_ABP); if (!outfile) { error("Error saving log: Could not create file %s", szLogFilename); return; } const char* szMessageType[] = { "INFO", "WARNING", "ERROR", "DEBUG", "DETAIL"}; char tmp2[1024]; strncpy(tmp2, szText, 1024); tmp2[1024-1] = '\0'; // replace bad chars for (char* p = tmp2; *p; p++) { char ch = *p; if (ch == '\n' || ch == '\r' || ch == '\t') { *p = ' '; } } time_t tm = time(NULL); time_t rawtime = tm + g_pOptions->GetTimeCorrection(); char szTime[50]; #ifdef HAVE_CTIME_R_3 ctime_r(&rawtime, szTime, 50); #else ctime_r(&rawtime, szTime); #endif szTime[50-1] = '\0'; szTime[strlen(szTime) - 1] = '\0'; // trim LF fprintf(outfile, "%s\t%u\t%s\t%s%s", szTime, (int)tm, szMessageType[eKind], tmp2, LINE_ENDING); fclose(outfile); } void DiskState::LoadNZBMessages(int iNZBID, MessageList* pMessages) { // Important: // - Other threads may be writing into the log-file at any time; // - The log-file may also be deleted from another thread; char szLogFilename[1024]; snprintf(szLogFilename, 1024, "%sn%i.log", g_pOptions->GetQueueDir(), iNZBID); szLogFilename[1024-1] = '\0'; if (!Util::FileExists(szLogFilename)) { return; } FILE* infile = fopen(szLogFilename, FOPEN_RB); if (!infile) { error("Error reading log: could not open file %s", szLogFilename); return; } int iID = 0; char szLine[1024]; while (fgets(szLine, sizeof(szLine), infile)) { Util::TrimRight(szLine); // time (skip formatted time first) char* p = strchr(szLine, '\t'); if (!p) goto exit; int iTime = atoi(p + 1); // kind p = strchr(p + 1, '\t'); if (!p) goto exit; char* szKind = p + 1; Message::EKind eKind = Message::mkError; if (!strncmp(szKind, "INFO", 4)) { eKind = Message::mkInfo; } else if (!strncmp(szKind, "WARNING", 7)) { eKind = Message::mkWarning; } else if (!strncmp(szKind, "ERROR", 5)) { eKind = Message::mkError; } else if (!strncmp(szKind, "DETAIL", 6)) { eKind = Message::mkDetail; } else if (!strncmp(szKind, "DEBUG", 5)) { eKind = Message::mkDebug; } // text p = strchr(p + 1, '\t'); if (!p) goto exit; char* szText = p + 1; Message* pMessage = new Message(++iID, eKind, (time_t)iTime, szText); pMessages->push_back(pMessage); } exit: fclose(infile); return; } nzbget-16.4/daemon/queue/Scanner.h0000644000175000017500000001065212630544544016715 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 SCANNER_H #define SCANNER_H #include #include #include "DownloadInfo.h" #include "Thread.h" #include "Service.h" class Scanner : public Service { public: enum EAddStatus { asSkipped, asSuccess, asFailed }; private: class FileData { private: char* m_szFilename; long long m_iSize; time_t m_tLastChange; public: FileData(const char* szFilename); ~FileData(); const char* GetFilename() { return m_szFilename; } long long GetSize() { return m_iSize; } void SetSize(long long lSize) { m_iSize = lSize; } time_t GetLastChange() { return m_tLastChange; } void SetLastChange(time_t tLastChange) { m_tLastChange = tLastChange; } }; typedef std::deque 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; NZBInfo* m_pUrlInfo; EAddStatus* m_pAddStatus; int* m_pNZBID; 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, NZBInfo* pUrlInfo, EAddStatus* pAddStatus, int* pNZBID); ~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; } NZBInfo* GetUrlInfo() { return m_pUrlInfo; } void SetAddStatus(EAddStatus eAddStatus); void SetNZBID(int iNZBID); }; typedef std::deque QueueList; bool m_bRequestedNZBDirScan; int m_iNZBDirInterval; bool m_bScanScript; int m_iPass; FileList m_FileList; QueueList m_QueueList; bool m_bScanning; Mutex m_mutexScan; void CheckIncomingNZBs(const char* szDirectory, const char* szCategory, bool bCheckStat); 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, NZBInfo* pUrlInfo, int* pNZBID); void ProcessIncomingFile(const char* szDirectory, const char* szBaseFilename, const char* szFullFilename, const char* szCategory); bool CanProcessFile(const char* szFullFilename, bool bCheckStat); void DropOldFiles(); void ClearQueueList(); protected: virtual int ServiceInterval() { return 200; } virtual void ServiceWork(); public: Scanner(); ~Scanner(); void InitOptions(); void ScanNZBDir(bool bSyncMode); EAddStatus AddExternalFile(const char* szNZBName, const char* szCategory, int iPriority, const char* szDupeKey, int iDupeScore, EDupeMode eDupeMode, NZBParameterList* pParameters, bool bAddTop, bool bAddPaused, NZBInfo* pUrlInfo, const char* szFileName, const char* szBuffer, int iBufSize, int* pNZBID); void InitPPParameters(const char* szCategory, NZBParameterList* pParameters, bool bReset); }; extern Scanner* g_pScanner; #endif nzbget-16.4/daemon/queue/NZBFile.cpp0000644000175000017500000005370312630544544017114 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #include #ifdef WIN32 #include #import named_guids using namespace MSXML; #else #include #include #include #include #endif #include "nzbget.h" #include "NZBFile.h" #include "Log.h" #include "DownloadInfo.h" #include "Options.h" #include "DiskState.h" #include "Util.h" NZBFile::NZBFile(const char* szFileName, const char* szCategory) { debug("Creating NZBFile"); m_szFileName = strdup(szFileName); m_szPassword = NULL; m_pNZBInfo = new NZBInfo(); 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; m_iTagContentLen = 0; #endif } NZBFile::~NZBFile() { debug("Destroying NZBFile"); // Cleanup free(m_szFileName); free(m_szPassword); #ifndef WIN32 delete m_pFileInfo; free(m_szTagContent); #endif delete m_pNZBInfo; } void NZBFile::LogDebugInfo() { info(" NZBFile %s", m_szFileName); } void NZBFile::AddArticle(FileInfo* pFileInfo, ArticleInfo* pArticleInfo) { // make Article-List big enough while ((int)pFileInfo->GetArticles()->size() < pArticleInfo->GetPartNumber()) pFileInfo->GetArticles()->push_back(NULL); int index = pArticleInfo->GetPartNumber() - 1; if ((*pFileInfo->GetArticles())[index]) { delete (*pFileInfo->GetArticles())[index]; } (*pFileInfo->GetArticles())[index] = pArticleInfo; } void NZBFile::AddFileInfo(FileInfo* pFileInfo) { // 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(); ) { 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()) { delete pFileInfo; return; } lMissedSize += iUncountedArticles * lOneSize; lSize += lMissedSize; m_pNZBInfo->GetFileList()->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) { // Example subject: some garbage "title" yEnc (10/99) // strip the "yEnc (10/99)"-suffix char szSubject[1024]; strncpy(szSubject, pFileInfo->GetSubject(), sizeof(szSubject)); szSubject[1024-1] = '\0'; char* end = szSubject + strlen(szSubject) - 1; if (*end == ')') { end--; while (strchr("0123456789", *end) && end > szSubject) end--; if (*end == '/') { end--; while (strchr("0123456789", *end) && end > szSubject) end--; if (end - 6 > szSubject && !strncmp(end - 6, " yEnc (", 7)) { end[-6] = '\0'; } } } if (TryQuotes) { // try to use the filename in quatation marks char* p = szSubject; 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'; pFileInfo->SetFilename(filename); free(filename); return; } } } } // tokenize subject, considering spaces as separators and quotation // marks as non separatable token delimiters. // then take the last token containing dot (".") as a filename typedef std::list TokenList; TokenList tokens; tokens.clear(); // tokenizing char* p = szSubject; char* start = p; bool quot = false; while (true) { char ch = *p; bool sep = (ch == '\"') || (!quot && ch == ' ') || (ch == '\0'); if (sep) { // end of token int len = (int)(p - start); if (len > 0) { char* token = (char*)malloc(len + 1); strncpy(token, start, len); token[len] = '\0'; tokens.push_back(token); } start = p; if (ch != '\"' || quot) { start++; } quot = *start == '\"'; if (quot) { start++; char* q = strchr(start, '\"'); if (q) { p = q - 1; } else { quot = false; } } } if (ch == '\0') { break; } p++; } if (!tokens.empty()) { // finding the best candidate for being a filename char* besttoken = tokens.back(); for (TokenList::reverse_iterator it = tokens.rbegin(); it != tokens.rend(); it++) { char* s = *it; char* p = strchr(s, '.'); if (p && (p[1] != '\0')) { besttoken = s; break; } } pFileInfo->SetFilename(besttoken); // free mem for (TokenList::iterator it = tokens.begin(); it != tokens.end(); it++) { free(*it); } } else { // subject is empty or contains only separators? debug("Could not extract Filename from Subject: %s. Using Subject as Filename", pFileInfo->GetSubject()); pFileInfo->SetFilename(pFileInfo->GetSubject()); } } bool NZBFile::HasDuplicateFilenames() { for (FileList::iterator it = m_pNZBInfo->GetFileList()->begin(); it != m_pNZBInfo->GetFileList()->end(); it++) { FileInfo* pFileInfo1 = *it; int iDupe = 1; for (FileList::iterator it2 = it + 1; it2 != m_pNZBInfo->GetFileList()->end(); it2++) { FileInfo* pFileInfo2 = *it2; if (!strcmp(pFileInfo1->GetFilename(), pFileInfo2->GetFilename()) && strcmp(pFileInfo1->GetSubject(), pFileInfo2->GetSubject())) { iDupe++; } } // If more than two files have the same parsed filename but different subjects, // this means, that the parsing was not correct. // in this case we take subjects as filenames to prevent // false "duplicate files"-alarm. // It's Ok for just two files to have the same filename, this is // an often case by posting-errors to repost bad files if (iDupe > 2 || (iDupe == 2 && m_pNZBInfo->GetFileList()->size() == 2)) { return true; } } return false; } /** * Generate filenames from subjects and check if the parsing of subject was correct */ void NZBFile::BuildFilenames() { for (FileList::iterator it = m_pNZBInfo->GetFileList()->begin(); it != m_pNZBInfo->GetFileList()->end(); it++) { FileInfo* pFileInfo = *it; ParseSubject(pFileInfo, true); } if (HasDuplicateFilenames()) { for (FileList::iterator it = m_pNZBInfo->GetFileList()->begin(); it != m_pNZBInfo->GetFileList()->end(); it++) { FileInfo* pFileInfo = *it; ParseSubject(pFileInfo, false); } } if (HasDuplicateFilenames()) { m_pNZBInfo->SetManyDupeFiles(true); for (FileList::iterator it = m_pNZBInfo->GetFileList()->begin(); it != m_pNZBInfo->GetFileList()->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() { TempFileList fileList; for (FileList::iterator it = m_pNZBInfo->GetFileList()->begin(); it != m_pNZBInfo->GetFileList()->end(); it++) { fileList.push_back(*it); } fileList.sort(CompareFileInfo); unsigned int iFullContentHash = 0; unsigned int iFilteredContentHash = 0; int iUseForFilteredCount = 0; for (TempFileList::iterator it = fileList.begin(); it != fileList.end(); it++) { FileInfo* pFileInfo = *it; // check file extension bool bSkip = !pFileInfo->GetParFile() && Util::MatchFileExt(pFileInfo->GetFilename(), g_pOptions->GetExtCleanupDisk(), ",;"); 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++; } } } // 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 (FileList::iterator it = m_pNZBInfo->GetFileList()->begin(); it != m_pNZBInfo->GetFileList()->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->SetRemainingSize(m_pNZBInfo->GetRemainingSize() + pFileInfo->GetRemainingSize()); 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()); m_pNZBInfo->SetRemainingParCount(m_pNZBInfo->GetRemainingParCount() + 1); } } m_pNZBInfo->UpdateMinMaxTime(); CalcHashes(); if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { for (FileList::iterator it = m_pNZBInfo->GetFileList()->begin(); it != m_pNZBInfo->GetFileList()->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, FOPEN_RB); if (!pFile) { return; } // obtain file size. fseek(pFile , 0 , SEEK_END); int iSize = (int)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, ""); if (szMetaPassword) { szMetaPassword += 22; // length of '' char* szEnd = strstr(szMetaPassword, ""); if (szEnd) { *szEnd = '\0'; WebUtil::XmlDecode(szMetaPassword); free(m_szPassword); m_szPassword = strdup(szMetaPassword); } } free(buf); } #ifdef WIN32 bool NZBFile::Parse() { CoInitialize(NULL); HRESULT hr; MSXML::IXMLDOMDocumentPtr doc; hr = doc.CreateInstance(MSXML::CLSID_DOMDocument); if (FAILED(hr)) { return false; } // 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(m_szFileName)*3 + 1); EncodeURL(m_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; char szMessageText[1024]; snprintf(szMessageText, 1024, "Error parsing nzb-file %s: %s", Util::BaseFileName(m_szFileName), szErrMsg); szMessageText[1024-1] = '\0'; m_pNZBInfo->AddMessage(Message::mkError, szMessageText); return false; } if (!ParseNZB(doc)) { return false; } if (GetNZBInfo()->GetFileList()->empty()) { char szMessageText[1024]; snprintf(szMessageText, 1024, "Error parsing nzb-file %s: file has no content", Util::BaseFileName(m_szFileName)); szMessageText[1024-1] = '\0'; m_pNZBInfo->AddMessage(Message::mkError, szMessageText); return false; } ProcessFiles(); return true; } void NZBFile::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 = (unsigned char)ch >> 4; *szURL++ = a > 9 ? a - 10 + 'a' : a + '0'; a = ch & 0xF; *szURL++ = a > 9 ? a - 10 + 'a' : a + '0'; } } *szURL = NULL; } 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++) { node = fileList->Getitem(i); MSXML::IXMLDOMNodePtr attribute = node->Getattributes()->getNamedItem("subject"); if (!attribute) return false; _bstr_t subject(attribute->Gettext()); FileInfo* pFileInfo = new FileInfo(); pFileInfo->SetSubject(subject); attribute = node->Getattributes()->getNamedItem("date"); if (attribute) { _bstr_t date(attribute->Gettext()); pFileInfo->SetTime(atoi(date)); } MSXML::IXMLDOMNodeListPtr groupList = node->selectNodes("groups/group"); for (int g = 0; g < groupList->Getlength(); g++) { MSXML::IXMLDOMNodePtr node = groupList->Getitem(g); _bstr_t group = node->Gettext(); pFileInfo->GetGroups()->push_back(strdup((const char*)group)); } MSXML::IXMLDOMNodeListPtr segmentList = node->selectNodes("segments/segment"); for (int g = 0; g < segmentList->Getlength(); g++) { MSXML::IXMLDOMNodePtr node = segmentList->Getitem(g); _bstr_t id = node->Gettext(); char szId[2048]; snprintf(szId, 2048, "<%s>", (const char*)id); MSXML::IXMLDOMNodePtr attribute = node->Getattributes()->getNamedItem("number"); if (!attribute) return false; _bstr_t number(attribute->Gettext()); attribute = node->Getattributes()->getNamedItem("bytes"); if (!attribute) return false; _bstr_t bytes(attribute->Gettext()); int partNumber = atoi(number); int lsize = atoi(bytes); if (partNumber > 0) { ArticleInfo* pArticle = new ArticleInfo(); pArticle->SetPartNumber(partNumber); pArticle->SetMessageID(szId); pArticle->SetSize(lsize); AddArticle(pFileInfo, pArticle); } } AddFileInfo(pFileInfo); } return true; } #else bool NZBFile::Parse() { xmlSAXHandler SAX_handler = {0}; SAX_handler.startElement = reinterpret_cast(SAX_StartElement); SAX_handler.endElement = reinterpret_cast(SAX_EndElement); SAX_handler.characters = reinterpret_cast(SAX_characters); SAX_handler.error = reinterpret_cast(SAX_error); SAX_handler.getEntity = reinterpret_cast(SAX_getEntity); m_bIgnoreNextError = false; int ret = xmlSAXUserParseFile(&SAX_handler, this, m_szFileName); if (ret != 0) { char szMessageText[1024]; snprintf(szMessageText, 1024, "Error parsing nzb-file %s", Util::BaseFileName(m_szFileName)); szMessageText[1024-1] = '\0'; m_pNZBInfo->AddMessage(Message::mkError, szMessageText); return false; } if (m_pNZBInfo->GetFileList()->empty()) { char szMessageText[1024]; snprintf(szMessageText, 1024, "Error parsing nzb-file %s: file has no content", Util::BaseFileName(m_szFileName)); szMessageText[1024-1] = '\0'; m_pNZBInfo->AddMessage(Message::mkError, szMessageText); return false; } ProcessFiles(); return true; } void NZBFile::Parse_StartElement(const char *name, const char **atts) { char szTagAttrMessage[1024]; snprintf(szTagAttrMessage, 1024, "Malformed nzb-file, tag <%s> must have attributes", name); szTagAttrMessage[1024-1] = '\0'; if (m_szTagContent) { free(m_szTagContent); m_szTagContent = NULL; m_iTagContentLen = 0; } if (!strcmp("file", name)) { m_pFileInfo = new FileInfo(); m_pFileInfo->SetFilename(m_szFileName); if (!atts) { m_pNZBInfo->AddMessage(Message::mkWarning, szTagAttrMessage); return; } for (int i = 0; atts[i]; i += 2) { const char* attrname = atts[i]; const char* attrvalue = atts[i + 1]; if (!strcmp("subject", attrname)) { m_pFileInfo->SetSubject(attrvalue); } if (!strcmp("date", attrname)) { m_pFileInfo->SetTime(atoi(attrvalue)); } } } else if (!strcmp("segment", name)) { if (!m_pFileInfo) { m_pNZBInfo->AddMessage(Message::mkWarning, "Malformed nzb-file, tag without tag "); return; } if (!atts) { m_pNZBInfo->AddMessage(Message::mkWarning, szTagAttrMessage); return; } long long lsize = -1; int partNumber = -1; for (int i = 0; atts[i]; i += 2) { const char* attrname = atts[i]; const char* attrvalue = atts[i + 1]; if (!strcmp("bytes", attrname)) { lsize = atol(attrvalue); } if (!strcmp("number", attrname)) { partNumber = atol(attrvalue); } } if (partNumber > 0) { // new segment, add it! m_pArticle = new ArticleInfo(); m_pArticle->SetPartNumber(partNumber); m_pArticle->SetSize(lsize); AddArticle(m_pFileInfo, m_pArticle); } } else if (!strcmp("meta", name)) { if (!atts) { m_pNZBInfo->AddMessage(Message::mkWarning, szTagAttrMessage); return; } m_bPassword = atts[0] && atts[1] && !strcmp("type", atts[0]) && !strcmp("password", atts[1]); } } void NZBFile::Parse_EndElement(const char *name) { if (!strcmp("file", name)) { // Close the file element, add the new file to file-list AddFileInfo(m_pFileInfo); m_pFileInfo = NULL; m_pArticle = NULL; } else if (!strcmp("group", name)) { if (!m_pFileInfo) { // error: bad nzb-file return; } m_pFileInfo->GetGroups()->push_back(m_szTagContent); m_szTagContent = NULL; m_iTagContentLen = 0; } else if (!strcmp("segment", name)) { if (!m_pFileInfo || !m_pArticle) { // error: bad nzb-file return; } // Get the #text part char ID[2048]; snprintf(ID, 2048, "<%s>", m_szTagContent); 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) { 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 NZBFile::SAX_StartElement(NZBFile* pFile, const char *name, const char **atts) { pFile->Parse_StartElement(name, atts); } void NZBFile::SAX_EndElement(NZBFile* pFile, const char *name) { pFile->Parse_EndElement(name); } void NZBFile::SAX_characters(NZBFile* 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* NZBFile::SAX_getEntity(NZBFile* pFile, const char * name) { xmlEntityPtr e = xmlGetPredefinedEntity((xmlChar* )name); if (!e) { pFile->GetNZBInfo()->AddMessage(Message::mkWarning, "entity not found"); pFile->m_bIgnoreNextError = true; } return e; } void NZBFile::SAX_error(NZBFile* 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'; char szTextMessage[1024]; snprintf(szTextMessage, 1024, "Error parsing nzb-file: %s", szErrMsg); szTextMessage[1024-1] = '\0'; pFile->GetNZBInfo()->AddMessage(Message::mkError, szTextMessage); } #endif nzbget-16.4/daemon/queue/QueueCoordinator.cpp0000644000175000017500000012600312630544544021145 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2005 Bo Cordes Petersen * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #include #include #ifndef WIN32 #include #include #endif #include #include "nzbget.h" #include "QueueCoordinator.h" #include "Options.h" #include "ServerPool.h" #include "ArticleDownloader.h" #include "ArticleWriter.h" #include "DiskState.h" #include "Util.h" #include "Decoder.h" #include "StatMeter.h" bool QueueCoordinator::CoordinatorDownloadQueue::EditEntry( int ID, EEditAction eAction, int iOffset, const char* szText) { return m_pOwner->m_QueueEditor.EditEntry(&m_pOwner->m_DownloadQueue, ID, eAction, iOffset, szText); } bool QueueCoordinator::CoordinatorDownloadQueue::EditList( IDList* pIDList, NameList* pNameList, EMatchMode eMatchMode, EEditAction eAction, int iOffset, const char* szText) { m_bMassEdit = true; bool bRet = m_pOwner->m_QueueEditor.EditList(&m_pOwner->m_DownloadQueue, pIDList, pNameList, eMatchMode, eAction, iOffset, szText); m_bMassEdit = false; if (m_bWantSave) { Save(); } return bRet; } void QueueCoordinator::CoordinatorDownloadQueue::Save() { if (m_bMassEdit) { m_bWantSave = true; return; } if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { g_pDiskState->SaveDownloadQueue(this); } m_bWantSave = false; } QueueCoordinator::QueueCoordinator() { debug("Creating QueueCoordinator"); m_bHasMoreJobs = true; m_iServerConfigGeneration = 0; g_pLog->RegisterDebuggable(this); m_DownloadQueue.m_pOwner = this; CoordinatorDownloadQueue::Init(&m_DownloadQueue); } QueueCoordinator::~QueueCoordinator() { debug("Destroying QueueCoordinator"); // Cleanup g_pLog->UnregisterDebuggable(this); debug("Deleting ArticleDownloaders"); for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++) { delete *it; } m_ActiveDownloads.clear(); CoordinatorDownloadQueue::Final(); debug("QueueCoordinator destroyed"); } void QueueCoordinator::Load() { DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); bool bStatLoaded = true; bool bPerfectServerMatch = true; bool bQueueLoaded = false; if (g_pOptions->GetServerMode() && g_pOptions->GetSaveQueue()) { bStatLoaded = g_pStatMeter->Load(&bPerfectServerMatch); if (g_pOptions->GetReloadQueue() && g_pDiskState->DownloadQueueExists()) { bQueueLoaded = g_pDiskState->LoadDownloadQueue(pDownloadQueue, g_pServerPool->GetServers()); } else { g_pDiskState->DiscardDownloadQueue(); } } if (bQueueLoaded && bStatLoaded) { g_pDiskState->CleanupTempDir(pDownloadQueue); } if (bQueueLoaded && bStatLoaded && !bPerfectServerMatch) { debug("Changes in section of config file detected, resaving queue"); // re-save current server list into diskstate to update server ids g_pStatMeter->Save(); // re-save queue into diskstate to update server ids pDownloadQueue->Save(); // re-save file states into diskstate to update server ids if (g_pOptions->GetServerMode() && g_pOptions->GetSaveQueue()) { for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; if (g_pOptions->GetContinuePartial()) { for (FileList::iterator it2 = pNZBInfo->GetFileList()->begin(); it2 != pNZBInfo->GetFileList()->end(); it2++) { FileInfo* pFileInfo = *it2; if (!pFileInfo->GetArticles()->empty()) { g_pDiskState->SaveFileState(pFileInfo, false); } } } for (CompletedFiles::iterator it2 = pNZBInfo->GetCompletedFiles()->begin(); it2 != pNZBInfo->GetCompletedFiles()->end(); it2++) { CompletedFile* pCompletedFile = *it2; if (pCompletedFile->GetStatus() != CompletedFile::cfSuccess && pCompletedFile->GetID() > 0) { FileInfo* pFileInfo = new FileInfo(pCompletedFile->GetID()); if (g_pDiskState->LoadFileState(pFileInfo, g_pServerPool->GetServers(), false)) { g_pDiskState->SaveFileState(pFileInfo, true); } delete pFileInfo; } } } } } CoordinatorDownloadQueue::Loaded(); DownloadQueue::Unlock(); } void QueueCoordinator::Run() { debug("Entering QueueCoordinator-loop"); Load(); AdjustDownloadsLimit(); bool bWasStandBy = true; bool bArticeDownloadsRunning = false; int iResetCounter = 0; g_pStatMeter->IntervalCheck(); while (!IsStopped()) { bool bDownloadsChecked = false; bool bDownloadStarted = false; NNTPConnection* pConnection = g_pServerPool->GetConnection(0, NULL, NULL); if (pConnection) { // start download for next article FileInfo* pFileInfo; ArticleInfo* pArticleInfo; bool bFreeConnection = false; DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); bool bHasMoreArticles = GetNextArticle(pDownloadQueue, pFileInfo, pArticleInfo); bArticeDownloadsRunning = !m_ActiveDownloads.empty(); bDownloadsChecked = true; m_bHasMoreJobs = bHasMoreArticles || bArticeDownloadsRunning; if (bHasMoreArticles && !IsStopped() && (int)m_ActiveDownloads.size() < m_iDownloadsLimit && (!g_pOptions->GetTempPauseDownload() || pFileInfo->GetExtraPriority())) { StartArticleDownload(pFileInfo, pArticleInfo, pConnection); bArticeDownloadsRunning = true; bDownloadStarted = true; } else { bFreeConnection = true; } DownloadQueue::Unlock(); if (bFreeConnection) { g_pServerPool->FreeConnection(pConnection, false); } } if (!bDownloadsChecked) { DownloadQueue::Lock(); bArticeDownloadsRunning = !m_ActiveDownloads.empty(); DownloadQueue::Unlock(); } bool bStandBy = !bArticeDownloadsRunning; if (bStandBy != bWasStandBy) { g_pStatMeter->EnterLeaveStandBy(bStandBy); bWasStandBy = bStandBy; if (bStandBy) { SavePartialState(); } } // sleep longer in StandBy int iSleepInterval = bDownloadStarted ? 0 : bStandBy ? 100 : 5; usleep(iSleepInterval * 1000); if (!bStandBy) { g_pStatMeter->AddSpeedReading(0); } Util::SetStandByMode(bStandBy); iResetCounter += iSleepInterval; if (iResetCounter >= 1000) { // this code should not be called too often, once per second is OK g_pServerPool->CloseUnusedConnections(); ResetHangingDownloads(); if (!bStandBy) { SavePartialState(); } iResetCounter = 0; g_pStatMeter->IntervalCheck(); AdjustDownloadsLimit(); } } // waiting for downloads debug("QueueCoordinator: waiting for Downloads to complete"); bool completed = false; while (!completed) { DownloadQueue::Lock(); completed = m_ActiveDownloads.size() == 0; DownloadQueue::Unlock(); usleep(100 * 1000); ResetHangingDownloads(); } debug("QueueCoordinator: Downloads are completed"); SavePartialState(); 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, NZBInfo* pUrlInfo, bool bAddFirst) { debug("Adding NZBFile to queue"); NZBInfo* pNZBInfo = pNZBFile->GetNZBInfo(); DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); DownloadQueue::Aspect foundAspect = { DownloadQueue::eaNzbFound, pDownloadQueue, pNZBInfo, NULL }; pDownloadQueue->Notify(&foundAspect); NZBInfo::EDeleteStatus eDeleteStatus = pNZBInfo->GetDeleteStatus(); if (eDeleteStatus != NZBInfo::dsNone) { bool bAllPaused = !pNZBInfo->GetFileList()->empty(); for (FileList::iterator it = pNZBInfo->GetFileList()->begin(); it != pNZBInfo->GetFileList()->end(); it++) { FileInfo* pFileInfo = *it; bAllPaused &= pFileInfo->GetPaused(); if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { g_pDiskState->DiscardFile(pFileInfo, true, false, false); } } pNZBInfo->SetDeletePaused(bAllPaused); } if (eDeleteStatus != NZBInfo::dsManual) { // NZBInfo will be added either to queue or to history as duplicate // and therefore can be detached from NZBFile. pNZBFile->DetachNZBInfo(); } if (eDeleteStatus == NZBInfo::dsNone) { if (g_pOptions->GetDupeCheck() && pNZBInfo->GetDupeMode() != dmForce) { CheckDupeFileInfos(pNZBInfo); } if (pUrlInfo) { // insert at the URL position for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pPosNzbInfo = *it; if (pPosNzbInfo == pUrlInfo) { pDownloadQueue->GetQueue()->insert(it, pNZBInfo); break; } } } else if (bAddFirst) { pDownloadQueue->GetQueue()->push_front(pNZBInfo); } else { pDownloadQueue->GetQueue()->push_back(pNZBInfo); } } if (pUrlInfo) { pNZBInfo->SetID(pUrlInfo->GetID()); pDownloadQueue->GetQueue()->Remove(pUrlInfo); delete pUrlInfo; } if (eDeleteStatus == NZBInfo::dsNone) { pNZBInfo->PrintMessage(Message::mkInfo, "Collection %s added to queue", pNZBInfo->GetName()); } if (eDeleteStatus != NZBInfo::dsManual) { DownloadQueue::Aspect addedAspect = { DownloadQueue::eaNzbAdded, pDownloadQueue, pNZBInfo, NULL }; pDownloadQueue->Notify(&addedAspect); } pDownloadQueue->Save(); DownloadQueue::Unlock(); } void QueueCoordinator::CheckDupeFileInfos(NZBInfo* pNZBInfo) { debug("CheckDupeFileInfos"); if (!g_pOptions->GetDupeCheck() || pNZBInfo->GetDupeMode() == dmForce) { return; } FileList dupeList(true); int index1 = 0; for (FileList::iterator it = pNZBInfo->GetFileList()->begin(); it != pNZBInfo->GetFileList()->end(); it++) { index1++; FileInfo* pFileInfo = *it; bool dupe = false; int index2 = 0; for (FileList::iterator it2 = pNZBInfo->GetFileList()->begin(); it2 != pNZBInfo->GetFileList()->end(); it2++) { index2++; FileInfo* pFileInfo2 = *it2; if (pFileInfo != pFileInfo2 && !strcmp(pFileInfo->GetFilename(), pFileInfo2->GetFilename()) && (pFileInfo->GetSize() < pFileInfo2->GetSize() || (pFileInfo->GetSize() == pFileInfo2->GetSize() && index2 < index1))) { warn("File \"%s\" appears twice in collection, adding only the biggest file", pFileInfo->GetFilename()); dupe = true; break; } } if (dupe) { dupeList.push_back(pFileInfo); continue; } } for (FileList::iterator it = dupeList.begin(); it != dupeList.end(); it++) { FileInfo* pFileInfo = *it; StatFileInfo(pFileInfo, false); pNZBInfo->GetFileList()->Remove(pFileInfo); if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { g_pDiskState->DiscardFile(pFileInfo, true, false, false); } } } void QueueCoordinator::Stop() { Thread::Stop(); debug("Stopping ArticleDownloads"); DownloadQueue::Lock(); for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++) { (*it)->Stop(); } DownloadQueue::Unlock(); debug("ArticleDownloads are notified"); } /* * Returns next article for download. */ bool QueueCoordinator::GetNextArticle(DownloadQueue* pDownloadQueue, FileInfo* &pFileInfo, ArticleInfo* &pArticleInfo) { // find an unpaused file with the highest priority, then take the next article from the file. // if the file doesn't have any articles left for download, we store that fact and search again, // ignoring all files which were previously marked as not having any articles. // special case: if the file has ExtraPriority-flag set, it has the highest priority and the // Paused-flag is ignored. //debug("QueueCoordinator::GetNextArticle()"); bool bOK = false; // pCheckedFiles stores bool* pCheckedFiles = NULL; time_t tCurDate = time(NULL); while (!bOK) { pFileInfo = NULL; int iNum = 0; int iFileNum = 0; for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; for (FileList::iterator it2 = pNZBInfo->GetFileList()->begin(); it2 != pNZBInfo->GetFileList()->end(); it2++) { FileInfo* pFileInfo1 = *it2; if ((!pCheckedFiles || !pCheckedFiles[iNum]) && !pFileInfo1->GetPaused() && !pFileInfo1->GetDeleted() && (g_pOptions->GetPropagationDelay() == 0 || (int)pFileInfo1->GetTime() < (int)tCurDate - g_pOptions->GetPropagationDelay()) && (!g_pOptions->GetPauseDownload() || pNZBInfo->GetForcePriority()) && (!pFileInfo || (pFileInfo1->GetExtraPriority() == pFileInfo->GetExtraPriority() && pFileInfo1->GetNZBInfo()->GetPriority() > pFileInfo->GetNZBInfo()->GetPriority()) || (pFileInfo1->GetExtraPriority() > pFileInfo->GetExtraPriority()))) { pFileInfo = pFileInfo1; iFileNum = iNum; } iNum++; } } if (!pFileInfo) { // there are no more files for download break; } if (pFileInfo->GetArticles()->empty() && g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { g_pDiskState->LoadArticles(pFileInfo); } // check if the file has any articles left for download for (FileInfo::Articles::iterator at = pFileInfo->GetArticles()->begin(); at != pFileInfo->GetArticles()->end(); at++) { pArticleInfo = *at; if (pArticleInfo->GetStatus() == ArticleInfo::aiUndefined) { bOK = true; break; } } if (!bOK) { // the file doesn't have any articles left for download, we mark the file as such if (!pCheckedFiles) { int iTotalFileCount = 0; for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; iTotalFileCount += pNZBInfo->GetFileList()->size(); } if (iTotalFileCount > 0) { int iArrSize = sizeof(bool) * iTotalFileCount; pCheckedFiles = (bool*)malloc(iArrSize); memset(pCheckedFiles, false, iArrSize); } } if (pCheckedFiles) { pCheckedFiles[iFileNum] = true; } } } free(pCheckedFiles); return bOK; } void QueueCoordinator::StartArticleDownload(FileInfo* pFileInfo, ArticleInfo* pArticleInfo, NNTPConnection* pConnection) { debug("Starting new ArticleDownloader"); ArticleDownloader* pArticleDownloader = new ArticleDownloader(); pArticleDownloader->SetAutoDestroy(true); pArticleDownloader->Attach(this); pArticleDownloader->SetFileInfo(pFileInfo); pArticleDownloader->SetArticleInfo(pArticleInfo); pArticleDownloader->SetConnection(pConnection); 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); pFileInfo->GetNZBInfo()->SetActiveDownloads(pFileInfo->GetNZBInfo()->GetActiveDownloads() + 1); m_ActiveDownloads.push_back(pArticleDownloader); pArticleDownloader->Start(); } void QueueCoordinator::Update(Subject* Caller, void* Aspect) { debug("Notification from ArticleDownloader received"); ArticleDownloader* pArticleDownloader = (ArticleDownloader*)Caller; if ((pArticleDownloader->GetStatus() == ArticleDownloader::adFinished) || (pArticleDownloader->GetStatus() == ArticleDownloader::adFailed) || (pArticleDownloader->GetStatus() == ArticleDownloader::adRetry)) { ArticleCompleted(pArticleDownloader); } } void QueueCoordinator::ArticleCompleted(ArticleDownloader* pArticleDownloader) { debug("Article downloaded"); FileInfo* pFileInfo = pArticleDownloader->GetFileInfo(); NZBInfo* pNZBInfo = pFileInfo->GetNZBInfo(); ArticleInfo* pArticleInfo = pArticleDownloader->GetArticleInfo(); bool bRetry = false; bool fileCompleted = false; DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); if (pArticleDownloader->GetStatus() == ArticleDownloader::adFinished) { pArticleInfo->SetStatus(ArticleInfo::aiFinished); pFileInfo->SetSuccessSize(pFileInfo->GetSuccessSize() + pArticleInfo->GetSize()); pNZBInfo->SetCurrentSuccessSize(pNZBInfo->GetCurrentSuccessSize() + pArticleInfo->GetSize()); pNZBInfo->SetParCurrentSuccessSize(pNZBInfo->GetParCurrentSuccessSize() + (pFileInfo->GetParFile() ? pArticleInfo->GetSize() : 0)); pFileInfo->SetSuccessArticles(pFileInfo->GetSuccessArticles() + 1); pNZBInfo->SetCurrentSuccessArticles(pNZBInfo->GetCurrentSuccessArticles() + 1); } else if (pArticleDownloader->GetStatus() == ArticleDownloader::adFailed) { pArticleInfo->SetStatus(ArticleInfo::aiFailed); pFileInfo->SetFailedSize(pFileInfo->GetFailedSize() + pArticleInfo->GetSize()); pNZBInfo->SetCurrentFailedSize(pNZBInfo->GetCurrentFailedSize() + pArticleInfo->GetSize()); pNZBInfo->SetParCurrentFailedSize(pNZBInfo->GetParCurrentFailedSize() + (pFileInfo->GetParFile() ? pArticleInfo->GetSize() : 0)); pFileInfo->SetFailedArticles(pFileInfo->GetFailedArticles() + 1); pNZBInfo->SetCurrentFailedArticles(pNZBInfo->GetCurrentFailedArticles() + 1); } else if (pArticleDownloader->GetStatus() == ArticleDownloader::adRetry) { pArticleInfo->SetStatus(ArticleInfo::aiUndefined); bRetry = true; } if (!bRetry) { pFileInfo->SetRemainingSize(pFileInfo->GetRemainingSize() - pArticleInfo->GetSize()); pNZBInfo->SetRemainingSize(pNZBInfo->GetRemainingSize() - pArticleInfo->GetSize()); if (pFileInfo->GetPaused()) { pNZBInfo->SetPausedSize(pNZBInfo->GetPausedSize() - pArticleInfo->GetSize()); } pFileInfo->SetCompletedArticles(pFileInfo->GetCompletedArticles() + 1); fileCompleted = (int)pFileInfo->GetArticles()->size() == pFileInfo->GetCompletedArticles(); pFileInfo->GetServerStats()->ListOp(pArticleDownloader->GetServerStats(), ServerStatList::soAdd); pNZBInfo->GetCurrentServerStats()->ListOp(pArticleDownloader->GetServerStats(), ServerStatList::soAdd); pFileInfo->SetPartialChanged(true); } if (!pFileInfo->GetFilenameConfirmed() && pArticleDownloader->GetStatus() == ArticleDownloader::adFinished && pArticleDownloader->GetArticleFilename()) { pFileInfo->SetFilename(pArticleDownloader->GetArticleFilename()); pFileInfo->SetFilenameConfirmed(true); if (g_pOptions->GetDupeCheck() && pNZBInfo->GetDupeMode() != dmForce && !pNZBInfo->GetManyDupeFiles() && Util::FileExists(pNZBInfo->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(pDownloadQueue, pFileInfo); } } pNZBInfo->SetDownloadedSize(pNZBInfo->GetDownloadedSize() + pArticleDownloader->GetDownloadedSize()); bool deleteFileObj = false; if (fileCompleted && !pFileInfo->GetDeleted()) { // all jobs done DownloadQueue::Unlock(); pArticleDownloader->CompleteFileParts(); pDownloadQueue = DownloadQueue::Lock(); deleteFileObj = true; } CheckHealth(pDownloadQueue, pFileInfo); bool hasOtherDownloaders = false; for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++) { ArticleDownloader* pDownloader = *it; if (pDownloader != pArticleDownloader && pDownloader->GetFileInfo() == pFileInfo) { 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); pNZBInfo->SetActiveDownloads(pNZBInfo->GetActiveDownloads() - 1); if (deleteFileObj) { DeleteFileInfo(pDownloadQueue, pFileInfo, fileCompleted); pDownloadQueue->Save(); } DownloadQueue::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()); } pNZBInfo->GetServerStats()->ListOp(pFileInfo->GetServerStats(), ServerStatList::soAdd); } 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()); pNZBInfo->SetCurrentSuccessArticles(pNZBInfo->GetCurrentSuccessArticles() - pFileInfo->GetSuccessArticles()); pNZBInfo->SetCurrentFailedArticles(pNZBInfo->GetCurrentFailedArticles() - pFileInfo->GetFailedArticles()); pNZBInfo->GetCurrentServerStats()->ListOp(pFileInfo->GetServerStats(), ServerStatList::soSubtract); 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()); } pNZBInfo->SetRemainingSize(pNZBInfo->GetRemainingSize() - pFileInfo->GetRemainingSize()); if (pFileInfo->GetPaused()) { pNZBInfo->SetPausedSize(pNZBInfo->GetPausedSize() - pFileInfo->GetRemainingSize()); } } if (pFileInfo->GetParFile()) { pNZBInfo->SetRemainingParCount(pNZBInfo->GetRemainingParCount() - 1); } if (pFileInfo->GetPaused()) { pNZBInfo->SetPausedFileCount(pNZBInfo->GetPausedFileCount() - 1); } } void QueueCoordinator::DeleteFileInfo(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo, bool bCompleted) { while (g_pArticleCache->FileBusy(pFileInfo)) { usleep(5*1000); } bool fileDeleted = pFileInfo->GetDeleted(); pFileInfo->SetDeleted(true); StatFileInfo(pFileInfo, bCompleted); if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode() && (!bCompleted || (pFileInfo->GetMissedArticles() == 0 && pFileInfo->GetFailedArticles() == 0))) { g_pDiskState->DiscardFile(pFileInfo, true, true, false); } if (!bCompleted) { DiscardDiskFile(pFileInfo); } NZBInfo* pNZBInfo = pFileInfo->GetNZBInfo(); DownloadQueue::Aspect aspect = { bCompleted && !fileDeleted ? DownloadQueue::eaFileCompleted : DownloadQueue::eaFileDeleted, pDownloadQueue, pNZBInfo, pFileInfo }; pDownloadQueue->Notify(&aspect); // nzb-file could be deleted from queue in "Notify", check if it is still in queue. if (std::find(pDownloadQueue->GetQueue()->begin(), pDownloadQueue->GetQueue()->end(), pNZBInfo) != pDownloadQueue->GetQueue()->end()) { pNZBInfo->GetFileList()->Remove(pFileInfo); delete pFileInfo; } } void QueueCoordinator::DiscardDiskFile(FileInfo* pFileInfo) { // deleting temporary files if (!g_pOptions->GetDirectWrite()) { for (FileInfo::Articles::iterator it = pFileInfo->GetArticles()->begin(); it != pFileInfo->GetArticles()->end(); it++) { ArticleInfo* pa = *it; if (pa->GetResultFilename()) { remove(pa->GetResultFilename()); } } } if (g_pOptions->GetDirectWrite() && pFileInfo->GetOutputFilename()) { remove(pFileInfo->GetOutputFilename()); } } void QueueCoordinator::SavePartialState() { if (!(g_pOptions->GetServerMode() && g_pOptions->GetSaveQueue() && g_pOptions->GetContinuePartial())) { return; } DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; for (FileList::iterator it2 = pNZBInfo->GetFileList()->begin(); it2 != pNZBInfo->GetFileList()->end(); it2++) { FileInfo* pFileInfo = *it2; if (pFileInfo->GetPartialChanged()) { debug("Saving partial state for %s", pFileInfo->GetFilename()); g_pDiskState->SaveFileState(pFileInfo, false); pFileInfo->SetPartialChanged(false); } } } DownloadQueue::Unlock(); } void QueueCoordinator::CheckHealth(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo) { if (g_pOptions->GetHealthCheck() == Options::hcNone || pFileInfo->GetNZBInfo()->GetHealthPaused() || pFileInfo->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsHealth || pFileInfo->GetNZBInfo()->CalcHealth() >= pFileInfo->GetNZBInfo()->CalcCriticalHealth(true) || (g_pOptions->GetParScan() == Options::psDupe && g_pOptions->GetHealthCheck() == Options::hcDelete && pFileInfo->GetNZBInfo()->GetSuccessArticles() * 100 / pFileInfo->GetNZBInfo()->GetTotalArticles() > 10)) { return; } if (g_pOptions->GetHealthCheck() == Options::hcPause) { warn("Pausing %s due to health %.1f%% below critical %.1f%%", pFileInfo->GetNZBInfo()->GetName(), pFileInfo->GetNZBInfo()->CalcHealth() / 10.0, pFileInfo->GetNZBInfo()->CalcCriticalHealth(true) / 10.0); pFileInfo->GetNZBInfo()->SetHealthPaused(true); pDownloadQueue->EditEntry(pFileInfo->GetNZBInfo()->GetID(), DownloadQueue::eaGroupPause, 0, NULL); } else if (g_pOptions->GetHealthCheck() == Options::hcDelete) { pFileInfo->GetNZBInfo()->PrintMessage(Message::mkWarning, "Cancelling download and deleting %s due to health %.1f%% below critical %.1f%%", pFileInfo->GetNZBInfo()->GetName(), pFileInfo->GetNZBInfo()->CalcHealth() / 10.0, pFileInfo->GetNZBInfo()->CalcCriticalHealth(true) / 10.0); pFileInfo->GetNZBInfo()->SetDeleteStatus(NZBInfo::dsHealth); pDownloadQueue->EditEntry(pFileInfo->GetNZBInfo()->GetID(), DownloadQueue::eaGroupDelete, 0, NULL); } } void QueueCoordinator::LogDebugInfo() { DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); info(" ---------- Queue"); long long lRemaining, lRemainingForced; pDownloadQueue->CalcRemainingSize(&lRemaining, &lRemainingForced); info(" Remaining: %.1f MB, Forced: %.1f MB", lRemaining / 1024.0 / 1024.0, lRemainingForced / 1024.0 / 1024.0); info(" Download: %s, Post-process: %s, Scan: %s", (g_pOptions->GetPauseDownload() ? "paused" : g_pOptions->GetTempPauseDownload() ? "temp-paused" : "active"), (g_pOptions->GetPausePostProcess() ? "paused" : "active"), (g_pOptions->GetPauseScan() ? "paused" : "active")); info(" ---------- QueueCoordinator"); info(" Active Downloads: %i, Limit: %i", m_ActiveDownloads.size(), m_iDownloadsLimit); for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++) { ArticleDownloader* pArticleDownloader = *it; pArticleDownloader->LogDebugInfo(); } DownloadQueue::Unlock(); } void QueueCoordinator::ResetHangingDownloads() { if (g_pOptions->GetTerminateTimeout() == 0 && g_pOptions->GetArticleTimeout() == 0) { return; } DownloadQueue::Lock(); time_t tm = ::time(NULL); for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end();) { ArticleDownloader* pArticleDownloader = *it; if (tm - pArticleDownloader->GetLastUpdateTime() > g_pOptions->GetArticleTimeout() + 1 && pArticleDownloader->GetStatus() == ArticleDownloader::adRunning) { error("Cancelling hanging download %s @ %s", pArticleDownloader->GetInfoName(), pArticleDownloader->GetConnectionName()); pArticleDownloader->Stop(); } if (tm - pArticleDownloader->GetLastUpdateTime() > g_pOptions->GetTerminateTimeout() && pArticleDownloader->GetStatus() == ArticleDownloader::adRunning) { ArticleInfo* pArticleInfo = pArticleDownloader->GetArticleInfo(); debug("Terminating hanging download %s", pArticleDownloader->GetInfoName()); if (pArticleDownloader->Terminate()) { error("Terminated hanging download %s @ %s", pArticleDownloader->GetInfoName(), pArticleDownloader->GetConnectionName()); pArticleInfo->SetStatus(ArticleInfo::aiUndefined); } else { error("Could not terminate hanging download %s @ %s", pArticleDownloader->GetInfoName(), pArticleDownloader->GetConnectionName()); } m_ActiveDownloads.erase(it); pArticleDownloader->GetFileInfo()->SetActiveDownloads(pArticleDownloader->GetFileInfo()->GetActiveDownloads() - 1); pArticleDownloader->GetFileInfo()->GetNZBInfo()->SetActiveDownloads(pArticleDownloader->GetFileInfo()->GetNZBInfo()->GetActiveDownloads() - 1); pArticleDownloader->GetFileInfo()->GetNZBInfo()->SetDownloadedSize(pArticleDownloader->GetFileInfo()->GetNZBInfo()->GetDownloadedSize() + pArticleDownloader->GetDownloadedSize()); // it's not safe to destroy pArticleDownloader, because the state of object is unknown delete pArticleDownloader; it = m_ActiveDownloads.begin(); continue; } it++; } DownloadQueue::Unlock(); } /* * Returns True if Entry was deleted from Queue or False if it was scheduled for Deletion. * NOTE: "False" does not mean unsuccess; the entry is (or will be) deleted in any case. */ bool QueueCoordinator::DeleteQueueEntry(DownloadQueue* pDownloadQueue, FileInfo* pFileInfo) { pFileInfo->SetDeleted(true); bool bDownloading = false; for (ActiveDownloads::iterator it = m_ActiveDownloads.begin(); it != m_ActiveDownloads.end(); it++) { ArticleDownloader* pArticleDownloader = *it; if (pArticleDownloader->GetFileInfo() == pFileInfo) { bDownloading = true; pArticleDownloader->Stop(); } } if (!bDownloading) { DeleteFileInfo(pDownloadQueue, pFileInfo, false); } return bDownloading; } bool QueueCoordinator::SetQueueEntryCategory(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, const char* szCategory) { if (pNZBInfo->GetPostInfo()) { error("Could not change category for %s. File in post-process-stage", pNZBInfo->GetName()); return false; } char szOldDestDir[1024]; strncpy(szOldDestDir, pNZBInfo->GetDestDir(), 1024); szOldDestDir[1024-1] = '\0'; pNZBInfo->SetCategory(szCategory); pNZBInfo->BuildDestDirName(); bool bDirUnchanged = !strcmp(pNZBInfo->GetDestDir(), szOldDestDir); bool bOK = bDirUnchanged || ArticleWriter::MoveCompletedFiles(pNZBInfo, szOldDestDir); return bOK; } bool QueueCoordinator::SetQueueEntryName(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, const char* szName) { if (pNZBInfo->GetPostInfo()) { error("Could not rename %s. File in post-process-stage", pNZBInfo->GetName()); return false; } if (Util::EmptyStr(szName)) { error("Could not rename %s. The new name cannot be empty", pNZBInfo->GetName()); return false; } char szNZBNicename[1024]; NZBInfo::MakeNiceNZBName(szName, szNZBNicename, sizeof(szNZBNicename), false); pNZBInfo->SetName(szNZBNicename); if (pNZBInfo->GetKind() == NZBInfo::nkUrl) { char szFilename[1024]; snprintf(szFilename, 1024, "%s.nzb", szNZBNicename); szFilename[1024-1] = '\0'; pNZBInfo->SetFilename(szFilename); return true; } char szOldDestDir[1024]; strncpy(szOldDestDir, pNZBInfo->GetDestDir(), 1024); szOldDestDir[1024-1] = '\0'; pNZBInfo->BuildDestDirName(); bool bDirUnchanged = !strcmp(pNZBInfo->GetDestDir(), szOldDestDir); bool bOK = bDirUnchanged || ArticleWriter::MoveCompletedFiles(pNZBInfo, szOldDestDir); return bOK; } bool QueueCoordinator::MergeQueueEntries(DownloadQueue* pDownloadQueue, NZBInfo* pDestNZBInfo, NZBInfo* pSrcNZBInfo) { if (pDestNZBInfo->GetPostInfo() || pSrcNZBInfo->GetPostInfo()) { error("Could not merge %s and %s. File in post-process-stage", pDestNZBInfo->GetName(), pSrcNZBInfo->GetName()); return false; } if (pDestNZBInfo->GetKind() == NZBInfo::nkUrl || pSrcNZBInfo->GetKind() == NZBInfo::nkUrl) { error("Could not merge %s and %s. URLs cannot be merged", pDestNZBInfo->GetName(), pSrcNZBInfo->GetName()); return false; } // set new dest directory, new category and move downloaded files to new dest directory pSrcNZBInfo->SetFilename(pSrcNZBInfo->GetFilename()); SetQueueEntryCategory(pDownloadQueue, pSrcNZBInfo, pDestNZBInfo->GetCategory()); // reattach file items to new NZBInfo-object for (FileList::iterator it = pSrcNZBInfo->GetFileList()->begin(); it != pSrcNZBInfo->GetFileList()->end(); it++) { FileInfo* pFileInfo = *it; pFileInfo->SetNZBInfo(pDestNZBInfo); pDestNZBInfo->GetFileList()->push_back(pFileInfo); } pSrcNZBInfo->GetFileList()->clear(); pDestNZBInfo->SetFileCount(pDestNZBInfo->GetFileCount() + pSrcNZBInfo->GetFileCount()); pDestNZBInfo->SetActiveDownloads(pDestNZBInfo->GetActiveDownloads() + pSrcNZBInfo->GetActiveDownloads()); pDestNZBInfo->SetFullContentHash(0); pDestNZBInfo->SetFilteredContentHash(0); pDestNZBInfo->SetSize(pDestNZBInfo->GetSize() + pSrcNZBInfo->GetSize()); pDestNZBInfo->SetRemainingSize(pDestNZBInfo->GetRemainingSize() + pSrcNZBInfo->GetRemainingSize()); pDestNZBInfo->SetPausedFileCount(pDestNZBInfo->GetPausedFileCount() + pSrcNZBInfo->GetPausedFileCount()); pDestNZBInfo->SetPausedSize(pDestNZBInfo->GetPausedSize() + pSrcNZBInfo->GetPausedSize()); 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()); pDestNZBInfo->SetRemainingParCount(pDestNZBInfo->GetRemainingParCount() + pSrcNZBInfo->GetRemainingParCount()); pDestNZBInfo->SetTotalArticles(pDestNZBInfo->GetTotalArticles() + pSrcNZBInfo->GetTotalArticles()); pDestNZBInfo->SetSuccessArticles(pDestNZBInfo->GetSuccessArticles() + pSrcNZBInfo->GetSuccessArticles()); pDestNZBInfo->SetFailedArticles(pDestNZBInfo->GetFailedArticles() + pSrcNZBInfo->GetFailedArticles()); pDestNZBInfo->SetCurrentSuccessArticles(pDestNZBInfo->GetCurrentSuccessArticles() + pSrcNZBInfo->GetCurrentSuccessArticles()); pDestNZBInfo->SetCurrentFailedArticles(pDestNZBInfo->GetCurrentFailedArticles() + pSrcNZBInfo->GetCurrentFailedArticles()); pDestNZBInfo->GetServerStats()->ListOp(pSrcNZBInfo->GetServerStats(), ServerStatList::soAdd); pDestNZBInfo->GetCurrentServerStats()->ListOp(pSrcNZBInfo->GetCurrentServerStats(), ServerStatList::soAdd); pDestNZBInfo->SetMinTime(pSrcNZBInfo->GetMinTime() < pDestNZBInfo->GetMinTime() ? pSrcNZBInfo->GetMinTime() : pDestNZBInfo->GetMinTime()); pDestNZBInfo->SetMaxTime(pSrcNZBInfo->GetMaxTime() > pDestNZBInfo->GetMaxTime() ? pSrcNZBInfo->GetMaxTime() : pDestNZBInfo->GetMaxTime()); pDestNZBInfo->SetDownloadedSize(pDestNZBInfo->GetDownloadedSize() + pSrcNZBInfo->GetDownloadedSize()); pDestNZBInfo->SetDownloadSec(pDestNZBInfo->GetDownloadSec() + pSrcNZBInfo->GetDownloadSec()); pDestNZBInfo->SetDownloadStartTime((pDestNZBInfo->GetDownloadStartTime() > 0 && pDestNZBInfo->GetDownloadStartTime() < pSrcNZBInfo->GetDownloadStartTime()) || pSrcNZBInfo->GetDownloadStartTime() == 0 ? pDestNZBInfo->GetDownloadStartTime() : pSrcNZBInfo->GetDownloadStartTime()); // reattach completed file items to new NZBInfo-object for (CompletedFiles::iterator it = pSrcNZBInfo->GetCompletedFiles()->begin(); it != pSrcNZBInfo->GetCompletedFiles()->end(); it++) { CompletedFile* pCompletedFile = *it; pDestNZBInfo->GetCompletedFiles()->push_back(pCompletedFile); } pSrcNZBInfo->GetCompletedFiles()->clear(); // concatenate QueuedFilenames using character '|' as separator int iLen = strlen(pDestNZBInfo->GetQueuedFilename()) + strlen(pSrcNZBInfo->GetQueuedFilename()) + 1; char* szQueuedFilename = (char*)malloc(iLen); snprintf(szQueuedFilename, iLen, "%s|%s", pDestNZBInfo->GetQueuedFilename(), pSrcNZBInfo->GetQueuedFilename()); szQueuedFilename[iLen - 1] = '\0'; pDestNZBInfo->SetQueuedFilename(szQueuedFilename); free(szQueuedFilename); pDownloadQueue->GetQueue()->Remove(pSrcNZBInfo); g_pDiskState->DiscardFiles(pSrcNZBInfo); delete pSrcNZBInfo; 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. */ bool QueueCoordinator::SplitQueueEntries(DownloadQueue* pDownloadQueue, FileList* pFileList, const char* szName, NZBInfo** pNewNZBInfo) { if (pFileList->empty()) { return false; } NZBInfo* pSrcNZBInfo = NULL; for (FileList::iterator it = pFileList->begin(); it != pFileList->end(); it++) { FileInfo* pFileInfo = *it; if (pFileInfo->GetActiveDownloads() > 0 || pFileInfo->GetCompletedArticles() > 0) { error("Could not split %s. File is already (partially) downloaded", pFileInfo->GetFilename()); return false; } if (pFileInfo->GetNZBInfo()->GetPostInfo()) { error("Could not split %s. File in post-process-stage", pFileInfo->GetFilename()); return false; } if (!pSrcNZBInfo) { pSrcNZBInfo = pFileInfo->GetNZBInfo(); } } NZBInfo* pNZBInfo = new NZBInfo(); pDownloadQueue->GetQueue()->push_back(pNZBInfo); pNZBInfo->SetFilename(pSrcNZBInfo->GetFilename()); pNZBInfo->SetName(szName); pNZBInfo->SetCategory(pSrcNZBInfo->GetCategory()); pNZBInfo->SetFullContentHash(0); pNZBInfo->SetFilteredContentHash(0); pNZBInfo->SetPriority(pSrcNZBInfo->GetPriority()); pNZBInfo->BuildDestDirName(); pNZBInfo->SetQueuedFilename(pSrcNZBInfo->GetQueuedFilename()); pNZBInfo->GetParameters()->CopyFrom(pSrcNZBInfo->GetParameters()); pSrcNZBInfo->SetFullContentHash(0); pSrcNZBInfo->SetFilteredContentHash(0); for (FileList::iterator it = pFileList->begin(); it != pFileList->end(); it++) { FileInfo* pFileInfo = *it; DownloadQueue::Aspect aspect = { DownloadQueue::eaFileDeleted, pDownloadQueue, pFileInfo->GetNZBInfo(), pFileInfo }; pDownloadQueue->Notify(&aspect); pFileInfo->SetNZBInfo(pNZBInfo); pNZBInfo->GetFileList()->push_back(pFileInfo); pSrcNZBInfo->GetFileList()->Remove(pFileInfo); pSrcNZBInfo->SetFileCount(pSrcNZBInfo->GetFileCount() - 1); pSrcNZBInfo->SetSize(pSrcNZBInfo->GetSize() - pFileInfo->GetSize()); pSrcNZBInfo->SetRemainingSize(pSrcNZBInfo->GetRemainingSize() - pFileInfo->GetRemainingSize()); pSrcNZBInfo->SetCurrentSuccessSize(pSrcNZBInfo->GetCurrentSuccessSize() - pFileInfo->GetSuccessSize()); pSrcNZBInfo->SetCurrentFailedSize(pSrcNZBInfo->GetCurrentFailedSize() - pFileInfo->GetFailedSize() - pFileInfo->GetMissedSize()); pSrcNZBInfo->SetTotalArticles(pSrcNZBInfo->GetTotalArticles() - pFileInfo->GetTotalArticles()); pSrcNZBInfo->SetCurrentSuccessArticles(pSrcNZBInfo->GetCurrentSuccessArticles() - pFileInfo->GetSuccessArticles()); pSrcNZBInfo->SetCurrentFailedArticles(pSrcNZBInfo->GetCurrentFailedArticles() - pFileInfo->GetFailedArticles()); pSrcNZBInfo->GetCurrentServerStats()->ListOp(pFileInfo->GetServerStats(), ServerStatList::soSubtract); pNZBInfo->SetFileCount(pNZBInfo->GetFileCount() + 1); pNZBInfo->SetSize(pNZBInfo->GetSize() + pFileInfo->GetSize()); pNZBInfo->SetRemainingSize(pNZBInfo->GetRemainingSize() + pFileInfo->GetRemainingSize()); pNZBInfo->SetCurrentSuccessSize(pNZBInfo->GetCurrentSuccessSize() + pFileInfo->GetSuccessSize()); pNZBInfo->SetCurrentFailedSize(pNZBInfo->GetCurrentFailedSize() + pFileInfo->GetFailedSize() + pFileInfo->GetMissedSize()); pNZBInfo->SetTotalArticles(pNZBInfo->GetTotalArticles() + pFileInfo->GetTotalArticles()); pNZBInfo->SetCurrentSuccessArticles(pNZBInfo->GetCurrentSuccessArticles() + pFileInfo->GetSuccessArticles()); pNZBInfo->SetCurrentFailedArticles(pNZBInfo->GetCurrentFailedArticles() + pFileInfo->GetFailedArticles()); pNZBInfo->GetCurrentServerStats()->ListOp(pFileInfo->GetServerStats(), ServerStatList::soAdd); if (pFileInfo->GetParFile()) { pSrcNZBInfo->SetParSize(pSrcNZBInfo->GetParSize() - pFileInfo->GetSize()); pSrcNZBInfo->SetParCurrentSuccessSize(pSrcNZBInfo->GetParCurrentSuccessSize() - pFileInfo->GetSuccessSize()); pSrcNZBInfo->SetParCurrentFailedSize(pSrcNZBInfo->GetParCurrentFailedSize() - pFileInfo->GetFailedSize() - pFileInfo->GetMissedSize()); pSrcNZBInfo->SetRemainingParCount(pSrcNZBInfo->GetRemainingParCount() - 1); pNZBInfo->SetParSize(pNZBInfo->GetParSize() + pFileInfo->GetSize()); pNZBInfo->SetParCurrentSuccessSize(pNZBInfo->GetParCurrentSuccessSize() + pFileInfo->GetSuccessSize()); pNZBInfo->SetParCurrentFailedSize(pNZBInfo->GetParCurrentFailedSize() + pFileInfo->GetFailedSize() + pFileInfo->GetMissedSize()); pNZBInfo->SetRemainingParCount(pNZBInfo->GetRemainingParCount() + 1); } if (pFileInfo->GetPaused()) { pSrcNZBInfo->SetPausedFileCount(pSrcNZBInfo->GetPausedFileCount() - 1); pSrcNZBInfo->SetPausedSize(pSrcNZBInfo->GetPausedSize() - pFileInfo->GetRemainingSize()); pNZBInfo->SetPausedFileCount(pSrcNZBInfo->GetPausedFileCount() + 1); pNZBInfo->SetPausedSize(pNZBInfo->GetPausedSize() + pFileInfo->GetRemainingSize()); } } pNZBInfo->UpdateMinMaxTime(); if (pSrcNZBInfo->GetCompletedFiles()->empty()) { pSrcNZBInfo->UpdateMinMaxTime(); } if (pSrcNZBInfo->GetFileList()->empty()) { pDownloadQueue->GetQueue()->Remove(pSrcNZBInfo); g_pDiskState->DiscardFiles(pSrcNZBInfo); delete pSrcNZBInfo; } *pNewNZBInfo = pNZBInfo; return true; } nzbget-16.4/daemon/queue/QueueEditor.cpp0000644000175000017500000007752612630544544020127 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #include #include #include #include #ifndef WIN32 #include #include #endif #include #include "nzbget.h" #include "DownloadInfo.h" #include "QueueEditor.h" #include "Options.h" #include "Log.h" #include "Util.h" #include "QueueCoordinator.h" #include "PrePostProcessor.h" #include "HistoryCoordinator.h" #include "UrlCoordinator.h" const int MAX_ID = 1000000000; class GroupSorter { public: enum ESortCriteria { scName, scSize, scRemainingSize, scAge, scCategory, scPriority }; enum ESortOrder { soAscending, soDescending, soAuto }; private: NZBList* m_pNZBList; QueueEditor::ItemList* m_pSortItemList; ESortCriteria m_eSortCriteria; ESortOrder m_eSortOrder; void AlignSelectedGroups(); public: GroupSorter(NZBList* pNZBList, QueueEditor::ItemList* pSortItemList) : m_pNZBList(pNZBList), m_pSortItemList(pSortItemList) {} bool Execute(const char* szSort); bool operator()(NZBInfo* pNZBInfo1, NZBInfo* pNZBInfo2) const; }; bool GroupSorter::Execute(const char* szSort) { if (!strcasecmp(szSort, "name") || !strcasecmp(szSort, "name+") || !strcasecmp(szSort, "name-")) { m_eSortCriteria = scName; } else if (!strcasecmp(szSort, "size") || !strcasecmp(szSort, "size+") || !strcasecmp(szSort, "size-")) { m_eSortCriteria = scSize; } else if (!strcasecmp(szSort, "left") || !strcasecmp(szSort, "left+") || !strcasecmp(szSort, "left-")) { m_eSortCriteria = scRemainingSize; } else if (!strcasecmp(szSort, "age") || !strcasecmp(szSort, "age+") || !strcasecmp(szSort, "age-")) { m_eSortCriteria = scAge; } else if (!strcasecmp(szSort, "category") || !strcasecmp(szSort, "category+") || !strcasecmp(szSort, "category-")) { m_eSortCriteria = scCategory; } else if (!strcasecmp(szSort, "priority") || !strcasecmp(szSort, "priority+") || !strcasecmp(szSort, "priority-")) { m_eSortCriteria = scPriority; } else { error("Could not sort groups: incorrect sort order (%s)", szSort); return false; } char lastCh = szSort[strlen(szSort) - 1]; if (lastCh == '+') { m_eSortOrder = soAscending; } else if (lastCh == '-') { m_eSortOrder = soDescending; } else { m_eSortOrder = soAuto; } AlignSelectedGroups(); NZBList tempList = *m_pNZBList; ESortOrder eOrigSortOrder = m_eSortOrder; if (m_eSortOrder == soAuto && m_eSortCriteria == scPriority) { m_eSortOrder = soDescending; } std::sort(m_pNZBList->begin(), m_pNZBList->end(), *this); if (eOrigSortOrder == soAuto && tempList == *m_pNZBList) { m_eSortOrder = m_eSortOrder == soDescending ? soAscending : soDescending; std::sort(m_pNZBList->begin(), m_pNZBList->end(), *this); } tempList.clear(); // prevent destroying of elements return true; } bool GroupSorter::operator()(NZBInfo* pNZBInfo1, NZBInfo* pNZBInfo2) const { // if list of ID is empty - sort all items bool bSortItem1 = m_pSortItemList->empty(); bool bSortItem2 = m_pSortItemList->empty(); for (QueueEditor::ItemList::iterator it = m_pSortItemList->begin(); it != m_pSortItemList->end(); it++) { QueueEditor::EditItem* pItem = *it; bSortItem1 |= pItem->m_pNZBInfo == pNZBInfo1; bSortItem2 |= pItem->m_pNZBInfo == pNZBInfo2; } if (!bSortItem1 || !bSortItem2) { return false; } bool ret = false; if (m_eSortOrder == soDescending) { std::swap(pNZBInfo1, pNZBInfo2); } switch (m_eSortCriteria) { case scName: ret = strcmp(pNZBInfo1->GetName(), pNZBInfo2->GetName()) < 0; break; case scSize: ret = pNZBInfo1->GetSize() < pNZBInfo2->GetSize(); break; case scRemainingSize: ret = pNZBInfo1->GetRemainingSize() - pNZBInfo1->GetPausedSize() < pNZBInfo2->GetRemainingSize() - pNZBInfo2->GetPausedSize(); break; case scAge: ret = pNZBInfo1->GetMinTime() > pNZBInfo2->GetMinTime(); break; case scCategory: ret = strcmp(pNZBInfo1->GetCategory(), pNZBInfo2->GetCategory()) < 0; break; case scPriority: ret = pNZBInfo1->GetPriority() < pNZBInfo2->GetPriority(); break; } return ret; } void GroupSorter::AlignSelectedGroups() { NZBInfo* pLastNZBInfo = NULL; unsigned int iLastNum = 0; unsigned int iNum = 0; while (iNum < m_pNZBList->size()) { NZBInfo* pNZBInfo = m_pNZBList->at(iNum); bool bSelected = false; for (QueueEditor::ItemList::iterator it = m_pSortItemList->begin(); it != m_pSortItemList->end(); it++) { QueueEditor::EditItem* pItem = *it; if (pItem->m_pNZBInfo == pNZBInfo) { bSelected = true; break; } } if (bSelected) { if (pLastNZBInfo && iNum - iLastNum > 1) { m_pNZBList->erase(m_pNZBList->begin() + iNum); m_pNZBList->insert(m_pNZBList->begin() + iLastNum + 1, pNZBInfo); iLastNum++; } else { iLastNum = iNum; } pLastNZBInfo = pNZBInfo; } iNum++; } } QueueEditor::EditItem::EditItem(FileInfo* pFileInfo, NZBInfo* pNZBInfo, int iOffset) { m_pFileInfo = pFileInfo; m_pNZBInfo = pNZBInfo; m_iOffset = iOffset; } QueueEditor::QueueEditor() { debug("Creating QueueEditor"); } QueueEditor::~QueueEditor() { debug("Destroying QueueEditor"); } FileInfo* QueueEditor::FindFileInfo(int iID) { for (NZBList::iterator it = m_pDownloadQueue->GetQueue()->begin(); it != m_pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; for (FileList::iterator it2 = pNZBInfo->GetFileList()->begin(); it2 != pNZBInfo->GetFileList()->end(); it2++) { FileInfo* pFileInfo = *it2; if (pFileInfo->GetID() == iID) { return pFileInfo; } } } return NULL; } /* * Set the pause flag of the specific entry in the queue */ void QueueEditor::PauseUnpauseEntry(FileInfo* pFileInfo, bool bPause) { pFileInfo->SetPaused(bPause); } /* * Removes entry */ void QueueEditor::DeleteEntry(FileInfo* pFileInfo) { pFileInfo->GetNZBInfo()->PrintMessage( pFileInfo->GetNZBInfo()->GetDeleting() ? Message::mkDetail : Message::mkInfo, "Deleting file %s from download queue", pFileInfo->GetFilename()); g_pQueueCoordinator->DeleteQueueEntry(m_pDownloadQueue, pFileInfo); } /* * Moves entry in the queue */ void QueueEditor::MoveEntry(FileInfo* pFileInfo, int iOffset) { int iEntry = 0; for (FileList::iterator it = pFileInfo->GetNZBInfo()->GetFileList()->begin(); it != pFileInfo->GetNZBInfo()->GetFileList()->end(); it++) { FileInfo* pFileInfo2 = *it; if (pFileInfo2 == pFileInfo) { break; } iEntry ++; } int iNewEntry = iEntry + iOffset; int iSize = (int)pFileInfo->GetNZBInfo()->GetFileList()->size(); if (iNewEntry < 0) { iNewEntry = 0; } if (iNewEntry > iSize - 1) { iNewEntry = (int)iSize - 1; } if (iNewEntry >= 0 && iNewEntry <= iSize - 1) { pFileInfo->GetNZBInfo()->GetFileList()->erase(pFileInfo->GetNZBInfo()->GetFileList()->begin() + iEntry); pFileInfo->GetNZBInfo()->GetFileList()->insert(pFileInfo->GetNZBInfo()->GetFileList()->begin() + iNewEntry, pFileInfo); } } /* * Moves group in the queue */ void QueueEditor::MoveGroup(NZBInfo* pNZBInfo, int iOffset) { int iEntry = 0; for (NZBList::iterator it = m_pDownloadQueue->GetQueue()->begin(); it != m_pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo2 = *it; if (pNZBInfo2 == pNZBInfo) { break; } iEntry ++; } int iNewEntry = iEntry + iOffset; int iSize = (int)m_pDownloadQueue->GetQueue()->size(); if (iNewEntry < 0) { iNewEntry = 0; } if (iNewEntry > iSize - 1) { iNewEntry = (int)iSize - 1; } if (iNewEntry >= 0 && iNewEntry <= iSize - 1) { m_pDownloadQueue->GetQueue()->erase(m_pDownloadQueue->GetQueue()->begin() + iEntry); m_pDownloadQueue->GetQueue()->insert(m_pDownloadQueue->GetQueue()->begin() + iNewEntry, pNZBInfo); } } bool QueueEditor::EditEntry(DownloadQueue* pDownloadQueue, int ID, DownloadQueue::EEditAction eAction, int iOffset, const char* szText) { m_pDownloadQueue = pDownloadQueue; IDList cIDList; cIDList.push_back(ID); return InternEditList(NULL, &cIDList, eAction, iOffset, szText); } bool QueueEditor::EditList(DownloadQueue* pDownloadQueue, IDList* pIDList, NameList* pNameList, DownloadQueue::EMatchMode eMatchMode, DownloadQueue::EEditAction eAction, int iOffset, const char* szText) { if (eAction == DownloadQueue::eaPostDelete) { return g_pPrePostProcessor->EditList(pDownloadQueue, pIDList, eAction, iOffset, szText); } else if (DownloadQueue::eaHistoryDelete <= eAction && eAction <= DownloadQueue::eaHistorySetName) { return g_pHistoryCoordinator->EditList(pDownloadQueue, pIDList, eAction, iOffset, szText); } m_pDownloadQueue = pDownloadQueue; bool bOK = true; if (pNameList) { pIDList = new IDList(); bOK = BuildIDListFromNameList(pIDList, pNameList, eMatchMode, eAction); } bOK = bOK && (InternEditList(NULL, pIDList, eAction, iOffset, szText) || eMatchMode == DownloadQueue::mmRegEx); m_pDownloadQueue->Save(); if (pNameList) { delete pIDList; } return bOK; } bool QueueEditor::InternEditList(ItemList* pItemList, IDList* pIDList, DownloadQueue::EEditAction eAction, int iOffset, const char* szText) { ItemList itemList; if (!pItemList) { pItemList = &itemList; PrepareList(pItemList, pIDList, eAction, iOffset); } switch (eAction) { case DownloadQueue::eaFilePauseAllPars: case DownloadQueue::eaFilePauseExtraPars: PauseParsInGroups(pItemList, eAction == DownloadQueue::eaFilePauseExtraPars); break; case DownloadQueue::eaGroupMerge: return MergeGroups(pItemList); case DownloadQueue::eaGroupSort: return SortGroups(pItemList, szText); case DownloadQueue::eaFileSplit: return SplitGroup(pItemList, szText); case DownloadQueue::eaFileReorder: ReorderFiles(pItemList); break; default: for (ItemList::iterator it = pItemList->begin(); it != pItemList->end(); it++) { EditItem* pItem = *it; switch (eAction) { case DownloadQueue::eaFilePause: PauseUnpauseEntry(pItem->m_pFileInfo, true); break; case DownloadQueue::eaFileResume: PauseUnpauseEntry(pItem->m_pFileInfo, false); break; case DownloadQueue::eaFileMoveOffset: case DownloadQueue::eaFileMoveTop: case DownloadQueue::eaFileMoveBottom: MoveEntry(pItem->m_pFileInfo, pItem->m_iOffset); break; case DownloadQueue::eaFileDelete: DeleteEntry(pItem->m_pFileInfo); break; case DownloadQueue::eaGroupSetPriority: SetNZBPriority(pItem->m_pNZBInfo, szText); break; case DownloadQueue::eaGroupSetCategory: case DownloadQueue::eaGroupApplyCategory: SetNZBCategory(pItem->m_pNZBInfo, szText, eAction == DownloadQueue::eaGroupApplyCategory); break; case DownloadQueue::eaGroupSetName: SetNZBName(pItem->m_pNZBInfo, szText); break; case DownloadQueue::eaGroupSetDupeKey: case DownloadQueue::eaGroupSetDupeScore: case DownloadQueue::eaGroupSetDupeMode: SetNZBDupeParam(pItem->m_pNZBInfo, eAction, szText); break; case DownloadQueue::eaGroupSetParameter: SetNZBParameter(pItem->m_pNZBInfo, szText); break; case DownloadQueue::eaGroupMoveTop: case DownloadQueue::eaGroupMoveBottom: case DownloadQueue::eaGroupMoveOffset: MoveGroup(pItem->m_pNZBInfo, pItem->m_iOffset); break; case DownloadQueue::eaGroupPause: case DownloadQueue::eaGroupResume: case DownloadQueue::eaGroupPauseAllPars: case DownloadQueue::eaGroupPauseExtraPars: EditGroup(pItem->m_pNZBInfo, eAction, iOffset, szText); break; case DownloadQueue::eaGroupDelete: case DownloadQueue::eaGroupDupeDelete: case DownloadQueue::eaGroupFinalDelete: if (pItem->m_pNZBInfo->GetKind() == NZBInfo::nkUrl) { DeleteUrl(pItem->m_pNZBInfo, eAction); } else { EditGroup(pItem->m_pNZBInfo, eAction, iOffset, szText); } default: // suppress compiler warning "enumeration not handled in switch" break; } delete pItem; } } return pItemList->size() > 0; } void QueueEditor::PrepareList(ItemList* pItemList, IDList* pIDList, DownloadQueue::EEditAction eAction, int iOffset) { if (eAction == DownloadQueue::eaFileMoveTop || eAction == DownloadQueue::eaGroupMoveTop) { iOffset = -MAX_ID; } else if (eAction == DownloadQueue::eaFileMoveBottom || eAction == DownloadQueue::eaGroupMoveBottom) { iOffset = MAX_ID; } pItemList->reserve(pIDList->size()); if ((iOffset != 0) && (eAction == DownloadQueue::eaFileMoveOffset || eAction == DownloadQueue::eaFileMoveTop || eAction == DownloadQueue::eaFileMoveBottom)) { // add IDs to list in order they currently have in download queue for (NZBList::iterator it = m_pDownloadQueue->GetQueue()->begin(); it != m_pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; int iNrEntries = (int)pNZBInfo->GetFileList()->size(); int iLastDestPos = -1; int iStart, iEnd, iStep; if (iOffset < 0) { iStart = 0; iEnd = iNrEntries; iStep = 1; } else { iStart = iNrEntries - 1; iEnd = -1; iStep = -1; } for (int iIndex = iStart; iIndex != iEnd; iIndex += iStep) { FileInfo* pFileInfo = pNZBInfo->GetFileList()->at(iIndex); IDList::iterator it2 = std::find(pIDList->begin(), pIDList->end(), pFileInfo->GetID()); if (it2 != pIDList->end()) { int iWorkOffset = iOffset; int iDestPos = iIndex + iWorkOffset; if (iLastDestPos == -1) { if (iDestPos < 0) { iWorkOffset = -iIndex; } else if (iDestPos > iNrEntries - 1) { iWorkOffset = iNrEntries - 1 - iIndex; } } else { if (iWorkOffset < 0 && iDestPos <= iLastDestPos) { iWorkOffset = iLastDestPos - iIndex + 1; } else if (iWorkOffset > 0 && iDestPos >= iLastDestPos) { iWorkOffset = iLastDestPos - iIndex - 1; } } iLastDestPos = iIndex + iWorkOffset; pItemList->push_back(new EditItem(pFileInfo, NULL, iWorkOffset)); } } } } else if ((iOffset != 0) && (eAction == DownloadQueue::eaGroupMoveOffset || eAction == DownloadQueue::eaGroupMoveTop || eAction == DownloadQueue::eaGroupMoveBottom)) { // add IDs to list in order they currently have in download queue // per group only one FileInfo is added to the list int iNrEntries = (int)m_pDownloadQueue->GetQueue()->size(); int iLastDestPos = -1; int iStart, iEnd, iStep; if (iOffset < 0) { iStart = 0; iEnd = iNrEntries; iStep = 1; } else { iStart = iNrEntries - 1; iEnd = -1; iStep = -1; } for (int iIndex = iStart; iIndex != iEnd; iIndex += iStep) { NZBInfo* pNZBInfo = m_pDownloadQueue->GetQueue()->at(iIndex); IDList::iterator it2 = std::find(pIDList->begin(), pIDList->end(), pNZBInfo->GetID()); if (it2 != pIDList->end()) { int iWorkOffset = iOffset; int iDestPos = iIndex + iWorkOffset; if (iLastDestPos == -1) { if (iDestPos < 0) { iWorkOffset = -iIndex; } else if (iDestPos > iNrEntries - 1) { iWorkOffset = iNrEntries - 1 - iIndex; } } else { if (iWorkOffset < 0 && iDestPos <= iLastDestPos) { iWorkOffset = iLastDestPos - iIndex + 1; } else if (iWorkOffset > 0 && iDestPos >= iLastDestPos) { iWorkOffset = iLastDestPos - iIndex - 1; } } iLastDestPos = iIndex + iWorkOffset; pItemList->push_back(new EditItem(NULL, pNZBInfo, iWorkOffset)); } } } else if (eAction < DownloadQueue::eaGroupMoveOffset) { // check ID range int iMaxID = 0; int iMinID = MAX_ID; for (NZBList::iterator it = m_pDownloadQueue->GetQueue()->begin(); it != m_pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; for (FileList::iterator it2 = pNZBInfo->GetFileList()->begin(); it2 != pNZBInfo->GetFileList()->end(); it2++) { FileInfo* pFileInfo = *it2; int ID = pFileInfo->GetID(); if (ID > iMaxID) { iMaxID = ID; } if (ID < iMinID) { iMinID = ID; } } } //add IDs to list in order they were transmitted in command for (IDList::iterator it = pIDList->begin(); it != pIDList->end(); it++) { int iID = *it; if (iMinID <= iID && iID <= iMaxID) { FileInfo* pFileInfo = FindFileInfo(iID); if (pFileInfo) { pItemList->push_back(new EditItem(pFileInfo, NULL, iOffset)); } } } } else { // check ID range int iMaxID = 0; int iMinID = MAX_ID; for (NZBList::iterator it = m_pDownloadQueue->GetQueue()->begin(); it != m_pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; int ID = pNZBInfo->GetID(); if (ID > iMaxID) { iMaxID = ID; } if (ID < iMinID) { iMinID = ID; } } //add IDs to list in order they were transmitted in command for (IDList::iterator it = pIDList->begin(); it != pIDList->end(); it++) { int iID = *it; if (iMinID <= iID && iID <= iMaxID) { for (NZBList::iterator it2 = m_pDownloadQueue->GetQueue()->begin(); it2 != m_pDownloadQueue->GetQueue()->end(); it2++) { NZBInfo* pNZBInfo = *it2; if (iID == pNZBInfo->GetID()) { pItemList->push_back(new EditItem(NULL, pNZBInfo, iOffset)); } } } } } } bool QueueEditor::BuildIDListFromNameList(IDList* pIDList, NameList* pNameList, DownloadQueue::EMatchMode eMatchMode, DownloadQueue::EEditAction eAction) { #ifndef HAVE_REGEX_H if (eMatchMode == mmRegEx) { return false; } #endif std::set uniqueIDs; for (NameList::iterator it = pNameList->begin(); it != pNameList->end(); it++) { const char* szName = *it; RegEx *pRegEx = NULL; if (eMatchMode == DownloadQueue::mmRegEx) { pRegEx = new RegEx(szName); if (!pRegEx->IsValid()) { delete pRegEx; return false; } } bool bFound = false; for (NZBList::iterator it3 = m_pDownloadQueue->GetQueue()->begin(); it3 != m_pDownloadQueue->GetQueue()->end(); it3++) { NZBInfo* pNZBInfo = *it3; for (FileList::iterator it2 = pNZBInfo->GetFileList()->begin(); it2 != pNZBInfo->GetFileList()->end(); it2++) { FileInfo* pFileInfo = *it2; if (eAction < DownloadQueue::eaGroupMoveOffset) { // file action char szFilename[MAX_PATH]; snprintf(szFilename, sizeof(szFilename) - 1, "%s/%s", pFileInfo->GetNZBInfo()->GetName(), Util::BaseFileName(pFileInfo->GetFilename())); if (((!pRegEx && !strcmp(szFilename, szName)) || (pRegEx && pRegEx->Match(szFilename))) && (uniqueIDs.find(pFileInfo->GetID()) == uniqueIDs.end())) { uniqueIDs.insert(pFileInfo->GetID()); pIDList->push_back(pFileInfo->GetID()); bFound = true; } } } if (eAction >= DownloadQueue::eaGroupMoveOffset) { // group action const char *szFilename = pNZBInfo->GetName(); if (((!pRegEx && !strcmp(szFilename, szName)) || (pRegEx && pRegEx->Match(szFilename))) && (uniqueIDs.find(pNZBInfo->GetID()) == uniqueIDs.end())) { uniqueIDs.insert(pNZBInfo->GetID()); pIDList->push_back(pNZBInfo->GetID()); bFound = true; } } } delete pRegEx; if (!bFound && (eMatchMode == DownloadQueue::mmName)) { return false; } } return true; } bool QueueEditor::EditGroup(NZBInfo* pNZBInfo, DownloadQueue::EEditAction eAction, int iOffset, const char* szText) { ItemList itemList; bool bAllPaused = true; int iID = pNZBInfo->GetID(); // collecting files belonging to group for (FileList::iterator it = pNZBInfo->GetFileList()->begin(); it != pNZBInfo->GetFileList()->end(); it++) { FileInfo* pFileInfo = *it; itemList.push_back(new EditItem(pFileInfo, NULL, 0)); bAllPaused &= pFileInfo->GetPaused(); } if (eAction == DownloadQueue::eaGroupDelete || eAction == DownloadQueue::eaGroupDupeDelete || eAction == DownloadQueue::eaGroupFinalDelete) { pNZBInfo->SetDeleting(true); pNZBInfo->SetAvoidHistory(eAction == DownloadQueue::eaGroupFinalDelete); pNZBInfo->SetDeletePaused(bAllPaused); if (eAction == DownloadQueue::eaGroupDupeDelete) { pNZBInfo->SetDeleteStatus(NZBInfo::dsDupe); } pNZBInfo->SetCleanupDisk(CanCleanupDisk(pNZBInfo)); } DownloadQueue::EEditAction GroupToFileMap[] = { (DownloadQueue::EEditAction)0, DownloadQueue::eaFileMoveOffset, DownloadQueue::eaFileMoveTop, DownloadQueue::eaFileMoveBottom, DownloadQueue::eaFilePause, DownloadQueue::eaFileResume, DownloadQueue::eaFileDelete, DownloadQueue::eaFilePauseAllPars, DownloadQueue::eaFilePauseExtraPars, DownloadQueue::eaFileReorder, DownloadQueue::eaFileSplit, DownloadQueue::eaFileMoveOffset, DownloadQueue::eaFileMoveTop, DownloadQueue::eaFileMoveBottom, DownloadQueue::eaFilePause, DownloadQueue::eaFileResume, DownloadQueue::eaFileDelete, DownloadQueue::eaFileDelete, DownloadQueue::eaFileDelete, DownloadQueue::eaFilePauseAllPars, DownloadQueue::eaFilePauseExtraPars, (DownloadQueue::EEditAction)0, (DownloadQueue::EEditAction)0, (DownloadQueue::EEditAction)0, (DownloadQueue::EEditAction)0 }; bool bOK = InternEditList(&itemList, NULL, GroupToFileMap[eAction], iOffset, szText); if ((eAction == DownloadQueue::eaGroupDelete || eAction == DownloadQueue::eaGroupDupeDelete || eAction == DownloadQueue::eaGroupFinalDelete) && // NZBInfo could have been destroyed already m_pDownloadQueue->GetQueue()->Find(iID)) { DownloadQueue::Aspect deleteAspect = { DownloadQueue::eaNzbDeleted, m_pDownloadQueue, pNZBInfo, NULL }; m_pDownloadQueue->Notify(&deleteAspect); } return bOK; } void QueueEditor::PauseParsInGroups(ItemList* pItemList, bool bExtraParsOnly) { while (true) { FileList GroupFileList; FileInfo* pFirstFileInfo = NULL; for (ItemList::iterator it = pItemList->begin(); it != pItemList->end(); ) { EditItem* pItem = *it; if (!pFirstFileInfo || (pFirstFileInfo->GetNZBInfo() == pItem->m_pFileInfo->GetNZBInfo())) { GroupFileList.push_back(pItem->m_pFileInfo); if (!pFirstFileInfo) { pFirstFileInfo = pItem->m_pFileInfo; } delete pItem; pItemList->erase(it); it = pItemList->begin(); continue; } it++; } if (!GroupFileList.empty()) { PausePars(&GroupFileList, bExtraParsOnly); } else { break; } } } /** * If the parameter "bExtraParsOnly" is set to "false", then we pause all par2-files. * If the parameter "bExtraParsOnly" is set to "true", we use the following strategy: * At first we find all par-files, which do not have "vol" in their names, then we pause * all vols and do not affect all just-pars. * In a case, if there are no just-pars, but only vols, we find the smallest vol-file * and do not affect it, but pause all other pars. */ void QueueEditor::PausePars(FileList* pFileList, bool bExtraParsOnly) { debug("QueueEditor: Pausing pars"); FileList Pars, Vols; for (FileList::iterator it = pFileList->begin(); it != pFileList->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 (!bExtraParsOnly) { pFileInfo->SetPaused(true); } else { if (strstr(szLoFileName, ".vol")) { Vols.push_back(pFileInfo); } else { Pars.push_back(pFileInfo); } } } } if (bExtraParsOnly) { if (!Pars.empty()) { for (FileList::iterator it = Vols.begin(); it != Vols.end(); it++) { FileInfo* pFileInfo = *it; pFileInfo->SetPaused(true); } } else { // pausing all Vol-files except the smallest one FileInfo* pSmallest = NULL; for (FileList::iterator it = Vols.begin(); it != Vols.end(); it++) { FileInfo* pFileInfo = *it; if (!pSmallest) { pSmallest = pFileInfo; } else if (pSmallest->GetSize() > pFileInfo->GetSize()) { pSmallest->SetPaused(true); pSmallest = pFileInfo; } else { pFileInfo->SetPaused(true); } } } } } void QueueEditor::SetNZBPriority(NZBInfo* pNZBInfo, const char* szPriority) { debug("Setting priority %s for %s", szPriority, pNZBInfo->GetName()); int iPriority = atoi(szPriority); pNZBInfo->SetPriority(iPriority); } void QueueEditor::SetNZBCategory(NZBInfo* pNZBInfo, const char* szCategory, bool bApplyParams) { debug("QueueEditor: setting category '%s' for '%s'", szCategory, pNZBInfo->GetName()); bool bOldUnpack = g_pOptions->GetUnpack(); const char* szOldPostScript = g_pOptions->GetPostScript(); if (bApplyParams && !Util::EmptyStr(pNZBInfo->GetCategory())) { Options::Category* pCategory = g_pOptions->FindCategory(pNZBInfo->GetCategory(), false); if (pCategory) { bOldUnpack = pCategory->GetUnpack(); if (!Util::EmptyStr(pCategory->GetPostScript())) { szOldPostScript = pCategory->GetPostScript(); } } } g_pQueueCoordinator->SetQueueEntryCategory(m_pDownloadQueue, pNZBInfo, szCategory); if (!bApplyParams) { return; } bool bNewUnpack = g_pOptions->GetUnpack(); const char* szNewPostScript = g_pOptions->GetPostScript(); if (!Util::EmptyStr(pNZBInfo->GetCategory())) { Options::Category* pCategory = g_pOptions->FindCategory(pNZBInfo->GetCategory(), false); if (pCategory) { bNewUnpack = pCategory->GetUnpack(); if (!Util::EmptyStr(pCategory->GetPostScript())) { szNewPostScript = pCategory->GetPostScript(); } } } if (bOldUnpack != bNewUnpack) { pNZBInfo->GetParameters()->SetParameter("*Unpack:", bNewUnpack ? "yes" : "no"); } if (strcasecmp(szOldPostScript, szNewPostScript)) { // add new params not existed in old category Tokenizer tokNew(szNewPostScript, ",;"); while (const char* szNewScriptName = tokNew.Next()) { bool bFound = false; const char* szOldScriptName; Tokenizer tokOld(szOldPostScript, ",;"); while ((szOldScriptName = tokOld.Next()) && !bFound) { bFound = !strcasecmp(szNewScriptName, szOldScriptName); } if (!bFound) { char szParam[1024]; snprintf(szParam, 1024, "%s:", szNewScriptName); szParam[1024-1] = '\0'; pNZBInfo->GetParameters()->SetParameter(szParam, "yes"); } } // remove old params not existed in new category Tokenizer tokOld(szOldPostScript, ",;"); while (const char* szOldScriptName = tokOld.Next()) { bool bFound = false; const char* szNewScriptName; Tokenizer tokNew(szNewPostScript, ",;"); while ((szNewScriptName = tokNew.Next()) && !bFound) { bFound = !strcasecmp(szNewScriptName, szOldScriptName); } if (!bFound) { char szParam[1024]; snprintf(szParam, 1024, "%s:", szOldScriptName); szParam[1024-1] = '\0'; pNZBInfo->GetParameters()->SetParameter(szParam, "no"); } } } } void QueueEditor::SetNZBName(NZBInfo* pNZBInfo, const char* szName) { debug("QueueEditor: renaming '%s' to '%s'", pNZBInfo->GetName(), szName); g_pQueueCoordinator->SetQueueEntryName(m_pDownloadQueue, pNZBInfo, szName); } /** * Check if deletion of already downloaded files is possible (when nzb id 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. */ bool QueueEditor::CanCleanupDisk(NZBInfo* pNZBInfo) { if (pNZBInfo->GetDeleteStatus() != NZBInfo::dsNone) { return true; } for (FileList::iterator it = pNZBInfo->GetFileList()->begin(); it != pNZBInfo->GetFileList()->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")) { // non-par file found return true; } } return false; } bool QueueEditor::MergeGroups(ItemList* pItemList) { if (pItemList->size() == 0) { return false; } bool bOK = true; EditItem* pDestItem = pItemList->front(); for (ItemList::iterator it = pItemList->begin() + 1; it != pItemList->end(); it++) { EditItem* pItem = *it; if (pItem->m_pNZBInfo != pDestItem->m_pNZBInfo) { debug("merge %s to %s", pItem->m_pNZBInfo->GetFilename(), pDestItem->m_pNZBInfo->GetFilename()); if (g_pQueueCoordinator->MergeQueueEntries(m_pDownloadQueue, pDestItem->m_pNZBInfo, pItem->m_pNZBInfo)) { bOK = false; } } delete pItem; } delete pDestItem; return bOK; } bool QueueEditor::SplitGroup(ItemList* pItemList, const char* szName) { if (pItemList->size() == 0) { return false; } FileList fileList(false); for (ItemList::iterator it = pItemList->begin(); it != pItemList->end(); it++) { EditItem* pItem = *it; fileList.push_back(pItem->m_pFileInfo); delete pItem; } NZBInfo* pNewNZBInfo = NULL; bool bOK = g_pQueueCoordinator->SplitQueueEntries(m_pDownloadQueue, &fileList, szName, &pNewNZBInfo); return bOK; } bool QueueEditor::SortGroups(ItemList* pItemList, const char* szSort) { GroupSorter sorter(m_pDownloadQueue->GetQueue(), pItemList); return sorter.Execute(szSort); } void QueueEditor::ReorderFiles(ItemList* pItemList) { if (pItemList->size() == 0) { return; } EditItem* pFirstItem = pItemList->front(); NZBInfo* pNZBInfo = pFirstItem->m_pFileInfo->GetNZBInfo(); unsigned int iInsertPos = 0; // now can reorder for (ItemList::iterator it = pItemList->begin(); it != pItemList->end(); it++) { EditItem* pItem = *it; FileInfo* pFileInfo = pItem->m_pFileInfo; // move file item FileList::iterator it2 = std::find(pNZBInfo->GetFileList()->begin(), pNZBInfo->GetFileList()->end(), pFileInfo); if (it2 != pNZBInfo->GetFileList()->end()) { pNZBInfo->GetFileList()->erase(it2); pNZBInfo->GetFileList()->insert(pNZBInfo->GetFileList()->begin() + iInsertPos, pFileInfo); iInsertPos++; } delete pItem; } } void QueueEditor::SetNZBParameter(NZBInfo* pNZBInfo, const char* szParamString) { debug("QueueEditor: setting nzb parameter '%s' for '%s'", szParamString, Util::BaseFileName(pNZBInfo->GetFilename())); char* szStr = strdup(szParamString); char* szValue = strchr(szStr, '='); if (szValue) { *szValue = '\0'; szValue++; pNZBInfo->GetParameters()->SetParameter(szStr, szValue); } else { error("Could not set nzb parameter for %s: invalid argument: %s", pNZBInfo->GetName(), szParamString); } free(szStr); } void QueueEditor::SetNZBDupeParam(NZBInfo* pNZBInfo, DownloadQueue::EEditAction eAction, const char* szText) { debug("QueueEditor: setting dupe parameter %i='%s' for '%s'", (int)eAction, szText, pNZBInfo->GetName()); switch (eAction) { case DownloadQueue::eaGroupSetDupeKey: pNZBInfo->SetDupeKey(szText); break; case DownloadQueue::eaGroupSetDupeScore: pNZBInfo->SetDupeScore(atoi(szText)); break; case DownloadQueue::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; } } bool QueueEditor::DeleteUrl(NZBInfo* pNZBInfo, DownloadQueue::EEditAction eAction) { return g_pUrlCoordinator->DeleteQueueEntry(m_pDownloadQueue, pNZBInfo, eAction == DownloadQueue::eaGroupFinalDelete); } nzbget-16.4/daemon/queue/DownloadInfo.h0000644000175000017500000010477112630544544017715 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 DOWNLOADINFO_H #define DOWNLOADINFO_H #include #include #include #include "Observer.h" #include "Log.h" #include "Thread.h" class NZBInfo; class DownloadQueue; class PostInfo; 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 ServerStatListBase; class ServerStatList : public ServerStatListBase { public: enum EStatOperation { soSet, soAdd, soSubtract }; public: ~ServerStatList(); void StatOp(int iServerID, int iSuccessArticles, int iFailedArticles, EStatOperation eStatOperation); void ListOp(ServerStatList* pServerStats, EStatOperation eStatOperation); void Clear(); }; class ArticleInfo { public: enum EStatus { aiUndefined, aiRunning, aiFinished, aiFailed }; private: int m_iPartNumber; char* m_szMessageID; int m_iSize; char* m_pSegmentContent; long long m_iSegmentOffset; int m_iSegmentSize; EStatus m_eStatus; char* m_szResultFilename; unsigned long m_lCrc; public: ArticleInfo(); ~ArticleInfo(); void SetPartNumber(int s) { m_iPartNumber = s; } int GetPartNumber() { return m_iPartNumber; } const char* GetMessageID() { return m_szMessageID; } void SetMessageID(const char* szMessageID); void SetSize(int iSize) { m_iSize = iSize; } int GetSize() { return m_iSize; } void AttachSegment(char* pContent, long long iOffset, int iSize); void DiscardSegment(); const char* GetSegmentContent() { return m_pSegmentContent; } void SetSegmentOffset(long long iSegmentOffset) { m_iSegmentOffset = iSegmentOffset; } long long GetSegmentOffset() { return m_iSegmentOffset; } void SetSegmentSize(int iSegmentSize) { m_iSegmentSize = iSegmentSize; } int GetSegmentSize() { return m_iSegmentSize; } EStatus GetStatus() { return m_eStatus; } void SetStatus(EStatus Status) { m_eStatus = Status; } const char* GetResultFilename() { return m_szResultFilename; } void SetResultFilename(const char* v); unsigned long GetCrc() { return m_lCrc; } void SetCrc(unsigned long lCrc) { m_lCrc = lCrc; } }; class FileInfo { public: typedef std::vector Articles; typedef std::vector Groups; private: int m_iID; NZBInfo* m_pNZBInfo; Articles m_Articles; Groups m_Groups; ServerStatList m_ServerStats; char* m_szSubject; 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_iCompletedArticles; bool m_bOutputInitialized; char* m_szOutputFilename; Mutex* m_pMutexOutputFile; bool m_bExtraPriority; int m_iActiveDownloads; bool m_bAutoDeleted; int m_iCachedArticles; bool m_bPartialChanged; static int m_iIDGen; static int m_iIDMax; friend class CompletedFile; public: FileInfo(int iID = 0); ~FileInfo(); int GetID() { return m_iID; } void SetID(int iID); static void ResetGenID(bool bMax); NZBInfo* GetNZBInfo() { return m_pNZBInfo; } void SetNZBInfo(NZBInfo* pNZBInfo) { m_pNZBInfo = pNZBInfo; } Articles* GetArticles() { return &m_Articles; } Groups* GetGroups() { return &m_Groups; } const char* GetSubject() { return m_szSubject; } void SetSubject(const char* szSubject); const char* GetFilename() { return m_szFilename; } void SetFilename(const char* szFilename); void MakeValidFilename(); bool GetFilenameConfirmed() { return m_bFilenameConfirmed; } void SetFilenameConfirmed(bool bFilenameConfirmed) { m_bFilenameConfirmed = bFilenameConfirmed; } 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 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; } void SetPaused(bool bPaused); bool GetDeleted() { return m_bDeleted; } void SetDeleted(bool Deleted) { m_bDeleted = Deleted; } int GetCompletedArticles() { return m_iCompletedArticles; } void SetCompletedArticles(int iCompletedArticles) { m_iCompletedArticles = iCompletedArticles; } bool GetParFile() { return m_bParFile; } void SetParFile(bool bParFile) { m_bParFile = bParFile; } void ClearArticles(); void LockOutputFile(); void UnlockOutputFile(); const char* GetOutputFilename() { return m_szOutputFilename; } void SetOutputFilename(const char* szOutputFilename); bool GetOutputInitialized() { return m_bOutputInitialized; } void SetOutputInitialized(bool bOutputInitialized) { m_bOutputInitialized = bOutputInitialized; } bool GetExtraPriority() { return m_bExtraPriority; } void SetExtraPriority(bool bExtraPriority) { m_bExtraPriority = bExtraPriority; } int GetActiveDownloads() { return m_iActiveDownloads; } void SetActiveDownloads(int iActiveDownloads); bool GetAutoDeleted() { return m_bAutoDeleted; } void SetAutoDeleted(bool bAutoDeleted) { m_bAutoDeleted = bAutoDeleted; } int GetCachedArticles() { return m_iCachedArticles; } void SetCachedArticles(int iCachedArticles) { m_iCachedArticles = iCachedArticles; } bool GetPartialChanged() { return m_bPartialChanged; } void SetPartialChanged(bool bPartialChanged) { m_bPartialChanged = bPartialChanged; } ServerStatList* GetServerStats() { return &m_ServerStats; } }; typedef std::deque FileListBase; class FileList : public FileListBase { private: bool m_bOwnObjects; public: FileList(bool bOwnObjects = false) { m_bOwnObjects = bOwnObjects; } ~FileList(); void Clear(); void Remove(FileInfo* pFileInfo); }; class CompletedFile { public: enum EStatus { cfUnknown, cfSuccess, cfPartial, cfFailure }; private: int m_iID; char* m_szFileName; EStatus m_eStatus; unsigned long m_lCrc; public: CompletedFile(int iID, const char* szFileName, EStatus eStatus, unsigned long lCrc); ~CompletedFile(); int GetID() { return m_iID; } void SetFileName(const char* szFileName); const char* GetFileName() { return m_szFileName; } EStatus GetStatus() { return m_eStatus; } unsigned long GetCrc() { return m_lCrc; } }; typedef std::deque CompletedFiles; class NZBParameter { private: char* m_szName; char* m_szValue; void SetValue(const char* szValue); friend class NZBParameterList; public: NZBParameter(const char* szName); ~NZBParameter(); const char* GetName() { return m_szName; } const char* GetValue() { return m_szValue; } }; typedef std::deque 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 ScriptStatusListBase; class ScriptStatusList : public ScriptStatusListBase { public: ~ScriptStatusList(); void Add(const char* szScriptName, ScriptStatus::EStatus eStatus); void Clear(); ScriptStatus::EStatus CalcTotalStatus(); }; enum EDupeMode { dmScore, dmAll, dmForce }; class NZBInfo { public: enum ERenameStatus { rsNone, rsSkipped, rsFailure, rsSuccess }; enum EParStatus { psNone, psSkipped, psFailure, psSuccess, psRepairPossible, psManual }; enum EUnpackStatus { usNone, usSkipped, usFailure, usSuccess, usSpace, usPassword }; enum ECleanupStatus { csNone, csFailure, csSuccess }; enum EMoveStatus { msNone, msFailure, msSuccess }; enum EDeleteStatus { dsNone, dsManual, dsHealth, dsDupe, dsBad, dsGood, dsCopy, dsScan }; enum EMarkStatus { ksNone, ksBad, ksGood, ksSuccess }; enum EUrlStatus { lsNone, lsRunning, lsFinished, lsFailed, lsRetry, lsScanSkipped, lsScanFailed }; enum EKind { nkNzb, nkUrl }; static const int FORCE_PRIORITY = 900; friend class DupInfo; private: int m_iID; EKind m_eKind; char* m_szURL; 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_lRemainingSize; int m_iPausedFileCount; long long m_lPausedSize; int m_iRemainingParCount; int m_iActiveDownloads; 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; int m_iCurrentSuccessArticles; int m_iCurrentFailedArticles; time_t m_tMinTime; time_t m_tMaxTime; int m_iPriority; CompletedFiles m_completedFiles; ERenameStatus m_eRenameStatus; EParStatus m_eParStatus; EUnpackStatus m_eUnpackStatus; ECleanupStatus m_eCleanupStatus; EMoveStatus m_eMoveStatus; EDeleteStatus m_eDeleteStatus; EMarkStatus m_eMarkStatus; EUrlStatus m_eUrlStatus; int m_iExtraParBlocks; bool m_bAddUrlPaused; bool m_bDeletePaused; bool m_bManyDupeFiles; char* m_szQueuedFilename; 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; FileList m_FileList; NZBParameterList m_ppParameters; ScriptStatusList m_scriptStatuses; ServerStatList m_ServerStats; ServerStatList m_CurrentServerStats; Mutex m_mutexLog; MessageList m_Messages; int m_iIDMessageGen; PostInfo* m_pPostInfo; long long m_lDownloadedSize; time_t m_tDownloadStartTime; int m_iDownloadSec; int m_iPostTotalSec; int m_iParSec; int m_iRepairSec; int m_iUnpackSec; bool m_bReprocess; time_t m_tQueueScriptTime; bool m_bParFull; int m_iMessageCount; int m_iCachedMessageCount; int m_iFeedID; static int m_iIDGen; static int m_iIDMax; void ClearMessages(); public: NZBInfo(); ~NZBInfo(); int GetID() { return m_iID; } void SetID(int iID); static void ResetGenID(bool bMax); static int GenerateID(); EKind GetKind() { return m_eKind; } void SetKind(EKind eKind) { m_eKind = eKind; } const char* GetURL() { return m_szURL; } // needs locking (for shared objects) void SetURL(const char* szURL); // needs locking (for shared objects) const char* GetFilename() { return m_szFilename; } void SetFilename(const char* szFilename); static void MakeNiceNZBName(const char* szNZBFilename, char* szBuffer, int iSize, bool bRemoveExt); static void MakeNiceUrlName(const char* szURL, const char* szNZBFilename, char* szBuffer, int iSize); 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) 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 GetRemainingSize() { return m_lRemainingSize; } void SetRemainingSize(long long lRemainingSize) { m_lRemainingSize = lRemainingSize; } long long GetPausedSize() { return m_lPausedSize; } void SetPausedSize(long long lPausedSize) { m_lPausedSize = lPausedSize; } int GetPausedFileCount() { return m_iPausedFileCount; } void SetPausedFileCount(int iPausedFileCount) { m_iPausedFileCount = iPausedFileCount; } int GetRemainingParCount() { return m_iRemainingParCount; } void SetRemainingParCount(int iRemainingParCount) { m_iRemainingParCount = iRemainingParCount; } int GetActiveDownloads() { return m_iActiveDownloads; } void SetActiveDownloads(int iActiveDownloads); 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; } int GetCurrentSuccessArticles() { return m_iCurrentSuccessArticles; } void SetCurrentSuccessArticles(int iCurrentSuccessArticles) { m_iCurrentSuccessArticles = iCurrentSuccessArticles; } int GetCurrentFailedArticles() { return m_iCurrentFailedArticles; } void SetCurrentFailedArticles(int iCurrentFailedArticles) { m_iCurrentFailedArticles = iCurrentFailedArticles; } int GetPriority() { return m_iPriority; } void SetPriority(int iPriority) { m_iPriority = iPriority; } bool GetForcePriority() { return m_iPriority >= FORCE_PRIORITY; } time_t GetMinTime() { return m_tMinTime; } void SetMinTime(time_t tMinTime) { m_tMinTime = tMinTime; } time_t GetMaxTime() { return m_tMaxTime; } void SetMaxTime(time_t tMaxTime) { m_tMaxTime = tMaxTime; } void BuildDestDirName(); void BuildFinalDirName(char* szFinalDirBuf, int iBufSize); CompletedFiles* GetCompletedFiles() { return &m_completedFiles; } // needs locking (for shared objects) void ClearCompletedFiles(); 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; } ECleanupStatus GetCleanupStatus() { return m_eCleanupStatus; } void SetCleanupStatus(ECleanupStatus eCleanupStatus) { m_eCleanupStatus = eCleanupStatus; } EMoveStatus GetMoveStatus() { return m_eMoveStatus; } void SetMoveStatus(EMoveStatus eMoveStatus) { m_eMoveStatus = eMoveStatus; } EDeleteStatus GetDeleteStatus() { return m_eDeleteStatus; } void SetDeleteStatus(EDeleteStatus eDeleteStatus) { m_eDeleteStatus = eDeleteStatus; } EMarkStatus GetMarkStatus() { return m_eMarkStatus; } void SetMarkStatus(EMarkStatus eMarkStatus) { m_eMarkStatus = eMarkStatus; } EUrlStatus GetUrlStatus() { return m_eUrlStatus; } int GetExtraParBlocks() { return m_iExtraParBlocks; } void SetExtraParBlocks(int iExtraParBlocks) { m_iExtraParBlocks = iExtraParBlocks; } void SetUrlStatus(EUrlStatus eUrlStatus) { m_eUrlStatus = eUrlStatus; } const char* GetQueuedFilename() { return m_szQueuedFilename; } void SetQueuedFilename(const char* szQueuedFilename); 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; } void SetCleanupDisk(bool bCleanupDisk) { m_bCleanupDisk = bCleanupDisk; } bool GetUnpackCleanedUpDisk() { return m_bUnpackCleanedUpDisk; } void SetUnpackCleanedUpDisk(bool bUnpackCleanedUpDisk) { m_bUnpackCleanedUpDisk = bUnpackCleanedUpDisk; } bool GetAddUrlPaused() { return m_bAddUrlPaused; } void SetAddUrlPaused(bool bAddUrlPaused) { m_bAddUrlPaused = bAddUrlPaused; } FileList* GetFileList() { return &m_FileList; } // needs locking (for shared objects) NZBParameterList* GetParameters() { return &m_ppParameters; } // needs locking (for shared objects) ScriptStatusList* GetScriptStatuses() { return &m_scriptStatuses; } // needs locking (for shared objects) ServerStatList* GetServerStats() { return &m_ServerStats; } ServerStatList* GetCurrentServerStats() { return &m_CurrentServerStats; } int CalcHealth(); int CalcCriticalHealth(bool bAllowEstimation); 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; } long long GetDownloadedSize() { return m_lDownloadedSize; } void SetDownloadedSize(long long lDownloadedSize) { m_lDownloadedSize = lDownloadedSize; } int GetDownloadSec() { return m_iDownloadSec; } void SetDownloadSec(int iDownloadSec) { m_iDownloadSec = iDownloadSec; } int GetPostTotalSec() { return m_iPostTotalSec; } void SetPostTotalSec(int iPostTotalSec) { m_iPostTotalSec = iPostTotalSec; } int GetParSec() { return m_iParSec; } void SetParSec(int iParSec) { m_iParSec = iParSec; } int GetRepairSec() { return m_iRepairSec; } void SetRepairSec(int iRepairSec) { m_iRepairSec = iRepairSec; } int GetUnpackSec() { return m_iUnpackSec; } void SetUnpackSec(int iUnpackSec) { m_iUnpackSec = iUnpackSec; } time_t GetDownloadStartTime() { return m_tDownloadStartTime; } void SetDownloadStartTime(time_t tDownloadStartTime) { m_tDownloadStartTime = tDownloadStartTime; } void SetReprocess(bool bReprocess) { m_bReprocess = bReprocess; } bool GetReprocess() { return m_bReprocess; } time_t GetQueueScriptTime() { return m_tQueueScriptTime; } void SetQueueScriptTime(time_t tQueueScriptTime) { m_tQueueScriptTime = tQueueScriptTime; } void SetParFull(bool bParFull) { m_bParFull = bParFull; } bool GetParFull() { return m_bParFull; } int GetFeedID() { return m_iFeedID; } void SetFeedID(int iFeedID) { m_iFeedID = iFeedID; } void CopyFileList(NZBInfo* pSrcNZBInfo); void UpdateMinMaxTime(); PostInfo* GetPostInfo() { return m_pPostInfo; } void EnterPostProcess(); void LeavePostProcess(); bool IsDupeSuccess(); const char* MakeTextStatus(bool bIgnoreScriptStatus); void AddMessage(Message::EKind eKind, const char* szText); void PrintMessage(Message::EKind eKind, const char* szFormat, ...); int GetMessageCount() { return m_iMessageCount; } void SetMessageCount(int iMessageCount) { m_iMessageCount = iMessageCount; } int GetCachedMessageCount() { return m_iCachedMessageCount; } MessageList* LockCachedMessages(); void UnlockCachedMessages(); }; typedef std::deque NZBQueueBase; class NZBList : public NZBQueueBase { private: bool m_bOwnObjects; public: NZBList(bool bOwnObjects = false) { m_bOwnObjects = bOwnObjects; } ~NZBList(); void Clear(); void Add(NZBInfo* pNZBInfo, bool bAddTop); void Remove(NZBInfo* pNZBInfo); NZBInfo* Find(int iID); }; class PostInfo { public: enum EStage { ptQueued, ptLoadingPars, ptVerifyingSources, ptRepairing, ptVerifyingRepaired, ptRenaming, ptUnpacking, ptMoving, ptExecutingScript, ptFinished }; typedef std::vector ParredFiles; private: NZBInfo* m_pNZBInfo; bool m_bWorking; bool m_bDeleted; bool m_bRequestParCheck; bool m_bForceParFull; bool m_bForceRepair; bool m_bParRepaired; bool m_bUnpackTried; bool m_bPassListTried; int m_eLastUnpackStatus; EStage m_eStage; char* m_szProgressLabel; int m_iFileProgress; int m_iStageProgress; time_t m_tStartTime; time_t m_tStageTime; Thread* m_pPostThread; ParredFiles m_ParredFiles; public: PostInfo(); ~PostInfo(); NZBInfo* GetNZBInfo() { return m_pNZBInfo; } void SetNZBInfo(NZBInfo* pNZBInfo) { m_pNZBInfo = pNZBInfo; } EStage GetStage() { return m_eStage; } void SetStage(EStage eStage) { m_eStage = eStage; } void SetProgressLabel(const char* szProgressLabel); const char* GetProgressLabel() { return m_szProgressLabel; } int GetFileProgress() { return m_iFileProgress; } void SetFileProgress(int iFileProgress) { m_iFileProgress = iFileProgress; } int GetStageProgress() { return m_iStageProgress; } void SetStageProgress(int iStageProgress) { m_iStageProgress = iStageProgress; } time_t GetStartTime() { return m_tStartTime; } void SetStartTime(time_t tStartTime) { m_tStartTime = tStartTime; } time_t GetStageTime() { return m_tStageTime; } void SetStageTime(time_t tStageTime) { m_tStageTime = tStageTime; } bool GetWorking() { return m_bWorking; } void SetWorking(bool bWorking) { m_bWorking = bWorking; } bool GetDeleted() { return m_bDeleted; } void SetDeleted(bool bDeleted) { m_bDeleted = bDeleted; } bool GetRequestParCheck() { return m_bRequestParCheck; } void SetRequestParCheck(bool bRequestParCheck) { m_bRequestParCheck = bRequestParCheck; } bool GetForceParFull() { return m_bForceParFull; } void SetForceParFull(bool bForceParFull) { m_bForceParFull = bForceParFull; } bool GetForceRepair() { return m_bForceRepair; } void SetForceRepair(bool bForceRepair) { m_bForceRepair = bForceRepair; } bool GetParRepaired() { return m_bParRepaired; } void SetParRepaired(bool bParRepaired) { m_bParRepaired = bParRepaired; } bool GetUnpackTried() { return m_bUnpackTried; } void SetUnpackTried(bool bUnpackTried) { m_bUnpackTried = bUnpackTried; } bool GetPassListTried() { return m_bPassListTried; } void SetPassListTried(bool bPassListTried) { m_bPassListTried = bPassListTried; } int GetLastUnpackStatus() { return m_eLastUnpackStatus; } void SetLastUnpackStatus(int eUnpackStatus) { m_eLastUnpackStatus = eUnpackStatus; } Thread* GetPostThread() { return m_pPostThread; } void SetPostThread(Thread* pPostThread) { m_pPostThread = pPostThread; } ParredFiles* GetParredFiles() { return &m_ParredFiles; } }; typedef std::vector IDList; typedef std::vector NameList; class DupInfo { public: enum EStatus { dsUndefined, dsSuccess, dsFailed, dsDeleted, dsDupe, dsBad, dsGood }; private: int m_iID; 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(); int GetID() { return m_iID; } void SetID(int iID); 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: enum EKind { hkUnknown, hkNzb, hkUrl, hkDup }; private: EKind m_eKind; void* m_pInfo; time_t m_tTime; public: HistoryInfo(NZBInfo* pNZBInfo); HistoryInfo(DupInfo* pDupInfo); ~HistoryInfo(); EKind GetKind() { return m_eKind; } int GetID(); NZBInfo* GetNZBInfo() { return (NZBInfo*)m_pInfo; } DupInfo* GetDupInfo() { return (DupInfo*)m_pInfo; } void DiscardNZBInfo() { m_pInfo = NULL; } time_t GetTime() { return m_tTime; } void SetTime(time_t tTime) { m_tTime = tTime; } void GetName(char* szBuffer, int iSize); // needs locking (for shared objects) }; typedef std::deque HistoryListBase; class HistoryList : public HistoryListBase { public: ~HistoryList(); HistoryInfo* Find(int iID); }; class DownloadQueue : public Subject { public: enum EAspectAction { eaNzbFound, eaNzbAdded, eaNzbDeleted, eaFileCompleted, eaFileDeleted, eaUrlCompleted }; struct Aspect { EAspectAction eAction; DownloadQueue* pDownloadQueue; NZBInfo* pNZBInfo; FileInfo* pFileInfo; }; enum EEditAction { eaFileMoveOffset = 1, // move files to m_iOffset relative to the current position in download-queue eaFileMoveTop, // move files to the top of download-queue eaFileMoveBottom, // move files to the bottom of download-queue eaFilePause, // pause files eaFileResume, // resume (unpause) files eaFileDelete, // delete files eaFilePauseAllPars, // pause only (all) pars (does not affect other files) eaFilePauseExtraPars, // pause only (almost all) pars, except main par-file (does not affect other files) eaFileReorder, // set file order eaFileSplit, // split - create new group from selected files eaGroupMoveOffset, // move group to m_iOffset relative to the current position in download-queue eaGroupMoveTop, // move group to the top of download-queue eaGroupMoveBottom, // move group to the bottom of download-queue eaGroupPause, // pause group eaGroupResume, // resume (unpause) group eaGroupDelete, // delete group and put to history eaGroupDupeDelete, // delete group, put to history and mark as duplicate eaGroupFinalDelete, // delete group without adding to history eaGroupPauseAllPars, // pause only (all) pars (does not affect other files) in group eaGroupPauseExtraPars, // pause only (almost all) pars in group, except main par-file (does not affect other files) eaGroupSetPriority, // set priority for groups eaGroupSetCategory, // set or change category for a group eaGroupApplyCategory, // set or change category for a group and reassign pp-params according to category settings eaGroupMerge, // merge groups eaGroupSetParameter, // set post-process parameter for group eaGroupSetName, // set group name (rename group) eaGroupSetDupeKey, // set duplicate key eaGroupSetDupeScore, // set duplicate score eaGroupSetDupeMode, // set duplicate mode eaGroupSort, // sort groups eaPostDelete, // cancel post-processing eaHistoryDelete, // hide history-item eaHistoryFinalDelete, // delete history-item eaHistoryReturn, // move history-item back to download queue eaHistoryProcess, // move history-item back to download queue and start postprocessing eaHistoryRedownload, // move history-item back to download queue for redownload eaHistorySetParameter, // set post-process parameter for history-item eaHistorySetDupeKey, // set duplicate key eaHistorySetDupeScore, // set duplicate score eaHistorySetDupeMode, // set duplicate mode eaHistorySetDupeBackup, // set duplicate backup flag eaHistoryMarkBad, // mark history-item as bad (and download other duplicate) eaHistoryMarkGood, // mark history-item as good (and push it into dup-history) eaHistoryMarkSuccess, // mark history-item as success (and do nothing more) eaHistorySetCategory, // set or change category for history-item eaHistorySetName // set history-item name (rename) }; enum EMatchMode { mmID = 1, mmName, mmRegEx }; private: NZBList m_Queue; HistoryList m_History; Mutex m_LockMutex; static DownloadQueue* g_pDownloadQueue; static bool g_bLoaded; protected: DownloadQueue() : m_Queue(true) {} static void Init(DownloadQueue* pGlobalInstance) { g_pDownloadQueue = pGlobalInstance; } static void Final() { g_pDownloadQueue = NULL; } static void Loaded() { g_bLoaded = true; } public: static bool IsLoaded() { return g_bLoaded; } static DownloadQueue* Lock(); static void Unlock(); NZBList* GetQueue() { return &m_Queue; } HistoryList* GetHistory() { return &m_History; } virtual bool EditEntry(int ID, EEditAction eAction, int iOffset, const char* szText) = 0; virtual bool EditList(IDList* pIDList, NameList* pNameList, EMatchMode eMatchMode, EEditAction eAction, int iOffset, const char* szText) = 0; virtual void Save() = 0; void CalcRemainingSize(long long* pRemaining, long long* pRemainingForced); }; #endif nzbget-16.4/daemon/queue/Scanner.cpp0000644000175000017500000004445312630544544017256 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #include #ifdef WIN32 #include #else #include #endif #include #include "nzbget.h" #include "Scanner.h" #include "Options.h" #include "Log.h" #include "QueueCoordinator.h" #include "HistoryCoordinator.h" #include "ScanScript.h" #include "Util.h" Scanner::FileData::FileData(const char* szFilename) { m_szFilename = strdup(szFilename); m_iSize = 0; m_tLastChange = 0; } 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, NZBInfo* pUrlInfo, EAddStatus* pAddStatus, int* pNZBID) { 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_pUrlInfo = pUrlInfo; m_pAddStatus = pAddStatus; m_pNZBID = pNZBID; 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; } } void Scanner::QueueData::SetNZBID(int iNZBID) { if (m_pNZBID) { *m_pNZBID = iNZBID; } } Scanner::Scanner() { debug("Creating Scanner"); m_bRequestedNZBDirScan = false; m_bScanning = false; m_iNZBDirInterval = 0; m_iPass = 0; m_bScanScript = false; } Scanner::~Scanner() { debug("Destroying Scanner"); for (FileList::iterator it = m_FileList.begin(); it != m_FileList.end(); it++) { delete *it; } m_FileList.clear(); ClearQueueList(); } void Scanner::InitOptions() { m_iNZBDirInterval = g_pOptions->GetNzbDirInterval() * 1000; const char* szScanScript = g_pOptions->GetScanScript(); m_bScanScript = szScanScript && strlen(szScanScript) > 0; } void Scanner::ClearQueueList() { for (QueueList::iterator it = m_QueueList.begin(); it != m_QueueList.end(); it++) { delete *it; } m_QueueList.clear(); } void Scanner::ServiceWork() { if (!DownloadQueue::IsLoaded()) { return; } m_mutexScan.Lock(); if (m_bRequestedNZBDirScan || (!g_pOptions->GetPauseScan() && g_pOptions->GetNzbDirInterval() > 0 && m_iNZBDirInterval >= g_pOptions->GetNzbDirInterval() * 1000)) { // check nzbdir every g_pOptions->GetNzbDirInterval() seconds or if requested bool bCheckStat = !m_bRequestedNZBDirScan; m_bRequestedNZBDirScan = false; m_bScanning = true; CheckIncomingNZBs(g_pOptions->GetNzbDir(), "", bCheckStat); if (!bCheckStat && m_bScanScript) { // if immediate scan requested, we need second scan to process files extracted by NzbProcess-script CheckIncomingNZBs(g_pOptions->GetNzbDir(), "", bCheckStat); } m_bScanning = false; m_iNZBDirInterval = 0; // if NzbDirFileAge is less than NzbDirInterval (that can happen if NzbDirInterval // is set for rare scans like once per hour) we make 4 scans: // - 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->GetNzbDirInterval() > 0 && g_pOptions->GetNzbDirFileAge() < g_pOptions->GetNzbDirInterval()) { int iMaxPass = m_bScanScript ? 3 : 1; if (m_iPass < iMaxPass) { // scheduling another scan of incoming directory in NzbDirFileAge seconds. m_iNZBDirInterval = (g_pOptions->GetNzbDirInterval() - g_pOptions->GetNzbDirFileAge()) * 1000; m_iPass++; } else { m_iPass = 0; } } DropOldFiles(); ClearQueueList(); } m_iNZBDirInterval += 200; m_mutexScan.Unlock(); } /** * Check if there are files in directory for incoming nzb-files * and add them to download queue */ void Scanner::CheckIncomingNZBs(const char* szDirectory, const char* szCategory, bool bCheckStat) { DirBrowser dir(szDirectory); while (const char* filename = dir.Next()) { char fullfilename[1023 + 1]; // one char reserved for the trailing slash (if needed) snprintf(fullfilename, 1023, "%s%s", szDirectory, filename); fullfilename[1023 - 1] = '\0'; bool bIsDirectory = Util::DirectoryExists(fullfilename); // check subfolders if (bIsDirectory && strcmp(filename, ".") && strcmp(filename, "..")) { fullfilename[strlen(fullfilename) + 1] = '\0'; fullfilename[strlen(fullfilename)] = PATH_SEPARATOR; const char* szUseCategory = filename; char szSubCategory[1024]; if (strlen(szCategory) > 0) { snprintf(szSubCategory, 1023, "%s%c%s", szCategory, PATH_SEPARATOR, filename); szSubCategory[1024 - 1] = '\0'; szUseCategory = szSubCategory; } CheckIncomingNZBs(fullfilename, szUseCategory, bCheckStat); } else if (!bIsDirectory && CanProcessFile(fullfilename, bCheckStat)) { ProcessIncomingFile(szDirectory, filename, fullfilename, szCategory); } } } /** * Only files which were not changed during last g_pOptions->GetNzbDirFileAge() seconds * can be processed. That prevents the processing of files, which are currently being * copied into nzb-directory (eg. being downloaded in web-browser). */ bool Scanner::CanProcessFile(const char* szFullFilename, bool bCheckStat) { const char* szExtension = strrchr(szFullFilename, '.'); if (!szExtension || !strcasecmp(szExtension, ".queued") || !strcasecmp(szExtension, ".error") || !strcasecmp(szExtension, ".processed")) { return false; } if (!bCheckStat) { return true; } long long lSize = Util::FileSize(szFullFilename); time_t tCurrent = time(NULL); bool bCanProcess = false; bool bInList = false; for (FileList::iterator it = m_FileList.begin(); it != m_FileList.end(); it++) { FileData* pFileData = *it; if (!strcmp(pFileData->GetFilename(), szFullFilename)) { bInList = true; if (pFileData->GetSize() == lSize && tCurrent - pFileData->GetLastChange() >= g_pOptions->GetNzbDirFileAge()) { bCanProcess = true; delete pFileData; m_FileList.erase(it); } else { pFileData->SetSize(lSize); if (pFileData->GetSize() != lSize) { pFileData->SetLastChange(tCurrent); } } break; } } if (!bInList) { FileData* pFileData = new FileData(szFullFilename); pFileData->SetSize(lSize); pFileData->SetLastChange(tCurrent); m_FileList.push_back(pFileData); } return bCanProcess; } /** * Remove old files from the list of monitored files. * Normally these files are deleted from the list when they are processed. * However if a file was detected by function "CanProcessFile" once but wasn't * processed later (for example if the user deleted it), it will stay in the list, * until we remove it here. */ void Scanner::DropOldFiles() { time_t tCurrent = time(NULL); int i = 0; for (FileList::iterator it = m_FileList.begin(); it != m_FileList.end(); ) { FileData* pFileData = *it; if ((tCurrent - pFileData->GetLastChange() >= (g_pOptions->GetNzbDirInterval() + g_pOptions->GetNzbDirFileAge()) * 2) || // can occur if the system clock was adjusted tCurrent < pFileData->GetLastChange()) { debug("Removing file %s from scan file list", pFileData->GetFilename()); delete pFileData; m_FileList.erase(it); it = m_FileList.begin() + i; } else { it++; i++; } } } void Scanner::ProcessIncomingFile(const char* szDirectory, const char* szBaseFilename, const char* szFullFilename, const char* szCategory) { const char* szExtension = strrchr(szBaseFilename, '.'); if (!szExtension) { return; } char* szNZBName = strdup(""); char* szNZBCategory = strdup(szCategory); NZBParameterList* pParameters = new NZBParameterList(); int iPriority = 0; bool bAddTop = false; bool bAddPaused = false; char* szDupeKey = strdup(""); int iDupeScore = 0; EDupeMode eDupeMode = dmScore; EAddStatus eAddStatus = asSkipped; QueueData* pQueueData = NULL; NZBInfo* pUrlInfo = NULL; int iNZBID = 0; 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(); free(szDupeKey); szDupeKey = strdup(pQueueData->GetDupeKey()); iDupeScore = pQueueData->GetDupeScore(); eDupeMode = pQueueData->GetDupeMode(); bAddTop = pQueueData->GetAddTop(); bAddPaused = pQueueData->GetAddPaused(); pParameters->CopyFrom(pQueueData->GetParameters()); pUrlInfo = pQueueData->GetUrlInfo(); } } InitPPParameters(szNZBCategory, pParameters, false); bool bExists = true; if (m_bScanScript && strcasecmp(szExtension, ".nzb_processed")) { ScanScriptController::ExecuteScripts(szFullFilename, pUrlInfo ? pUrlInfo->GetURL() : "", szDirectory, &szNZBName, &szNZBCategory, &iPriority, pParameters, &bAddTop, &bAddPaused, &szDupeKey, &iDupeScore, &eDupeMode); bExists = Util::FileExists(szFullFilename); if (bExists && strcasecmp(szExtension, ".nzb")) { char bakname2[1024]; bool bRenameOK = Util::RenameBak(szFullFilename, "processed", false, bakname2, 1024); if (!bRenameOK) { char szSysErrStr[256]; error("Could not rename file %s to %s: %s", szFullFilename, bakname2, Util::GetLastErrorMessage(szSysErrStr, sizeof(szSysErrStr))); } } } if (!strcasecmp(szExtension, ".nzb_processed")) { char szRenamedName[1024]; bool bRenameOK = Util::RenameBak(szFullFilename, "nzb", true, szRenamedName, 1024); if (bRenameOK) { bool bAdded = AddFileToQueue(szRenamedName, szNZBName, szNZBCategory, iPriority, szDupeKey, iDupeScore, eDupeMode, pParameters, bAddTop, bAddPaused, pUrlInfo, &iNZBID); eAddStatus = bAdded ? asSuccess : asFailed; } else { 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")) { bool bAdded = AddFileToQueue(szFullFilename, szNZBName, szNZBCategory, iPriority, szDupeKey, iDupeScore, eDupeMode, pParameters, bAddTop, bAddPaused, pUrlInfo, &iNZBID); eAddStatus = bAdded ? asSuccess : asFailed; } delete pParameters; free(szNZBName); free(szNZBCategory); free(szDupeKey); if (pQueueData) { pQueueData->SetAddStatus(eAddStatus); pQueueData->SetNZBID(iNZBID); } } void Scanner::InitPPParameters(const char* szCategory, NZBParameterList* pParameters, bool bReset) { bool bUnpack = g_pOptions->GetUnpack(); const char* szPostScript = g_pOptions->GetPostScript(); if (!Util::EmptyStr(szCategory)) { Options::Category* pCategory = g_pOptions->FindCategory(szCategory, false); if (pCategory) { bUnpack = pCategory->GetUnpack(); if (!Util::EmptyStr(pCategory->GetPostScript())) { szPostScript = pCategory->GetPostScript(); } } } if (bReset) { for (ScriptConfig::Scripts::iterator it = g_pScriptConfig->GetScripts()->begin(); it != g_pScriptConfig->GetScripts()->end(); it++) { ScriptConfig::Script* pScript = *it; char szParam[1024]; snprintf(szParam, 1024, "%s:", pScript->GetName()); szParam[1024-1] = '\0'; pParameters->SetParameter(szParam, NULL); } } pParameters->SetParameter("*Unpack:", bUnpack ? "yes" : "no"); if (!Util::EmptyStr(szPostScript)) { // split szPostScript into tokens and create pp-parameter for each token Tokenizer tok(szPostScript, ",;"); while (const char* szScriptName = tok.Next()) { char szParam[1024]; snprintf(szParam, 1024, "%s:", szScriptName); szParam[1024-1] = '\0'; pParameters->SetParameter(szParam, "yes"); } } } 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, NZBInfo* pUrlInfo, int* pNZBID) { const char* szBasename = Util::BaseFileName(szFilename); info("Adding collection %s to queue", szBasename); NZBFile* pNZBFile = new NZBFile(szFilename, szCategory); bool bOK = pNZBFile->Parse(); if (!bOK) { error("Could not add collection %s to queue", szBasename); } char bakname2[1024]; if (!Util::RenameBak(szFilename, pNZBFile ? "queued" : "error", false, bakname2, 1024)) { bOK = false; char szSysErrStr[256]; error("Could not rename file %s to %s: %s", szFilename, bakname2, Util::GetLastErrorMessage(szSysErrStr, sizeof(szSysErrStr))); } NZBInfo* pNZBInfo = pNZBFile->GetNZBInfo(); pNZBInfo->SetQueuedFilename(bakname2); if (szNZBName && strlen(szNZBName) > 0) { pNZBInfo->SetName(NULL); #ifdef WIN32 char* szAnsiFilename = strdup(szNZBName); WebUtil::Utf8ToAnsi(szAnsiFilename, strlen(szAnsiFilename) + 1); pNZBInfo->SetFilename(szAnsiFilename); free(szAnsiFilename); #else pNZBInfo->SetFilename(szNZBName); #endif pNZBInfo->BuildDestDirName(); } pNZBInfo->SetDupeKey(szDupeKey); pNZBInfo->SetDupeScore(iDupeScore); pNZBInfo->SetDupeMode(eDupeMode); pNZBInfo->SetPriority(iPriority); if (pUrlInfo) { pNZBInfo->SetURL(pUrlInfo->GetURL()); pNZBInfo->SetUrlStatus(pUrlInfo->GetUrlStatus()); pNZBInfo->SetFeedID(pUrlInfo->GetFeedID()); } if (pNZBFile->GetPassword()) { pNZBInfo->GetParameters()->SetParameter("*Unpack:Password", pNZBFile->GetPassword()); } pNZBInfo->GetParameters()->CopyFrom(pParameters); for (::FileList::iterator it = pNZBInfo->GetFileList()->begin(); it != pNZBInfo->GetFileList()->end(); it++) { FileInfo* pFileInfo = *it; pFileInfo->SetPaused(bAddPaused); } if (bOK) { g_pQueueCoordinator->AddNZBFileToQueue(pNZBFile, pUrlInfo, bAddTop); } else if (!pUrlInfo) { pNZBFile->GetNZBInfo()->SetDeleteStatus(NZBInfo::dsScan); g_pQueueCoordinator->AddNZBFileToQueue(pNZBFile, pUrlInfo, bAddTop); } if (pNZBID) { *pNZBID = pNZBInfo->GetID(); } delete pNZBFile; return bOK; } void Scanner::ScanNZBDir(bool bSyncMode) { 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, NZBInfo* pUrlInfo, const char* szFileName, const char* szBuffer, int iBufSize, int* pNZBID) { 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, "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(szUseCategory, true); if (pCategory && strcmp(szUseCategory, 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, pUrlInfo, &eAddStatus, pNZBID); free(szUseCategory); m_QueueList.push_back(pQueueData); m_mutexScan.Unlock(); ScanNZBDir(true); return eAddStatus; } nzbget-16.4/daemon/postprocess/0000755000175000017500000000000012630544544016407 5ustar andreasandreasnzbget-16.4/daemon/postprocess/PrePostProcessor.h0000644000175000017500000000572312630544544022063 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 PREPOSTPROCESSOR_H #define PREPOSTPROCESSOR_H #include #include "Thread.h" #include "Observer.h" #include "DownloadInfo.h" #include "ParCoordinator.h" class PrePostProcessor : public Thread { private: class DownloadQueueObserver: public Observer { public: PrePostProcessor* m_pOwner; virtual void Update(Subject* Caller, void* Aspect) { m_pOwner->DownloadQueueUpdate(Caller, Aspect); } }; private: ParCoordinator m_ParCoordinator; DownloadQueueObserver m_DownloadQueueObserver; int m_iJobCount; NZBInfo* m_pCurJob; const char* m_szPauseReason; bool IsNZBFileCompleted(NZBInfo* pNZBInfo, bool bIgnorePausedPars, bool bAllowOnlyOneDeleted); bool IsNZBFileDownloading(NZBInfo* pNZBInfo); void CheckPostQueue(); void JobCompleted(DownloadQueue* pDownloadQueue, PostInfo* pPostInfo); void StartJob(DownloadQueue* pDownloadQueue, PostInfo* pPostInfo); void SaveQueue(DownloadQueue* pDownloadQueue); void SanitisePostQueue(DownloadQueue* pDownloadQueue); void UpdatePauseState(bool bNeedPause, const char* szReason); void NZBFound(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo); void NZBDeleted(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo); void NZBCompleted(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, bool bSaveQueue); bool PostQueueDelete(DownloadQueue* pDownloadQueue, IDList* pIDList); void DeletePostThread(PostInfo* pPostInfo); NZBInfo* GetNextJob(DownloadQueue* pDownloadQueue); void DownloadQueueUpdate(Subject* Caller, void* Aspect); void DeleteCleanup(NZBInfo* pNZBInfo); public: PrePostProcessor(); virtual ~PrePostProcessor(); virtual void Run(); virtual void Stop(); bool HasMoreJobs() { return m_iJobCount > 0; } int GetJobCount() { return m_iJobCount; } bool EditList(DownloadQueue* pDownloadQueue, IDList* pIDList, DownloadQueue::EEditAction eAction, int iOffset, const char* szText); void NZBAdded(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo); void NZBDownloaded(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo); }; extern PrePostProcessor* g_pPrePostProcessor; #endif nzbget-16.4/daemon/postprocess/ParCoordinator.cpp0000644000175000017500000005637212630544544022056 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #include #include #include #ifdef WIN32 #include #else #include #endif #include "nzbget.h" #include "ParCoordinator.h" #include "DupeCoordinator.h" #include "ParParser.h" #include "Options.h" #include "DiskState.h" #include "Log.h" #include "Util.h" #ifndef DISABLE_PARCHECK bool ParCoordinator::PostParChecker::RequestMorePars(int iBlockNeeded, int* pBlockFound) { return m_pOwner->RequestMorePars(m_pPostInfo->GetNZBInfo(), GetParFilename(), iBlockNeeded, pBlockFound); } 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_pPostInfo->GetNZBInfo()->AddMessage(eKind, szText); } void ParCoordinator::PostParChecker::RegisterParredFile(const char* szFilename) { m_pPostInfo->GetParredFiles()->push_back(strdup(szFilename)); } bool ParCoordinator::PostParChecker::IsParredFile(const char* szFilename) { for (PostInfo::ParredFiles::iterator it = m_pPostInfo->GetParredFiles()->begin(); it != m_pPostInfo->GetParredFiles()->end(); it++) { const char* szParredFile = *it; if (!strcasecmp(szParredFile, szFilename)) { return true; } } return false; } ParChecker::EFileStatus ParCoordinator::PostParChecker::FindFileCrc(const char* szFilename, unsigned long* lCrc, SegmentList* pSegments) { CompletedFile* pCompletedFile = NULL; for (CompletedFiles::iterator it = m_pPostInfo->GetNZBInfo()->GetCompletedFiles()->begin(); it != m_pPostInfo->GetNZBInfo()->GetCompletedFiles()->end(); it++) { CompletedFile* pCompletedFile2 = *it; if (!strcasecmp(pCompletedFile2->GetFileName(), szFilename)) { pCompletedFile = pCompletedFile2; break; } } if (!pCompletedFile) { return ParChecker::fsUnknown; } debug("Found completed file: %s, CRC: %.8x, Status: %i", Util::BaseFileName(pCompletedFile->GetFileName()), pCompletedFile->GetCrc(), (int)pCompletedFile->GetStatus()); *lCrc = pCompletedFile->GetCrc(); if (pCompletedFile->GetStatus() == CompletedFile::cfPartial && pCompletedFile->GetID() > 0 && !m_pPostInfo->GetNZBInfo()->GetReprocess()) { FileInfo* pTmpFileInfo = new FileInfo(pCompletedFile->GetID()); if (!g_pDiskState->LoadFileState(pTmpFileInfo, NULL, true)) { delete pTmpFileInfo; return ParChecker::fsUnknown; } for (FileInfo::Articles::iterator it = pTmpFileInfo->GetArticles()->begin(); it != pTmpFileInfo->GetArticles()->end(); it++) { ArticleInfo* pa = *it; ParChecker::Segment* pSegment = new Segment(pa->GetStatus() == ArticleInfo::aiFinished, pa->GetSegmentOffset(), pa->GetSegmentSize(), pa->GetCrc()); pSegments->push_back(pSegment); } delete pTmpFileInfo; } return pCompletedFile->GetStatus() == CompletedFile::cfSuccess ? ParChecker::fsSuccess : pCompletedFile->GetStatus() == CompletedFile::cfFailure && !m_pPostInfo->GetNZBInfo()->GetReprocess() ? ParChecker::fsFailure : pCompletedFile->GetStatus() == CompletedFile::cfPartial && pSegments->size() > 0 && !m_pPostInfo->GetNZBInfo()->GetReprocess()? ParChecker::fsPartial : ParChecker::fsUnknown; } void ParCoordinator::PostParChecker::RequestDupeSources(DupeSourceList* pDupeSourceList) { DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); NZBList dupeList; g_pDupeCoordinator->ListHistoryDupes(pDownloadQueue, m_pPostInfo->GetNZBInfo(), &dupeList); if (!dupeList.empty()) { PostDupeMatcher dupeMatcher(m_pPostInfo); PrintMessage(Message::mkInfo, "Checking %s for dupe scan usability", m_pPostInfo->GetNZBInfo()->GetName()); bool bSizeComparisonPossible = dupeMatcher.Prepare(); for (NZBList::iterator it = dupeList.begin(); it != dupeList.end(); it++) { NZBInfo* pDupeNZBInfo = *it; if (bSizeComparisonPossible) { PrintMessage(Message::mkInfo, "Checking %s for dupe scan usability", Util::BaseFileName(pDupeNZBInfo->GetDestDir())); } bool bUseDupe = !bSizeComparisonPossible || dupeMatcher.MatchDupeContent(pDupeNZBInfo->GetDestDir()); if (bUseDupe) { PrintMessage(Message::mkInfo, "Adding %s to dupe scan sources", Util::BaseFileName(pDupeNZBInfo->GetDestDir())); pDupeSourceList->push_back(new ParChecker::DupeSource(pDupeNZBInfo->GetID(), pDupeNZBInfo->GetDestDir())); } } if (pDupeSourceList->empty()) { PrintMessage(Message::mkInfo, "No usable dupe scan sources found"); } } DownloadQueue::Unlock(); } void ParCoordinator::PostParChecker::StatDupeSources(DupeSourceList* pDupeSourceList) { DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); int iTotalExtraParBlocks = 0; for (DupeSourceList::iterator it = pDupeSourceList->begin(); it != pDupeSourceList->end(); it++) { DupeSource* pDupeSource = *it; if (pDupeSource->GetUsedBlocks() > 0) { for (HistoryList::iterator it = pDownloadQueue->GetHistory()->begin(); it != pDownloadQueue->GetHistory()->end(); it++) { HistoryInfo* pHistoryInfo = *it; if (pHistoryInfo->GetKind() == HistoryInfo::hkNzb && pHistoryInfo->GetNZBInfo()->GetID() == pDupeSource->GetID()) { pHistoryInfo->GetNZBInfo()->SetExtraParBlocks(pHistoryInfo->GetNZBInfo()->GetExtraParBlocks() - pDupeSource->GetUsedBlocks()); } } } iTotalExtraParBlocks += pDupeSource->GetUsedBlocks(); } m_pPostInfo->GetNZBInfo()->SetExtraParBlocks(m_pPostInfo->GetNZBInfo()->GetExtraParBlocks() + iTotalExtraParBlocks); DownloadQueue::Unlock(); } 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_pPostInfo->GetNZBInfo()->AddMessage(eKind, szText); } void ParCoordinator::PostParRenamer::RegisterParredFile(const char* szFilename) { m_pPostInfo->GetParredFiles()->push_back(strdup(szFilename)); } /** * Update file name in the CompletedFiles-list of NZBInfo */ void ParCoordinator::PostParRenamer::RegisterRenamedFile(const char* szOldFilename, const char* szNewFileName) { for (CompletedFiles::iterator it = m_pPostInfo->GetNZBInfo()->GetCompletedFiles()->begin(); it != m_pPostInfo->GetNZBInfo()->GetCompletedFiles()->end(); it++) { CompletedFile* pCompletedFile = *it; if (!strcasecmp(pCompletedFile->GetFileName(), szOldFilename)) { pCompletedFile->SetFileName(szNewFileName); break; } } } void ParCoordinator::PostDupeMatcher::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_pPostInfo->GetNZBInfo()->AddMessage(eKind, szText); } #endif ParCoordinator::ParCoordinator() { debug("Creating ParCoordinator"); #ifndef DISABLE_PARCHECK m_bStopped = false; m_ParChecker.m_pOwner = this; m_ParRenamer.m_pOwner = this; #endif } ParCoordinator::~ParCoordinator() { debug("Destroying ParCoordinator"); } #ifndef DISABLE_PARCHECK void ParCoordinator::Stop() { debug("Stopping ParCoordinator"); m_bStopped = true; if (m_ParChecker.IsRunning()) { m_ParChecker.Stop(); int iMSecWait = 5000; while (m_ParChecker.IsRunning() && iMSecWait > 0) { usleep(50 * 1000); iMSecWait -= 50; } if (m_ParChecker.IsRunning()) { warn("Terminating par-check for %s", m_ParChecker.GetInfoName()); m_ParChecker.Kill(); } } } #endif void ParCoordinator::PausePars(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo) { debug("ParCoordinator: Pausing pars"); pDownloadQueue->EditEntry(pNZBInfo->GetID(), DownloadQueue::eaGroupPauseExtraPars, 0, NULL); } #ifndef DISABLE_PARCHECK /** * DownloadQueue must be locked prior to call of this function. */ void ParCoordinator::StartParCheckJob(PostInfo* pPostInfo) { m_eCurrentJob = jkParCheck; m_ParChecker.SetPostInfo(pPostInfo); m_ParChecker.SetDestDir(pPostInfo->GetNZBInfo()->GetDestDir()); m_ParChecker.SetNZBName(pPostInfo->GetNZBInfo()->GetName()); m_ParChecker.SetParTime(time(NULL)); m_ParChecker.SetDownloadSec(pPostInfo->GetNZBInfo()->GetDownloadSec()); m_ParChecker.SetParQuick(g_pOptions->GetParQuick() && !pPostInfo->GetForceParFull()); m_ParChecker.SetForceRepair(pPostInfo->GetForceRepair()); m_ParChecker.PrintMessage(Message::mkInfo, "Checking pars for %s", pPostInfo->GetNZBInfo()->GetName()); pPostInfo->SetWorking(true); m_ParChecker.Start(); } /** * DownloadQueue must be locked prior to call of this function. */ void ParCoordinator::StartParRenameJob(PostInfo* pPostInfo) { 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(szDestDir); m_ParRenamer.SetInfoName(pPostInfo->GetNZBInfo()->GetName()); m_ParRenamer.SetDetectMissing(pPostInfo->GetNZBInfo()->GetUnpackStatus() == NZBInfo::usNone); m_ParRenamer.PrintMessage(Message::mkInfo, "Checking renamed files for %s", pPostInfo->GetNZBInfo()->GetName()); pPostInfo->SetWorking(true); m_ParRenamer.Start(); } bool ParCoordinator::Cancel() { if (m_eCurrentJob == jkParCheck) { if (!m_ParChecker.GetCancelled()) { debug("Cancelling par-repair for %s", m_ParChecker.GetInfoName()); m_ParChecker.Cancel(); return true; } } else if (m_eCurrentJob == jkParRename) { if (!m_ParRenamer.GetCancelled()) { debug("Cancelling par-rename for %s", m_ParRenamer.GetInfoName()); m_ParRenamer.Cancel(); return true; } } return false; } /** * DownloadQueue must be locked prior to call of this function. */ bool ParCoordinator::AddPar(FileInfo* pFileInfo, bool bDeleted) { bool bSameCollection = m_ParChecker.IsRunning() && pFileInfo->GetNZBInfo() == m_ParChecker.GetPostInfo()->GetNZBInfo(); if (bSameCollection && !bDeleted) { char szFullFilename[1024]; snprintf(szFullFilename, 1024, "%s%c%s", pFileInfo->GetNZBInfo()->GetDestDir(), (int)PATH_SEPARATOR, pFileInfo->GetFilename()); szFullFilename[1024-1] = '\0'; m_ParChecker.AddParFile(szFullFilename); } else { m_ParChecker.QueueChanged(); } return bSameCollection; } void ParCoordinator::ParCheckCompleted() { DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); 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) { pPostInfo->GetNZBInfo()->SetParStatus(NZBInfo::psSuccess); pPostInfo->SetParRepaired(m_ParChecker.GetStatus() == ParChecker::psRepaired); } else if (m_ParChecker.GetStatus() == ParChecker::psRepairPossible && pPostInfo->GetNZBInfo()->GetParStatus() != NZBInfo::psFailure) { pPostInfo->GetNZBInfo()->SetParStatus(NZBInfo::psRepairPossible); } else { pPostInfo->GetNZBInfo()->SetParStatus(NZBInfo::psFailure); } int iWaitTime = pPostInfo->GetNZBInfo()->GetDownloadSec() - m_ParChecker.GetDownloadSec(); pPostInfo->SetStartTime(pPostInfo->GetStartTime() + (time_t)iWaitTime); int iParSec = (int)(time(NULL) - m_ParChecker.GetParTime()) - iWaitTime; pPostInfo->GetNZBInfo()->SetParSec(pPostInfo->GetNZBInfo()->GetParSec() + iParSec); pPostInfo->GetNZBInfo()->SetParFull(m_ParChecker.GetParFull()); pPostInfo->SetWorking(false); pPostInfo->SetStage(PostInfo::ptQueued); pDownloadQueue->Save(); DownloadQueue::Unlock(); } /** * Unpause par2-files * returns true, if the files with required number of blocks were unpaused, * or false if there are no more files in queue for this collection or not enough blocks. * special case: returns true if there are any unpaused par2-files in the queue regardless * of the amount of blocks; this is to keep par-checker wait for download completion. */ bool ParCoordinator::RequestMorePars(NZBInfo* pNZBInfo, const char* szParFilename, int iBlockNeeded, int* pBlockFound) { DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); Blocks blocks; blocks.clear(); int iBlockFound = 0; int iCurBlockFound = 0; FindPars(pDownloadQueue, pNZBInfo, szParFilename, &blocks, true, true, &iCurBlockFound); iBlockFound += iCurBlockFound; if (iBlockFound < iBlockNeeded) { FindPars(pDownloadQueue, pNZBInfo, szParFilename, &blocks, true, false, &iCurBlockFound); iBlockFound += iCurBlockFound; } if (iBlockFound < iBlockNeeded) { FindPars(pDownloadQueue, pNZBInfo, szParFilename, &blocks, false, false, &iCurBlockFound); iBlockFound += iCurBlockFound; } if (iBlockFound >= iBlockNeeded) { // 1. first unpause all files with par-blocks less or equal iBlockNeeded // starting from the file with max block count. // if par-collection was built exponentially and all par-files present, // this step selects par-files with exact number of blocks we need. while (iBlockNeeded > 0) { BlockInfo* pBestBlockInfo = NULL; for (Blocks::iterator it = blocks.begin(); it != blocks.end(); it++) { BlockInfo* pBlockInfo = *it; if (pBlockInfo->m_iBlockCount <= iBlockNeeded && (!pBestBlockInfo || pBestBlockInfo->m_iBlockCount < pBlockInfo->m_iBlockCount)) { pBestBlockInfo = pBlockInfo; } } if (pBestBlockInfo) { if (pBestBlockInfo->m_pFileInfo->GetPaused()) { 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); } iBlockNeeded -= pBestBlockInfo->m_iBlockCount; blocks.remove(pBestBlockInfo); delete pBestBlockInfo; } else { break; } } // 2. then unpause other files // this step only needed if the par-collection was built not exponentially // or not all par-files present (or some of them were corrupted) // this step is not optimal, but we hope, that the first step will work good // in most cases and we will not need the second step often while (iBlockNeeded > 0) { BlockInfo* pBlockInfo = blocks.front(); if (pBlockInfo->m_pFileInfo->GetPaused()) { 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); } iBlockNeeded -= pBlockInfo->m_iBlockCount; } } bool bHasUnpausedParFiles = false; for (FileList::iterator it = pNZBInfo->GetFileList()->begin(); it != pNZBInfo->GetFileList()->end(); it++) { FileInfo* pFileInfo = *it; if (pFileInfo->GetParFile() && !pFileInfo->GetPaused()) { bHasUnpausedParFiles = true; break; } } DownloadQueue::Unlock(); if (pBlockFound) { *pBlockFound = iBlockFound; } for (Blocks::iterator it = blocks.begin(); it != blocks.end(); it++) { delete *it; } blocks.clear(); bool bOK = iBlockNeeded <= 0 || bHasUnpausedParFiles; return bOK; } void ParCoordinator::FindPars(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, const char* szParFilename, Blocks* pBlocks, bool bStrictParName, bool bExactParName, int* pBlockFound) { *pBlockFound = 0; // extract base name from m_szParFilename (trim .par2-extension and possible .vol-part) char* szBaseParFilename = Util::BaseFileName(szParFilename); char szMainBaseFilename[1024]; int iMainBaseLen = 0; if (!ParParser::ParseParFilename(szBaseParFilename, &iMainBaseLen, NULL)) { // should not happen pNZBInfo->PrintMessage(Message::mkError, "Internal error: could not parse filename %s", szBaseParFilename); return; } int maxlen = iMainBaseLen < 1024 ? iMainBaseLen : 1024 - 1; strncpy(szMainBaseFilename, szBaseParFilename, maxlen); szMainBaseFilename[maxlen] = '\0'; for (char* p = szMainBaseFilename; *p; p++) *p = tolower(*p); // convert string to lowercase for (FileList::iterator it = pNZBInfo->GetFileList()->begin(); it != pNZBInfo->GetFileList()->end(); it++) { FileInfo* pFileInfo = *it; int iBlocks = 0; if (ParParser::ParseParFilename(pFileInfo->GetFilename(), NULL, &iBlocks) && iBlocks > 0) { bool bUseFile = true; if (bExactParName) { bUseFile = ParParser::SameParCollection(pFileInfo->GetFilename(), Util::BaseFileName(szParFilename)); } else if (bStrictParName) { // the pFileInfo->GetFilename() may be not confirmed and may contain // additional texts if Subject could not be parsed correctly 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 char szCandidateFileName[1024]; snprintf(szCandidateFileName, 1024, "%s.par2", szMainBaseFilename); szCandidateFileName[1024-1] = '\0'; if (!strstr(szLoFileName, szCandidateFileName)) { snprintf(szCandidateFileName, 1024, "%s.vol", szMainBaseFilename); szCandidateFileName[1024-1] = '\0'; bUseFile = strstr(szLoFileName, szCandidateFileName); } } bool bAlreadyAdded = false; // check if file is not in the list already if (bUseFile) { for (Blocks::iterator it = pBlocks->begin(); it != pBlocks->end(); it++) { BlockInfo* pBlockInfo = *it; if (pBlockInfo->m_pFileInfo == pFileInfo) { bAlreadyAdded = true; break; } } } // if it is a par2-file with blocks and it was from the same NZB-request // and it belongs to the same file collection (same base name), // then OK, we can use it if (bUseFile && !bAlreadyAdded) { BlockInfo* pBlockInfo = new BlockInfo(); pBlockInfo->m_pFileInfo = pFileInfo; pBlockInfo->m_iBlockCount = iBlocks; pBlocks->push_back(pBlockInfo); *pBlockFound += iBlocks; } } } } void ParCoordinator::UpdateParCheckProgress() { DownloadQueue::Lock(); PostInfo* pPostInfo = m_ParChecker.GetPostInfo(); if (m_ParChecker.GetFileProgress() == 0) { pPostInfo->SetProgressLabel(m_ParChecker.GetProgressLabel()); } pPostInfo->SetFileProgress(m_ParChecker.GetFileProgress()); pPostInfo->SetStageProgress(m_ParChecker.GetStageProgress()); PostInfo::EStage StageKind[] = { PostInfo::ptLoadingPars, PostInfo::ptVerifyingSources, PostInfo::ptRepairing, PostInfo::ptVerifyingRepaired }; PostInfo::EStage eStage = StageKind[m_ParChecker.GetStage()]; time_t tCurrent = time(NULL); if (pPostInfo->GetStage() != eStage) { pPostInfo->SetStage(eStage); pPostInfo->SetStageTime(tCurrent); if (pPostInfo->GetStage() == PostInfo::ptRepairing) { m_ParChecker.SetRepairTime(tCurrent); } else if (pPostInfo->GetStage() == PostInfo::ptVerifyingRepaired) { int iRepairSec = (int)(tCurrent - m_ParChecker.GetRepairTime()); pPostInfo->GetNZBInfo()->SetRepairSec(pPostInfo->GetNZBInfo()->GetRepairSec() + iRepairSec); } } bool bParCancel = false; if (!m_ParChecker.GetCancelled()) { if ((g_pOptions->GetParTimeLimit() > 0) && m_ParChecker.GetStage() == ParChecker::ptRepairing && ((g_pOptions->GetParTimeLimit() > 5 && tCurrent - pPostInfo->GetStageTime() > 5 * 60) || (g_pOptions->GetParTimeLimit() <= 5 && tCurrent - pPostInfo->GetStageTime() > 1 * 60))) { // first five (or one) minutes elapsed, now can check the estimated time int iEstimatedRepairTime = (int)((tCurrent - pPostInfo->GetStartTime()) * 1000 / (pPostInfo->GetStageProgress() > 0 ? pPostInfo->GetStageProgress() : 1)); if (iEstimatedRepairTime > g_pOptions->GetParTimeLimit() * 60) { debug("Estimated repair time %i seconds", iEstimatedRepairTime); 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; } } } if (bParCancel) { m_ParChecker.Cancel(); } DownloadQueue::Unlock(); CheckPauseState(pPostInfo); } void ParCoordinator::CheckPauseState(PostInfo* pPostInfo) { if (g_pOptions->GetPausePostProcess() && !pPostInfo->GetNZBInfo()->GetForcePriority()) { time_t tStageTime = pPostInfo->GetStageTime(); time_t tStartTime = pPostInfo->GetStartTime(); time_t tParTime = m_ParChecker.GetParTime(); time_t tRepairTime = m_ParChecker.GetRepairTime(); time_t tWaitTime = time(NULL); // wait until Post-processor is unpaused while (g_pOptions->GetPausePostProcess() && !pPostInfo->GetNZBInfo()->GetForcePriority() && !m_bStopped) { usleep(50 * 1000); // update time stamps time_t tDelta = time(NULL) - tWaitTime; if (tStageTime > 0) { pPostInfo->SetStageTime(tStageTime + tDelta); } if (tStartTime > 0) { pPostInfo->SetStartTime(tStartTime + tDelta); } if (tParTime > 0) { m_ParChecker.SetParTime(tParTime + tDelta); } if (tRepairTime > 0) { m_ParChecker.SetRepairTime(tRepairTime + tDelta); } } } } void ParCoordinator::ParRenameCompleted() { DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); PostInfo* pPostInfo = m_ParRenamer.GetPostInfo(); pPostInfo->GetNZBInfo()->SetRenameStatus(m_ParRenamer.GetStatus() == ParRenamer::psSuccess ? NZBInfo::rsSuccess : NZBInfo::rsFailure); if (m_ParRenamer.HasMissedFiles() && pPostInfo->GetNZBInfo()->GetParStatus() <= NZBInfo::psSkipped) { m_ParRenamer.PrintMessage(Message::mkInfo, "Requesting par-check/repair for %s to restore missing files ", m_ParRenamer.GetInfoName()); pPostInfo->SetRequestParCheck(true); } pPostInfo->SetWorking(false); pPostInfo->SetStage(PostInfo::ptQueued); pDownloadQueue->Save(); DownloadQueue::Unlock(); } void ParCoordinator::UpdateParRenameProgress() { DownloadQueue::Lock(); PostInfo* pPostInfo = m_ParRenamer.GetPostInfo(); pPostInfo->SetProgressLabel(m_ParRenamer.GetProgressLabel()); pPostInfo->SetStageProgress(m_ParRenamer.GetStageProgress()); time_t tCurrent = time(NULL); if (pPostInfo->GetStage() != PostInfo::ptRenaming) { pPostInfo->SetStage(PostInfo::ptRenaming); pPostInfo->SetStageTime(tCurrent); } DownloadQueue::Unlock(); CheckPauseState(pPostInfo); } #endif nzbget-16.4/daemon/postprocess/ParParser.h0000644000175000017500000000236712630544544020467 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 PARPARSER_H #define PARPARSER_H #include class ParParser { public: typedef std::deque ParFileList; static bool FindMainPars(const char* szPath, ParFileList* pFileList); static bool ParseParFilename(const char* szParFilename, int* iBaseNameLen, int* iBlocks); static bool SameParCollection(const char* szFilename1, const char* szFilename2); }; #endif nzbget-16.4/daemon/postprocess/Unpack.cpp0000644000175000017500000006755712630544544020360 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2013-2015 Andrey Prygunkov * * 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 #include #include #include #ifndef WIN32 #include #endif #include #include "nzbget.h" #include "Unpack.h" #include "Log.h" #include "Util.h" #include "ParParser.h" #include "Options.h" void UnpackController::FileList::Clear() { for (iterator it = begin(); it != end(); it++) { free(*it); } clear(); } bool UnpackController::FileList::Exists(const char* szFilename) { for (iterator it = begin(); it != end(); it++) { char* szFilename1 = *it; if (!strcmp(szFilename1, szFilename)) { return true; } } return false; } UnpackController::ParamList::~ParamList() { for (iterator it = begin(); it != end(); it++) { free(*it); } } bool UnpackController::ParamList::Exists(const char* szParam) { for (iterator it = begin(); it != end(); it++) { char* szParam1 = *it; if (!strcmp(szParam1, szParam)) { return true; } } return false; } void UnpackController::StartJob(PostInfo* pPostInfo) { UnpackController* pUnpackController = new UnpackController(); pUnpackController->m_pPostInfo = pPostInfo; pUnpackController->SetAutoDestroy(false); pPostInfo->SetPostThread(pUnpackController); pUnpackController->Start(); } void UnpackController::Run() { time_t tStart = time(NULL); // the locking is needed for accessing the members of NZBInfo DownloadQueue::Lock(); strncpy(m_szDestDir, m_pPostInfo->GetNZBInfo()->GetDestDir(), 1024); m_szDestDir[1024-1] = '\0'; strncpy(m_szName, m_pPostInfo->GetNZBInfo()->GetName(), 1024); m_szName[1024-1] = '\0'; m_bCleanedUpDisk = false; m_szPassword[0] = '\0'; m_szFinalDir[0] = '\0'; m_bFinalDirCreated = false; m_bUnpackOK = true; m_bUnpackStartError = false; m_bUnpackSpaceError = false; m_bUnpackDecryptError = false; m_bUnpackPasswordError = false; m_bAutoTerminated = false; m_bPassListTried = false; 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) { strncpy(m_szPassword, pParameter->GetValue(), 1024-1); m_szPassword[1024-1] = '\0'; } DownloadQueue::Unlock(); snprintf(m_szInfoName, 1024, "unpack for %s", m_szName); m_szInfoName[1024-1] = '\0'; snprintf(m_szInfoNameUp, 1024, "Unpack for %s", m_szName); // first letter in upper case m_szInfoNameUp[1024-1] = '\0'; m_bHasParFiles = ParParser::FindMainPars(m_szDestDir, NULL); if (bUnpack) { bool bScanNonStdFiles = m_pPostInfo->GetNZBInfo()->GetRenameStatus() > NZBInfo::rsSkipped || m_pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psSuccess || !m_bHasParFiles; CheckArchiveFiles(bScanNonStdFiles); } SetInfoName(m_szInfoName); SetWorkingDir(m_szDestDir); bool bHasFiles = m_bHasRarFiles || m_bHasNonStdRarFiles || m_bHasSevenZipFiles || m_bHasSevenZipMultiFiles || m_bHasSplittedFiles; if (m_pPostInfo->GetUnpackTried() && !m_pPostInfo->GetParRepaired() && (!Util::EmptyStr(m_szPassword) || Util::EmptyStr(g_pOptions->GetUnpackPassFile()) || m_pPostInfo->GetPassListTried())) { PrintMessage(Message::mkInfo, "Second unpack attempt skipped for %s due to par-check not repaired anything", m_szName); PrintMessage(Message::mkError, m_pPostInfo->GetLastUnpackStatus() == (int)NZBInfo::usPassword ? "%s failed: checksum error in the encrypted file. Corrupt file or wrong password." : "%s failed.", m_szInfoNameUp); m_pPostInfo->GetNZBInfo()->SetUnpackStatus((NZBInfo::EUnpackStatus)m_pPostInfo->GetLastUnpackStatus()); m_pPostInfo->SetStage(PostInfo::ptQueued); } else if (bUnpack && bHasFiles) { PrintMessage(Message::mkInfo, "Unpacking %s", m_szName); CreateUnpackDir(); if (m_bHasRarFiles || m_bHasNonStdRarFiles) { UnpackArchives(upUnrar, false); } if (m_bHasSevenZipFiles && m_bUnpackOK) { UnpackArchives(upSevenZip, false); } if (m_bHasSevenZipMultiFiles && m_bUnpackOK) { UnpackArchives(upSevenZip, true); } if (m_bHasSplittedFiles && m_bUnpackOK) { JoinSplittedFiles(); } Completed(); m_JoinedFiles.Clear(); } else { 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 && m_pPostInfo->GetNZBInfo()->GetRenameStatus() <= NZBInfo::rsSkipped && m_bHasParFiles) { RequestParCheck(false); } else #endif { m_pPostInfo->GetNZBInfo()->SetUnpackStatus(NZBInfo::usSkipped); m_pPostInfo->SetStage(PostInfo::ptQueued); } } int iUnpackSec = (int)(time(NULL) - tStart); m_pPostInfo->GetNZBInfo()->SetUnpackSec(m_pPostInfo->GetNZBInfo()->GetUnpackSec() + iUnpackSec); m_pPostInfo->SetWorking(false); } void UnpackController::UnpackArchives(EUnpacker eUnpacker, bool bMultiVolumes) { if (!m_pPostInfo->GetUnpackTried() || m_pPostInfo->GetParRepaired()) { ExecuteUnpack(eUnpacker, m_szPassword, bMultiVolumes); if (!m_bUnpackOK && m_bHasParFiles && !m_bUnpackPasswordError && m_pPostInfo->GetNZBInfo()->GetParStatus() <= NZBInfo::psSkipped) { // for rar4- or 7z-archives try par-check first, before trying password file return; } } else { m_bUnpackOK = false; m_bUnpackDecryptError = m_pPostInfo->GetLastUnpackStatus() == (int)NZBInfo::usPassword; } if (!m_bUnpackOK && !m_bUnpackStartError && !m_bUnpackSpaceError && (m_bUnpackDecryptError || m_bUnpackPasswordError) && (!GetTerminated() || m_bAutoTerminated) && Util::EmptyStr(m_szPassword) && !Util::EmptyStr(g_pOptions->GetUnpackPassFile())) { FILE* infile = fopen(g_pOptions->GetUnpackPassFile(), FOPEN_RB); if (!infile) { PrintMessage(Message::mkError, "Could not open file %s", g_pOptions->GetUnpackPassFile()); return; } char szPassword[512]; while (!m_bUnpackOK && !m_bUnpackStartError && !m_bUnpackSpaceError && (m_bUnpackDecryptError || m_bUnpackPasswordError) && fgets(szPassword, sizeof(szPassword) - 1, infile)) { // trim trailing and char* szEnd = szPassword + strlen(szPassword) - 1; while (szEnd >= szPassword && (*szEnd == '\n' || *szEnd == '\r')) *szEnd-- = '\0'; if (!Util::EmptyStr(szPassword)) { if (IsStopped() && m_bAutoTerminated) { ScriptController::Resume(); Thread::Resume(); } m_bUnpackDecryptError = false; m_bUnpackPasswordError = false; m_bAutoTerminated = false; PrintMessage(Message::mkInfo, "Trying password %s for %s", szPassword, m_szName); ExecuteUnpack(eUnpacker, szPassword, bMultiVolumes); } } fclose(infile); m_bPassListTried = !IsStopped() || m_bAutoTerminated; } } void UnpackController::ExecuteUnpack(EUnpacker eUnpacker, const char* szPassword, bool bMultiVolumes) { switch (eUnpacker) { case upUnrar: ExecuteUnrar(szPassword); break; case upSevenZip: ExecuteSevenZip(szPassword, bMultiVolumes); break; } } void UnpackController::ExecuteUnrar(const char* szPassword) { // Format: // unrar x -y -p- -o+ *.rar ./_unpack/ ParamList params; if (!PrepareCmdParams(g_pOptions->GetUnrarCmd(), ¶ms, "unrar")) { return; } if (!params.Exists("x") && !params.Exists("e")) { params.push_back(strdup("x")); } params.push_back(strdup("-y")); if (!Util::EmptyStr(szPassword)) { char szPasswordParam[1024]; snprintf(szPasswordParam, 1024, "-p%s", szPassword); szPasswordParam[1024-1] = '\0'; params.push_back(strdup(szPasswordParam)); } else { params.push_back(strdup("-p-")); } if (!params.Exists("-o+") && !params.Exists("-o-")) { params.push_back(strdup("-o+")); } params.push_back(strdup(m_bHasNonStdRarFiles ? "*.*" : "*.rar")); char szUnpackDirParam[1024]; snprintf(szUnpackDirParam, 1024, "%s%c", m_szUnpackDir, PATH_SEPARATOR); szUnpackDirParam[1024-1] = '\0'; params.push_back(strdup(szUnpackDirParam)); params.push_back(NULL); SetArgs((const char**)¶ms.front(), false); SetScript(params.at(0)); SetLogPrefix("Unrar"); ResetEnv(); m_bAllOKMessageReceived = false; m_eUnpacker = upUnrar; SetProgressLabel(""); int iExitCode = Execute(); 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) { PrintMessage(Message::mkError, "Unrar error code: %i", iExitCode); } } void UnpackController::ExecuteSevenZip(const char* szPassword, bool bMultiVolumes) { // Format: // 7z x -y -p- -o./_unpack *.7z // OR // 7z x -y -p- -o./_unpack *.7z.001 ParamList params; if (!PrepareCmdParams(g_pOptions->GetSevenZipCmd(), ¶ms, "7-Zip")) { return; } if (!params.Exists("x") && !params.Exists("e")) { params.push_back(strdup("x")); } params.push_back(strdup("-y")); if (!Util::EmptyStr(szPassword)) { char szPasswordParam[1024]; snprintf(szPasswordParam, 1024, "-p%s", szPassword); szPasswordParam[1024-1] = '\0'; params.push_back(strdup(szPasswordParam)); } else { params.push_back(strdup("-p-")); } char szUnpackDirParam[1024]; snprintf(szUnpackDirParam, 1024, "-o%s", m_szUnpackDir); szUnpackDirParam[1024-1] = '\0'; params.push_back(strdup(szUnpackDirParam)); params.push_back(strdup(bMultiVolumes ? "*.7z.001" : "*.7z")); params.push_back(NULL); SetArgs((const char**)¶ms.front(), false); SetScript(params.at(0)); ResetEnv(); m_bAllOKMessageReceived = false; m_eUnpacker = upSevenZip; PrintMessage(Message::mkInfo, "Executing 7-Zip"); SetLogPrefix("7-Zip"); SetProgressLabel(""); int iExitCode = Execute(); SetLogPrefix(NULL); SetProgressLabel(""); m_bUnpackOK = iExitCode == 0 && m_bAllOKMessageReceived && !GetTerminated(); m_bUnpackStartError = iExitCode == -1; if (!m_bUnpackOK && iExitCode > 0) { PrintMessage(Message::mkError, "7-Zip error code: %i", iExitCode); } } bool UnpackController::PrepareCmdParams(const char* szCommand, ParamList* pParams, const char* szInfoName) { if (Util::FileExists(szCommand)) { pParams->push_back(strdup(szCommand)); return true; } char** pCmdArgs = NULL; if (!Util::SplitCommandLine(szCommand, &pCmdArgs)) { PrintMessage(Message::mkError, "Could not start %s, failed to parse command line: %s", szInfoName, szCommand); m_bUnpackOK = false; m_bUnpackStartError = true; return false; } for (char** szArgPtr = pCmdArgs; *szArgPtr; szArgPtr++) { pParams->push_back(*szArgPtr); } free(pCmdArgs); return true; } void UnpackController::JoinSplittedFiles() { SetLogPrefix("Join"); SetProgressLabel(""); m_pPostInfo->SetStageProgress(0); // determine groups FileList groups; RegEx regExSplitExt(".*\\.[a-z,0-9]{3}\\.001$"); DirBrowser dir(m_szDestDir); while (const char* filename = dir.Next()) { char szFullFilename[1024]; snprintf(szFullFilename, 1024, "%s%c%s", m_szDestDir, PATH_SEPARATOR, filename); szFullFilename[1024-1] = '\0'; if (strcmp(filename, ".") && strcmp(filename, "..") && !Util::DirectoryExists(szFullFilename)) { if (regExSplitExt.Match(filename) && !FileHasRarSignature(szFullFilename)) { if (!JoinFile(filename)) { m_bUnpackOK = false; break; } } } } SetLogPrefix(NULL); SetProgressLabel(""); } bool UnpackController::JoinFile(const char* szFragBaseName) { char szDestBaseName[1024]; strncpy(szDestBaseName, szFragBaseName, 1024); szDestBaseName[1024-1] = '\0'; // trim extension char* szExtension = strrchr(szDestBaseName, '.'); *szExtension = '\0'; char szFullFilename[1024]; snprintf(szFullFilename, 1024, "%s%c%s", m_szDestDir, PATH_SEPARATOR, szFragBaseName); szFullFilename[1024-1] = '\0'; long long lFirstSegmentSize = Util::FileSize(szFullFilename); long long lDifSegmentSize = 0; // Validate joinable file: // - fragments have continuous numbers (no holes); // - fragments have the same size (except of the last fragment); // - the last fragment must be smaller than other fragments, // if it has the same size it is probably not the last and there are missing fragments. RegEx regExSplitExt(".*\\.[a-z,0-9]{3}\\.[0-9]{3}$"); int iCount = 0; int iMin = -1; int iMax = -1; int iDifSizeCount = 0; int iDifSizeMin = 999999; DirBrowser dir(m_szDestDir); while (const char* filename = dir.Next()) { snprintf(szFullFilename, 1024, "%s%c%s", m_szDestDir, PATH_SEPARATOR, filename); szFullFilename[1024-1] = '\0'; if (strcmp(filename, ".") && strcmp(filename, "..") && !Util::DirectoryExists(szFullFilename) && regExSplitExt.Match(filename)) { const char* szSegExt = strrchr(filename, '.'); int iSegNum = atoi(szSegExt + 1); iCount++; iMin = iSegNum < iMin || iMin == -1 ? iSegNum : iMin; iMax = iSegNum > iMax ? iSegNum : iMax; long long lSegmentSize = Util::FileSize(szFullFilename); if (lSegmentSize != lFirstSegmentSize) { iDifSizeCount++; iDifSizeMin = iSegNum < iDifSizeMin ? iSegNum : iDifSizeMin; lDifSegmentSize = lSegmentSize; } } } int iCorrectedCount = iCount - (iMin == 0 ? 1 : 0); if ((iMin > 1) || iCorrectedCount != iMax || ((iDifSizeMin != iCorrectedCount || iDifSizeMin > iMax) && m_pPostInfo->GetNZBInfo()->GetParStatus() != NZBInfo::psSuccess)) { PrintMessage(Message::mkWarning, "Could not join splitted file %s: missing fragments detected", szDestBaseName); return false; } // Now can join PrintMessage(Message::mkInfo, "Joining splitted file %s", szDestBaseName); m_pPostInfo->SetStageProgress(0); char szErrBuf[256]; char szDestFilename[1024]; snprintf(szDestFilename, 1024, "%s%c%s", m_szUnpackDir, PATH_SEPARATOR, szDestBaseName); szDestFilename[1024-1] = '\0'; FILE* pOutFile = fopen(szDestFilename, FOPEN_WBP); if (!pOutFile) { PrintMessage(Message::mkError, "Could not create file %s: %s", szDestFilename, Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf))); return false; } if (g_pOptions->GetWriteBuffer() > 0) { setvbuf(pOutFile, NULL, _IOFBF, g_pOptions->GetWriteBuffer() * 1024); } long long lTotalSize = lFirstSegmentSize * (iCount - 1) + lDifSegmentSize; long long lWritten = 0; static const int BUFFER_SIZE = 1024 * 50; char* buffer = (char*)malloc(BUFFER_SIZE); bool bOK = true; for (int i = iMin; i <= iMax; i++) { PrintMessage(Message::mkInfo, "Joining from %s.%.3i", szDestBaseName, i); char szMessage[1024]; snprintf(szMessage, 1024, "Joining from %s.%.3i", szDestBaseName, i); szMessage[1024-1] = '\0'; SetProgressLabel(szMessage); char szFragFilename[1024]; snprintf(szFragFilename, 1024, "%s%c%s.%.3i", m_szDestDir, PATH_SEPARATOR, szDestBaseName, i); szFragFilename[1024-1] = '\0'; if (!Util::FileExists(szFragFilename)) { break; } FILE* pInFile = fopen(szFragFilename, FOPEN_RB); if (pInFile) { int cnt = BUFFER_SIZE; while (cnt == BUFFER_SIZE) { cnt = (int)fread(buffer, 1, BUFFER_SIZE, pInFile); fwrite(buffer, 1, cnt, pOutFile); lWritten += cnt; m_pPostInfo->SetStageProgress(int(lWritten * 1000 / lTotalSize)); } fclose(pInFile); char szFragFilename[1024]; snprintf(szFragFilename, 1024, "%s.%.3i", szDestBaseName, i); szFragFilename[1024-1] = '\0'; m_JoinedFiles.push_back(strdup(szFragFilename)); } else { PrintMessage(Message::mkError, "Could not open file %s", szFragFilename); bOK = false; break; } } fclose(pOutFile); free(buffer); return bOK; } void UnpackController::Completed() { bool bCleanupSuccess = Cleanup(); if (m_bUnpackOK && bCleanupSuccess) { PrintMessage(Message::mkInfo, "%s %s", m_szInfoNameUp, "successful"); 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_pPostInfo->GetNZBInfo()->GetParFull()) && !m_bUnpackStartError && !m_bUnpackSpaceError && !m_bUnpackPasswordError && (!GetTerminated() || m_bAutoTerminated) && m_bHasParFiles) { RequestParCheck(!Util::EmptyStr(m_szPassword) || Util::EmptyStr(g_pOptions->GetUnpackPassFile()) || m_bPassListTried || !(m_bUnpackDecryptError || m_bUnpackPasswordError) || m_pPostInfo->GetNZBInfo()->GetParStatus() > NZBInfo::psSkipped); } else #endif { PrintMessage(Message::mkError, "%s failed", m_szInfoNameUp); m_pPostInfo->GetNZBInfo()->SetUnpackStatus( m_bUnpackSpaceError ? NZBInfo::usSpace : m_bUnpackPasswordError || m_bUnpackDecryptError ? NZBInfo::usPassword : NZBInfo::usFailure); m_pPostInfo->SetStage(PostInfo::ptQueued); } } } #ifndef DISABLE_PARCHECK void UnpackController::RequestParCheck(bool bForceRepair) { PrintMessage(Message::mkInfo, "%s requested %s", m_szInfoNameUp, bForceRepair ? "par-check with forced repair" : "par-check/repair"); m_pPostInfo->SetRequestParCheck(true); m_pPostInfo->SetForceRepair(bForceRepair); m_pPostInfo->SetStage(PostInfo::ptFinished); m_pPostInfo->SetUnpackTried(true); m_pPostInfo->SetPassListTried(m_bPassListTried); m_pPostInfo->SetLastUnpackStatus((int)(m_bUnpackSpaceError ? NZBInfo::usSpace : m_bUnpackPasswordError || m_bUnpackDecryptError ? NZBInfo::usPassword : NZBInfo::usFailure)); } #endif void UnpackController::CreateUnpackDir() { 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'; 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'; char szErrBuf[1024]; if (!Util::ForceDirectories(m_szUnpackDir, szErrBuf, sizeof(szErrBuf))) { PrintMessage(Message::mkError, "Could not create directory %s: %s", m_szUnpackDir, szErrBuf); } } void UnpackController::CheckArchiveFiles(bool bScanNonStdFiles) { m_bHasRarFiles = false; m_bHasNonStdRarFiles = false; m_bHasSevenZipFiles = false; m_bHasSevenZipMultiFiles = false; m_bHasSplittedFiles = false; RegEx regExRar(".*\\.rar$"); RegEx regExRarMultiSeq(".*\\.(r|s)[0-9][0-9]$"); RegEx regExSevenZip(".*\\.7z$"); RegEx regExSevenZipMulti(".*\\.7z\\.[0-9]+$"); RegEx regExNumExt(".*\\.[0-9]+$"); RegEx regExSplitExt(".*\\.[a-z,0-9]{3}\\.[0-9]{3}$"); DirBrowser dir(m_szDestDir); while (const char* filename = dir.Next()) { char szFullFilename[1024]; snprintf(szFullFilename, 1024, "%s%c%s", m_szDestDir, PATH_SEPARATOR, filename); szFullFilename[1024-1] = '\0'; if (strcmp(filename, ".") && strcmp(filename, "..") && !Util::DirectoryExists(szFullFilename)) { const char* szExt = strchr(filename, '.'); int iExtNum = szExt ? atoi(szExt + 1) : -1; if (regExRar.Match(filename)) { m_bHasRarFiles = true; } else if (regExSevenZip.Match(filename)) { m_bHasSevenZipFiles = true; } else if (regExSevenZipMulti.Match(filename)) { m_bHasSevenZipMultiFiles = true; } else if (bScanNonStdFiles && !m_bHasNonStdRarFiles && iExtNum > 1 && !regExRarMultiSeq.Match(filename) && regExNumExt.Match(filename) && FileHasRarSignature(szFullFilename)) { m_bHasNonStdRarFiles = true; } else if (regExSplitExt.Match(filename) && (iExtNum == 0 || iExtNum == 1)) { m_bHasSplittedFiles = 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, FOPEN_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: // - move unpacked files to destination dir; // - remove _unpack-dir; // - delete archive-files. // By failure: // - remove _unpack-dir. bool bOK = true; FileList extractedFiles; if (m_bUnpackOK) { // moving files back DirBrowser dir(m_szUnpackDir); while (const char* filename = dir.Next()) { if (strcmp(filename, ".") && strcmp(filename, "..")) { char szSrcFile[1024]; snprintf(szSrcFile, 1024, "%s%c%s", m_szUnpackDir, PATH_SEPARATOR, filename); szSrcFile[1024-1] = '\0'; char szDstFile[1024]; snprintf(szDstFile, 1024, "%s%c%s", m_szFinalDir[0] != '\0' ? m_szFinalDir : m_szDestDir, PATH_SEPARATOR, filename); szDstFile[1024-1] = '\0'; // silently overwrite existing files remove(szDstFile); bool bHiddenFile = filename[0] == '.'; if (!Util::MoveFile(szSrcFile, szDstFile) && !bHiddenFile) { char szErrBuf[256]; PrintMessage(Message::mkError, "Could not move file %s to %s: %s", szSrcFile, szDstFile, Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf))); bOK = false; } extractedFiles.push_back(strdup(filename)); } } } char szErrBuf[256]; if (bOK && !Util::DeleteDirectoryWithContent(m_szUnpackDir, szErrBuf, sizeof(szErrBuf))) { PrintMessage(Message::mkError, "Could not delete temporary directory %s: %s", m_szUnpackDir, szErrBuf); } if (!m_bUnpackOK && m_bFinalDirCreated) { Util::RemoveDirectory(m_szFinalDir); } if (m_bUnpackOK && bOK && g_pOptions->GetUnpackCleanupDisk()) { PrintMessage(Message::mkInfo, "Deleting archive files"); RegEx regExRar(".*\\.rar$"); RegEx regExRarMultiSeq(".*\\.[r-z][0-9][0-9]$"); RegEx regExSevenZip(".*\\.7z$|.*\\.7z\\.[0-9]+$"); RegEx regExNumExt(".*\\.[0-9]+$"); RegEx regExSplitExt(".*\\.[a-z,0-9]{3}\\.[0-9]{3}$"); DirBrowser dir(m_szDestDir); while (const char* filename = dir.Next()) { char szFullFilename[1024]; snprintf(szFullFilename, 1024, "%s%c%s", m_szDestDir, PATH_SEPARATOR, filename); szFullFilename[1024-1] = '\0'; 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)) || (m_bHasSplittedFiles && regExSplitExt.Match(filename) && m_JoinedFiles.Exists(filename)))) { PrintMessage(Message::mkInfo, "Deleting file %s", filename); if (remove(szFullFilename) != 0) { char szErrBuf[256]; PrintMessage(Message::mkError, "Could not delete file %s: %s", szFullFilename, Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf))); } } } m_bCleanedUpDisk = true; } extractedFiles.Clear(); return bOK; } /** * Unrar prints progress information into the same line using backspace control character. * In order to print progress continuously we analyze the output after every char * and update post-job progress information. */ bool UnpackController::ReadLine(char* szBuf, int iBufSize, FILE* pStream) { bool bPrinted = false; int i = 0; for (; i < iBufSize - 1; i++) { int ch = fgetc(pStream); szBuf[i] = ch; szBuf[i+1] = '\0'; if (ch == EOF) { break; } if (ch == '\n') { i++; break; } char* szBackspace = strrchr(szBuf, '\b'); if (szBackspace) { if (!bPrinted) { char tmp[1024]; strncpy(tmp, szBuf, 1024); tmp[1024-1] = '\0'; char* szTmpPercent = strrchr(tmp, '\b'); if (szTmpPercent) { *szTmpPercent = '\0'; } if (strncmp(szBuf, "...", 3)) { ProcessOutput(tmp); } bPrinted = true; } if (strchr(szBackspace, '%')) { int iPercent = atoi(szBackspace + 1); m_pPostInfo->SetStageProgress(iPercent * 10); } } } szBuf[i] = '\0'; if (bPrinted) { szBuf[0] = '\0'; } return i > 0; } void UnpackController::AddMessage(Message::EKind eKind, const char* szText) { char szMsgText[1024]; strncpy(szMsgText, szText, 1024); szMsgText[1024-1] = '\0'; int iLen = strlen(szText); // Modify unrar messages for better readability: // remove the destination path part from message "Extracting file.xxx" if (m_eUnpacker == upUnrar && !strncmp(szText, "Unrar: Extracting ", 19) && !strncmp(szText + 19, m_szUnpackDir, strlen(m_szUnpackDir))) { snprintf(szMsgText, 1024, "Unrar: Extracting %s", szText + 19 + strlen(m_szUnpackDir) + 1); szMsgText[1024-1] = '\0'; } m_pPostInfo->GetNZBInfo()->AddMessage(eKind, szMsgText); if (m_eUnpacker == upUnrar && !strncmp(szMsgText, "Unrar: UNRAR ", 6) && strstr(szMsgText, " Copyright ") && strstr(szMsgText, " Alexander Roshal")) { // 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(szMsgText, "Unrar: Extracting ", 18)) { SetProgressLabel(szMsgText + 7); } if (m_eUnpacker == upUnrar && !strncmp(szText, "Unrar: Extracting from ", 23)) { const char *szFilename = szText + 23; debug("Filename: %s", szFilename); SetProgressLabel(szText + 7); } if (m_eUnpacker == upUnrar && (!strncmp(szText, "Unrar: Checksum error in the encrypted file", 42) || !strncmp(szText, "Unrar: CRC failed in the encrypted file", 39))) { m_bUnpackDecryptError = true; } if (m_eUnpacker == upUnrar && !strncmp(szText, "Unrar: The specified password is incorrect.'", 43)) { m_bUnpackPasswordError = true; } if (m_eUnpacker == upSevenZip && (iLen > 18 && !strncmp(szText + iLen - 45, "Data Error in encrypted file. Wrong password?", 45))) { m_bUnpackDecryptError = true; } if (!IsStopped() && (m_bUnpackDecryptError || m_bUnpackPasswordError || strstr(szText, " : packed data CRC failed in volume") || strstr(szText, " : packed data checksum error in volume") || (iLen > 13 && !strncmp(szText + iLen - 13, " - CRC failed", 13)) || (iLen > 18 && !strncmp(szText + iLen - 18, " - checksum failed", 18)) || !strncmp(szText, "Unrar: WARNING: You need to start extraction from a previous volume", 67))) { char szMsgText[1024]; snprintf(szMsgText, 1024, "Cancelling %s due to errors", m_szInfoName); szMsgText[1024-1] = '\0'; m_pPostInfo->GetNZBInfo()->AddMessage(Message::mkWarning, szMsgText); m_bAutoTerminated = true; Stop(); } if ((m_eUnpacker == upUnrar && !strncmp(szText, "Unrar: All OK", 13)) || (m_eUnpacker == upSevenZip && !strncmp(szText, "7-Zip: Everything is Ok", 23))) { m_bAllOKMessageReceived = true; } } void UnpackController::Stop() { debug("Stopping unpack"); Thread::Stop(); Terminate(); } void UnpackController::SetProgressLabel(const char* szProgressLabel) { DownloadQueue::Lock(); m_pPostInfo->SetProgressLabel(szProgressLabel); DownloadQueue::Unlock(); } nzbget-16.4/daemon/postprocess/ParCoordinator.h0000644000175000017500000001101712630544544021506 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 PARCOORDINATOR_H #define PARCOORDINATOR_H #include #include #include "DownloadInfo.h" #ifndef DISABLE_PARCHECK #include "ParChecker.h" #include "ParRenamer.h" #include "DupeMatcher.h" #endif class ParCoordinator { private: #ifndef DISABLE_PARCHECK class PostParChecker: public ParChecker { private: ParCoordinator* m_pOwner; PostInfo* m_pPostInfo; time_t m_tParTime; time_t m_tRepairTime; int m_iDownloadSec; 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, ...); virtual void RegisterParredFile(const char* szFilename); virtual bool IsParredFile(const char* szFilename); virtual EFileStatus FindFileCrc(const char* szFilename, unsigned long* lCrc, SegmentList* pSegments); virtual void RequestDupeSources(DupeSourceList* pDupeSourceList); virtual void StatDupeSources(DupeSourceList* pDupeSourceList); public: PostInfo* GetPostInfo() { return m_pPostInfo; } void SetPostInfo(PostInfo* pPostInfo) { m_pPostInfo = pPostInfo; } time_t GetParTime() { return m_tParTime; } void SetParTime(time_t tParTime) { m_tParTime = tParTime; } time_t GetRepairTime() { return m_tRepairTime; } void SetRepairTime(time_t tRepairTime) { m_tRepairTime = tRepairTime; } int GetDownloadSec() { return m_iDownloadSec; } void SetDownloadSec(int iDownloadSec) { m_iDownloadSec = iDownloadSec; } friend class ParCoordinator; }; class PostParRenamer: public ParRenamer { private: ParCoordinator* m_pOwner; PostInfo* m_pPostInfo; protected: virtual void UpdateProgress(); virtual void Completed() { m_pOwner->ParRenameCompleted(); } virtual void PrintMessage(Message::EKind eKind, const char* szFormat, ...); virtual void RegisterParredFile(const char* szFilename); virtual void RegisterRenamedFile(const char* szOldFilename, const char* szNewFileName); public: PostInfo* GetPostInfo() { return m_pPostInfo; } void SetPostInfo(PostInfo* pPostInfo) { m_pPostInfo = pPostInfo; } friend class ParCoordinator; }; class PostDupeMatcher: public DupeMatcher { private: PostInfo* m_pPostInfo; protected: virtual void PrintMessage(Message::EKind eKind, const char* szFormat, ...); public: PostDupeMatcher(PostInfo* pPostInfo): DupeMatcher(pPostInfo->GetNZBInfo()->GetDestDir(), pPostInfo->GetNZBInfo()->GetSize() - pPostInfo->GetNZBInfo()->GetParSize()), m_pPostInfo(pPostInfo) {} }; struct BlockInfo { FileInfo* m_pFileInfo; int m_iBlockCount; }; typedef std::list Blocks; enum EJobKind { jkParCheck, jkParRename }; private: PostParChecker m_ParChecker; bool m_bStopped; PostParRenamer m_ParRenamer; EJobKind m_eCurrentJob; protected: void UpdateParCheckProgress(); void UpdateParRenameProgress(); void ParCheckCompleted(); void ParRenameCompleted(); void CheckPauseState(PostInfo* pPostInfo); bool RequestMorePars(NZBInfo* pNZBInfo, const char* szParFilename, int iBlockNeeded, int* pBlockFound); #endif public: ParCoordinator(); virtual ~ParCoordinator(); void PausePars(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo); #ifndef DISABLE_PARCHECK bool AddPar(FileInfo* pFileInfo, bool bDeleted); void FindPars(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, const char* szParFilename, Blocks* pBlocks, bool bStrictParName, bool bExactParName, int* pBlockFound); void StartParCheckJob(PostInfo* pPostInfo); void StartParRenameJob(PostInfo* pPostInfo); void Stop(); bool Cancel(); #endif }; #endif nzbget-16.4/daemon/postprocess/ParRenamer.cpp0000644000175000017500000002742212630544544021156 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2013-2015 Andrey Prygunkov * * 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 #ifndef DISABLE_PARCHECK #include #include #include #include #ifndef WIN32 #include #endif #include "par2cmdline.h" #include "par2repairer.h" #include "md5.h" #include "nzbget.h" #include "ParRenamer.h" #include "ParParser.h" #include "Log.h" #include "Options.h" #include "Util.h" class ParRenamerRepairer : public Par2Repairer { public: friend class ParRenamer; }; ParRenamer::FileHash::FileHash(const char* szFilename, const char* szHash) { m_szFilename = strdup(szFilename); m_szHash = strdup(szHash); m_bFileExists = false; } ParRenamer::FileHash::~FileHash() { free(m_szFilename); free(m_szHash); } ParRenamer::ParRenamer() { debug("Creating ParRenamer"); m_eStatus = psFailed; m_szDestDir = NULL; m_szInfoName = NULL; m_szProgressLabel = (char*)malloc(1024); m_iStageProgress = 0; m_bCancelled = false; m_bHasMissedFiles = false; m_bDetectMissing = false; } ParRenamer::~ParRenamer() { debug("Destroying ParRenamer"); free(m_szDestDir); free(m_szInfoName); free(m_szProgressLabel); Cleanup(); } void ParRenamer::Cleanup() { 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(); } void ParRenamer::SetDestDir(const char * szDestDir) { free(m_szDestDir); m_szDestDir = strdup(szDestDir); } void ParRenamer::SetInfoName(const char * szInfoName) { free(m_szInfoName); m_szInfoName = strdup(szInfoName); } void ParRenamer::Cancel() { m_bCancelled = true; } void ParRenamer::Run() { Cleanup(); m_bCancelled = false; m_iFileCount = 0; m_iCurFile = 0; m_iRenamedCount = 0; m_bHasMissedFiles = false; m_eStatus = psFailed; snprintf(m_szProgressLabel, 1024, "Checking renamed files for %s", m_szInfoName); m_szProgressLabel[1024-1] = '\0'; m_iStageProgress = 0; UpdateProgress(); 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); if (m_FileHashList.empty()) { int iSavedCurFile = m_iCurFile; CheckFiles(szDestDir, true); m_iCurFile = iSavedCurFile; // restore progress indicator LoadParFiles(szDestDir); } CheckFiles(szDestDir, false); if (m_bDetectMissing) { CheckMissing(); } } if (m_bCancelled) { PrintMessage(Message::mkWarning, "Renaming cancelled for %s", m_szInfoName); } else if (m_iRenamedCount > 0) { PrintMessage(Message::mkInfo, "Successfully renamed %i file(s) for %s", m_iRenamedCount, m_szInfoName); m_eStatus = psSuccess; } else { PrintMessage(Message::mkInfo, "No renamed files found for %s", m_szInfoName); } Cleanup(); Completed(); } 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) { ParParser::ParFileList parFileList; ParParser::FindMainPars(szDestDir, &parFileList); for (ParParser::ParFileList::iterator it = parFileList.begin(); it != parFileList.end(); it++) { char* szParFilename = *it; char szFullParFilename[1024]; snprintf(szFullParFilename, 1024, "%s%c%s", szDestDir, PATH_SEPARATOR, szParFilename); szFullParFilename[1024-1] = '\0'; LoadParFile(szFullParFilename); free(*it); } } void ParRenamer::LoadParFile(const char* szParFilename) { ParRenamerRepairer* pRepairer = new ParRenamerRepairer(); if (!pRepairer->LoadPacketsFromFile(szParFilename)) { PrintMessage(Message::mkWarning, "Could not load par2-file %s", szParFilename); delete pRepairer; return; } for (map::iterator it = pRepairer->sourcefilemap.begin(); it != pRepairer->sourcefilemap.end(); it++) { if (m_bCancelled) { break; } Par2RepairerSourceFile* sourceFile = (*it).second; if (!sourceFile || !sourceFile->GetDescriptionPacket()) { PrintMessage(Message::mkWarning, "Damaged par2-file detected: %s", szParFilename); continue; } m_FileHashList.push_back(new FileHash(sourceFile->GetDescriptionPacket()->FileName().c_str(), sourceFile->GetDescriptionPacket()->Hash16k().print().c_str())); RegisterParredFile(sourceFile->GetDescriptionPacket()->FileName().c_str()); } delete pRepairer; } void ParRenamer::CheckFiles(const char* szDestDir, bool bRenamePars) { 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", szDestDir, PATH_SEPARATOR, filename); szFullFilename[1024-1] = '\0'; 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++; if (bRenamePars) { CheckParFile(szDestDir, szFullFilename); } else { CheckRegularFile(szDestDir, szFullFilename); } } } } } void ParRenamer::CheckMissing() { for (FileHashList::iterator it = m_FileHashList.begin(); it != m_FileHashList.end(); it++) { FileHash* pFileHash = *it; if (!pFileHash->GetFileExists()) { if (Util::MatchFileExt(pFileHash->GetFilename(), g_pOptions->GetParIgnoreExt(), ",;") || Util::MatchFileExt(pFileHash->GetFilename(), g_pOptions->GetExtCleanupDisk(), ",;")) { PrintMessage(Message::mkInfo, "File %s is missing, ignoring", pFileHash->GetFilename()); } else { PrintMessage(Message::mkInfo, "File %s is missing", pFileHash->GetFilename()); m_bHasMissedFiles = true; } } } } bool ParRenamer::IsSplittedFragment(const char* szFilename, const char* szCorrectName) { bool bSplittedFragement = false; const char* szDiskBasename = Util::BaseFileName(szFilename); const char* szExtension = strrchr(szDiskBasename, '.'); int iBaseLen = strlen(szCorrectName); if (szExtension && !strncasecmp(szDiskBasename, szCorrectName, iBaseLen)) { const char* p = szDiskBasename + iBaseLen; if (*p == '.') { for (p++; *p && strchr("0123456789", *p); p++) ; bSplittedFragement = !*p; bSplittedFragement = bSplittedFragement && atoi(szDiskBasename + iBaseLen + 1) <= 1; // .000 or .001 } } return bSplittedFragement; } void ParRenamer::CheckRegularFile(const char* szDestDir, const char* szFilename) { debug("Computing hash for %s", szFilename); const int iBlockSize = 16*1024; FILE* pFile = fopen(szFilename, FOPEN_RB); if (!pFile) { PrintMessage(Message::mkError, "Could not open file %s", szFilename); return; } // load first 16K of the file into buffer void* pBuffer = malloc(iBlockSize); int iReadBytes = fread(pBuffer, 1, iBlockSize, pFile); int iError = ferror(pFile); if (iReadBytes != iBlockSize && iError) { PrintMessage(Message::mkError, "Could not read file %s", szFilename); return; } fclose(pFile); MD5Hash hash16k; MD5Context context; context.Update(pBuffer, iReadBytes); context.Final(hash16k); free(pBuffer); debug("file: %s; hash16k: %s", Util::BaseFileName(szFilename), hash16k.print().c_str()); for (FileHashList::iterator it = m_FileHashList.begin(); it != m_FileHashList.end(); it++) { FileHash* pFileHash = *it; if (!strcmp(pFileHash->GetHash(), hash16k.print().c_str())) { debug("Found correct filename: %s", pFileHash->GetFilename()); pFileHash->SetFileExists(true); char szDstFilename[1024]; snprintf(szDstFilename, 1024, "%s%c%s", szDestDir, PATH_SEPARATOR, pFileHash->GetFilename()); szDstFilename[1024-1] = '\0'; if (!Util::FileExists(szDstFilename) && !IsSplittedFragment(szFilename, pFileHash->GetFilename())) { RenameFile(szFilename, szDstFilename); } break; } } } /* * For files not having par2-extensions: checks if the file is a par2-file and renames * it according to its set-id. */ void ParRenamer::CheckParFile(const char* szDestDir, const char* szFilename) { debug("Checking par2-header for %s", szFilename); const char* szBasename = Util::BaseFileName(szFilename); const char* szExtension = strrchr(szBasename, '.'); if (szExtension && !strcasecmp(szExtension, ".par2")) { // do not process files already having par2-extension return; } FILE* pFile = fopen(szFilename, FOPEN_RB); if (!pFile) { PrintMessage(Message::mkError, "Could not open file %s", szFilename); return; } // load par2-header PACKET_HEADER header; int iReadBytes = fread(&header, 1, sizeof(header), pFile); int iError = ferror(pFile); if (iReadBytes != sizeof(header) && iError) { PrintMessage(Message::mkError, "Could not read file %s", szFilename); return; } fclose(pFile); // Check the packet header if (packet_magic != header.magic || // not par2-file sizeof(PACKET_HEADER) > header.length || // packet length is too small 0 != (header.length & 3) || // packet length is not a multiple of 4 Util::FileSize(szFilename) < (int)header.length) // packet would extend beyond the end of the file { // not par2-file or damaged header, ignoring the file return; } char szSetId[33]; strncpy(szSetId, header.setid.print().c_str(), sizeof(szSetId)); szSetId[33-1] = '\0'; for (char* p = szSetId; *p; p++) *p = tolower(*p); // convert string to lowercase debug("Renaming: %s; setid: %s", Util::BaseFileName(szFilename), szSetId); char szDestFileName[1024]; int iNum = 1; while (iNum == 1 || Util::FileExists(szDestFileName)) { snprintf(szDestFileName, 1024, "%s%c%s.vol%03i+01.PAR2", szDestDir, PATH_SEPARATOR, szSetId, iNum); szDestFileName[1024-1] = '\0'; iNum++; } RenameFile(szFilename, szDestFileName); } void ParRenamer::RenameFile(const char* szSrcFilename, const char* szDestFileName) { PrintMessage(Message::mkInfo, "Renaming %s to %s", Util::BaseFileName(szSrcFilename), Util::BaseFileName(szDestFileName)); if (!Util::MoveFile(szSrcFilename, szDestFileName)) { char szErrBuf[256]; PrintMessage(Message::mkError, "Could not rename %s to %s: %s", szSrcFilename, szDestFileName, Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf))); return; } m_iRenamedCount++; // notify about new file name RegisterRenamedFile(Util::BaseFileName(szSrcFilename), Util::BaseFileName(szDestFileName)); } #endif nzbget-16.4/daemon/postprocess/DupeMatcher.h0000644000175000017500000000306212630544544020762 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2015 Andrey Prygunkov * * 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 DUPEMATCHER_H #define DUPEMATCHER_H #include "Log.h" class DupeMatcher { private: char* m_szDestDir; long long m_lExpectedSize; long long m_lMaxSize; bool m_bCompressed; void FindLargestFile(const char* szDirectory, char* szFilenameBuf, int iBufLen, long long* pMaxSize, bool* pCompressed); friend class RarLister; protected: virtual void PrintMessage(Message::EKind eKind, const char* szFormat, ...) {} public: DupeMatcher(const char* szDestDir, long long lExpectedSize); ~DupeMatcher(); bool Prepare(); bool MatchDupeContent(const char* szDupeDir); static bool SizeDiffOK(long long lSize1, long long lSize2, int iMaxDiffPercent); }; #endif nzbget-16.4/daemon/postprocess/Unpack.h0000644000175000017500000000634512630544544020011 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2013-2015 Andrey Prygunkov * * 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 UNPACK_H #define UNPACK_H #include #include #include "Log.h" #include "Thread.h" #include "DownloadInfo.h" #include "Script.h" class UnpackController : public Thread, public ScriptController { private: enum EUnpacker { upUnrar, upSevenZip }; typedef std::deque FileListBase; class FileList : public FileListBase { public: void Clear(); bool Exists(const char* szFilename); }; typedef std::vector ParamListBase; class ParamList : public ParamListBase { public: ~ParamList(); bool Exists(const char* szParam); }; private: PostInfo* m_pPostInfo; char m_szName[1024]; char m_szInfoName[1024]; char m_szInfoNameUp[1024]; char m_szDestDir[1024]; 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_bHasSplittedFiles; bool m_bUnpackOK; bool m_bUnpackStartError; bool m_bUnpackSpaceError; bool m_bUnpackDecryptError; bool m_bUnpackPasswordError; bool m_bCleanedUpDisk; bool m_bAutoTerminated; EUnpacker m_eUnpacker; bool m_bFinalDirCreated; FileList m_JoinedFiles; bool m_bPassListTried; protected: virtual bool ReadLine(char* szBuf, int iBufSize, FILE* pStream); virtual void AddMessage(Message::EKind eKind, const char* szText); void ExecuteUnpack(EUnpacker eUnpacker, const char* szPassword, bool bMultiVolumes); void ExecuteUnrar(const char* szPassword); void ExecuteSevenZip(const char* szPassword, bool bMultiVolumes); void UnpackArchives(EUnpacker eUnpacker, bool bMultiVolumes); void JoinSplittedFiles(); bool JoinFile(const char* szFragBaseName); void Completed(); void CreateUnpackDir(); bool Cleanup(); void CheckArchiveFiles(bool bScanNonStdFiles); void SetProgressLabel(const char* szProgressLabel); #ifndef DISABLE_PARCHECK void RequestParCheck(bool bForceRepair); #endif bool FileHasRarSignature(const char* szFilename); bool PrepareCmdParams(const char* szCommand, ParamList* pParams, const char* szInfoName); public: virtual void Run(); virtual void Stop(); static void StartJob(PostInfo* pPostInfo); }; #endif nzbget-16.4/daemon/postprocess/ParRenamer.h0000644000175000017500000000667712630544544020634 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2013-2014 Andrey Prygunkov * * 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 PARRENAMER_H #define PARRENAMER_H #ifndef DISABLE_PARCHECK #include #include "Thread.h" #include "Log.h" class ParRenamer : public Thread { public: enum EStatus { psFailed, psSuccess }; class FileHash { private: char* m_szFilename; char* m_szHash; bool m_bFileExists; public: FileHash(const char* szFilename, const char* szHash); ~FileHash(); const char* GetFilename() { return m_szFilename; } const char* GetHash() { return m_szHash; } bool GetFileExists() { return m_bFileExists; } void SetFileExists(bool bFileExists) { m_bFileExists = bFileExists; } }; typedef std::deque FileHashList; typedef std::deque DirList; private: char* m_szInfoName; char* m_szDestDir; EStatus m_eStatus; char* m_szProgressLabel; int m_iStageProgress; bool m_bCancelled; DirList m_DirList; FileHashList m_FileHashList; int m_iFileCount; int m_iCurFile; int m_iRenamedCount; bool m_bHasMissedFiles; bool m_bDetectMissing; void Cleanup(); void ClearHashList(); void BuildDirList(const char* szDestDir); void CheckDir(const char* szDestDir); void LoadParFiles(const char* szDestDir); void LoadParFile(const char* szParFilename); void CheckFiles(const char* szDestDir, bool bRenamePars); void CheckRegularFile(const char* szDestDir, const char* szFilename); void CheckParFile(const char* szDestDir, const char* szFilename); bool IsSplittedFragment(const char* szFilename, const char* szCorrectName); void CheckMissing(); void RenameFile(const char* szSrcFilename, const char* szDestFileName); protected: virtual void UpdateProgress() {} virtual void Completed() {} virtual void PrintMessage(Message::EKind eKind, const char* szFormat, ...) {} virtual void RegisterParredFile(const char* szFilename) {} virtual void RegisterRenamedFile(const char* szOldFilename, const char* szNewFileName) {} const char* GetProgressLabel() { return m_szProgressLabel; } int GetStageProgress() { return m_iStageProgress; } public: ParRenamer(); virtual ~ParRenamer(); virtual void Run(); void SetDestDir(const char* szDestDir); const char* GetInfoName() { return m_szInfoName; } void SetInfoName(const char* szInfoName); void SetStatus(EStatus eStatus); EStatus GetStatus() { return m_eStatus; } void Cancel(); bool GetCancelled() { return m_bCancelled; } bool HasMissedFiles() { return m_bHasMissedFiles; } void SetDetectMissing(bool bDetectMissing) { m_bDetectMissing = bDetectMissing; } }; #endif #endif nzbget-16.4/daemon/postprocess/ParChecker.cpp0000644000175000017500000013600312630544544021125 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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" #include "Mmsystem.h" #endif #ifndef DISABLE_PARCHECK #include #include #include #include #ifndef WIN32 #include #endif #include #include #include "par2cmdline.h" #include "par2repairer.h" #include "nzbget.h" #include "Thread.h" #include "ParChecker.h" #include "ParParser.h" #include "Log.h" #include "Options.h" #include "Util.h" const char* Par2CmdLineErrStr[] = { "OK", "data files are damaged and there is enough recovery data available to repair them", "data files are damaged and there is insufficient recovery data available to be able to repair them", "there was something wrong with the command line arguments", "the PAR2 files did not contain sufficient information about the data files to be able to verify them", "repair completed but the data files still appear to be damaged", "an error occured when accessing files", "internal error occurred", "out of memory" }; // Sleep interval for synchronisation (microseconds) #ifdef WIN32 // Windows doesn't allow sleep intervals less than one millisecond #define SYNC_SLEEP_INTERVAL 1000 #else #define SYNC_SLEEP_INTERVAL 100 #endif class RepairThread; class Repairer : public Par2Repairer { private: typedef vector Threads; CommandLine commandLine; ParChecker* m_pOwner; Threads m_Threads; bool m_bParallel; Mutex progresslock; virtual void BeginRepair(); virtual void EndRepair(); void RepairBlock(u32 inputindex, u32 outputindex, size_t blocklength); protected: virtual void sig_filename(std::string filename) { m_pOwner->signal_filename(filename); } virtual void sig_progress(int progress) { m_pOwner->signal_progress(progress); } virtual void sig_done(std::string filename, int available, int total) { m_pOwner->signal_done(filename, available, total); } virtual bool ScanDataFile(DiskFile *diskfile, Par2RepairerSourceFile* &sourcefile, MatchType &matchtype, MD5Hash &hashfull, MD5Hash &hash16k, u32 &count); virtual bool RepairData(u32 inputindex, size_t blocklength); public: Repairer(ParChecker* pOwner) { m_pOwner = pOwner; } Result PreProcess(const char *szParFilename); Result Process(bool dorepair); friend class ParChecker; friend class RepairThread; }; class RepairThread : public Thread { private: Repairer* m_pOwner; u32 m_inputindex; u32 m_outputindex; size_t m_blocklength; volatile bool m_bWorking; protected: virtual void Run(); public: RepairThread(Repairer* pOwner) { this->m_pOwner = pOwner; m_bWorking = false; } void RepairBlock(u32 inputindex, u32 outputindex, size_t blocklength); bool IsWorking() { return m_bWorking; } }; Result Repairer::PreProcess(const char *szParFilename) { char szMemParam[20]; snprintf(szMemParam, 20, "-m%i", g_pOptions->GetParBuffer()); szMemParam[20-1] = '\0'; if (g_pOptions->GetParScan() == Options::psFull) { char szWildcardParam[1024]; strncpy(szWildcardParam, szParFilename, 1024); szWildcardParam[1024-1] = '\0'; char* szBasename = Util::BaseFileName(szWildcardParam); if (szBasename != szWildcardParam && strlen(szBasename) > 0) { szBasename[0] = '*'; szBasename[1] = '\0'; } const char* argv[] = { "par2", "r", "-v", "-v", szMemParam, szParFilename, szWildcardParam }; if (!commandLine.Parse(7, (char**)argv)) { return eInvalidCommandLineArguments; } } else { const char* argv[] = { "par2", "r", "-v", "-v", szMemParam, szParFilename }; if (!commandLine.Parse(6, (char**)argv)) { return eInvalidCommandLineArguments; } } return Par2Repairer::PreProcess(commandLine); } Result Repairer::Process(bool dorepair) { Result res = Par2Repairer::Process(commandLine, dorepair); debug("ParChecker: Process-result=%i", res); return res; } bool Repairer::ScanDataFile(DiskFile *diskfile, Par2RepairerSourceFile* &sourcefile, MatchType &matchtype, MD5Hash &hashfull, MD5Hash &hash16k, u32 &count) { if (m_pOwner->GetParQuick() && sourcefile) { string path; string name; DiskFile::SplitFilename(diskfile->FileName(), path, name); sig_filename(name); if (!(m_pOwner->GetStage() == ParChecker::ptVerifyingRepaired && m_pOwner->GetParFull())) { int iAvailableBlocks = sourcefile->BlockCount(); ParChecker::EFileStatus eFileStatus = m_pOwner->VerifyDataFile(diskfile, sourcefile, &iAvailableBlocks); if (eFileStatus != ParChecker::fsUnknown) { sig_done(name, iAvailableBlocks, sourcefile->BlockCount()); sig_progress(1000); matchtype = eFileStatus == ParChecker::fsSuccess ? eFullMatch : eFileStatus == ParChecker::fsPartial ? ePartialMatch : eNoMatch; m_pOwner->SetParFull(false); return true; } } } return Par2Repairer::ScanDataFile(diskfile, sourcefile, matchtype, hashfull, hash16k, count); } void Repairer::BeginRepair() { int iMaxThreads = g_pOptions->GetParThreads() > 0 ? g_pOptions->GetParThreads() : Util::NumberOfCpuCores(); iMaxThreads = iMaxThreads > 0 ? iMaxThreads : 1; int iThreads = iMaxThreads > (int)missingblockcount ? (int)missingblockcount : iMaxThreads; m_pOwner->PrintMessage(Message::mkInfo, "Using %i of max %i thread(s) to repair %i block(s) for %s", iThreads, iMaxThreads, (int)missingblockcount, m_pOwner->m_szNZBName); m_bParallel = iThreads > 1; if (m_bParallel) { for (int i = 0; i < iThreads; i++) { RepairThread* pRepairThread = new RepairThread(this); m_Threads.push_back(pRepairThread); pRepairThread->SetAutoDestroy(true); pRepairThread->Start(); } #ifdef WIN32 timeBeginPeriod(1); #endif } } void Repairer::EndRepair() { if (m_bParallel) { for (Threads::iterator it = m_Threads.begin(); it != m_Threads.end(); it++) { RepairThread* pRepairThread = (RepairThread*)*it; pRepairThread->Stop(); } #ifdef WIN32 timeEndPeriod(1); #endif } } bool Repairer::RepairData(u32 inputindex, size_t blocklength) { if (!m_bParallel) { return false; } for (u32 outputindex = 0; outputindex < missingblockcount; ) { bool bJobAdded = false; for (Threads::iterator it = m_Threads.begin(); it != m_Threads.end(); it++) { RepairThread* pRepairThread = (RepairThread*)*it; if (!pRepairThread->IsWorking()) { pRepairThread->RepairBlock(inputindex, outputindex, blocklength); outputindex++; bJobAdded = true; break; } } if (cancelled) { break; } if (!bJobAdded) { usleep(SYNC_SLEEP_INTERVAL); } } // Wait until all m_Threads complete their jobs bool bWorking = true; while (bWorking) { bWorking = false; for (Threads::iterator it = m_Threads.begin(); it != m_Threads.end(); it++) { RepairThread* pRepairThread = (RepairThread*)*it; if (pRepairThread->IsWorking()) { bWorking = true; usleep(SYNC_SLEEP_INTERVAL); break; } } } return true; } void Repairer::RepairBlock(u32 inputindex, u32 outputindex, size_t blocklength) { // Select the appropriate part of the output buffer void *outbuf = &((u8*)outputbuffer)[chunksize * outputindex]; // Process the data rs.Process(blocklength, inputindex, inputbuffer, outputindex, outbuf); if (noiselevel > CommandLine::nlQuiet) { // Update a progress indicator progresslock.Lock(); u32 oldfraction = (u32)(1000 * progress / totaldata); progress += blocklength; u32 newfraction = (u32)(1000 * progress / totaldata); progresslock.Unlock(); if (oldfraction != newfraction) { sig_progress(newfraction); } } } void RepairThread::Run() { while (!IsStopped()) { if (m_bWorking) { m_pOwner->RepairBlock(m_inputindex, m_outputindex, m_blocklength); m_bWorking = false; } else { usleep(SYNC_SLEEP_INTERVAL); } } } void RepairThread::RepairBlock(u32 inputindex, u32 outputindex, size_t blocklength) { m_inputindex = inputindex; m_outputindex = outputindex; m_blocklength = blocklength; m_bWorking = true; } class MissingFilesComparator { private: const char* m_szBaseParFilename; public: MissingFilesComparator(const char* szBaseParFilename) : m_szBaseParFilename(szBaseParFilename) {} bool operator()(CommandLine::ExtraFile* pFirst, CommandLine::ExtraFile* pSecond) const; }; /* * Files with the same name as in par-file (and a differnt extension) are * placed at the top of the list to be scanned first. */ bool MissingFilesComparator::operator()(CommandLine::ExtraFile* pFile1, CommandLine::ExtraFile* pFile2) const { char name1[1024]; strncpy(name1, Util::BaseFileName(pFile1->FileName().c_str()), 1024); name1[1024-1] = '\0'; if (char* ext = strrchr(name1, '.')) *ext = '\0'; // trim extension char name2[1024]; strncpy(name2, Util::BaseFileName(pFile2->FileName().c_str()), 1024); name2[1024-1] = '\0'; if (char* ext = strrchr(name2, '.')) *ext = '\0'; // trim extension return strcmp(name1, m_szBaseParFilename) == 0 && strcmp(name1, name2) != 0; } ParChecker::Segment::Segment(bool bSuccess, long long iOffset, int iSize, unsigned long lCrc) { m_bSuccess = bSuccess; m_iOffset = iOffset; m_iSize = iSize; m_lCrc = lCrc; } ParChecker::SegmentList::~SegmentList() { for (iterator it = begin(); it != end(); it++) { delete *it; } } ParChecker::DupeSource::DupeSource(int iID, const char* szDirectory) { m_iID = iID; m_szDirectory = strdup(szDirectory); m_iUsedBlocks = 0; } ParChecker::DupeSource::~DupeSource() { free(m_szDirectory); } ParChecker::ParChecker() { debug("Creating ParChecker"); 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; m_iQuickFiles = 0; m_bVerifyingExtraFiles = false; m_bCancelled = false; m_eStage = ptLoadingPars; m_bParQuick = false; m_bForceRepair = false; m_bParFull = false; } ParChecker::~ParChecker() { debug("Destroying ParChecker"); free(m_szDestDir); free(m_szNZBName); free(m_szInfoName); free(m_szProgressLabel); Cleanup(); } void ParChecker::Cleanup() { delete (Repairer*)m_pRepairer; m_pRepairer = NULL; for (FileList::iterator it = m_QueuedParFiles.begin(); it != m_QueuedParFiles.end() ;it++) { free(*it); } m_QueuedParFiles.clear(); for (FileList::iterator it = m_ProcessedFiles.begin(); it != m_ProcessedFiles.end() ;it++) { free(*it); } m_ProcessedFiles.clear(); m_sourceFiles.clear(); for (DupeSourceList::iterator it = m_DupeSources.begin(); it != m_DupeSources.end() ;it++) { free(*it); } m_DupeSources.clear(); free(m_szErrMsg); m_szErrMsg = NULL; } void ParChecker::SetDestDir(const char * szDestDir) { 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) { free(m_szInfoName); m_szInfoName = strdup(szInfoName); } void ParChecker::Run() { m_eStatus = RunParCheckAll(); if (m_eStatus == psRepairNotNeeded && m_bParQuick && m_bForceRepair && !m_bCancelled) { PrintMessage(Message::mkInfo, "Performing full par-check for %s", m_szNZBName); m_bParQuick = false; m_eStatus = RunParCheckAll(); } Completed(); } ParChecker::EStatus ParChecker::RunParCheckAll() { ParParser::ParFileList fileList; if (!ParParser::FindMainPars(m_szDestDir, &fileList)) { PrintMessage(Message::mkError, "Could not start par-check for %s. Could not find any par-files", m_szNZBName); return psFailed; } EStatus eAllStatus = psRepairNotNeeded; m_bCancelled = false; m_bParFull = true; for (ParParser::ParFileList::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; ParParser::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 (eAllStatus > eStatus) { eAllStatus = eStatus; } if (g_pOptions->GetBrokenLog()) { WriteBrokenLog(eStatus); } } free(szParFilename); } return eAllStatus; } ParChecker::EStatus ParChecker::RunParCheck(const char* szParFilename) { Cleanup(); m_szParFilename = szParFilename; m_eStage = ptLoadingPars; m_iProcessedFiles = 0; m_iExtraFiles = 0; m_iQuickFiles = 0; m_bVerifyingExtraFiles = false; m_bHasDamagedFiles = false; EStatus eStatus = psFailed; PrintMessage(Message::mkInfo, "Verifying %s", m_szInfoName); debug("par: %s", m_szParFilename); snprintf(m_szProgressLabel, 1024, "Verifying %s", m_szInfoName); m_szProgressLabel[1024-1] = '\0'; m_iFileProgress = 0; m_iStageProgress = 0; UpdateProgress(); Result res = (Result)PreProcessPar(); if (IsStopped() || res != eSuccess) { Cleanup(); return psFailed; } m_eStage = ptVerifyingSources; Repairer* pRepairer = (Repairer*)m_pRepairer; res = pRepairer->Process(false); if (!m_bParQuick) { CheckEmptyFiles(); } bool bAddedSplittedFragments = false; if (m_bHasDamagedFiles && !IsStopped() && res == eRepairNotPossible) { bAddedSplittedFragments = AddSplittedFragments(); if (bAddedSplittedFragments) { res = pRepairer->Process(false); } } if (m_bHasDamagedFiles && !IsStopped() && pRepairer->missingfilecount > 0 && !(bAddedSplittedFragments && res == eRepairPossible) && (g_pOptions->GetParScan() == Options::psExtended || g_pOptions->GetParScan() == Options::psDupe)) { if (AddMissingFiles()) { res = pRepairer->Process(false); } } if (m_bHasDamagedFiles && !IsStopped() && res == eRepairNotPossible) { res = (Result)ProcessMorePars(); } if (m_bHasDamagedFiles && !IsStopped() && res == eRepairNotPossible && g_pOptions->GetParScan() == Options::psDupe) { if (AddDupeFiles()) { res = pRepairer->Process(false); if (!IsStopped() && res == eRepairNotPossible) { res = (Result)ProcessMorePars(); } } } if (IsStopped()) { Cleanup(); return psFailed; } eStatus = psFailed; if (res == eSuccess || !m_bHasDamagedFiles) { 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); if (res == eSuccess) { PrintMessage(Message::mkInfo, "Successfully repaired %s", m_szInfoName); eStatus = psRepaired; StatDupeSources(&m_DupeSources); 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(this); m_pRepairer = pRepairer; 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_bCancelled) { 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 (missingblockcount <= 0) { return eRepairPossible; } if (bMoreFilesLoaded) { PrintMessage(Message::mkInfo, "Need more %i par-block(s) for %s", missingblockcount, m_szInfoName); } m_mutexQueuedParFiles.Lock(); bool hasMorePars = !m_QueuedParFiles.empty(); m_mutexQueuedParFiles.Unlock(); if (!hasMorePars) { int iBlockFound = 0; bool requested = RequestMorePars(missingblockcount, &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) { 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; } if (!hasMorePars) { // wait until new files are added by "AddParFile" or a change is signaled by "QueueChanged" bool bQueuedParFilesChanged = false; while (!bQueuedParFilesChanged && !IsStopped() && !m_bCancelled) { m_mutexQueuedParFiles.Lock(); bQueuedParFilesChanged = m_bQueuedParFilesChanged; m_mutexQueuedParFiles.Unlock(); usleep(100 * 1000); } } } if (IsStopped() || m_bCancelled) { break; } bMoreFilesLoaded = LoadMorePars(); if (bMoreFilesLoaded) { pRepairer->UpdateVerificationResults(); res = pRepairer->Process(false); } } return res; } bool ParChecker::LoadMorePars() { m_mutexQueuedParFiles.Lock(); FileList moreFiles; moreFiles.assign(m_QueuedParFiles.begin(), m_QueuedParFiles.end()); m_QueuedParFiles.clear(); m_mutexQueuedParFiles.Unlock(); for (FileList::iterator it = moreFiles.begin(); it != moreFiles.end() ;it++) { char* szParFilename = *it; bool loadedOK = ((Repairer*)m_pRepairer)->LoadPacketsFromFile(szParFilename); if (loadedOK) { PrintMessage(Message::mkInfo, "File %s successfully loaded for par-check", Util::BaseFileName(szParFilename), m_szInfoName); } else { PrintMessage(Message::mkInfo, "Could not load file %s for par-check", Util::BaseFileName(szParFilename), m_szInfoName); } free(szParFilename); } return !moreFiles.empty(); } void ParChecker::AddParFile(const char * szParFilename) { m_mutexQueuedParFiles.Lock(); m_QueuedParFiles.push_back(strdup(szParFilename)); m_bQueuedParFilesChanged = true; m_mutexQueuedParFiles.Unlock(); } void ParChecker::QueueChanged() { m_mutexQueuedParFiles.Lock(); m_bQueuedParFilesChanged = true; m_mutexQueuedParFiles.Unlock(); } bool ParChecker::AddSplittedFragments() { std::list extrafiles; DirBrowser dir(m_szDestDir); while (const char* filename = dir.Next()) { if (strcmp(filename, ".") && strcmp(filename, "..") && strcmp(filename, "_brokenlog.txt") && !IsParredFile(filename) && !IsProcessedFile(filename)) { for (std::vector::iterator it = ((Repairer*)m_pRepairer)->sourcefiles.begin(); it != ((Repairer*)m_pRepairer)->sourcefiles.end(); it++) { Par2RepairerSourceFile *sourcefile = *it; std::string target = sourcefile->TargetFileName(); const char* szFilename2 = target.c_str(); const char* szBasename2 = Util::BaseFileName(szFilename2); int iBaseLen = strlen(szBasename2); if (!strncasecmp(filename, szBasename2, iBaseLen)) { const char* p = filename + iBaseLen; if (*p == '.') { for (p++; *p && strchr("0123456789", *p); p++) ; if (!*p) { debug("Found splitted fragment %s", filename); char fullfilename[1024]; snprintf(fullfilename, 1024, "%s%c%s", m_szDestDir, PATH_SEPARATOR, filename); fullfilename[1024-1] = '\0'; CommandLine::ExtraFile extrafile(fullfilename, Util::FileSize(fullfilename)); extrafiles.push_back(extrafile); } } } } } } bool bFragmentsAdded = false; if (!extrafiles.empty()) { m_iExtraFiles += extrafiles.size(); m_bVerifyingExtraFiles = true; PrintMessage(Message::mkInfo, "Found %i splitted fragments for %s", (int)extrafiles.size(), m_szInfoName); bFragmentsAdded = ((Repairer*)m_pRepairer)->VerifyExtraFiles(extrafiles); ((Repairer*)m_pRepairer)->UpdateVerificationResults(); m_bVerifyingExtraFiles = false; } return bFragmentsAdded; } bool ParChecker::AddMissingFiles() { return AddExtraFiles(true, false, m_szDestDir); } bool ParChecker::AddDupeFiles() { char szDirectory[1024]; strncpy(szDirectory, m_szParFilename, 1024); szDirectory[1024-1] = '\0'; bool bAdded = AddExtraFiles(false, false, szDirectory); if (((Repairer*)m_pRepairer)->missingblockcount > 0) { // scanning directories of duplicates RequestDupeSources(&m_DupeSources); if (!m_DupeSources.empty()) { int iWasBlocksMissing = ((Repairer*)m_pRepairer)->missingblockcount; for (DupeSourceList::iterator it = m_DupeSources.begin(); it != m_DupeSources.end(); it++) { DupeSource* pDupeSource = *it; if (((Repairer*)m_pRepairer)->missingblockcount > 0 && Util::DirectoryExists(pDupeSource->GetDirectory())) { int iWasBlocksMissing2 = ((Repairer*)m_pRepairer)->missingblockcount; bool bOneAdded = AddExtraFiles(false, true, pDupeSource->GetDirectory()); bAdded |= bOneAdded; int iBlocksMissing2 = ((Repairer*)m_pRepairer)->missingblockcount; pDupeSource->SetUsedBlocks(pDupeSource->GetUsedBlocks() + (iWasBlocksMissing2 - iBlocksMissing2)); } } int iBlocksMissing = ((Repairer*)m_pRepairer)->missingblockcount; if (iBlocksMissing < iWasBlocksMissing) { PrintMessage(Message::mkInfo, "Found extra %i blocks in dupe sources", iWasBlocksMissing - iBlocksMissing); } else { PrintMessage(Message::mkInfo, "No extra blocks found in dupe sources"); } } } return bAdded; } bool ParChecker::AddExtraFiles(bool bOnlyMissing, bool bExternalDir, const char* szDirectory) { if (bExternalDir) { PrintMessage(Message::mkInfo, "Performing dupe par-scan for %s in %s", m_szInfoName, Util::BaseFileName(szDirectory)); } else { PrintMessage(Message::mkInfo, "Performing extra par-scan for %s", m_szInfoName); } std::list extrafiles; DirBrowser dir(szDirectory); while (const char* filename = dir.Next()) { if (strcmp(filename, ".") && strcmp(filename, "..") && strcmp(filename, "_brokenlog.txt") && (bExternalDir || (!IsParredFile(filename) && !IsProcessedFile(filename)))) { char fullfilename[1024]; snprintf(fullfilename, 1024, "%s%c%s", szDirectory, PATH_SEPARATOR, filename); fullfilename[1024-1] = '\0'; extrafiles.push_back(new CommandLine::ExtraFile(fullfilename, Util::FileSize(fullfilename))); } } // Sort the list char* szBaseParFilename = strdup(Util::BaseFileName(m_szParFilename)); if (char* ext = strrchr(szBaseParFilename, '.')) *ext = '\0'; // trim extension extrafiles.sort(MissingFilesComparator(szBaseParFilename)); free(szBaseParFilename); // Scan files bool bFilesAdded = false; if (!extrafiles.empty()) { m_iExtraFiles += extrafiles.size(); m_bVerifyingExtraFiles = true; std::list extrafiles1; // adding files one by one until all missing files are found while (!IsStopped() && !m_bCancelled && extrafiles.size() > 0) { CommandLine::ExtraFile* pExtraFile = extrafiles.front(); extrafiles.pop_front(); extrafiles1.clear(); extrafiles1.push_back(*pExtraFile); int iWasFilesMissing = ((Repairer*)m_pRepairer)->missingfilecount; int iWasBlocksMissing = ((Repairer*)m_pRepairer)->missingblockcount; ((Repairer*)m_pRepairer)->VerifyExtraFiles(extrafiles1); ((Repairer*)m_pRepairer)->UpdateVerificationResults(); bool bFileAdded = iWasFilesMissing > (int)((Repairer*)m_pRepairer)->missingfilecount; bool bBlockAdded = iWasBlocksMissing > (int)((Repairer*)m_pRepairer)->missingblockcount; if (bFileAdded && !bExternalDir) { PrintMessage(Message::mkInfo, "Found missing file %s", Util::BaseFileName(pExtraFile->FileName().c_str())); RegisterParredFile(Util::BaseFileName(pExtraFile->FileName().c_str())); } else if (bBlockAdded) { PrintMessage(Message::mkInfo, "Found %i missing blocks", iWasBlocksMissing - (int)((Repairer*)m_pRepairer)->missingblockcount); } bFilesAdded |= bFileAdded | bBlockAdded; delete pExtraFile; if (bOnlyMissing && ((Repairer*)m_pRepairer)->missingfilecount == 0) { PrintMessage(Message::mkInfo, "All missing files found, aborting par-scan"); break; } if (!bOnlyMissing && ((Repairer*)m_pRepairer)->missingblockcount == 0) { PrintMessage(Message::mkInfo, "All missing blocks found, aborting par-scan"); break; } } m_bVerifyingExtraFiles = false; // free any remaining objects for (std::list::iterator it = extrafiles.begin(); it != extrafiles.end() ;it++) { delete *it; } } return bFilesAdded; } bool ParChecker::IsProcessedFile(const char* szFilename) { for (FileList::iterator it = m_ProcessedFiles.begin(); it != m_ProcessedFiles.end(); it++) { const char* szProcessedFilename = *it; if (!strcasecmp(Util::BaseFileName(szProcessedFilename), szFilename)) { return true; } } return false; } void ParChecker::signal_filename(std::string str) { if (!m_lastFilename.compare(str)) { return; } m_lastFilename = str; const char* szStageMessage[] = { "Loading file", "Verifying file", "Repairing file", "Verifying repaired file" }; if (m_eStage == ptRepairing) { m_eStage = ptVerifyingRepaired; } // don't print progress messages when verifying repaired files in quick verification mode, // because repaired files are not verified in this mode if (!(m_eStage == ptVerifyingRepaired && m_bParQuick)) { PrintMessage(Message::mkInfo, "%s %s", szStageMessage[m_eStage], str.c_str()); } if (m_eStage == ptLoadingPars || m_eStage == ptVerifyingSources) { m_ProcessedFiles.push_back(strdup(str.c_str())); } snprintf(m_szProgressLabel, 1024, "%s %s", szStageMessage[m_eStage], str.c_str()); m_szProgressLabel[1024-1] = '\0'; m_iFileProgress = 0; UpdateProgress(); } void ParChecker::signal_progress(int progress) { m_iFileProgress = (int)progress; if (m_eStage == ptRepairing) { // calculating repair-data for all files m_iStageProgress = m_iFileProgress; } else { // processing individual files int iTotalFiles = 0; int iProcessedFiles = m_iProcessedFiles; if (m_eStage == ptVerifyingRepaired) { // repairing individual files iTotalFiles = m_iFilesToRepair; } else { // verifying individual files iTotalFiles = ((Repairer*)m_pRepairer)->sourcefiles.size() + m_iExtraFiles; if (m_iExtraFiles > 0) { // during extra par scan don't count quickly verified files; // extra files require much more time for verification; // counting only fully scanned files improves estimated time accuracy. iTotalFiles -= m_iQuickFiles; iProcessedFiles -= m_iQuickFiles; } } if (iTotalFiles > 0) { if (m_iFileProgress < 1000) { m_iStageProgress = (iProcessedFiles * 1000 + m_iFileProgress) / iTotalFiles; } else { m_iStageProgress = iProcessedFiles * 1000 / iTotalFiles; } } else { m_iStageProgress = 0; } } debug("Current-progress: %i, Total-progress: %i", m_iFileProgress, m_iStageProgress); UpdateProgress(); } void ParChecker::signal_done(std::string str, int available, int total) { m_iProcessedFiles++; if (m_eStage == ptVerifyingSources) { if (available < total && !m_bVerifyingExtraFiles) { const char* szFilename = str.c_str(); bool bFileExists = true; for (std::vector::iterator it = ((Repairer*)m_pRepairer)->sourcefiles.begin(); it != ((Repairer*)m_pRepairer)->sourcefiles.end(); it++) { Par2RepairerSourceFile *sourcefile = *it; if (sourcefile && !strcmp(szFilename, Util::BaseFileName(sourcefile->TargetFileName().c_str())) && !sourcefile->GetTargetExists()) { bFileExists = false; break; } } bool bIgnore = Util::MatchFileExt(szFilename, g_pOptions->GetParIgnoreExt(), ",;") || Util::MatchFileExt(szFilename, g_pOptions->GetExtCleanupDisk(), ",;"); m_bHasDamagedFiles |= !bIgnore; if (bFileExists) { PrintMessage(Message::mkWarning, "File %s has %i bad block(s) of total %i block(s)%s", szFilename, total - available, total, bIgnore ? ", ignoring" : ""); } else { PrintMessage(Message::mkWarning, "File %s with %i block(s) is missing%s", szFilename, total, bIgnore ? ", ignoring" : ""); } if (!IsProcessedFile(szFilename)) { m_ProcessedFiles.push_back(strdup(szFilename)); } } } } /* * Only if ParQuick isn't enabled: * For empty damaged files the callback-function "signal_done" isn't called and the flag "m_bHasDamagedFiles" * therefore isn't set. In this function we expicitly check such files. */ void ParChecker::CheckEmptyFiles() { for (std::vector::iterator it = ((Repairer*)m_pRepairer)->sourcefiles.begin(); it != ((Repairer*)m_pRepairer)->sourcefiles.end(); it++) { Par2RepairerSourceFile* sourcefile = *it; if (sourcefile && sourcefile->GetDescriptionPacket()) { // GetDescriptionPacket()->FileName() returns a temp string object, which we need to hold for a while std::string filename = sourcefile->GetDescriptionPacket()->FileName(); const char* szFilename = filename.c_str(); if (!Util::EmptyStr(szFilename) && !IsProcessedFile(szFilename)) { bool bIgnore = Util::MatchFileExt(szFilename, g_pOptions->GetParIgnoreExt(), ",;") || Util::MatchFileExt(szFilename, g_pOptions->GetExtCleanupDisk(), ",;"); m_bHasDamagedFiles |= !bIgnore; int total = sourcefile->GetVerificationPacket() ? sourcefile->GetVerificationPacket()->BlockCount() : 0; PrintMessage(Message::mkWarning, "File %s has %i bad block(s) of total %i block(s)%s", szFilename, total, total, bIgnore ? ", ignoring" : ""); } } else { m_bHasDamagedFiles = true; } } } void ParChecker::Cancel() { ((Repairer*)m_pRepairer)->cancelled = true; m_bCancelled = true; QueueChanged(); } 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, FOPEN_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::iterator it = ((Repairer*)m_pRepairer)->sourcefiles.begin(); it != ((Repairer*)m_pRepairer)->sourcefiles.end(); it++) { Par2RepairerSourceFile* sourcefile = (Par2RepairerSourceFile*)*it; vector::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::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()); } } } /** * This function implements quick par verification replacing the standard verification routine * from libpar2: * - for successfully downloaded files the function compares CRC of the file computed during * download with CRC stored in PAR2-file; * - for partially downloaded files the CRCs of articles are compared with block-CRCs stored * in PAR2-file; * - for completely failed files (not a single successful article) no verification is needed at all. * * Limitation of the function: * This function requires every block in the file to have an unique CRC (across all blocks * of the par-set). Otherwise the full verification is performed. * The limitation can be avoided by using something more smart than "verificationhashtable.Lookup" * but in the real life all blocks have unique CRCs and the simple "Lookup" works good enough. */ ParChecker::EFileStatus ParChecker::VerifyDataFile(void* pDiskfile, void* pSourcefile, int* pAvailableBlocks) { if (m_eStage != ptVerifyingSources) { // skipping verification for repaired files, assuming the files were correctly repaired, // the only reason for incorrect files after repair are hardware errors (memory, disk), // but this isn't something NZBGet should care about. return fsSuccess; } DiskFile* pDiskFile = (DiskFile*)pDiskfile; Par2RepairerSourceFile* pSourceFile = (Par2RepairerSourceFile*)pSourcefile; if (!pSourcefile || !pSourceFile->GetTargetExists()) { return fsUnknown; } VerificationPacket* packet = pSourceFile->GetVerificationPacket(); if (!packet) { return fsUnknown; } std::string filename = pSourceFile->GetTargetFile()->FileName(); const char* szFilename = filename.c_str(); if (Util::FileSize(szFilename) == 0 && pSourceFile->BlockCount() > 0) { *pAvailableBlocks = 0; return fsFailure; } // find file status and CRC computed during download unsigned long lDownloadCrc; SegmentList segments; EFileStatus eFileStatus = FindFileCrc(Util::BaseFileName(szFilename), &lDownloadCrc, &segments); ValidBlocks validBlocks; if (eFileStatus == fsFailure || eFileStatus == fsUnknown) { return eFileStatus; } else if ((eFileStatus == fsSuccess && !VerifySuccessDataFile(pDiskfile, pSourcefile, lDownloadCrc)) || (eFileStatus == fsPartial && !VerifyPartialDataFile(pDiskfile, pSourcefile, &segments, &validBlocks))) { PrintMessage(Message::mkWarning, "Quick verification failed for %s file %s, performing full verification instead", eFileStatus == fsSuccess ? "good" : "damaged", Util::BaseFileName(szFilename)); return fsUnknown; // let libpar2 do the full verification of the file } // attach verification blocks to the file *pAvailableBlocks = 0; u64 blocksize = ((Repairer*)m_pRepairer)->mainpacket->BlockSize(); std::deque undoList; for (unsigned int i = 0; i < packet->BlockCount(); i++) { if (eFileStatus == fsSuccess || validBlocks.at(i)) { const FILEVERIFICATIONENTRY* entry = packet->VerificationEntry(i); u32 blockCrc = entry->crc; // Look for a match const VerificationHashEntry* pHashEntry = ((Repairer*)m_pRepairer)->verificationhashtable.Lookup(blockCrc); if (!pHashEntry || pHashEntry->SourceFile() != pSourceFile || pHashEntry->IsSet()) { // no match found, revert back the changes made by "pHashEntry->SetBlock" for (std::deque::iterator it = undoList.begin(); it != undoList.end(); it++) { const VerificationHashEntry* pUndoEntry = *it; pUndoEntry->SetBlock(NULL, 0); } return fsUnknown; } undoList.push_back(pHashEntry); pHashEntry->SetBlock(pDiskFile, i*blocksize); (*pAvailableBlocks)++; } } m_iQuickFiles++; PrintMessage(Message::mkDetail, "Quickly verified %s file %s", eFileStatus == fsSuccess ? "good" : "damaged", Util::BaseFileName(szFilename)); return eFileStatus; } bool ParChecker::VerifySuccessDataFile(void* pDiskfile, void* pSourcefile, unsigned long lDownloadCrc) { Par2RepairerSourceFile* pSourceFile = (Par2RepairerSourceFile*)pSourcefile; u64 blocksize = ((Repairer*)m_pRepairer)->mainpacket->BlockSize(); VerificationPacket* packet = pSourceFile->GetVerificationPacket(); // extend lDownloadCrc to block size lDownloadCrc = CRCUpdateBlock(lDownloadCrc ^ 0xFFFFFFFF, (size_t)(blocksize * packet->BlockCount() > pSourceFile->GetTargetFile()->FileSize() ? blocksize * packet->BlockCount() - pSourceFile->GetTargetFile()->FileSize() : 0) ) ^ 0xFFFFFFFF; debug("Download-CRC: %.8x", lDownloadCrc); // compute file CRC using CRCs of blocks unsigned long lParCrc = 0; for (unsigned int i = 0; i < packet->BlockCount(); i++) { const FILEVERIFICATIONENTRY* entry = packet->VerificationEntry(i); u32 blockCrc = entry->crc; lParCrc = i == 0 ? blockCrc : Util::Crc32Combine(lParCrc, blockCrc, (unsigned long)blocksize); } debug("Block-CRC: %x, filename: %s", lParCrc, Util::BaseFileName(pSourceFile->GetTargetFile()->FileName().c_str())); return lParCrc == lDownloadCrc; } bool ParChecker::VerifyPartialDataFile(void* pDiskfile, void* pSourcefile, SegmentList* pSegments, ValidBlocks* pValidBlocks) { Par2RepairerSourceFile* pSourceFile = (Par2RepairerSourceFile*)pSourcefile; VerificationPacket* packet = pSourceFile->GetVerificationPacket(); long long blocksize = ((Repairer*)m_pRepairer)->mainpacket->BlockSize(); std::string filename = pSourceFile->GetTargetFile()->FileName(); const char* szFilename = filename.c_str(); long long iFileSize = pSourceFile->GetTargetFile()->FileSize(); // determine presumably valid and bad blocks based on article download status pValidBlocks->resize(packet->BlockCount(), false); for (int i = 0; i < (int)pValidBlocks->size(); i++) { long long blockStart = i * blocksize; long long blockEnd = blockStart + blocksize < iFileSize - 1 ? blockStart + blocksize : iFileSize - 1; bool bBlockOK = false; bool bBlockEnd = false; u64 iCurOffset = 0; for (SegmentList::iterator it = pSegments->begin(); it != pSegments->end(); it++) { Segment* pSegment = *it; if (!bBlockOK && pSegment->GetSuccess() && pSegment->GetOffset() <= blockStart && pSegment->GetOffset() + pSegment->GetSize() >= blockStart) { bBlockOK = true; iCurOffset = pSegment->GetOffset(); } if (bBlockOK) { if (!(pSegment->GetSuccess() && pSegment->GetOffset() == iCurOffset)) { bBlockOK = false; break; } if (pSegment->GetOffset() + pSegment->GetSize() >= blockEnd) { bBlockEnd = true; break; } iCurOffset = pSegment->GetOffset() + pSegment->GetSize(); } } pValidBlocks->at(i) = bBlockOK && bBlockEnd; } char szErrBuf[256]; FILE* infile = fopen(szFilename, FOPEN_RB); if (!infile) { PrintMessage(Message::mkError, "Could not open file %s: %s", szFilename, Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf))); } // For each sequential range of presumably valid blocks: // - compute par-CRC of the range of blocks using block CRCs; // - compute download-CRC for the same byte range using CRCs of articles; if articles and block // overlap - read a little bit of data from the file and calculate its CRC; // - compare two CRCs - they must match; if not - the file is more damaged than we thought - // let libpar2 do the full verification of the file in this case. unsigned long lParCrc = 0; int iBlockStart = -1; pValidBlocks->push_back(false); // end marker for (int i = 0; i < (int)pValidBlocks->size(); i++) { bool bValidBlock = pValidBlocks->at(i); if (bValidBlock) { if (iBlockStart == -1) { iBlockStart = i; } const FILEVERIFICATIONENTRY* entry = packet->VerificationEntry(i); u32 blockCrc = entry->crc; lParCrc = iBlockStart == i ? blockCrc : Util::Crc32Combine(lParCrc, blockCrc, (unsigned long)blocksize); } else { if (iBlockStart > -1) { int iBlockEnd = i - 1; long long iBytesStart = iBlockStart * blocksize; long long iBytesEnd = iBlockEnd * blocksize + blocksize - 1; unsigned long lDownloadCrc = 0; bool bOK = SmartCalcFileRangeCrc(infile, iBytesStart, iBytesEnd < iFileSize - 1 ? iBytesEnd : iFileSize - 1, pSegments, &lDownloadCrc); if (bOK && iBytesEnd > iFileSize - 1) { // for the last block: extend lDownloadCrc to block size lDownloadCrc = CRCUpdateBlock(lDownloadCrc ^ 0xFFFFFFFF, (size_t)(iBytesEnd - (iFileSize - 1))) ^ 0xFFFFFFFF; } if (!bOK || lDownloadCrc != lParCrc) { fclose(infile); return false; } } iBlockStart = -1; } } fclose(infile); return true; } /* * Compute CRC of bytes range of file using CRCs of segments and reading some data directly * from file if necessary */ bool ParChecker::SmartCalcFileRangeCrc(FILE* pFile, long long lStart, long long lEnd, SegmentList* pSegments, unsigned long* pDownloadCrc) { unsigned long lDownloadCrc = 0; bool bStarted = false; for (SegmentList::iterator it = pSegments->begin(); it != pSegments->end(); it++) { Segment* pSegment = *it; if (!bStarted && pSegment->GetOffset() > lStart) { // read start of range from file if (!DumbCalcFileRangeCrc(pFile, lStart, pSegment->GetOffset() - 1, &lDownloadCrc)) { return false; } if (pSegment->GetOffset() + pSegment->GetSize() >= lEnd) { break; } bStarted = true; } if (pSegment->GetOffset() >= lStart && pSegment->GetOffset() + pSegment->GetSize() <= lEnd) { lDownloadCrc = !bStarted ? pSegment->GetCrc() : Util::Crc32Combine(lDownloadCrc, pSegment->GetCrc(), (unsigned long)pSegment->GetSize()); bStarted = true; } if (pSegment->GetOffset() + pSegment->GetSize() == lEnd) { break; } if (pSegment->GetOffset() + pSegment->GetSize() > lEnd) { // read end of range from file unsigned long lPartialCrc = 0; if (!DumbCalcFileRangeCrc(pFile, pSegment->GetOffset(), lEnd, &lPartialCrc)) { return false; } lDownloadCrc = Util::Crc32Combine(lDownloadCrc, (unsigned long)lPartialCrc, (unsigned long)(lEnd - pSegment->GetOffset() + 1)); break; } } *pDownloadCrc = lDownloadCrc; return true; } /* * Compute CRC of bytes range of file reading the data directly from file */ bool ParChecker::DumbCalcFileRangeCrc(FILE* pFile, long long lStart, long long lEnd, unsigned long* pDownloadCrc) { if (fseek(pFile, lStart, SEEK_SET)) { return false; } static const int BUFFER_SIZE = 1024 * 64; unsigned char* buffer = (unsigned char*)malloc(BUFFER_SIZE); unsigned long lDownloadCrc = 0xFFFFFFFF; int cnt = BUFFER_SIZE; while (cnt == BUFFER_SIZE && lStart < lEnd) { int iNeedBytes = lEnd - lStart + 1 > BUFFER_SIZE ? BUFFER_SIZE : (int)(lEnd - lStart + 1); cnt = (int)fread(buffer, 1, iNeedBytes, pFile); lDownloadCrc = Util::Crc32m(lDownloadCrc, buffer, cnt); lStart += cnt; } free(buffer); lDownloadCrc ^= 0xFFFFFFFF; *pDownloadCrc = lDownloadCrc; return true; } #endif nzbget-16.4/daemon/postprocess/ParChecker.h0000644000175000017500000001477412630544544020604 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 PARCHECKER_H #define PARCHECKER_H #ifndef DISABLE_PARCHECK #include #include #include #include "Thread.h" #include "Log.h" class ParChecker : public Thread { public: enum EStatus { psFailed, psRepairPossible, psRepaired, psRepairNotNeeded }; enum EStage { ptLoadingPars, ptVerifyingSources, ptRepairing, ptVerifyingRepaired, }; enum EFileStatus { fsUnknown, fsSuccess, fsPartial, fsFailure }; class Segment { private: bool m_bSuccess; long long m_iOffset; int m_iSize; unsigned long m_lCrc; public: Segment(bool bSuccess, long long iOffset, int iSize, unsigned long lCrc); bool GetSuccess() { return m_bSuccess; } long long GetOffset() { return m_iOffset; } int GetSize() { return m_iSize; } unsigned long GetCrc() { return m_lCrc; } }; typedef std::deque SegmentListBase; class SegmentList : public SegmentListBase { public: ~SegmentList(); }; class DupeSource { private: int m_iID; char* m_szDirectory; int m_iUsedBlocks; public: DupeSource(int iID, const char* szDirectory); ~DupeSource(); int GetID() { return m_iID; } const char* GetDirectory() { return m_szDirectory; } int GetUsedBlocks() { return m_iUsedBlocks; } void SetUsedBlocks(int iUsedBlocks) { m_iUsedBlocks = iUsedBlocks; } }; typedef std::deque DupeSourceList; typedef std::deque FileList; typedef std::deque SourceList; typedef std::vector ValidBlocks; friend class Repairer; private: char* m_szInfoName; char* m_szDestDir; char* m_szNZBName; const char* m_szParFilename; EStatus m_eStatus; EStage m_eStage; // declared as void* to prevent the including of libpar2-headers into this header-file void* m_pRepairer; char* m_szErrMsg; FileList m_QueuedParFiles; Mutex m_mutexQueuedParFiles; bool m_bQueuedParFilesChanged; FileList m_ProcessedFiles; int m_iProcessedFiles; int m_iFilesToRepair; int m_iExtraFiles; int m_iQuickFiles; bool m_bVerifyingExtraFiles; char* m_szProgressLabel; int m_iFileProgress; int m_iStageProgress; bool m_bCancelled; SourceList m_sourceFiles; std::string m_lastFilename; bool m_bHasDamagedFiles; bool m_bParQuick; bool m_bForceRepair; bool m_bParFull; DupeSourceList m_DupeSources; void Cleanup(); EStatus RunParCheckAll(); EStatus RunParCheck(const char* szParFilename); int PreProcessPar(); bool LoadMainParBak(); int ProcessMorePars(); bool LoadMorePars(); bool AddSplittedFragments(); bool AddMissingFiles(); bool AddDupeFiles(); bool AddExtraFiles(bool bOnlyMissing, bool bExternalDir, const char* szDirectory); bool IsProcessedFile(const char* szFilename); void WriteBrokenLog(EStatus eStatus); void SaveSourceList(); void DeleteLeftovers(); void signal_filename(std::string str); void signal_progress(int progress); void signal_done(std::string str, int available, int total); // declared as void* to prevent the including of libpar2-headers into this header-file // DiskFile* pDiskfile, Par2RepairerSourceFile* pSourcefile EFileStatus VerifyDataFile(void* pDiskfile, void* pSourcefile, int* pAvailableBlocks); bool VerifySuccessDataFile(void* pDiskfile, void* pSourcefile, unsigned long lDownloadCrc); bool VerifyPartialDataFile(void* pDiskfile, void* pSourcefile, SegmentList* pSegments, ValidBlocks* pValidBlocks); bool SmartCalcFileRangeCrc(FILE* pFile, long long lStart, long long lEnd, SegmentList* pSegments, unsigned long* pDownloadCrc); bool DumbCalcFileRangeCrc(FILE* pFile, long long lStart, long long lEnd, unsigned long* pDownloadCrc); void CheckEmptyFiles(); protected: /** * Unpause par2-files * returns true, if the files with required number of blocks were unpaused, * or false if there are no more files in queue for this collection or not enough blocks */ virtual bool RequestMorePars(int iBlockNeeded, int* pBlockFound) = 0; virtual void UpdateProgress() {} virtual void Completed() {} virtual void PrintMessage(Message::EKind eKind, const char* szFormat, ...) {} virtual void RegisterParredFile(const char* szFilename) {} virtual bool IsParredFile(const char* szFilename) { return false; } virtual EFileStatus FindFileCrc(const char* szFilename, unsigned long* lCrc, SegmentList* pSegments) { return fsUnknown; } virtual void RequestDupeSources(DupeSourceList* pDupeSourceList) {} virtual void StatDupeSources(DupeSourceList* pDupeSourceList) {} EStage GetStage() { return m_eStage; } const char* GetProgressLabel() { return m_szProgressLabel; } int GetFileProgress() { return m_iFileProgress; } int GetStageProgress() { return m_iStageProgress; } public: ParChecker(); virtual ~ParChecker(); virtual void Run(); void SetDestDir(const char* szDestDir); const char* GetParFilename() { return m_szParFilename; } const char* GetInfoName() { return m_szInfoName; } void SetInfoName(const char* szInfoName); void SetNZBName(const char* szNZBName); void SetParQuick(bool bParQuick) { m_bParQuick = bParQuick; } bool GetParQuick() { return m_bParQuick; } void SetForceRepair(bool bForceRepair) { m_bForceRepair = bForceRepair; } bool GetForceRepair() { return m_bForceRepair; } void SetParFull(bool bParFull) { m_bParFull = bParFull; } bool GetParFull() { return m_bParFull; } EStatus GetStatus() { return m_eStatus; } void AddParFile(const char* szParFilename); void QueueChanged(); void Cancel(); bool GetCancelled() { return m_bCancelled; } }; #endif #endif nzbget-16.4/daemon/postprocess/DupeMatcher.cpp0000644000175000017500000001472712630544544021327 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2015 Andrey Prygunkov * * 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 #include #include #include #ifndef WIN32 #include #endif #include #include "nzbget.h" #include "DupeMatcher.h" #include "Log.h" #include "Util.h" #include "Options.h" #include "Script.h" #include "Thread.h" class RarLister : public Thread, public ScriptController { private: DupeMatcher* m_pOwner; long long m_lMaxSize; bool m_bCompressed; bool m_bLastSizeMax; long long m_lExpectedSize; char* m_szFilenameBuf; int m_iFilenameBufLen; char m_szLastFilename[1024]; protected: virtual void AddMessage(Message::EKind eKind, const char* szText); public: virtual void Run(); static bool FindLargestFile(DupeMatcher* pOwner, const char* szDirectory, char* szFilenameBuf, int iFilenameBufLen, long long lExpectedSize, int iTimeoutSec, long long* pMaxSize, bool* pCompressed); }; bool RarLister::FindLargestFile(DupeMatcher* pOwner, const char* szDirectory, char* szFilenameBuf, int iFilenameBufLen, long long lExpectedSize, int iTimeoutSec, long long* pMaxSize, bool* pCompressed) { RarLister unrar; unrar.m_pOwner = pOwner; unrar.m_lExpectedSize = lExpectedSize; unrar.m_lMaxSize = -1; unrar.m_bCompressed = false; unrar.m_bLastSizeMax = false; unrar.m_szFilenameBuf = szFilenameBuf; unrar.m_iFilenameBufLen = iFilenameBufLen; char** pCmdArgs = NULL; if (!Util::SplitCommandLine(g_pOptions->GetUnrarCmd(), &pCmdArgs)) { return false; } const char* szUnrarPath = *pCmdArgs; unrar.SetScript(szUnrarPath); const char* szArgs[4]; szArgs[0] = szUnrarPath; szArgs[1] = "lt"; szArgs[2] = "*.rar"; szArgs[3] = NULL; unrar.SetArgs(szArgs, false); unrar.SetWorkingDir(szDirectory); time_t curTime = time(NULL); unrar.Start(); // wait up to iTimeoutSec for unrar output while (unrar.IsRunning() && curTime + iTimeoutSec > time(NULL) && curTime >= time(NULL)) // in a case clock was changed { usleep(200 * 1000); } if (unrar.IsRunning()) { unrar.Terminate(); } // wait until terminated or killed while (unrar.IsRunning()) { usleep(200 * 1000); } for (char** szArgPtr = pCmdArgs; *szArgPtr; szArgPtr++) { free(*szArgPtr); } free(pCmdArgs); *pMaxSize = unrar.m_lMaxSize; *pCompressed = unrar.m_bCompressed; return true; } void RarLister::Run() { Execute(); } void RarLister::AddMessage(Message::EKind eKind, const char* szText) { if (!strncasecmp(szText, "Archive: ", 9)) { m_pOwner->PrintMessage(Message::mkDetail, "Reading file %s", szText + 9); } else if (!strncasecmp(szText, " Name: ", 14)) { strncpy(m_szLastFilename, szText + 14, sizeof(m_szLastFilename)); m_szLastFilename[sizeof(m_szLastFilename)-1] = '\0'; } else if (!strncasecmp(szText, " Size: ", 14)) { m_bLastSizeMax = false; long long lSize = atoll(szText + 14); if (lSize > m_lMaxSize) { m_lMaxSize = lSize; m_bLastSizeMax = true; strncpy(m_szFilenameBuf, m_szLastFilename, m_iFilenameBufLen); m_szFilenameBuf[m_iFilenameBufLen-1] = '\0'; } return; } if (m_bLastSizeMax && !strncasecmp(szText, " Compression: ", 14)) { m_bCompressed = !strstr(szText, " -m0"); if (m_lMaxSize > m_lExpectedSize || DupeMatcher::SizeDiffOK(m_lMaxSize, m_lExpectedSize, 20)) { // alread found the largest file, aborting unrar Terminate(); } } } DupeMatcher::DupeMatcher(const char* szDestDir, long long lExpectedSize) { m_szDestDir = strdup(szDestDir); m_lExpectedSize = lExpectedSize; m_lMaxSize = -1; m_bCompressed = false; } DupeMatcher::~DupeMatcher() { free(m_szDestDir); } bool DupeMatcher::SizeDiffOK(long long lSize1, long long lSize2, int iMaxDiffPercent) { if (lSize1 == 0 || lSize2 == 0) { return false; } long long lDiff = lSize1 - lSize2; lDiff = lDiff > 0 ? lDiff : -lDiff; long long lMax = lSize1 > lSize2 ? lSize1 : lSize2; int lDiffPercent = (int)(lDiff * 100 / lMax); return lDiffPercent < iMaxDiffPercent; } bool DupeMatcher::Prepare() { char szFilename[1024]; FindLargestFile(m_szDestDir, szFilename, sizeof(szFilename), &m_lMaxSize, &m_bCompressed); bool bSizeOK = SizeDiffOK(m_lMaxSize, m_lExpectedSize, 20); PrintMessage(Message::mkDetail, "Found main file %s with size %lli bytes%s", szFilename, m_lMaxSize, bSizeOK ? "" : ", size mismatch"); return bSizeOK; } bool DupeMatcher::MatchDupeContent(const char* szDupeDir) { long long lDupeMaxSize = 0; bool lDupeCompressed = false; char szFilename[1024]; FindLargestFile(szDupeDir, szFilename, sizeof(szFilename), &lDupeMaxSize, &lDupeCompressed); bool bOK = lDupeMaxSize == m_lMaxSize && lDupeCompressed == m_bCompressed; PrintMessage(Message::mkDetail, "Found main file %s with size %lli bytes%s", szFilename, m_lMaxSize, bOK ? "" : ", size mismatch"); return bOK; } void DupeMatcher::FindLargestFile(const char* szDirectory, char* szFilenameBuf, int iBufLen, long long* pMaxSize, bool* pCompressed) { *pMaxSize = 0; *pCompressed = false; DirBrowser dir(szDirectory); while (const char* filename = dir.Next()) { if (strcmp(filename, ".") && strcmp(filename, "..")) { char szFullFilename[1024]; snprintf(szFullFilename, 1024, "%s%c%s", szDirectory, PATH_SEPARATOR, filename); szFullFilename[1024-1] = '\0'; long long lFileSize = Util::FileSize(szFullFilename); if (lFileSize > *pMaxSize) { *pMaxSize = lFileSize; strncpy(szFilenameBuf, filename, iBufLen); szFilenameBuf[iBufLen-1] = '\0'; } if (Util::MatchFileExt(filename, ".rar", ",")) { RarLister::FindLargestFile(this, szDirectory, szFilenameBuf, iBufLen, m_lMaxSize, 60, pMaxSize, pCompressed); return; } } } } nzbget-16.4/daemon/postprocess/Cleanup.cpp0000644000175000017500000001617212630544544020511 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2013-2015 Andrey Prygunkov * * 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 #include #include #include #ifndef WIN32 #include #endif #include #include "nzbget.h" #include "Cleanup.h" #include "Log.h" #include "Util.h" #include "ParParser.h" #include "Options.h" void MoveController::StartJob(PostInfo* pPostInfo) { MoveController* pMoveController = new MoveController(); pMoveController->m_pPostInfo = pPostInfo; pMoveController->SetAutoDestroy(false); pPostInfo->SetPostThread(pMoveController); pMoveController->Start(); } void MoveController::Run() { // the locking is needed for accessing the members of NZBInfo DownloadQueue::Lock(); char szNZBName[1024]; strncpy(szNZBName, m_pPostInfo->GetNZBInfo()->GetName(), 1024); szNZBName[1024-1] = '\0'; char szInfoName[1024]; snprintf(szInfoName, 1024, "move for %s", m_pPostInfo->GetNZBInfo()->GetName()); szInfoName[1024-1] = '\0'; SetInfoName(szInfoName); strncpy(m_szInterDir, m_pPostInfo->GetNZBInfo()->GetDestDir(), 1024); m_szInterDir[1024-1] = '\0'; m_pPostInfo->GetNZBInfo()->BuildFinalDirName(m_szDestDir, 1024); m_szDestDir[1024-1] = '\0'; DownloadQueue::Unlock(); PrintMessage(Message::mkInfo, "Moving completed files for %s", szNZBName); bool bOK = MoveFiles(); szInfoName[0] = 'M'; // uppercase if (bOK) { PrintMessage(Message::mkInfo, "%s successful", szInfoName); // save new dest dir DownloadQueue::Lock(); m_pPostInfo->GetNZBInfo()->SetDestDir(m_szDestDir); m_pPostInfo->GetNZBInfo()->SetMoveStatus(NZBInfo::msSuccess); DownloadQueue::Unlock(); } else { PrintMessage(Message::mkError, "%s failed", szInfoName); m_pPostInfo->GetNZBInfo()->SetMoveStatus(NZBInfo::msFailure); } m_pPostInfo->SetStage(PostInfo::ptQueued); m_pPostInfo->SetWorking(false); } bool MoveController::MoveFiles() { char szErrBuf[1024]; if (!Util::ForceDirectories(m_szDestDir, szErrBuf, sizeof(szErrBuf))) { PrintMessage(Message::mkError, "Could not create directory %s: %s", m_szDestDir, szErrBuf); return false; } bool bOK = true; DirBrowser dir(m_szInterDir); while (const char* filename = dir.Next()) { if (strcmp(filename, ".") && strcmp(filename, "..")) { char szSrcFile[1024]; snprintf(szSrcFile, 1024, "%s%c%s", m_szInterDir, PATH_SEPARATOR, filename); szSrcFile[1024-1] = '\0'; char szDstFile[1024]; Util::MakeUniqueFilename(szDstFile, 1024, m_szDestDir, filename); bool bHiddenFile = filename[0] == '.'; if (!bHiddenFile) { PrintMessage(Message::mkInfo, "Moving file %s to %s", Util::BaseFileName(szSrcFile), m_szDestDir); } if (!Util::MoveFile(szSrcFile, szDstFile) && !bHiddenFile) { char szErrBuf[256]; PrintMessage(Message::mkError, "Could not move file %s to %s: %s", szSrcFile, szDstFile, Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf))); bOK = false; } } } if (bOK && !Util::DeleteDirectoryWithContent(m_szInterDir, szErrBuf, sizeof(szErrBuf))) { PrintMessage(Message::mkWarning, "Could not delete intermediate directory %s: %s", m_szInterDir, szErrBuf); } return bOK; } void MoveController::AddMessage(Message::EKind eKind, const char* szText) { m_pPostInfo->GetNZBInfo()->AddMessage(eKind, szText); } 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 DownloadQueue::Lock(); char szNZBName[1024]; strncpy(szNZBName, m_pPostInfo->GetNZBInfo()->GetName(), 1024); szNZBName[1024-1] = '\0'; char szInfoName[1024]; snprintf(szInfoName, 1024, "cleanup for %s", m_pPostInfo->GetNZBInfo()->GetName()); szInfoName[1024-1] = '\0'; SetInfoName(szInfoName); strncpy(m_szDestDir, m_pPostInfo->GetNZBInfo()->GetDestDir(), 1024); m_szDestDir[1024-1] = '\0'; bool bInterDir = strlen(g_pOptions->GetInterDir()) > 0 && !strncmp(m_szDestDir, g_pOptions->GetInterDir(), strlen(g_pOptions->GetInterDir())); if (bInterDir) { m_pPostInfo->GetNZBInfo()->BuildFinalDirName(m_szFinalDir, 1024); m_szFinalDir[1024-1] = '\0'; } else { m_szFinalDir[0] = '\0'; } DownloadQueue::Unlock(); PrintMessage(Message::mkInfo, "Cleaning up %s", szNZBName); bool bDeleted = false; bool bOK = Cleanup(m_szDestDir, &bDeleted); if (bOK && m_szFinalDir[0] != '\0') { bool bDeleted2 = false; bOK = Cleanup(m_szFinalDir, &bDeleted2); bDeleted = bDeleted || bDeleted2; } szInfoName[0] = 'C'; // uppercase if (bOK && bDeleted) { PrintMessage(Message::mkInfo, "%s successful", szInfoName); m_pPostInfo->GetNZBInfo()->SetCleanupStatus(NZBInfo::csSuccess); } else if (bOK) { PrintMessage(Message::mkInfo, "Nothing to cleanup for %s", szNZBName); m_pPostInfo->GetNZBInfo()->SetCleanupStatus(NZBInfo::csSuccess); } else { PrintMessage(Message::mkError, "%s failed", szInfoName); m_pPostInfo->GetNZBInfo()->SetCleanupStatus(NZBInfo::csFailure); } m_pPostInfo->SetStage(PostInfo::ptQueued); m_pPostInfo->SetWorking(false); } bool CleanupController::Cleanup(const char* szDestDir, bool *bDeleted) { *bDeleted = false; bool bOK = true; DirBrowser dir(szDestDir); while (const char* filename = dir.Next()) { char szFullFilename[1024]; snprintf(szFullFilename, 1024, "%s%c%s", szDestDir, PATH_SEPARATOR, filename); szFullFilename[1024-1] = '\0'; bool bIsDir = Util::DirectoryExists(szFullFilename); if (strcmp(filename, ".") && strcmp(filename, "..") && bIsDir) { bOK &= Cleanup(szFullFilename, bDeleted); } // check file extension bool bDeleteIt = Util::MatchFileExt(filename, g_pOptions->GetExtCleanupDisk(), ",;") && !bIsDir; if (bDeleteIt) { PrintMessage(Message::mkInfo, "Deleting file %s", filename); if (remove(szFullFilename) != 0) { char szErrBuf[256]; PrintMessage(Message::mkError, "Could not delete file %s: %s", szFullFilename, Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf))); bOK = false; } *bDeleted = true; } } return bOK; } void CleanupController::AddMessage(Message::EKind eKind, const char* szText) { m_pPostInfo->GetNZBInfo()->AddMessage(eKind, szText); } nzbget-16.4/daemon/postprocess/Cleanup.h0000644000175000017500000000330312630544544020146 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2013-2015 Andrey Prygunkov * * 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 CLEANUP_H #define CLEANUP_H #include "Log.h" #include "Thread.h" #include "DownloadInfo.h" #include "Script.h" class MoveController : public Thread, public ScriptController { private: PostInfo* m_pPostInfo; char m_szInterDir[1024]; char m_szDestDir[1024]; bool MoveFiles(); protected: virtual void AddMessage(Message::EKind eKind, const char* szText); public: virtual void Run(); 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); protected: virtual void AddMessage(Message::EKind eKind, const char* szText); public: virtual void Run(); static void StartJob(PostInfo* pPostInfo); }; #endif nzbget-16.4/daemon/postprocess/PrePostProcessor.cpp0000644000175000017500000006261712630544544022423 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #include #ifdef WIN32 #include #else #include #endif #include #include #include "nzbget.h" #include "PrePostProcessor.h" #include "Options.h" #include "Log.h" #include "HistoryCoordinator.h" #include "DupeCoordinator.h" #include "PostScript.h" #include "Util.h" #include "Unpack.h" #include "Cleanup.h" #include "NZBFile.h" #include "QueueScript.h" #include "ParParser.h" PrePostProcessor::PrePostProcessor() { debug("Creating PrePostProcessor"); m_iJobCount = 0; m_pCurJob = NULL; m_szPauseReason = NULL; m_DownloadQueueObserver.m_pOwner = this; DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); pDownloadQueue->Attach(&m_DownloadQueueObserver); DownloadQueue::Unlock(); } PrePostProcessor::~PrePostProcessor() { debug("Destroying PrePostProcessor"); } void PrePostProcessor::Run() { debug("Entering PrePostProcessor-loop"); while (!DownloadQueue::IsLoaded()) { usleep(20 * 1000); } if (g_pOptions->GetServerMode() && g_pOptions->GetSaveQueue() && g_pOptions->GetReloadQueue()) { DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); SanitisePostQueue(pDownloadQueue); DownloadQueue::Unlock(); } while (!IsStopped()) { if (!g_pOptions->GetTempPausePostprocess()) { // check post-queue every 200 msec CheckPostQueue(); } Util::SetStandByMode(!m_pCurJob); usleep(200 * 1000); } debug("Exiting PrePostProcessor-loop"); } void PrePostProcessor::Stop() { Thread::Stop(); DownloadQueue::Lock(); #ifndef DISABLE_PARCHECK m_ParCoordinator.Stop(); #endif if (m_pCurJob && m_pCurJob->GetPostInfo() && (m_pCurJob->GetPostInfo()->GetStage() == PostInfo::ptUnpacking || m_pCurJob->GetPostInfo()->GetStage() == PostInfo::ptExecutingScript) && m_pCurJob->GetPostInfo()->GetPostThread()) { Thread* pPostThread = m_pCurJob->GetPostInfo()->GetPostThread(); m_pCurJob->GetPostInfo()->SetPostThread(NULL); pPostThread->SetAutoDestroy(true); pPostThread->Stop(); } DownloadQueue::Unlock(); } void PrePostProcessor::DownloadQueueUpdate(Subject* Caller, void* Aspect) { if (IsStopped()) { return; } DownloadQueue::Aspect* pQueueAspect = (DownloadQueue::Aspect*)Aspect; if (pQueueAspect->eAction == DownloadQueue::eaNzbFound) { NZBFound(pQueueAspect->pDownloadQueue, pQueueAspect->pNZBInfo); } else if (pQueueAspect->eAction == DownloadQueue::eaNzbAdded) { NZBAdded(pQueueAspect->pDownloadQueue, pQueueAspect->pNZBInfo); } else if (pQueueAspect->eAction == DownloadQueue::eaNzbDeleted && pQueueAspect->pNZBInfo->GetDeleting() && !pQueueAspect->pNZBInfo->GetPostInfo() && !pQueueAspect->pNZBInfo->GetParCleanup() && pQueueAspect->pNZBInfo->GetFileList()->empty()) { // the deleting of nzbs is usually handled via eaFileDeleted-event, but when deleting nzb without // any files left the eaFileDeleted-event is not fired and we need to process eaNzbDeleted-event instead pQueueAspect->pNZBInfo->PrintMessage(Message::mkInfo, "Collection %s deleted from queue", pQueueAspect->pNZBInfo->GetName()); NZBDeleted(pQueueAspect->pDownloadQueue, pQueueAspect->pNZBInfo); } else if ((pQueueAspect->eAction == DownloadQueue::eaFileCompleted || pQueueAspect->eAction == DownloadQueue::eaFileDeleted)) { if (pQueueAspect->eAction == DownloadQueue::eaFileCompleted && !pQueueAspect->pNZBInfo->GetPostInfo()) { g_pQueueScriptCoordinator->EnqueueScript(pQueueAspect->pNZBInfo, QueueScriptCoordinator::qeFileDownloaded); } if ( #ifndef DISABLE_PARCHECK !m_ParCoordinator.AddPar(pQueueAspect->pFileInfo, pQueueAspect->eAction == DownloadQueue::eaFileDeleted) && #endif IsNZBFileCompleted(pQueueAspect->pNZBInfo, true, false) && !pQueueAspect->pNZBInfo->GetPostInfo() && (!pQueueAspect->pFileInfo->GetPaused() || IsNZBFileCompleted(pQueueAspect->pNZBInfo, false, false))) { if ((pQueueAspect->eAction == DownloadQueue::eaFileCompleted || (pQueueAspect->pFileInfo->GetAutoDeleted() && IsNZBFileCompleted(pQueueAspect->pNZBInfo, false, true))) && pQueueAspect->pFileInfo->GetNZBInfo()->GetDeleteStatus() != NZBInfo::dsHealth) { pQueueAspect->pNZBInfo->PrintMessage(Message::mkInfo, "Collection %s completely downloaded", pQueueAspect->pNZBInfo->GetName()); g_pQueueScriptCoordinator->EnqueueScript(pQueueAspect->pNZBInfo, QueueScriptCoordinator::qeNzbDownloaded); NZBDownloaded(pQueueAspect->pDownloadQueue, pQueueAspect->pNZBInfo); } else if ((pQueueAspect->eAction == DownloadQueue::eaFileDeleted || (pQueueAspect->eAction == DownloadQueue::eaFileCompleted && pQueueAspect->pFileInfo->GetNZBInfo()->GetDeleteStatus() > NZBInfo::dsNone)) && !pQueueAspect->pNZBInfo->GetParCleanup() && IsNZBFileCompleted(pQueueAspect->pNZBInfo, false, true)) { pQueueAspect->pNZBInfo->PrintMessage(Message::mkInfo, "Collection %s deleted from queue", pQueueAspect->pNZBInfo->GetName()); NZBDeleted(pQueueAspect->pDownloadQueue, pQueueAspect->pNZBInfo); } } } } void PrePostProcessor::NZBFound(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo) { if (g_pOptions->GetDupeCheck() && pNZBInfo->GetDupeMode() != dmForce) { g_pDupeCoordinator->NZBFound(pDownloadQueue, pNZBInfo); } } void PrePostProcessor::NZBAdded(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo) { if (g_pOptions->GetParCheck() != Options::pcForce) { m_ParCoordinator.PausePars(pDownloadQueue, pNZBInfo); } if (pNZBInfo->GetDeleteStatus() == NZBInfo::dsDupe || pNZBInfo->GetDeleteStatus() == NZBInfo::dsCopy || pNZBInfo->GetDeleteStatus() == NZBInfo::dsGood || pNZBInfo->GetDeleteStatus() == NZBInfo::dsScan) { NZBCompleted(pDownloadQueue, pNZBInfo, false); } else { g_pQueueScriptCoordinator->EnqueueScript(pNZBInfo, QueueScriptCoordinator::qeNzbAdded); } } void PrePostProcessor::NZBDownloaded(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo) { if (pNZBInfo->GetDeleteStatus() == NZBInfo::dsHealth || pNZBInfo->GetDeleteStatus() == NZBInfo::dsBad) { g_pQueueScriptCoordinator->EnqueueScript(pNZBInfo, QueueScriptCoordinator::qeNzbDeleted); } if (!pNZBInfo->GetPostInfo() && g_pOptions->GetDecode()) { pNZBInfo->PrintMessage(Message::mkInfo, "Queueing %s for post-processing", pNZBInfo->GetName()); pNZBInfo->EnterPostProcess(); m_iJobCount++; if (pNZBInfo->GetParStatus() == NZBInfo::psNone && g_pOptions->GetParCheck() != Options::pcAlways && g_pOptions->GetParCheck() != Options::pcForce) { pNZBInfo->SetParStatus(NZBInfo::psSkipped); } if (pNZBInfo->GetRenameStatus() == NZBInfo::rsNone && !g_pOptions->GetParRename()) { pNZBInfo->SetRenameStatus(NZBInfo::rsSkipped); } pDownloadQueue->Save(); } else { NZBCompleted(pDownloadQueue, pNZBInfo, true); } } void PrePostProcessor::NZBDeleted(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo) { if (pNZBInfo->GetDeleteStatus() == NZBInfo::dsNone) { pNZBInfo->SetDeleteStatus(NZBInfo::dsManual); } pNZBInfo->SetDeleting(false); DeleteCleanup(pNZBInfo); if (pNZBInfo->GetDeleteStatus() == NZBInfo::dsHealth || pNZBInfo->GetDeleteStatus() == NZBInfo::dsBad) { NZBDownloaded(pDownloadQueue, pNZBInfo); } else { NZBCompleted(pDownloadQueue, pNZBInfo, true); } } void PrePostProcessor::NZBCompleted(DownloadQueue* pDownloadQueue, NZBInfo* pNZBInfo, bool bSaveQueue) { bool bAddToHistory = g_pOptions->GetKeepHistory() > 0 && !pNZBInfo->GetAvoidHistory(); if (bAddToHistory) { g_pHistoryCoordinator->AddToHistory(pDownloadQueue, pNZBInfo); } pNZBInfo->SetAvoidHistory(false); bool bNeedSave = bAddToHistory; if (g_pOptions->GetDupeCheck() && pNZBInfo->GetDupeMode() != dmForce && (pNZBInfo->GetDeleteStatus() == NZBInfo::dsNone || pNZBInfo->GetDeleteStatus() == NZBInfo::dsHealth || pNZBInfo->GetDeleteStatus() == NZBInfo::dsBad || pNZBInfo->GetDeleteStatus() == NZBInfo::dsScan)) { g_pDupeCoordinator->NZBCompleted(pDownloadQueue, pNZBInfo); bNeedSave = true; } if (pNZBInfo->GetDeleteStatus() > NZBInfo::dsNone && pNZBInfo->GetDeleteStatus() != NZBInfo::dsHealth && pNZBInfo->GetDeleteStatus() != NZBInfo::dsBad) // nzbs deleted by health check or marked as bad are processed as downloaded with failure status { g_pQueueScriptCoordinator->EnqueueScript(pNZBInfo, QueueScriptCoordinator::qeNzbDeleted); } if (!bAddToHistory) { g_pHistoryCoordinator->DeleteDiskFiles(pNZBInfo); pDownloadQueue->GetQueue()->Remove(pNZBInfo); delete pNZBInfo; } if (bSaveQueue && bNeedSave) { pDownloadQueue->Save(); } } void PrePostProcessor::DeleteCleanup(NZBInfo* pNZBInfo) { if ((g_pOptions->GetDeleteCleanupDisk() && pNZBInfo->GetCleanupDisk()) || pNZBInfo->GetDeleteStatus() == NZBInfo::dsDupe) { // download was cancelled, deleting already downloaded files from disk for (CompletedFiles::reverse_iterator it = pNZBInfo->GetCompletedFiles()->rbegin(); it != pNZBInfo->GetCompletedFiles()->rend(); it++) { CompletedFile* pCompletedFile = *it; char szFullFileName[1024]; snprintf(szFullFileName, 1024, "%s%c%s", pNZBInfo->GetDestDir(), (int)PATH_SEPARATOR, pCompletedFile->GetFileName()); szFullFileName[1024-1] = '\0'; if (Util::FileExists(szFullFileName)) { detail("Deleting file %s", pCompletedFile->GetFileName()); remove(szFullFileName); } } // delete .out.tmp-files and _brokenlog.txt DirBrowser dir(pNZBInfo->GetDestDir()); while (const char* szFilename = dir.Next()) { int iLen = strlen(szFilename); if ((iLen > 8 && !strcmp(szFilename + iLen - 8, ".out.tmp")) || !strcmp(szFilename, "_brokenlog.txt")) { char szFullFilename[1024]; snprintf(szFullFilename, 1024, "%s%c%s", pNZBInfo->GetDestDir(), PATH_SEPARATOR, szFilename); szFullFilename[1024-1] = '\0'; detail("Deleting file %s", szFilename); remove(szFullFilename); } } // delete old directory (if empty) if (Util::DirEmpty(pNZBInfo->GetDestDir())) { rmdir(pNZBInfo->GetDestDir()); } } } void PrePostProcessor::CheckPostQueue() { DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); if (!m_pCurJob && m_iJobCount > 0) { m_pCurJob = GetNextJob(pDownloadQueue); } if (m_pCurJob) { PostInfo* pPostInfo = m_pCurJob->GetPostInfo(); if (!pPostInfo->GetWorking() && !IsNZBFileDownloading(m_pCurJob)) { #ifndef DISABLE_PARCHECK if (pPostInfo->GetRequestParCheck() && (pPostInfo->GetNZBInfo()->GetParStatus() <= NZBInfo::psSkipped || (pPostInfo->GetForceRepair() && !pPostInfo->GetNZBInfo()->GetParFull())) && g_pOptions->GetParCheck() != Options::pcManual) { pPostInfo->SetForceParFull(pPostInfo->GetNZBInfo()->GetParStatus() > NZBInfo::psSkipped); pPostInfo->GetNZBInfo()->SetParStatus(NZBInfo::psNone); pPostInfo->SetRequestParCheck(false); pPostInfo->SetStage(PostInfo::ptQueued); pPostInfo->GetNZBInfo()->GetScriptStatuses()->Clear(); DeletePostThread(pPostInfo); } else if (pPostInfo->GetRequestParCheck() && pPostInfo->GetNZBInfo()->GetParStatus() <= NZBInfo::psSkipped && g_pOptions->GetParCheck() == Options::pcManual) { pPostInfo->SetRequestParCheck(false); pPostInfo->GetNZBInfo()->SetParStatus(NZBInfo::psManual); DeletePostThread(pPostInfo); if (!pPostInfo->GetNZBInfo()->GetFileList()->empty()) { pPostInfo->GetNZBInfo()->PrintMessage(Message::mkInfo, "Downloading all remaining files for manual par-check for %s", pPostInfo->GetNZBInfo()->GetName()); pDownloadQueue->EditEntry(pPostInfo->GetNZBInfo()->GetID(), DownloadQueue::eaGroupResume, 0, NULL); pPostInfo->SetStage(PostInfo::ptFinished); } else { pPostInfo->GetNZBInfo()->PrintMessage(Message::mkInfo, "There are no par-files remain for download for %s", pPostInfo->GetNZBInfo()->GetName()); pPostInfo->SetStage(PostInfo::ptQueued); } } #endif if (pPostInfo->GetDeleted()) { pPostInfo->SetStage(PostInfo::ptFinished); } if (pPostInfo->GetStage() == PostInfo::ptQueued && (!g_pOptions->GetPausePostProcess() || pPostInfo->GetNZBInfo()->GetForcePriority())) { DeletePostThread(pPostInfo); StartJob(pDownloadQueue, pPostInfo); } else if (pPostInfo->GetStage() == PostInfo::ptFinished) { UpdatePauseState(false, NULL); JobCompleted(pDownloadQueue, pPostInfo); } else if (!g_pOptions->GetPausePostProcess()) { error("Internal error: invalid state in post-processor"); // TODO: cancel (delete) current job } } } DownloadQueue::Unlock(); } NZBInfo* PrePostProcessor::GetNextJob(DownloadQueue* pDownloadQueue) { NZBInfo* pNZBInfo = NULL; for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo1 = *it; if (pNZBInfo1->GetPostInfo() && !g_pQueueScriptCoordinator->HasJob(pNZBInfo1->GetID(), NULL) && (!pNZBInfo || pNZBInfo1->GetPriority() > pNZBInfo->GetPriority()) && (!g_pOptions->GetPausePostProcess() || pNZBInfo1->GetForcePriority())) { pNZBInfo = pNZBInfo1; } } return pNZBInfo; } /** * Reset the state of items after reloading from disk and * delete items which could not be resumed. * Also count the number of post-jobs. */ void PrePostProcessor::SanitisePostQueue(DownloadQueue* pDownloadQueue) { for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; PostInfo* pPostInfo = pNZBInfo->GetPostInfo(); if (pPostInfo) { m_iJobCount++; if (pPostInfo->GetStage() == PostInfo::ptExecutingScript || !Util::DirectoryExists(pNZBInfo->GetDestDir())) { pPostInfo->SetStage(PostInfo::ptFinished); } else { pPostInfo->SetStage(PostInfo::ptQueued); } } } } void PrePostProcessor::DeletePostThread(PostInfo* pPostInfo) { delete pPostInfo->GetPostThread(); pPostInfo->SetPostThread(NULL); } void PrePostProcessor::StartJob(DownloadQueue* pDownloadQueue, PostInfo* pPostInfo) { if (!pPostInfo->GetStartTime()) { pPostInfo->SetStartTime(time(NULL)); } #ifndef DISABLE_PARCHECK if (pPostInfo->GetNZBInfo()->GetRenameStatus() == NZBInfo::rsNone && pPostInfo->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsNone) { UpdatePauseState(g_pOptions->GetParPauseQueue(), "par-rename"); m_ParCoordinator.StartParRenameJob(pPostInfo); return; } else if (pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psNone && pPostInfo->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsNone) { if (ParParser::FindMainPars(pPostInfo->GetNZBInfo()->GetDestDir(), NULL)) { UpdatePauseState(g_pOptions->GetParPauseQueue(), "par-check"); m_ParCoordinator.StartParCheckJob(pPostInfo); } else { pPostInfo->GetNZBInfo()->PrintMessage(Message::mkInfo, "Nothing to par-check for %s", pPostInfo->GetNZBInfo()->GetName()); pPostInfo->GetNZBInfo()->SetParStatus(NZBInfo::psSkipped); pPostInfo->SetWorking(false); pPostInfo->SetStage(PostInfo::ptQueued); } return; } else if (pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psSkipped && ((g_pOptions->GetParScan() != Options::psDupe && pPostInfo->GetNZBInfo()->CalcHealth() < pPostInfo->GetNZBInfo()->CalcCriticalHealth(false) && pPostInfo->GetNZBInfo()->CalcCriticalHealth(false) < 1000) || pPostInfo->GetNZBInfo()->CalcHealth() == 0) && ParParser::FindMainPars(pPostInfo->GetNZBInfo()->GetDestDir(), NULL)) { pPostInfo->GetNZBInfo()->PrintMessage(Message::mkWarning, pPostInfo->GetNZBInfo()->CalcHealth() == 0 ? "Skipping par-check for %s due to health 0%%" : "Skipping par-check for %s due to health %.1f%% below critical %.1f%%", pPostInfo->GetNZBInfo()->GetName(), pPostInfo->GetNZBInfo()->CalcHealth() / 10.0, pPostInfo->GetNZBInfo()->CalcCriticalHealth(false) / 10.0); pPostInfo->GetNZBInfo()->SetParStatus(NZBInfo::psFailure); return; } else if (pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psSkipped && pPostInfo->GetNZBInfo()->GetFailedSize() - pPostInfo->GetNZBInfo()->GetParFailedSize() > 0 && ParParser::FindMainPars(pPostInfo->GetNZBInfo()->GetDestDir(), NULL)) { pPostInfo->GetNZBInfo()->PrintMessage(Message::mkInfo, "Collection %s with health %.1f%% needs par-check", pPostInfo->GetNZBInfo()->GetName(), pPostInfo->GetNZBInfo()->CalcHealth() / 10.0); pPostInfo->SetRequestParCheck(true); return; } #endif NZBParameter* pUnpackParameter = pPostInfo->GetNZBInfo()->GetParameters()->Find("*Unpack:", false); bool bUnpackParam = !(pUnpackParameter && !strcasecmp(pUnpackParameter->GetValue(), "no")); bool bUnpack = bUnpackParam && pPostInfo->GetNZBInfo()->GetUnpackStatus() == NZBInfo::usNone && pPostInfo->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsNone; bool bParFailed = pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psFailure || pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psRepairPossible || pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psManual; bool bCleanup = !bUnpack && pPostInfo->GetNZBInfo()->GetCleanupStatus() == NZBInfo::csNone && !Util::EmptyStr(g_pOptions->GetExtCleanupDisk()) && ((pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psSuccess && pPostInfo->GetNZBInfo()->GetUnpackStatus() != NZBInfo::usFailure && pPostInfo->GetNZBInfo()->GetUnpackStatus() != NZBInfo::usSpace && pPostInfo->GetNZBInfo()->GetUnpackStatus() != NZBInfo::usPassword) || (pPostInfo->GetNZBInfo()->GetUnpackStatus() == NZBInfo::usSuccess && pPostInfo->GetNZBInfo()->GetParStatus() != NZBInfo::psFailure) || ((pPostInfo->GetNZBInfo()->GetUnpackStatus() == NZBInfo::usNone || pPostInfo->GetNZBInfo()->GetUnpackStatus() == NZBInfo::usSkipped) && (pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psNone || pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psSkipped) && pPostInfo->GetNZBInfo()->CalcHealth() == 1000)); bool bMoveInter = !bUnpack && pPostInfo->GetNZBInfo()->GetMoveStatus() == NZBInfo::msNone && pPostInfo->GetNZBInfo()->GetUnpackStatus() != NZBInfo::usFailure && pPostInfo->GetNZBInfo()->GetUnpackStatus() != NZBInfo::usSpace && pPostInfo->GetNZBInfo()->GetUnpackStatus() != NZBInfo::usPassword && pPostInfo->GetNZBInfo()->GetParStatus() != NZBInfo::psFailure && pPostInfo->GetNZBInfo()->GetParStatus() != NZBInfo::psManual && pPostInfo->GetNZBInfo()->GetDeleteStatus() == NZBInfo::dsNone && !Util::EmptyStr(g_pOptions->GetInterDir()) && !strncmp(pPostInfo->GetNZBInfo()->GetDestDir(), g_pOptions->GetInterDir(), strlen(g_pOptions->GetInterDir())); bool bPostScript = true; if (bUnpack && bParFailed) { pPostInfo->GetNZBInfo()->PrintMessage(Message::mkWarning, "Skipping unpack for %s due to %s", pPostInfo->GetNZBInfo()->GetName(), pPostInfo->GetNZBInfo()->GetParStatus() == NZBInfo::psManual ? "required par-repair" : "par-failure"); pPostInfo->GetNZBInfo()->SetUnpackStatus(NZBInfo::usSkipped); bUnpack = false; } if (!bUnpack && !bMoveInter && !bPostScript) { pPostInfo->SetStage(PostInfo::ptFinished); return; } pPostInfo->SetProgressLabel(bUnpack ? "Unpacking" : bMoveInter ? "Moving" : "Executing post-process-script"); pPostInfo->SetWorking(true); pPostInfo->SetStage(bUnpack ? PostInfo::ptUnpacking : bMoveInter ? PostInfo::ptMoving : PostInfo::ptExecutingScript); pPostInfo->SetFileProgress(0); pPostInfo->SetStageProgress(0); pDownloadQueue->Save(); pPostInfo->SetStageTime(time(NULL)); if (bUnpack) { UpdatePauseState(g_pOptions->GetUnpackPauseQueue(), "unpack"); UnpackController::StartJob(pPostInfo); } else if (bCleanup) { UpdatePauseState(g_pOptions->GetUnpackPauseQueue() || g_pOptions->GetScriptPauseQueue(), "cleanup"); CleanupController::StartJob(pPostInfo); } else if (bMoveInter) { UpdatePauseState(g_pOptions->GetUnpackPauseQueue() || g_pOptions->GetScriptPauseQueue(), "move"); MoveController::StartJob(pPostInfo); } else { UpdatePauseState(g_pOptions->GetScriptPauseQueue(), "post-process-script"); PostScriptController::StartJob(pPostInfo); } } void PrePostProcessor::JobCompleted(DownloadQueue* pDownloadQueue, PostInfo* pPostInfo) { NZBInfo* pNZBInfo = pPostInfo->GetNZBInfo(); if (pPostInfo->GetStartTime() > 0) { pNZBInfo->SetPostTotalSec((int)(time(NULL) - pPostInfo->GetStartTime())); pPostInfo->SetStartTime(0); } DeletePostThread(pPostInfo); pNZBInfo->LeavePostProcess(); if (IsNZBFileCompleted(pNZBInfo, true, false)) { // Cleaning up queue if par-check was successful or unpack was successful or // health is 100% (if unpack and par-check were not performed) // or health is below critical health bool bCanCleanupQueue = ((pNZBInfo->GetParStatus() == NZBInfo::psSuccess || pNZBInfo->GetParStatus() == NZBInfo::psRepairPossible) && pNZBInfo->GetUnpackStatus() != NZBInfo::usFailure && pNZBInfo->GetUnpackStatus() != NZBInfo::usSpace && pNZBInfo->GetUnpackStatus() != NZBInfo::usPassword) || (pNZBInfo->GetUnpackStatus() == NZBInfo::usSuccess && pNZBInfo->GetParStatus() != NZBInfo::psFailure) || (pNZBInfo->GetUnpackStatus() <= NZBInfo::usSkipped && pNZBInfo->GetParStatus() != NZBInfo::psFailure && pNZBInfo->GetFailedSize() - pNZBInfo->GetParFailedSize() == 0) || (pNZBInfo->CalcHealth() < pNZBInfo->CalcCriticalHealth(false) && pNZBInfo->CalcCriticalHealth(false) < 1000); if (g_pOptions->GetParCleanupQueue() && bCanCleanupQueue && !pNZBInfo->GetFileList()->empty()) { pNZBInfo->PrintMessage(Message::mkInfo, "Cleaning up download queue for %s", pNZBInfo->GetName()); pNZBInfo->SetParCleanup(true); pDownloadQueue->EditEntry(pNZBInfo->GetID(), DownloadQueue::eaGroupDelete, 0, NULL); } if (pNZBInfo->GetUnpackCleanedUpDisk()) { pNZBInfo->ClearCompletedFiles(); } NZBCompleted(pDownloadQueue, pNZBInfo, false); } if (pNZBInfo == m_pCurJob) { m_pCurJob = NULL; } m_iJobCount--; pDownloadQueue->Save(); } bool PrePostProcessor::IsNZBFileCompleted(NZBInfo* pNZBInfo, bool bIgnorePausedPars, bool bAllowOnlyOneDeleted) { int iDeleted = 0; for (FileList::iterator it = pNZBInfo->GetFileList()->begin(); it != pNZBInfo->GetFileList()->end(); it++) { FileInfo* pFileInfo = *it; if (pFileInfo->GetDeleted()) { iDeleted++; } if (((!pFileInfo->GetPaused() || !bIgnorePausedPars || !pFileInfo->GetParFile()) && !pFileInfo->GetDeleted()) || (bAllowOnlyOneDeleted && iDeleted > 1)) { return false; } } return true; } bool PrePostProcessor::IsNZBFileDownloading(NZBInfo* pNZBInfo) { if (pNZBInfo->GetActiveDownloads()) { return true; } for (FileList::iterator it = pNZBInfo->GetFileList()->begin(); it != pNZBInfo->GetFileList()->end(); it++) { FileInfo* pFileInfo = *it; if (!pFileInfo->GetPaused()) { return true; } } return false; } void PrePostProcessor::UpdatePauseState(bool bNeedPause, const char* szReason) { if (bNeedPause && !g_pOptions->GetTempPauseDownload()) { info("Pausing download before %s", szReason); } else if (!bNeedPause && g_pOptions->GetTempPauseDownload()) { info("Unpausing download after %s", m_szPauseReason); } g_pOptions->SetTempPauseDownload(bNeedPause); m_szPauseReason = szReason; } bool PrePostProcessor::EditList(DownloadQueue* pDownloadQueue, IDList* pIDList, DownloadQueue::EEditAction eAction, int iOffset, const char* szText) { debug("Edit-command for post-processor received"); switch (eAction) { case DownloadQueue::eaPostDelete: return PostQueueDelete(pDownloadQueue, pIDList); default: return false; } } bool PrePostProcessor::PostQueueDelete(DownloadQueue* pDownloadQueue, IDList* pIDList) { bool bOK = false; for (IDList::iterator itID = pIDList->begin(); itID != pIDList->end(); itID++) { int iID = *itID; for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; PostInfo* pPostInfo = pNZBInfo->GetPostInfo(); if (pPostInfo && pNZBInfo->GetID() == iID) { if (pPostInfo->GetWorking()) { pPostInfo->GetNZBInfo()->PrintMessage(Message::mkInfo, "Deleting active post-job %s", pPostInfo->GetNZBInfo()->GetName()); pPostInfo->SetDeleted(true); #ifndef DISABLE_PARCHECK if (PostInfo::ptLoadingPars <= pPostInfo->GetStage() && pPostInfo->GetStage() <= PostInfo::ptRenaming) { if (m_ParCoordinator.Cancel()) { bOK = true; } } else #endif if (pPostInfo->GetPostThread()) { debug("Terminating %s for %s", (pPostInfo->GetStage() == PostInfo::ptUnpacking ? "unpack" : "post-process-script"), pPostInfo->GetNZBInfo()->GetName()); pPostInfo->GetPostThread()->Stop(); bOK = true; } else { error("Internal error in PrePostProcessor::QueueDelete"); } } else { pPostInfo->GetNZBInfo()->PrintMessage(Message::mkInfo, "Deleting queued post-job %s", pPostInfo->GetNZBInfo()->GetName()); JobCompleted(pDownloadQueue, pPostInfo); bOK = true; } break; } } } return bOK; } nzbget-16.4/daemon/postprocess/ParParser.cpp0000644000175000017500000000631712630544544021021 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #include #include #include #ifdef WIN32 #include #else #include #endif #include "nzbget.h" #include "Util.h" #include "ParParser.h" bool ParParser::FindMainPars(const char* szPath, ParFileList* pFileList) { if (pFileList) { pFileList->clear(); } DirBrowser dir(szPath); while (const char* filename = dir.Next()) { int iBaseLen = 0; if (ParseParFilename(filename, &iBaseLen, NULL)) { if (!pFileList) { return true; } // check if the base file already added to list bool exists = false; for (ParFileList::iterator it = pFileList->begin(); it != pFileList->end(); it++) { const char* filename2 = *it; exists = SameParCollection(filename, filename2); if (exists) { break; } } if (!exists) { pFileList->push_back(strdup(filename)); } } } return pFileList && !pFileList->empty(); } bool ParParser::SameParCollection(const char* szFilename1, const char* szFilename2) { int iBaseLen1 = 0, iBaseLen2 = 0; return ParseParFilename(szFilename1, &iBaseLen1, NULL) && ParseParFilename(szFilename2, &iBaseLen2, NULL) && iBaseLen1 == iBaseLen2 && !strncasecmp(szFilename1, szFilename2, iBaseLen1); } bool ParParser::ParseParFilename(const char* szParFilename, int* iBaseNameLen, int* iBlocks) { char szFilename[1024]; strncpy(szFilename, szParFilename, 1024); szFilename[1024-1] = '\0'; for (char* p = szFilename; *p; p++) *p = tolower(*p); // convert string to lowercase int iLen = strlen(szFilename); if (iLen < 6) { return false; } // find last occurence of ".par2" and trim filename after it 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; } *(szFilename + iLen - 5) = '\0'; int blockcnt = 0; char* p = strrchr(szFilename, '.'); if (p && !strncasecmp(p, ".vol", 4)) { char* b = strchr(p, '+'); if (!b) { b = strchr(p, '-'); } if (b) { blockcnt = atoi(b+1); *p = '\0'; } } if (iBaseNameLen) { *iBaseNameLen = strlen(szFilename); } if (iBlocks) { *iBlocks = blockcnt; } return true; } nzbget-16.4/daemon/frontend/0000755000175000017500000000000012630544544015642 5ustar andreasandreasnzbget-16.4/daemon/frontend/LoggableFrontend.h0000644000175000017500000000251212630544544021227 0ustar andreasandreas/* * This file if part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007 Andrey Prygunkov * * 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 LOGGABLEFRONTEND_H #define LOGGABLEFRONTEND_H #include "Frontend.h" #include "Log.h" class LoggableFrontend : public Frontend { protected: virtual void Run(); virtual void Update(); virtual void BeforePrint() {}; virtual void BeforeExit() {}; virtual void PrintMessage(Message* pMessage); virtual void PrintStatus() {}; virtual void PrintSkip(); public: LoggableFrontend(); }; #endif nzbget-16.4/daemon/frontend/NCursesFrontend.cpp0000644000175000017500000011305712630544544021437 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #ifndef DISABLE_CURSES #ifdef HAVE_NCURSES_H #include #endif #ifdef HAVE_NCURSES_NCURSES_H #include #endif #include #include #include #ifndef WIN32 #include #endif #include "nzbget.h" #include "NCursesFrontend.h" #include "Options.h" #include "Util.h" #ifdef HAVE_CURSES_H // curses.h header must be included last to avoid problems on Solaris // (and possibly other systems, that uses curses.h (not ncurses.h) #include // "#undef erase" is neccessary on Solaris #undef erase #endif #ifndef WIN32 // curses.h on Solaris declares "clear()" via DEFINE. That causes problems, because // it also affects calls to deque's method "clear()", producing compiler errors. // We use function "curses_clear()" to call macro "clear" of curses, then // undefine macro "clear". void curses_clear() { clear(); } #undef clear #endif extern void ExitProc(); static const int NCURSES_COLORPAIR_TEXT = 1; static const int NCURSES_COLORPAIR_INFO = 2; static const int NCURSES_COLORPAIR_WARNING = 3; static const int NCURSES_COLORPAIR_ERROR = 4; static const int NCURSES_COLORPAIR_DEBUG = 5; static const int NCURSES_COLORPAIR_DETAIL = 6; static const int NCURSES_COLORPAIR_STATUS = 7; static const int NCURSES_COLORPAIR_KEYBAR = 8; static const int NCURSES_COLORPAIR_INFOLINE = 9; static const int NCURSES_COLORPAIR_TEXTHIGHL = 10; static const int NCURSES_COLORPAIR_CURSOR = 11; static const int NCURSES_COLORPAIR_HINT = 12; static const int MAX_SCREEN_WIDTH = 512; #ifdef WIN32 static const int COLOR_BLACK = 0; static const int COLOR_BLUE = FOREGROUND_BLUE; static const int COLOR_RED = FOREGROUND_RED; static const int COLOR_GREEN = FOREGROUND_GREEN; static const int COLOR_WHITE = FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_GREEN; static const int COLOR_MAGENTA = FOREGROUND_RED | FOREGROUND_BLUE; static const int COLOR_CYAN = FOREGROUND_BLUE | FOREGROUND_GREEN; static const int COLOR_YELLOW = FOREGROUND_RED | FOREGROUND_GREEN; static const int READKEY_EMPTY = 0; #define KEY_DOWN VK_DOWN #define KEY_UP VK_UP #define KEY_PPAGE VK_PRIOR #define KEY_NPAGE VK_NEXT #define KEY_END VK_END #define KEY_HOME VK_HOME #define KEY_BACKSPACE VK_BACK #else static const int READKEY_EMPTY = ERR; #endif NCursesFrontend::NCursesFrontend() { m_iScreenHeight = 0; m_iScreenWidth = 0; m_iInputNumberIndex = 0; m_eInputMode = eNormal; m_bSummary = true; m_bFileList = true; m_iNeededLogEntries = 0; m_iQueueWinTop = 0; m_iQueueWinHeight = 0; m_iQueueWinClientHeight = 0; m_iMessagesWinTop = 0; m_iMessagesWinHeight = 0; m_iMessagesWinClientHeight = 0; m_iSelectedQueueEntry = 0; m_iQueueScrollOffset = 0; m_bShowNZBname = g_pOptions->GetCursesNZBName(); m_bShowTimestamp = g_pOptions->GetCursesTime(); m_bGroupFiles = g_pOptions->GetCursesGroup(); m_QueueWindowPercentage = 50; m_iDataUpdatePos = 0; m_bUpdateNextTime = false; m_iLastEditEntry = -1; m_bLastPausePars = false; m_szHint = NULL; // Setup curses #ifdef WIN32 HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); m_pScreenBuffer = NULL; m_pOldScreenBuffer = NULL; m_ColorAttr.clear(); CONSOLE_CURSOR_INFO ConsoleCursorInfo; GetConsoleCursorInfo(hConsole, &ConsoleCursorInfo); ConsoleCursorInfo.bVisible = false; SetConsoleCursorInfo(hConsole, &ConsoleCursorInfo); if (IsRemoteMode()) { SetConsoleTitle("NZBGet - remote mode"); } else { SetConsoleTitle("NZBGet"); } m_bUseColor = true; #else m_pWindow = initscr(); if (m_pWindow == NULL) { printf("ERROR: m_pWindow == NULL\n"); exit(-1); } keypad(stdscr, true); nodelay((WINDOW*)m_pWindow, true); noecho(); curs_set(0); m_bUseColor = has_colors(); #endif if (m_bUseColor) { #ifndef WIN32 start_color(); #endif init_pair(0, COLOR_WHITE, COLOR_BLUE); init_pair(NCURSES_COLORPAIR_TEXT, COLOR_WHITE, COLOR_BLACK); init_pair(NCURSES_COLORPAIR_INFO, COLOR_GREEN, COLOR_BLACK); init_pair(NCURSES_COLORPAIR_WARNING, COLOR_MAGENTA, COLOR_BLACK); init_pair(NCURSES_COLORPAIR_ERROR, COLOR_RED, COLOR_BLACK); init_pair(NCURSES_COLORPAIR_DEBUG, COLOR_WHITE, COLOR_BLACK); init_pair(NCURSES_COLORPAIR_DETAIL, COLOR_GREEN, COLOR_BLACK); init_pair(NCURSES_COLORPAIR_STATUS, COLOR_BLUE, COLOR_WHITE); init_pair(NCURSES_COLORPAIR_KEYBAR, COLOR_WHITE, COLOR_BLUE); init_pair(NCURSES_COLORPAIR_INFOLINE, COLOR_WHITE, COLOR_BLUE); init_pair(NCURSES_COLORPAIR_TEXTHIGHL, COLOR_BLACK, COLOR_CYAN); init_pair(NCURSES_COLORPAIR_CURSOR, COLOR_BLACK, COLOR_YELLOW); init_pair(NCURSES_COLORPAIR_HINT, COLOR_WHITE, COLOR_RED); } } NCursesFrontend::~NCursesFrontend() { #ifdef WIN32 free(m_pScreenBuffer); free(m_pOldScreenBuffer); m_ColorAttr.clear(); HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_CURSOR_INFO ConsoleCursorInfo; GetConsoleCursorInfo(hConsole, &ConsoleCursorInfo); ConsoleCursorInfo.bVisible = true; SetConsoleCursorInfo(hConsole, &ConsoleCursorInfo); #else keypad(stdscr, false); echo(); curs_set(1); endwin(); #endif printf("\n"); SetHint(NULL); } void NCursesFrontend::Run() { debug("Entering NCursesFrontend-loop"); m_iDataUpdatePos = 0; while (!IsStopped()) { // The data (queue and log) is updated each m_iUpdateInterval msec, // but the window is updated more often for better reaction on user's input bool updateNow = false; int iKey = ReadConsoleKey(); if (iKey != READKEY_EMPTY) { // Update now and next if a key is pressed. updateNow = true; m_bUpdateNextTime = true; } else if (m_bUpdateNextTime) { // Update due to key being pressed during previous call. updateNow = true; m_bUpdateNextTime = false; } else if (m_iDataUpdatePos <= 0) { updateNow = true; m_bUpdateNextTime = false; } if (updateNow) { Update(iKey); } if (m_iDataUpdatePos <= 0) { m_iDataUpdatePos = m_iUpdateInterval; } usleep(10 * 1000); m_iDataUpdatePos -= 10; } FreeData(); debug("Exiting NCursesFrontend-loop"); } void NCursesFrontend::NeedUpdateData() { m_iDataUpdatePos = 10; m_bUpdateNextTime = true; } void NCursesFrontend::Update(int iKey) { // Figure out how big the screen is CalcWindowSizes(); if (m_iDataUpdatePos <= 0) { FreeData(); m_iNeededLogEntries = m_iMessagesWinClientHeight; if (!PrepareData()) { return; } // recalculate frame sizes CalcWindowSizes(); } if (m_eInputMode == eEditQueue) { int iQueueSize = CalcQueueSize(); if (iQueueSize == 0) { m_iSelectedQueueEntry = 0; m_eInputMode = eNormal; } } //------------------------------------------ // Print Current NZBInfoList //------------------------------------------ if (m_iQueueWinHeight > 0) { PrintQueue(); } //------------------------------------------ // Print Messages //------------------------------------------ if (m_iMessagesWinHeight > 0) { PrintMessages(); } PrintStatus(); PrintKeyInputBar(); UpdateInput(iKey); RefreshScreen(); } void NCursesFrontend::CalcWindowSizes() { int iNrRows, iNrColumns; #ifdef WIN32 HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_SCREEN_BUFFER_INFO BufInfo; GetConsoleScreenBufferInfo(hConsole, &BufInfo); iNrRows = BufInfo.srWindow.Bottom - BufInfo.srWindow.Top + 1; iNrColumns = BufInfo.srWindow.Right - BufInfo.srWindow.Left + 1; #else getmaxyx(stdscr, iNrRows, iNrColumns); #endif if (iNrRows != m_iScreenHeight || iNrColumns != m_iScreenWidth) { #ifdef WIN32 m_iScreenBufferSize = iNrRows * iNrColumns * sizeof(CHAR_INFO); m_pScreenBuffer = (CHAR_INFO*)realloc(m_pScreenBuffer, m_iScreenBufferSize); memset(m_pScreenBuffer, 0, m_iScreenBufferSize); m_pOldScreenBuffer = (CHAR_INFO*)realloc(m_pOldScreenBuffer, m_iScreenBufferSize); memset(m_pOldScreenBuffer, 0, m_iScreenBufferSize); #else curses_clear(); #endif m_iScreenHeight = iNrRows; m_iScreenWidth = iNrColumns; } int iQueueSize = CalcQueueSize(); m_iQueueWinTop = 0; m_iQueueWinHeight = (m_iScreenHeight - 2) * m_QueueWindowPercentage / 100; if (m_iQueueWinHeight - 1 > iQueueSize) { m_iQueueWinHeight = iQueueSize > 0 ? iQueueSize + 1 : 1 + 1; } m_iQueueWinClientHeight = m_iQueueWinHeight - 1; if (m_iQueueWinClientHeight < 0) { m_iQueueWinClientHeight = 0; } m_iMessagesWinTop = m_iQueueWinTop + m_iQueueWinHeight; m_iMessagesWinHeight = m_iScreenHeight - m_iQueueWinHeight - 2; m_iMessagesWinClientHeight = m_iMessagesWinHeight - 1; if (m_iMessagesWinClientHeight < 0) { m_iMessagesWinClientHeight = 0; } } int NCursesFrontend::CalcQueueSize() { int iQueueSize = 0; DownloadQueue* pDownloadQueue = LockQueue(); if (m_bGroupFiles) { iQueueSize = pDownloadQueue->GetQueue()->size(); } else { for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; iQueueSize += pNZBInfo->GetFileList()->size(); } } UnlockQueue(); return iQueueSize; } void NCursesFrontend::PlotLine(const char * szString, int iRow, int iPos, int iColorPair) { char szBuffer[MAX_SCREEN_WIDTH]; snprintf(szBuffer, sizeof(szBuffer), "%-*s", m_iScreenWidth, szString); szBuffer[MAX_SCREEN_WIDTH - 1] = '\0'; int iLen = strlen(szBuffer); if (iLen > m_iScreenWidth - iPos && m_iScreenWidth - iPos < MAX_SCREEN_WIDTH) { szBuffer[m_iScreenWidth - iPos] = '\0'; } PlotText(szBuffer, iRow, iPos, iColorPair, false); } void NCursesFrontend::PlotText(const char * szString, int iRow, int iPos, int iColorPair, bool bBlink) { #ifdef WIN32 int iBufPos = iRow * m_iScreenWidth + iPos; int len = strlen(szString); for (int i = 0; i < len; i++) { char c = szString[i]; CharToOemBuff(&c, &c, 1); m_pScreenBuffer[iBufPos + i].Char.AsciiChar = c; m_pScreenBuffer[iBufPos + i].Attributes = m_ColorAttr[iColorPair]; } #else if( m_bUseColor ) { attron(COLOR_PAIR(iColorPair)); if (bBlink) { attron(A_BLINK); } } mvaddstr(iRow, iPos, (char*)szString); if( m_bUseColor ) { attroff(COLOR_PAIR(iColorPair)); if (bBlink) { attroff(A_BLINK); } } #endif } void NCursesFrontend::RefreshScreen() { #ifdef WIN32 bool bBufChanged = memcmp(m_pScreenBuffer, m_pOldScreenBuffer, m_iScreenBufferSize); if (bBufChanged) { COORD BufSize; BufSize.X = m_iScreenWidth; BufSize.Y = m_iScreenHeight; COORD BufCoord; BufCoord.X = 0; BufCoord.Y = 0; HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_SCREEN_BUFFER_INFO BufInfo; GetConsoleScreenBufferInfo(hConsole, &BufInfo); WriteConsoleOutput(hConsole, m_pScreenBuffer, BufSize, BufCoord, &BufInfo.srWindow); BufInfo.dwCursorPosition.X = BufInfo.srWindow.Right; BufInfo.dwCursorPosition.Y = BufInfo.srWindow.Bottom; SetConsoleCursorPosition(hConsole, BufInfo.dwCursorPosition); memcpy(m_pOldScreenBuffer, m_pScreenBuffer, m_iScreenBufferSize); } #else // Cursor placement wmove((WINDOW*)m_pWindow, m_iScreenHeight, m_iScreenWidth); // NCurses refresh refresh(); #endif } #ifdef WIN32 void NCursesFrontend::init_pair(int iColorNumber, WORD wForeColor, WORD wBackColor) { m_ColorAttr.resize(iColorNumber + 1); m_ColorAttr[iColorNumber] = wForeColor | (wBackColor << 4); } #endif void NCursesFrontend::PrintMessages() { int iLineNr = m_iMessagesWinTop; char szBuffer[MAX_SCREEN_WIDTH]; snprintf(szBuffer, sizeof(szBuffer), "%s Messages", m_bUseColor ? "" : "*** "); szBuffer[MAX_SCREEN_WIDTH - 1] = '\0'; PlotLine(szBuffer, iLineNr++, 0, NCURSES_COLORPAIR_INFOLINE); int iLine = iLineNr + m_iMessagesWinClientHeight - 1; int iLinesToPrint = m_iMessagesWinClientHeight; MessageList* pMessages = LockMessages(); // print messages from bottom for (int i = (int)pMessages->size() - 1; i >= 0 && iLinesToPrint > 0; i--) { int iPrintedLines = PrintMessage((*pMessages)[i], iLine, iLinesToPrint); iLine -= iPrintedLines; iLinesToPrint -= iPrintedLines; } if (iLinesToPrint > 0) { // too few messages, print them again from top iLine = iLineNr + m_iMessagesWinClientHeight - 1; while (iLinesToPrint-- > 0) { PlotLine("", iLine--, 0, NCURSES_COLORPAIR_TEXT); } int iLinesToPrint2 = m_iMessagesWinClientHeight; for (int i = (int)pMessages->size() - 1; i >= 0 && iLinesToPrint2 > 0; i--) { int iPrintedLines = PrintMessage((*pMessages)[i], iLine, iLinesToPrint2); iLine -= iPrintedLines; iLinesToPrint2 -= iPrintedLines; } } UnlockMessages(); } int NCursesFrontend::PrintMessage(Message* Msg, int iRow, int iMaxLines) { const char* szMessageType[] = { "INFO ", "WARNING ", "ERROR ", "DEBUG ", "DETAIL "}; const int iMessageTypeColor[] = { NCURSES_COLORPAIR_INFO, NCURSES_COLORPAIR_WARNING, NCURSES_COLORPAIR_ERROR, NCURSES_COLORPAIR_DEBUG, NCURSES_COLORPAIR_DETAIL }; char* szText = (char*)Msg->GetText(); if (m_bShowTimestamp) { int iLen = strlen(szText) + 50; 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); #else ctime_r(&rawtime, szTime); #endif szTime[50-1] = '\0'; szTime[strlen(szTime) - 1] = '\0'; // trim LF snprintf(szText, iLen, "%s - %s", szTime, Msg->GetText()); szText[iLen - 1] = '\0'; } else { szText = strdup(szText); } // replace some special characters with spaces for (char* p = szText; *p; p++) { if (*p == '\n' || *p == '\r' || *p == '\b') { *p = ' '; } } int iLen = strlen(szText); int iWinWidth = m_iScreenWidth - 8; int iMsgLines = iLen / iWinWidth; if (iLen % iWinWidth > 0) { iMsgLines++; } int iLines = 0; for (int i = iMsgLines - 1; i >= 0 && iLines < iMaxLines; i--) { int iR = iRow - iMsgLines + i + 1; PlotLine(szText + iWinWidth * i, iR, 8, NCURSES_COLORPAIR_TEXT); if (i == 0) { PlotText(szMessageType[Msg->GetKind()], iR, 0, iMessageTypeColor[Msg->GetKind()], false); } else { PlotText(" ", iR, 0, iMessageTypeColor[Msg->GetKind()], false); } iLines++; } free(szText); return iLines; } void NCursesFrontend::PrintStatus() { char tmp[MAX_SCREEN_WIDTH]; int iStatusRow = m_iScreenHeight - 2; char timeString[100]; timeString[0] = '\0'; int iCurrentDownloadSpeed = m_bStandBy ? 0 : m_iCurrentDownloadSpeed; if (iCurrentDownloadSpeed > 0 && !m_bPauseDownload) { long long remain_sec = (long long)(m_lRemainingSize / iCurrentDownloadSpeed); int h = (int)(remain_sec / 3600); int m = (int)((remain_sec % 3600) / 60); int s = (int)(remain_sec % 60); sprintf(timeString, " (~ %.2d:%.2d:%.2d)", h, m, s); } char szDownloadLimit[128]; if (m_iDownloadLimit > 0) { sprintf(szDownloadLimit, ", Limit %i KB/s", m_iDownloadLimit / 1024); } else { szDownloadLimit[0] = 0; } char szPostStatus[128]; if (m_iPostJobCount > 0) { sprintf(szPostStatus, ", %i post-job%s", m_iPostJobCount, m_iPostJobCount > 1 ? "s" : ""); } else { szPostStatus[0] = 0; } char szCurrentSpeed[20]; char szAverageSpeed[20]; char szRemainingSize[20]; int iAverageSpeed = (int)(m_iDnTimeSec > 0 ? m_iAllBytes / m_iDnTimeSec : 0); snprintf(tmp, MAX_SCREEN_WIDTH, " %d threads, %s, %s remaining%s%s%s%s, Avg. %s", m_iThreadCount, Util::FormatSpeed(szCurrentSpeed, sizeof(szCurrentSpeed), iCurrentDownloadSpeed), Util::FormatSize(szRemainingSize, sizeof(szRemainingSize), m_lRemainingSize), timeString, szPostStatus, m_bPauseDownload ? (m_bStandBy ? ", Paused" : ", Pausing") : "", szDownloadLimit, Util::FormatSpeed(szAverageSpeed, sizeof(szAverageSpeed), iAverageSpeed)); tmp[MAX_SCREEN_WIDTH - 1] = '\0'; PlotLine(tmp, iStatusRow, 0, NCURSES_COLORPAIR_STATUS); } void NCursesFrontend::PrintKeyInputBar() { int iQueueSize = CalcQueueSize(); int iInputBarRow = m_iScreenHeight - 1; if (m_szHint) { time_t tTime = time(NULL); if (tTime - m_tStartHint < 5) { PlotLine(m_szHint, iInputBarRow, 0, NCURSES_COLORPAIR_HINT); return; } else { SetHint(NULL); } } switch (m_eInputMode) { case eNormal: if (m_bGroupFiles) { PlotLine("(Q)uit | (E)dit | (P)ause | (R)ate | (W)indow | (G)roup | (T)ime", iInputBarRow, 0, NCURSES_COLORPAIR_KEYBAR); } else { PlotLine("(Q)uit | (E)dit | (P)ause | (R)ate | (W)indow | (G)roup | (T)ime | n(Z)b", iInputBarRow, 0, NCURSES_COLORPAIR_KEYBAR); } break; case eEditQueue: { const char* szStatus = NULL; if (m_iSelectedQueueEntry > 0 && iQueueSize > 1 && m_iSelectedQueueEntry == iQueueSize - 1) { szStatus = "(Q)uit | (E)xit | (P)ause | (D)elete | (U)p/(T)op"; } else if (iQueueSize > 1 && m_iSelectedQueueEntry == 0) { szStatus = "(Q)uit | (E)xit | (P)ause | (D)elete | dow(N)/(B)ottom"; } else if (iQueueSize > 1) { szStatus = "(Q)uit | (E)xit | (P)ause | (D)elete | (U)p/dow(N)/(T)op/(B)ottom"; } else { szStatus = "(Q)uit | (E)xit | (P)ause | (D)elete"; } PlotLine(szStatus, iInputBarRow, 0, NCURSES_COLORPAIR_KEYBAR); break; } case eDownloadRate: char szString[128]; snprintf(szString, 128, "Download rate: %i", m_iInputValue); szString[128-1] = '\0'; PlotLine(szString, iInputBarRow, 0, NCURSES_COLORPAIR_KEYBAR); // Print the cursor PlotText(" ", iInputBarRow, 15 + m_iInputNumberIndex, NCURSES_COLORPAIR_CURSOR, true); break; } } void NCursesFrontend::SetHint(const char* szHint) { free(m_szHint); m_szHint = NULL; if (szHint) { m_szHint = strdup(szHint); m_tStartHint = time(NULL); } } void NCursesFrontend::PrintQueue() { if (m_bGroupFiles) { PrintGroupQueue(); } else { PrintFileQueue(); } } void NCursesFrontend::PrintFileQueue() { DownloadQueue* pDownloadQueue = LockQueue(); int iLineNr = m_iQueueWinTop + 1; long long lRemaining = 0; long long lPaused = 0; int iPausedFiles = 0; int iFileNum = 0; for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; for (FileList::iterator it2 = pNZBInfo->GetFileList()->begin(); it2 != pNZBInfo->GetFileList()->end(); it2++, iFileNum++) { FileInfo* pFileInfo = *it2; if (iFileNum >= m_iQueueScrollOffset && iFileNum < m_iQueueScrollOffset + m_iQueueWinHeight -1) { PrintFilename(pFileInfo, iLineNr++, iFileNum == m_iSelectedQueueEntry); } if (pFileInfo->GetPaused()) { iPausedFiles++; lPaused += pFileInfo->GetRemainingSize(); } lRemaining += pFileInfo->GetRemainingSize(); } } if (iFileNum > 0) { char szRemaining[20]; char szUnpaused[20]; char szBuffer[MAX_SCREEN_WIDTH]; snprintf(szBuffer, sizeof(szBuffer), " %sFiles for downloading - %i / %i files in queue - %s / %s", m_bUseColor ? "" : "*** ", iFileNum, iFileNum - iPausedFiles, Util::FormatSize(szRemaining, sizeof(szRemaining), lRemaining), Util::FormatSize(szUnpaused, sizeof(szUnpaused), lRemaining - lPaused)); szBuffer[MAX_SCREEN_WIDTH - 1] = '\0'; PrintTopHeader(szBuffer, m_iQueueWinTop, true); } else { iLineNr--; char szBuffer[MAX_SCREEN_WIDTH]; snprintf(szBuffer, sizeof(szBuffer), "%s Files for downloading", m_bUseColor ? "" : "*** "); szBuffer[MAX_SCREEN_WIDTH - 1] = '\0'; PrintTopHeader(szBuffer, iLineNr++, true); PlotLine("Ready to receive nzb-job", iLineNr++, 0, NCURSES_COLORPAIR_TEXT); } UnlockQueue(); } void NCursesFrontend::PrintFilename(FileInfo * pFileInfo, int iRow, bool bSelected) { int color = 0; const char* Brace1 = "["; const char* Brace2 = "]"; if (m_eInputMode == eEditQueue && bSelected) { color = NCURSES_COLORPAIR_TEXTHIGHL; if (!m_bUseColor) { Brace1 = "<"; Brace2 = ">"; } } else { color = NCURSES_COLORPAIR_TEXT; } const char* szDownloading = ""; if (pFileInfo->GetActiveDownloads() > 0) { szDownloading = " *"; } char szPriority[100]; szPriority[0] = '\0'; if (pFileInfo->GetNZBInfo()->GetPriority() != 0) { sprintf(szPriority, " [%+i]", pFileInfo->GetNZBInfo()->GetPriority()); } char szCompleted[20]; szCompleted[0] = '\0'; if (pFileInfo->GetRemainingSize() < pFileInfo->GetSize()) { sprintf(szCompleted, ", %i%%", (int)(100 - pFileInfo->GetRemainingSize() * 100 / pFileInfo->GetSize())); } char szNZBNiceName[1024]; if (m_bShowNZBname) { strncpy(szNZBNiceName, pFileInfo->GetNZBInfo()->GetName(), 1023); int len = strlen(szNZBNiceName); szNZBNiceName[len] = PATH_SEPARATOR; szNZBNiceName[len + 1] = '\0'; } else { szNZBNiceName[0] = '\0'; } char szSize[20]; char szBuffer[MAX_SCREEN_WIDTH]; snprintf(szBuffer, MAX_SCREEN_WIDTH, "%s%i%s%s%s %s%s (%s%s)%s", Brace1, pFileInfo->GetID(), Brace2, szPriority, szDownloading, szNZBNiceName, pFileInfo->GetFilename(), Util::FormatSize(szSize, sizeof(szSize), pFileInfo->GetSize()), szCompleted, pFileInfo->GetPaused() ? " (paused)" : ""); szBuffer[MAX_SCREEN_WIDTH - 1] = '\0'; PlotLine(szBuffer, iRow, 0, color); } void NCursesFrontend::PrintTopHeader(char* szHeader, int iLineNr, bool bUpTime) { char szBuffer[MAX_SCREEN_WIDTH]; snprintf(szBuffer, sizeof(szBuffer), "%-*s", m_iScreenWidth, szHeader); szBuffer[MAX_SCREEN_WIDTH - 1] = '\0'; int iHeaderLen = strlen(szHeader); int iCharsLeft = m_iScreenWidth - iHeaderLen - 2; int iTime = bUpTime ? m_iUpTimeSec : m_iDnTimeSec; int d = iTime / 3600 / 24; int h = (iTime % (3600 * 24)) / 3600; int m = (iTime % 3600) / 60; int s = iTime % 60; char szTime[30]; if (d == 0) { snprintf(szTime, 30, "%.2d:%.2d:%.2d", h, m, s); if ((int)strlen(szTime) > iCharsLeft) { snprintf(szTime, 30, "%.2d:%.2d", h, m); } } else { snprintf(szTime, 30, "%i %s %.2d:%.2d:%.2d", d, (d == 1 ? "day" : "days"), h, m, s); if ((int)strlen(szTime) > iCharsLeft) { snprintf(szTime, 30, "%id %.2d:%.2d:%.2d", d, h, m, s); } if ((int)strlen(szTime) > iCharsLeft) { snprintf(szTime, 30, "%id %.2d:%.2d", d, h, m); } } szTime[29] = '\0'; const char* szShortCap = bUpTime ? " Up " : "Dn "; const char* szLongCap = bUpTime ? " Uptime " : " Download-time "; int iTimeLen = strlen(szTime); int iShortCapLen = strlen(szShortCap); int iLongCapLen = strlen(szLongCap); if (iCharsLeft - iTimeLen - iLongCapLen >= 0) { snprintf(szBuffer + m_iScreenWidth - iTimeLen - iLongCapLen, MAX_SCREEN_WIDTH - (m_iScreenWidth - iTimeLen - iLongCapLen), "%s%s", szLongCap, szTime); } else if (iCharsLeft - iTimeLen - iShortCapLen >= 0) { snprintf(szBuffer + m_iScreenWidth - iTimeLen - iShortCapLen, MAX_SCREEN_WIDTH - (m_iScreenWidth - iTimeLen - iShortCapLen), "%s%s", szShortCap, szTime); } else if (iCharsLeft - iTimeLen >= 0) { snprintf(szBuffer + m_iScreenWidth - iTimeLen, MAX_SCREEN_WIDTH - (m_iScreenWidth - iTimeLen), "%s", szTime); } PlotLine(szBuffer, iLineNr, 0, NCURSES_COLORPAIR_INFOLINE); } void NCursesFrontend::PrintGroupQueue() { int iLineNr = m_iQueueWinTop; DownloadQueue* pDownloadQueue = LockQueue(); if (pDownloadQueue->GetQueue()->empty()) { char szBuffer[MAX_SCREEN_WIDTH]; snprintf(szBuffer, sizeof(szBuffer), "%s NZBs for downloading", m_bUseColor ? "" : "*** "); szBuffer[MAX_SCREEN_WIDTH - 1] = '\0'; PrintTopHeader(szBuffer, iLineNr++, false); PlotLine("Ready to receive nzb-job", iLineNr++, 0, NCURSES_COLORPAIR_TEXT); } else { iLineNr++; ResetColWidths(); int iCalcLineNr = iLineNr; int i = 0; for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++, i++) { NZBInfo* pNZBInfo = *it; if (i >= m_iQueueScrollOffset && i < m_iQueueScrollOffset + m_iQueueWinHeight -1) { PrintGroupname(pNZBInfo, iCalcLineNr++, false, true); } } long long lRemaining = 0; long long lPaused = 0; i = 0; for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++, i++) { NZBInfo* pNZBInfo = *it; if (i >= m_iQueueScrollOffset && i < m_iQueueScrollOffset + m_iQueueWinHeight -1) { PrintGroupname(pNZBInfo, iLineNr++, i == m_iSelectedQueueEntry, false); } lRemaining += pNZBInfo->GetRemainingSize(); lPaused += pNZBInfo->GetPausedSize(); } char szRemaining[20]; Util::FormatSize(szRemaining, sizeof(szRemaining), lRemaining); char szUnpaused[20]; Util::FormatSize(szUnpaused, sizeof(szUnpaused), lRemaining - lPaused); char szBuffer[MAX_SCREEN_WIDTH]; snprintf(szBuffer, sizeof(szBuffer), " %sNZBs for downloading - %i NZBs in queue - %s / %s", m_bUseColor ? "" : "*** ", (int)pDownloadQueue->GetQueue()->size(), szRemaining, szUnpaused); szBuffer[MAX_SCREEN_WIDTH - 1] = '\0'; PrintTopHeader(szBuffer, m_iQueueWinTop, false); } UnlockQueue(); } void NCursesFrontend::ResetColWidths() { m_iColWidthFiles = 0; m_iColWidthTotal = 0; m_iColWidthLeft = 0; } void NCursesFrontend::PrintGroupname(NZBInfo* pNZBInfo, int iRow, bool bSelected, bool bCalcColWidth) { int color = NCURSES_COLORPAIR_TEXT; char chBrace1 = '['; char chBrace2 = ']'; if (m_eInputMode == eEditQueue && bSelected) { color = NCURSES_COLORPAIR_TEXTHIGHL; if (!m_bUseColor) { chBrace1 = '<'; chBrace2 = '>'; } } const char* szDownloading = ""; if (pNZBInfo->GetActiveDownloads() > 0) { szDownloading = " *"; } long long lUnpausedRemainingSize = pNZBInfo->GetRemainingSize() - pNZBInfo->GetPausedSize(); char szRemaining[20]; Util::FormatSize(szRemaining, sizeof(szRemaining), lUnpausedRemainingSize); char szPriority[100]; szPriority[0] = '\0'; if (pNZBInfo->GetPriority() != 0) { sprintf(szPriority, " [%+i]", pNZBInfo->GetPriority()); } char szBuffer[MAX_SCREEN_WIDTH]; // Format: // [id - id] Name Left-Files/Paused Total Left Time // [1-2] Nzb-name 999/999 999.99 MB 999.99 MB 00:00:00 int iNameLen = 0; if (bCalcColWidth) { iNameLen = m_iScreenWidth - 1 - 9 - 11 - 11 - 9; } else { iNameLen = m_iScreenWidth - 1 - m_iColWidthFiles - 2 - m_iColWidthTotal - 2 - m_iColWidthLeft - 2 - 9; } bool bPrintFormatted = iNameLen > 20; if (bPrintFormatted) { char szFiles[20]; snprintf(szFiles, 20, "%i/%i", (int)pNZBInfo->GetFileList()->size(), pNZBInfo->GetPausedFileCount()); szFiles[20-1] = '\0'; char szTotal[20]; Util::FormatSize(szTotal, sizeof(szTotal), pNZBInfo->GetSize()); char szNameWithIds[1024]; snprintf(szNameWithIds, 1024, "%c%i%c%s%s %s", chBrace1, pNZBInfo->GetID(), chBrace2, szPriority, szDownloading, pNZBInfo->GetName()); szNameWithIds[iNameLen] = '\0'; char szTime[100]; szTime[0] = '\0'; int iCurrentDownloadSpeed = m_bStandBy ? 0 : m_iCurrentDownloadSpeed; if (pNZBInfo->GetPausedSize() > 0 && lUnpausedRemainingSize == 0) { snprintf(szTime, 100, "[paused]"); Util::FormatSize(szRemaining, sizeof(szRemaining), pNZBInfo->GetRemainingSize()); } else if (iCurrentDownloadSpeed > 0 && !m_bPauseDownload) { long long remain_sec = (long long)(lUnpausedRemainingSize / iCurrentDownloadSpeed); int h = (int)(remain_sec / 3600); int m = (int)((remain_sec % 3600) / 60); int s = (int)(remain_sec % 60); if (h < 100) { snprintf(szTime, 100, "%.2d:%.2d:%.2d", h, m, s); } else { snprintf(szTime, 100, "99:99:99"); } } if (bCalcColWidth) { int iColWidthFiles = strlen(szFiles); m_iColWidthFiles = iColWidthFiles > m_iColWidthFiles ? iColWidthFiles : m_iColWidthFiles; int iColWidthTotal = strlen(szTotal); m_iColWidthTotal = iColWidthTotal > m_iColWidthTotal ? iColWidthTotal : m_iColWidthTotal; int iColWidthLeft = strlen(szRemaining); m_iColWidthLeft = iColWidthLeft > m_iColWidthLeft ? iColWidthLeft : m_iColWidthLeft; } else { snprintf(szBuffer, MAX_SCREEN_WIDTH, "%-*s %*s %*s %*s %8s", iNameLen, szNameWithIds, m_iColWidthFiles, szFiles, m_iColWidthTotal, szTotal, m_iColWidthLeft, szRemaining, szTime); } } else { snprintf(szBuffer, MAX_SCREEN_WIDTH, "%c%i%c%s %s", chBrace1, pNZBInfo->GetID(), chBrace2, szDownloading, pNZBInfo->GetName()); } szBuffer[MAX_SCREEN_WIDTH - 1] = '\0'; if (!bCalcColWidth) { PlotLine(szBuffer, iRow, 0, color); } } bool NCursesFrontend::EditQueue(DownloadQueue::EEditAction eAction, int iOffset) { int ID = 0; if (m_bGroupFiles) { DownloadQueue* pDownloadQueue = LockQueue(); if (m_iSelectedQueueEntry >= 0 && m_iSelectedQueueEntry < (int)pDownloadQueue->GetQueue()->size()) { NZBInfo* pNZBInfo = pDownloadQueue->GetQueue()->at(m_iSelectedQueueEntry); ID = pNZBInfo->GetID(); if (eAction == DownloadQueue::eaFilePause) { if (pNZBInfo->GetRemainingSize() == pNZBInfo->GetPausedSize()) { eAction = DownloadQueue::eaFileResume; } else if (pNZBInfo->GetPausedSize() == 0 && (pNZBInfo->GetRemainingParCount() > 0) && !(m_bLastPausePars && m_iLastEditEntry == m_iSelectedQueueEntry)) { eAction = DownloadQueue::eaFilePauseExtraPars; m_bLastPausePars = true; } else { eAction = DownloadQueue::eaFilePause; m_bLastPausePars = false; } } } UnlockQueue(); // map file-edit-actions to group-edit-actions DownloadQueue::EEditAction FileToGroupMap[] = { (DownloadQueue::EEditAction)0, DownloadQueue::eaGroupMoveOffset, DownloadQueue::eaGroupMoveTop, DownloadQueue::eaGroupMoveBottom, DownloadQueue::eaGroupPause, DownloadQueue::eaGroupResume, DownloadQueue::eaGroupDelete, DownloadQueue::eaGroupPauseAllPars, DownloadQueue::eaGroupPauseExtraPars }; eAction = FileToGroupMap[eAction]; } else { DownloadQueue* pDownloadQueue = LockQueue(); int iFileNum = 0; for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; for (FileList::iterator it2 = pNZBInfo->GetFileList()->begin(); it2 != pNZBInfo->GetFileList()->end(); it2++, iFileNum++) { if (m_iSelectedQueueEntry == iFileNum) { FileInfo* pFileInfo = *it2; ID = pFileInfo->GetID(); if (eAction == DownloadQueue::eaFilePause) { eAction = !pFileInfo->GetPaused() ? DownloadQueue::eaFilePause : DownloadQueue::eaFileResume; } } } } UnlockQueue(); } m_iLastEditEntry = m_iSelectedQueueEntry; NeedUpdateData(); if (ID != 0) { return ServerEditQueue(eAction, iOffset, ID); } else { return false; } } void NCursesFrontend::SetCurrentQueueEntry(int iEntry) { int iQueueSize = CalcQueueSize(); if (iEntry < 0) { iEntry = 0; } else if (iEntry > iQueueSize - 1) { iEntry = iQueueSize - 1; } if (iEntry > m_iQueueScrollOffset + m_iQueueWinClientHeight || iEntry < m_iQueueScrollOffset - m_iQueueWinClientHeight) { m_iQueueScrollOffset = iEntry - m_iQueueWinClientHeight / 2; } else if (iEntry < m_iQueueScrollOffset) { m_iQueueScrollOffset -= m_iQueueWinClientHeight; } else if (iEntry >= m_iQueueScrollOffset + m_iQueueWinClientHeight) { m_iQueueScrollOffset += m_iQueueWinClientHeight; } if (m_iQueueScrollOffset > iQueueSize - m_iQueueWinClientHeight) { m_iQueueScrollOffset = iQueueSize - m_iQueueWinClientHeight; } if (m_iQueueScrollOffset < 0) { m_iQueueScrollOffset = 0; } m_iSelectedQueueEntry = iEntry; } /* * Process keystrokes starting with the initialKey, which must not be * READKEY_EMPTY but has alread been set via ReadConsoleKey. */ void NCursesFrontend::UpdateInput(int initialKey) { int iKey = initialKey; while (iKey != READKEY_EMPTY) { int iQueueSize = CalcQueueSize(); // Normal or edit queue mode if (m_eInputMode == eNormal || m_eInputMode == eEditQueue) { switch (iKey) { case 'q': // Key 'q' for quit ExitProc(); break; case 'z': // show/hide NZBFilename m_bShowNZBname = !m_bShowNZBname; break; case 'w': // swicth window sizes if (m_QueueWindowPercentage == 50) { m_QueueWindowPercentage = 100; } else if (m_QueueWindowPercentage == 100 && m_eInputMode != eEditQueue) { m_QueueWindowPercentage = 0; } else { m_QueueWindowPercentage = 50; } CalcWindowSizes(); SetCurrentQueueEntry(m_iSelectedQueueEntry); break; case 'g': // group/ungroup files m_bGroupFiles = !m_bGroupFiles; SetCurrentQueueEntry(m_iSelectedQueueEntry); NeedUpdateData(); break; } } // Normal mode if (m_eInputMode == eNormal) { switch (iKey) { case 'p': // Key 'p' for pause if (!IsRemoteMode()) { info(m_bPauseDownload ? "Unpausing download" : "Pausing download"); } ServerPauseUnpause(!m_bPauseDownload); break; case 'e': case 10: // return case 13: // enter if (iQueueSize > 0) { m_eInputMode = eEditQueue; if (m_QueueWindowPercentage == 0) { m_QueueWindowPercentage = 50; } return; } break; case 'r': // Download rate m_eInputMode = eDownloadRate; m_iInputNumberIndex = 0; m_iInputValue = 0; return; case 't': // show/hide Timestamps m_bShowTimestamp = !m_bShowTimestamp; break; } } // Edit Queue mode if (m_eInputMode == eEditQueue) { switch (iKey) { case 'e': case 10: // return case 13: // enter m_eInputMode = eNormal; return; case KEY_DOWN: if (m_iSelectedQueueEntry < iQueueSize - 1) { SetCurrentQueueEntry(m_iSelectedQueueEntry + 1); } break; case KEY_UP: if (m_iSelectedQueueEntry > 0) { SetCurrentQueueEntry(m_iSelectedQueueEntry - 1); } break; case KEY_PPAGE: if (m_iSelectedQueueEntry > 0) { if (m_iSelectedQueueEntry == m_iQueueScrollOffset) { m_iQueueScrollOffset -= m_iQueueWinClientHeight; SetCurrentQueueEntry(m_iSelectedQueueEntry - m_iQueueWinClientHeight); } else { SetCurrentQueueEntry(m_iQueueScrollOffset); } } break; case KEY_NPAGE: if (m_iSelectedQueueEntry < iQueueSize - 1) { if (m_iSelectedQueueEntry == m_iQueueScrollOffset + m_iQueueWinClientHeight - 1) { m_iQueueScrollOffset += m_iQueueWinClientHeight; SetCurrentQueueEntry(m_iSelectedQueueEntry + m_iQueueWinClientHeight); } else { SetCurrentQueueEntry(m_iQueueScrollOffset + m_iQueueWinClientHeight - 1); } } break; case KEY_HOME: SetCurrentQueueEntry(0); break; case KEY_END: SetCurrentQueueEntry(iQueueSize > 0 ? iQueueSize - 1 : 0); break; case 'p': // Key 'p' for pause EditQueue(DownloadQueue::eaFilePause, 0); break; case 'd': SetHint(" Use Uppercase \"D\" for delete"); break; case 'D': // Delete entry if (EditQueue(DownloadQueue::eaFileDelete, 0)) { SetCurrentQueueEntry(m_iSelectedQueueEntry); } break; case 'u': if (EditQueue(DownloadQueue::eaFileMoveOffset, -1)) { SetCurrentQueueEntry(m_iSelectedQueueEntry - 1); } break; case 'n': if (EditQueue(DownloadQueue::eaFileMoveOffset, +1)) { SetCurrentQueueEntry(m_iSelectedQueueEntry + 1); } break; case 't': if (EditQueue(DownloadQueue::eaFileMoveTop, 0)) { SetCurrentQueueEntry(0); } break; case 'b': if (EditQueue(DownloadQueue::eaFileMoveBottom, 0)) { SetCurrentQueueEntry(iQueueSize > 0 ? iQueueSize - 1 : 0); } break; } } // Edit download rate input mode if (m_eInputMode == eDownloadRate) { // Numbers if (m_iInputNumberIndex < 5 && iKey >= '0' && iKey <= '9') { m_iInputValue = (m_iInputValue * 10) + (iKey - '0'); m_iInputNumberIndex++; } // Enter else if (iKey == 10 || iKey == 13) { ServerSetDownloadRate(m_iInputValue * 1024); m_eInputMode = eNormal; return; } // Escape else if (iKey == 27) { m_eInputMode = eNormal; return; } // Backspace else if (m_iInputNumberIndex > 0 && iKey == KEY_BACKSPACE) { int iRemain = m_iInputValue % 10; m_iInputValue = (m_iInputValue - iRemain) / 10; m_iInputNumberIndex--; } } iKey = ReadConsoleKey(); } } int NCursesFrontend::ReadConsoleKey() { #ifdef WIN32 HANDLE hConsole = GetStdHandle(STD_INPUT_HANDLE); DWORD NumberOfEvents; BOOL bOK = GetNumberOfConsoleInputEvents(hConsole, &NumberOfEvents); if (bOK && NumberOfEvents > 0) { while (NumberOfEvents--) { INPUT_RECORD InputRecord; DWORD NumberOfEventsRead; if (ReadConsoleInput(hConsole, &InputRecord, 1, &NumberOfEventsRead) && NumberOfEventsRead > 0 && InputRecord.EventType == KEY_EVENT && InputRecord.Event.KeyEvent.bKeyDown) { char c = tolower(InputRecord.Event.KeyEvent.wVirtualKeyCode); if (bool(InputRecord.Event.KeyEvent.dwControlKeyState & CAPSLOCK_ON) ^ bool(InputRecord.Event.KeyEvent.dwControlKeyState & SHIFT_PRESSED)) { c = toupper(c); } return c; } } } return READKEY_EMPTY; #else return getch(); #endif } #endif nzbget-16.4/daemon/frontend/ColoredFrontend.cpp0000644000175000017500000001103712630544544021437 0ustar andreasandreas/* * This file if part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2010 Andrey Prygunkov * * 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 #include #include #ifndef WIN32 #include #endif #include "nzbget.h" #include "ColoredFrontend.h" #include "Util.h" ColoredFrontend::ColoredFrontend() { m_bSummary = true; m_bNeedGoBack = false; #ifdef WIN32 m_hConsole = GetStdHandle(STD_OUTPUT_HANDLE); #endif } void ColoredFrontend::BeforePrint() { if (m_bNeedGoBack) { // go back one line #ifdef WIN32 CONSOLE_SCREEN_BUFFER_INFO BufInfo; GetConsoleScreenBufferInfo(m_hConsole, &BufInfo); BufInfo.dwCursorPosition.Y--; SetConsoleCursorPosition(m_hConsole, BufInfo.dwCursorPosition); #else printf("\r\033[1A"); #endif m_bNeedGoBack = false; } } void ColoredFrontend::PrintStatus() { char tmp[1024]; char timeString[100]; timeString[0] = '\0'; int iCurrentDownloadSpeed = m_bStandBy ? 0 : m_iCurrentDownloadSpeed; if (iCurrentDownloadSpeed > 0 && !m_bPauseDownload) { long long remain_sec = (long long)(m_lRemainingSize / iCurrentDownloadSpeed); int h = (int)(remain_sec / 3600); int m = (int)((remain_sec % 3600) / 60); int s = (int)(remain_sec % 60); sprintf(timeString, " (~ %.2d:%.2d:%.2d)", h, m, s); } char szDownloadLimit[128]; if (m_iDownloadLimit > 0) { sprintf(szDownloadLimit, ", Limit %i KB/s", m_iDownloadLimit / 1024); } else { szDownloadLimit[0] = 0; } char szPostStatus[128]; if (m_iPostJobCount > 0) { sprintf(szPostStatus, ", %i post-job%s", m_iPostJobCount, m_iPostJobCount > 1 ? "s" : ""); } else { szPostStatus[0] = 0; } #ifdef WIN32 char* szControlSeq = ""; #else printf("\033[s"); const char* szControlSeq = "\033[K"; #endif char szFileSize[20]; char szCurrendSpeed[20]; snprintf(tmp, 1024, " %d threads, %s, %s remaining%s%s%s%s%s\n", m_iThreadCount, Util::FormatSpeed(szCurrendSpeed, sizeof(szCurrendSpeed), iCurrentDownloadSpeed), Util::FormatSize(szFileSize, sizeof(szFileSize), m_lRemainingSize), timeString, szPostStatus, m_bPauseDownload ? (m_bStandBy ? ", Paused" : ", Pausing") : "", szDownloadLimit, szControlSeq); tmp[1024-1] = '\0'; printf("%s", tmp); m_bNeedGoBack = true; } void ColoredFrontend::PrintMessage(Message * pMessage) { #ifdef WIN32 switch (pMessage->GetKind()) { case Message::mkDebug: SetConsoleTextAttribute(m_hConsole, 8); printf("[DEBUG]"); break; case Message::mkError: SetConsoleTextAttribute(m_hConsole, 4); printf("[ERROR]"); break; case Message::mkWarning: SetConsoleTextAttribute(m_hConsole, 5); printf("[WARNING]"); break; case Message::mkInfo: SetConsoleTextAttribute(m_hConsole, 2); printf("[INFO]"); break; case Message::mkDetail: SetConsoleTextAttribute(m_hConsole, 2); printf("[DETAIL]"); break; } SetConsoleTextAttribute(m_hConsole, 7); char* msg = strdup(pMessage->GetText()); CharToOem(msg, msg); printf(" %s\n", msg); free(msg); #else const char* msg = pMessage->GetText(); switch (pMessage->GetKind()) { case Message::mkDebug: printf("[DEBUG] %s\033[K\n", msg); break; case Message::mkError: printf("\033[31m[ERROR]\033[39m %s\033[K\n", msg); break; case Message::mkWarning: printf("\033[35m[WARNING]\033[39m %s\033[K\n", msg); break; case Message::mkInfo: printf("\033[32m[INFO]\033[39m %s\033[K\n", msg); break; case Message::mkDetail: printf("\033[32m[DETAIL]\033[39m %s\033[K\n", msg); break; } #endif } void ColoredFrontend::PrintSkip() { #ifdef WIN32 printf(".....\n"); #else printf(".....\033[K\n"); #endif } void ColoredFrontend::BeforeExit() { if (IsRemoteMode()) { printf("\n"); } } nzbget-16.4/daemon/frontend/Frontend.cpp0000644000175000017500000002274212630544544020134 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #include #ifndef WIN32 #include #include #include #endif #include "nzbget.h" #include "Options.h" #include "Frontend.h" #include "Log.h" #include "Connection.h" #include "MessageBase.h" #include "RemoteClient.h" #include "Util.h" #include "StatMeter.h" Frontend::Frontend() { debug("Creating Frontend"); m_iNeededLogFirstID = 0; m_iNeededLogEntries = 0; m_bSummary = false; m_bFileList = false; m_iCurrentDownloadSpeed = 0; m_lRemainingSize = 0; m_bPauseDownload = false; m_iDownloadLimit = 0; m_iThreadCount = 0; m_iPostJobCount = 0; m_iUpTimeSec = 0; m_iDnTimeSec = 0; m_iAllBytes = 0; m_bStandBy = 0; m_iUpdateInterval = g_pOptions->GetUpdateInterval(); } bool Frontend::PrepareData() { if (IsRemoteMode()) { if (IsStopped()) { return false; } if (!RequestMessages() || ((m_bSummary || m_bFileList) && !RequestFileList())) { const char* szControlIP = !strcmp(g_pOptions->GetControlIP(), "0.0.0.0") ? "127.0.0.1" : g_pOptions->GetControlIP(); printf("\nUnable to send request to nzbget-server at %s (port %i) \n", szControlIP, g_pOptions->GetControlPort()); Stop(); return false; } } else { if (m_bSummary) { m_iCurrentDownloadSpeed = g_pStatMeter->CalcCurrentDownloadSpeed(); m_bPauseDownload = g_pOptions->GetPauseDownload(); m_iDownloadLimit = g_pOptions->GetDownloadRate(); m_iThreadCount = Thread::GetThreadCount(); g_pStatMeter->CalcTotalStat(&m_iUpTimeSec, &m_iDnTimeSec, &m_iAllBytes, &m_bStandBy); DownloadQueue *pDownloadQueue = DownloadQueue::Lock(); m_iPostJobCount = 0; for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end(); it++) { NZBInfo* pNZBInfo = *it; m_iPostJobCount += pNZBInfo->GetPostInfo() ? 1 : 0; } pDownloadQueue->CalcRemainingSize(&m_lRemainingSize, NULL); DownloadQueue::Unlock(); } } return true; } void Frontend::FreeData() { if (IsRemoteMode()) { m_RemoteMessages.Clear(); DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); pDownloadQueue->GetQueue()->Clear(); DownloadQueue::Unlock(); } } MessageList* Frontend::LockMessages() { if (IsRemoteMode()) { return &m_RemoteMessages; } else { return g_pLog->LockMessages(); } } void Frontend::UnlockMessages() { if (!IsRemoteMode()) { g_pLog->UnlockMessages(); } } DownloadQueue* Frontend::LockQueue() { return DownloadQueue::Lock(); } void Frontend::UnlockQueue() { DownloadQueue::Unlock(); } bool Frontend::IsRemoteMode() { return g_pOptions->GetRemoteClientMode(); } void Frontend::ServerPauseUnpause(bool bPause) { if (IsRemoteMode()) { RequestPauseUnpause(bPause); } else { g_pOptions->SetResumeTime(0); g_pOptions->SetPauseDownload(bPause); } } void Frontend::ServerSetDownloadRate(int iRate) { if (IsRemoteMode()) { RequestSetDownloadRate(iRate); } else { g_pOptions->SetDownloadRate(iRate); } } bool Frontend::ServerEditQueue(DownloadQueue::EEditAction eAction, int iOffset, int iID) { if (IsRemoteMode()) { return RequestEditQueue(eAction, iOffset, iID); } else { DownloadQueue* pDownloadQueue = LockQueue(); bool bOK = pDownloadQueue->EditEntry(iID, eAction, iOffset, NULL); UnlockQueue(); return bOK; } return false; } void Frontend::InitMessageBase(SNZBRequestBase* pMessageBase, int iRequest, int iSize) { 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'; } bool Frontend::RequestMessages() { const char* szControlIP = !strcmp(g_pOptions->GetControlIP(), "0.0.0.0") ? "127.0.0.1" : g_pOptions->GetControlIP(); Connection connection(szControlIP, g_pOptions->GetControlPort(), false); bool OK = connection.Connect(); if (!OK) { return false; } SNZBLogRequest LogRequest; InitMessageBase(&LogRequest.m_MessageBase, eRemoteRequestLog, sizeof(LogRequest)); LogRequest.m_iLines = htonl(m_iNeededLogEntries); if (m_iNeededLogEntries == 0) { LogRequest.m_iIDFrom = htonl(m_iNeededLogFirstID > 0 ? m_iNeededLogFirstID : 1); } else { LogRequest.m_iIDFrom = 0; } if (!connection.Send((char*)(&LogRequest), sizeof(LogRequest))) { return false; } // Now listen for the returned log SNZBLogResponse LogResponse; bool bRead = connection.Recv((char*) &LogResponse, sizeof(LogResponse)); if (!bRead || (int)ntohl(LogResponse.m_MessageBase.m_iSignature) != (int)NZBMESSAGE_SIGNATURE || ntohl(LogResponse.m_MessageBase.m_iStructSize) != sizeof(LogResponse)) { return false; } char* pBuf = NULL; if (ntohl(LogResponse.m_iTrailingDataLength) > 0) { pBuf = (char*)malloc(ntohl(LogResponse.m_iTrailingDataLength)); if (!connection.Recv(pBuf, ntohl(LogResponse.m_iTrailingDataLength))) { free(pBuf); return false; } } connection.Disconnect(); if (ntohl(LogResponse.m_iTrailingDataLength) > 0) { char* pBufPtr = (char*)pBuf; for (unsigned int i = 0; i < ntohl(LogResponse.m_iNrTrailingEntries); i++) { SNZBLogResponseEntry* pLogAnswer = (SNZBLogResponseEntry*) pBufPtr; char* szText = pBufPtr + sizeof(SNZBLogResponseEntry); Message* pMessage = new Message(ntohl(pLogAnswer->m_iID), (Message::EKind)ntohl(pLogAnswer->m_iKind), ntohl(pLogAnswer->m_tTime), szText); m_RemoteMessages.push_back(pMessage); pBufPtr += sizeof(SNZBLogResponseEntry) + ntohl(pLogAnswer->m_iTextLen); } free(pBuf); } return true; } bool Frontend::RequestFileList() { const char* szControlIP = !strcmp(g_pOptions->GetControlIP(), "0.0.0.0") ? "127.0.0.1" : g_pOptions->GetControlIP(); Connection connection(szControlIP, g_pOptions->GetControlPort(), false); bool OK = connection.Connect(); if (!OK) { return false; } SNZBListRequest ListRequest; InitMessageBase(&ListRequest.m_MessageBase, eRemoteRequestList, sizeof(ListRequest)); ListRequest.m_bFileList = htonl(m_bFileList); ListRequest.m_bServerState = htonl(m_bSummary); if (!connection.Send((char*)(&ListRequest), sizeof(ListRequest))) { return false; } // Now listen for the returned list SNZBListResponse ListResponse; bool bRead = connection.Recv((char*) &ListResponse, sizeof(ListResponse)); if (!bRead || (int)ntohl(ListResponse.m_MessageBase.m_iSignature) != (int)NZBMESSAGE_SIGNATURE || ntohl(ListResponse.m_MessageBase.m_iStructSize) != sizeof(ListResponse)) { return false; } char* pBuf = NULL; if (ntohl(ListResponse.m_iTrailingDataLength) > 0) { pBuf = (char*)malloc(ntohl(ListResponse.m_iTrailingDataLength)); if (!connection.Recv(pBuf, ntohl(ListResponse.m_iTrailingDataLength))) { free(pBuf); return false; } } connection.Disconnect(); if (m_bSummary) { m_bPauseDownload = ntohl(ListResponse.m_bDownloadPaused); m_lRemainingSize = Util::JoinInt64(ntohl(ListResponse.m_iRemainingSizeHi), ntohl(ListResponse.m_iRemainingSizeLo)); m_iCurrentDownloadSpeed = ntohl(ListResponse.m_iDownloadRate); m_iDownloadLimit = ntohl(ListResponse.m_iDownloadLimit); m_iThreadCount = ntohl(ListResponse.m_iThreadCount); m_iPostJobCount = ntohl(ListResponse.m_iPostJobCount); m_iUpTimeSec = ntohl(ListResponse.m_iUpTimeSec); m_iDnTimeSec = ntohl(ListResponse.m_iDownloadTimeSec); m_bStandBy = ntohl(ListResponse.m_bDownloadStandBy); m_iAllBytes = Util::JoinInt64(ntohl(ListResponse.m_iDownloadedBytesHi), ntohl(ListResponse.m_iDownloadedBytesLo)); } if (m_bFileList && ntohl(ListResponse.m_iTrailingDataLength) > 0) { RemoteClient client; client.SetVerbose(false); DownloadQueue* pDownloadQueue = LockQueue(); client.BuildFileList(&ListResponse, pBuf, pDownloadQueue); UnlockQueue(); } if (pBuf) { free(pBuf); } return true; } bool Frontend::RequestPauseUnpause(bool bPause) { RemoteClient client; client.SetVerbose(false); return client.RequestServerPauseUnpause(bPause, eRemotePauseUnpauseActionDownload); } bool Frontend::RequestSetDownloadRate(int iRate) { RemoteClient client; client.SetVerbose(false); return client.RequestServerSetDownloadRate(iRate); } bool Frontend::RequestEditQueue(DownloadQueue::EEditAction eAction, int iOffset, int iID) { RemoteClient client; client.SetVerbose(false); return client.RequestServerEditQueue(eAction, iOffset, NULL, &iID, 1, NULL, eRemoteMatchModeID); } nzbget-16.4/daemon/frontend/Frontend.h0000644000175000017500000000441312630544544017574 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 FRONTEND_H #define FRONTEND_H #include "Thread.h" #include "Log.h" #include "DownloadInfo.h" #include "MessageBase.h" #include "QueueEditor.h" class Frontend : public Thread { private: MessageList m_RemoteMessages; bool RequestMessages(); bool RequestFileList(); protected: bool m_bSummary; bool m_bFileList; unsigned int m_iNeededLogEntries; unsigned int m_iNeededLogFirstID; int m_iUpdateInterval; // summary int m_iCurrentDownloadSpeed; long long m_lRemainingSize; bool m_bPauseDownload; int m_iDownloadLimit; int m_iThreadCount; int m_iPostJobCount; int m_iUpTimeSec; int m_iDnTimeSec; long long m_iAllBytes; bool m_bStandBy; bool PrepareData(); void FreeData(); MessageList* LockMessages(); void UnlockMessages(); DownloadQueue* LockQueue(); void UnlockQueue(); bool IsRemoteMode(); void InitMessageBase(SNZBRequestBase* pMessageBase, int iRequest, int iSize); void ServerPauseUnpause(bool bPause); bool RequestPauseUnpause(bool bPause); void ServerSetDownloadRate(int iRate); bool RequestSetDownloadRate(int iRate); bool ServerEditQueue(DownloadQueue::EEditAction eAction, int iOffset, int iEntry); bool RequestEditQueue(DownloadQueue::EEditAction eAction, int iOffset, int iID); public: Frontend(); }; #endif nzbget-16.4/daemon/frontend/LoggableFrontend.cpp0000644000175000017500000000544412630544544021571 0ustar andreasandreas/* * This file if part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #include #ifndef WIN32 #include #endif #include "nzbget.h" #include "LoggableFrontend.h" #include "Log.h" LoggableFrontend::LoggableFrontend() { debug("Creating LoggableFrontend"); m_iNeededLogEntries = 0; m_bSummary = false; m_bFileList = false; } void LoggableFrontend::Run() { debug("Entering LoggableFrontend-loop"); while (!IsStopped()) { Update(); usleep(m_iUpdateInterval * 1000); } // Printing the last messages Update(); BeforeExit(); debug("Exiting LoggableFrontend-loop"); } void LoggableFrontend::Update() { if (!PrepareData()) { FreeData(); return; } BeforePrint(); MessageList* pMessages = LockMessages(); if (!pMessages->empty()) { Message* pFirstMessage = pMessages->front(); int iStart = m_iNeededLogFirstID - pFirstMessage->GetID() + 1; if (iStart < 0) { PrintSkip(); iStart = 0; } for (unsigned int i = (unsigned int)iStart; i < pMessages->size(); i++) { PrintMessage((*pMessages)[i]); m_iNeededLogFirstID = (*pMessages)[i]->GetID(); } } UnlockMessages(); PrintStatus(); FreeData(); fflush(stdout); } void LoggableFrontend::PrintMessage(Message * pMessage) { #ifdef WIN32 char* msg = strdup(pMessage->GetText()); CharToOem(msg, msg); #else const char* msg = pMessage->GetText(); #endif switch (pMessage->GetKind()) { case Message::mkDebug: printf("[DEBUG] %s\n", msg); break; case Message::mkError: printf("[ERROR] %s\n", msg); break; case Message::mkWarning: printf("[WARNING] %s\n", msg); break; case Message::mkInfo: printf("[INFO] %s\n", msg); break; case Message::mkDetail: printf("[DETAIL] %s\n", msg); break; } #ifdef WIN32 free(msg); #endif } void LoggableFrontend::PrintSkip() { printf(".....\n"); } nzbget-16.4/daemon/frontend/NCursesFrontend.h0000644000175000017500000000650512630544544021103 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2014 Andrey Prygunkov * * 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 NCURSESFRONTEND_H #define NCURSESFRONTEND_H #ifndef DISABLE_CURSES #include #include #include "Frontend.h" #include "Log.h" #include "DownloadInfo.h" class NCursesFrontend : public Frontend { private: enum EInputMode { eNormal, eEditQueue, eDownloadRate }; bool m_bUseColor; int m_iDataUpdatePos; bool m_bUpdateNextTime; int m_iScreenHeight; int m_iScreenWidth; int m_iQueueWinTop; int m_iQueueWinHeight; int m_iQueueWinClientHeight; int m_iMessagesWinTop; int m_iMessagesWinHeight; int m_iMessagesWinClientHeight; int m_iSelectedQueueEntry; int m_iLastEditEntry; bool m_bLastPausePars; int m_iQueueScrollOffset; char* m_szHint; time_t m_tStartHint; int m_iColWidthFiles; int m_iColWidthTotal; int m_iColWidthLeft; // Inputting numbers int m_iInputNumberIndex; int m_iInputValue; #ifdef WIN32 CHAR_INFO* m_pScreenBuffer; CHAR_INFO* m_pOldScreenBuffer; int m_iScreenBufferSize; std::vector m_ColorAttr; #else void* m_pWindow; // WINDOW* #endif EInputMode m_eInputMode; bool m_bShowNZBname; bool m_bShowTimestamp; bool m_bGroupFiles; int m_QueueWindowPercentage; #ifdef WIN32 void init_pair(int iColorNumber, WORD wForeColor, WORD wBackColor); #endif void PlotLine(const char * szString, int iRow, int iPos, int iColorPair); void PlotText(const char * szString, int iRow, int iPos, int iColorPair, bool bBlink); void PrintMessages(); void PrintQueue(); void PrintFileQueue(); void PrintFilename(FileInfo* pFileInfo, int iRow, bool bSelected); void PrintGroupQueue(); void ResetColWidths(); void PrintGroupname(NZBInfo* pNZBInfo, int iRow, bool bSelected, bool bCalcColWidth); void PrintTopHeader(char* szHeader, int iLineNr, bool bUpTime); int PrintMessage(Message* Msg, int iRow, int iMaxLines); void PrintKeyInputBar(); void PrintStatus(); void UpdateInput(int initialKey); void Update(int iKey); void SetCurrentQueueEntry(int iEntry); void CalcWindowSizes(); void RefreshScreen(); int ReadConsoleKey(); int CalcQueueSize(); void NeedUpdateData(); bool EditQueue(DownloadQueue::EEditAction eAction, int iOffset); void SetHint(const char* szHint); protected: virtual void Run(); public: NCursesFrontend(); virtual ~NCursesFrontend(); }; #endif #endif nzbget-16.4/daemon/frontend/ColoredFrontend.h0000644000175000017500000000255312630544544021107 0ustar andreasandreas/* * This file if part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007 Andrey Prygunkov * * 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 COLOREDFRONTEND_H #define COLOREDFRONTEND_H #include "LoggableFrontend.h" #include "Log.h" class ColoredFrontend : public LoggableFrontend { private: bool m_bNeedGoBack; #ifdef WIN32 HANDLE m_hConsole; #endif protected: virtual void BeforePrint(); virtual void PrintMessage(Message* pMessage); virtual void PrintStatus(); virtual void PrintSkip(); virtual void BeforeExit(); public: ColoredFrontend(); }; #endif nzbget-16.4/daemon/main/0000755000175000017500000000000012630544544014747 5ustar andreasandreasnzbget-16.4/daemon/main/Options.h0000644000175000017500000004003612630544544016556 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 OPTIONS_H #define OPTIONS_H #include #include #include #include "Thread.h" #include "Util.h" class Options { public: enum EWriteLog { wlNone, wlAppend, wlReset, wlRotate }; enum EMessageTarget { mtNone, mtScreen, mtLog, mtBoth }; enum EOutputMode { omLoggable, omColored, omNCurses }; enum EParCheck { pcAuto, pcAlways, pcForce, pcManual }; enum EParScan { psLimited, psExtended, psFull, psDupe }; enum EHealthCheck { hcPause, hcDelete, hcNone }; enum ESchedulerCommand { scPauseDownload, scUnpauseDownload, scPausePostProcess, scUnpausePostProcess, scDownloadRate, scScript, scProcess, scPauseScan, scUnpauseScan, scActivateServer, scDeactivateServer, scFetchFeed }; class OptEntry { private: char* m_szName; char* m_szValue; char* m_szDefValue; int m_iLineNo; void SetLineNo(int iLineNo) { m_iLineNo = iLineNo; } friend class Options; public: OptEntry(); OptEntry(const char* szName, const char* szValue); ~OptEntry(); void SetName(const char* szName); const char* GetName() { return m_szName; } void SetValue(const char* szValue); const char* GetValue() { return m_szValue; } const char* GetDefValue() { return m_szDefValue; } int GetLineNo() { return m_iLineNo; } bool Restricted(); }; typedef std::vector OptEntriesBase; class OptEntries: public OptEntriesBase { public: ~OptEntries(); OptEntry* FindOption(const char* szName); }; typedef std::vector NameList; typedef std::vector CmdOptList; class Category { private: char* m_szName; char* m_szDestDir; bool m_bUnpack; char* m_szPostScript; NameList m_Aliases; public: Category(const char* szName, const char* szDestDir, bool bUnpack, const char* szPostScript); ~Category(); const char* GetName() { return m_szName; } const char* GetDestDir() { return m_szDestDir; } bool GetUnpack() { return m_bUnpack; } const char* GetPostScript() { return m_szPostScript; } NameList* GetAliases() { return &m_Aliases; } }; typedef std::vector CategoriesBase; class Categories: public CategoriesBase { public: ~Categories(); Category* FindCategory(const char* szName, bool bSearchAliases); }; class Extender { public: virtual void AddNewsServer(int iID, bool bActive, const char* szName, const char* szHost, int iPort, const char* szUser, const char* szPass, bool bJoinGroup, bool bTLS, const char* szCipher, int iMaxConnections, int iRetention, int iLevel, int iGroup) = 0; virtual void AddFeed(int iID, const char* szName, const char* szUrl, int iInterval, const char* szFilter, bool bBacklog, bool bPauseNzb, const char* szCategory, int iPriority, const char* szFeedScript) {} virtual void AddTask(int iID, int iHours, int iMinutes, int iWeekDaysBits, ESchedulerCommand eCommand, const char* szParam) {} virtual void SetupFirstStart() {} }; private: OptEntries m_OptEntries; Mutex m_mutexOptEntries; Categories m_Categories; bool m_bNoDiskAccess; bool m_bFatalError; Extender* m_pExtender; // Options bool m_bConfigErrors; int m_iConfigLine; char* m_szAppDir; char* m_szConfigFilename; char* m_szDestDir; char* m_szInterDir; char* m_szTempDir; char* m_szQueueDir; char* m_szNzbDir; char* m_szWebDir; char* m_szConfigTemplate; char* m_szScriptDir; char* m_szRequiredDir; EMessageTarget m_eInfoTarget; EMessageTarget m_eWarningTarget; EMessageTarget m_eErrorTarget; EMessageTarget m_eDebugTarget; EMessageTarget m_eDetailTarget; bool m_bDecode; bool m_bBrokenLog; bool m_bNzbLog; int m_iArticleTimeout; int m_iUrlTimeout; int m_iTerminateTimeout; bool m_bAppendCategoryDir; bool m_bContinuePartial; int m_iRetries; int m_iRetryInterval; bool m_bSaveQueue; bool m_bFlushQueue; bool m_bDupeCheck; char* m_szControlIP; char* m_szControlUsername; char* m_szControlPassword; char* m_szRestrictedUsername; char* m_szRestrictedPassword; char* m_szAddUsername; char* m_szAddPassword; int m_iControlPort; bool m_bSecureControl; int m_iSecurePort; char* m_szSecureCert; char* m_szSecureKey; char* m_szAuthorizedIP; char* m_szLockFile; char* m_szDaemonUsername; EOutputMode m_eOutputMode; bool m_bReloadQueue; int m_iUrlConnections; int m_iLogBufferSize; EWriteLog m_eWriteLog; int m_iRotateLog; char* m_szLogFile; EParCheck m_eParCheck; bool m_bParRepair; EParScan m_eParScan; bool m_bParQuick; bool m_bParRename; int m_iParBuffer; int m_iParThreads; EHealthCheck m_eHealthCheck; char* m_szPostScript; char* m_szScriptOrder; char* m_szScanScript; char* m_szQueueScript; char* m_szFeedScript; bool m_bNoConfig; int m_iUMask; int m_iUpdateInterval; bool m_bCursesNZBName; bool m_bCursesTime; bool m_bCursesGroup; bool m_bCrcCheck; bool m_bDirectWrite; int m_iWriteBuffer; int m_iNzbDirInterval; int m_iNzbDirFileAge; bool m_bParCleanupQueue; int m_iDiskSpace; bool m_bTLS; bool m_bDumpCore; bool m_bParPauseQueue; bool m_bScriptPauseQueue; bool m_bNzbCleanupDisk; bool m_bDeleteCleanupDisk; int m_iParTimeLimit; int m_iKeepHistory; bool m_bAccurateRate; bool m_bUnpack; bool m_bUnpackCleanupDisk; char* m_szUnrarCmd; char* m_szSevenZipCmd; char* m_szUnpackPassFile; bool m_bUnpackPauseQueue; char* m_szExtCleanupDisk; char* m_szParIgnoreExt; int m_iFeedHistory; bool m_bUrlForce; int m_iTimeCorrection; int m_iPropagationDelay; int m_iArticleCache; int m_iEventInterval; // Current state bool m_bServerMode; bool m_bRemoteClientMode; bool m_bPauseDownload; bool m_bPausePostProcess; bool m_bPauseScan; bool m_bTempPauseDownload; int m_iDownloadRate; time_t m_tResumeTime; int m_iLocalTimeOffset; bool m_bTempPausePostprocess; void Init(const char* szExeName, const char* szConfigFilename, bool bNoConfig, CmdOptList* pCommandLineOptions, bool bNoDiskAccess, Extender* pExtender); void InitDefaults(); void InitOptions(); void InitOptFile(); void InitServers(); void InitCategories(); void InitScheduler(); void InitFeeds(); void InitCommandLineOptions(CmdOptList* pCommandLineOptions); void CheckOptions(); void Dump(); int ParseEnumValue(const char* OptName, int argc, const char* argn[], const int argv[]); int ParseIntValue(const char* OptName, int iBase); OptEntry* FindOption(const char* optname); const char* GetOption(const char* optname); void SetOption(const char* optname, const char* value); bool SetOptionString(const char* option); bool ValidateOptionName(const char* optname, const char* optvalue); void LoadConfigFile(); void CheckDir(char** dir, const char* szOptionName, const char* szParentDir, bool bAllowEmpty, bool bCreate); bool ParseTime(const char* szTime, int* pHours, int* pMinutes); bool ParseWeekDays(const char* szWeekDays, int* pWeekDaysBits); void ConfigError(const char* msg, ...); void ConfigWarn(const char* msg, ...); void LocateOptionSrcPos(const char *szOptionName); void ConvertOldOption(char *szOption, int iOptionBufLen, char *szValue, int iValueBufLen); public: Options(const char* szExeName, const char* szConfigFilename, bool bNoConfig, CmdOptList* pCommandLineOptions, Extender* pExtender); Options(CmdOptList* pCommandLineOptions, Extender* pExtender); ~Options(); bool SplitOptionString(const char* option, char** pOptName, char** pOptValue); bool GetFatalError() { return m_bFatalError; } OptEntries* LockOptEntries(); void UnlockOptEntries(); // Options const char* GetConfigFilename() { return m_szConfigFilename; } bool GetConfigErrors() { return m_bConfigErrors; } const char* GetAppDir() { return m_szAppDir; } const char* GetDestDir() { return m_szDestDir; } const char* GetInterDir() { return m_szInterDir; } const char* GetTempDir() { return m_szTempDir; } const char* GetQueueDir() { return m_szQueueDir; } const char* GetNzbDir() { return m_szNzbDir; } const char* GetWebDir() { return m_szWebDir; } const char* GetConfigTemplate() { return m_szConfigTemplate; } const char* GetScriptDir() { return m_szScriptDir; } const char* GetRequiredDir() { return m_szRequiredDir; } bool GetBrokenLog() const { return m_bBrokenLog; } bool GetNzbLog() const { return m_bNzbLog; } EMessageTarget GetInfoTarget() const { return m_eInfoTarget; } EMessageTarget GetWarningTarget() const { return m_eWarningTarget; } EMessageTarget GetErrorTarget() const { return m_eErrorTarget; } EMessageTarget GetDebugTarget() const { return m_eDebugTarget; } EMessageTarget GetDetailTarget() const { return m_eDetailTarget; } int GetArticleTimeout() { return m_iArticleTimeout; } int GetUrlTimeout() { return m_iUrlTimeout; } int GetTerminateTimeout() { return m_iTerminateTimeout; } bool GetDecode() { return m_bDecode; }; bool GetAppendCategoryDir() { return m_bAppendCategoryDir; } bool GetContinuePartial() { return m_bContinuePartial; } int GetRetries() { return m_iRetries; } int GetRetryInterval() { return m_iRetryInterval; } bool GetSaveQueue() { return m_bSaveQueue; } bool GetFlushQueue() { return m_bFlushQueue; } bool GetDupeCheck() { return m_bDupeCheck; } const char* GetControlIP() { return m_szControlIP; } const char* GetControlUsername() { return m_szControlUsername; } const char* GetControlPassword() { return m_szControlPassword; } const char* GetRestrictedUsername() { return m_szRestrictedUsername; } const char* GetRestrictedPassword() { return m_szRestrictedPassword; } const char* GetAddUsername() { return m_szAddUsername; } const char* GetAddPassword() { return m_szAddPassword; } int GetControlPort() { return m_iControlPort; } bool GetSecureControl() { return m_bSecureControl; } int GetSecurePort() { return m_iSecurePort; } const char* GetSecureCert() { return m_szSecureCert; } const char* GetSecureKey() { return m_szSecureKey; } const char* GetAuthorizedIP() { return m_szAuthorizedIP; } const char* GetLockFile() { return m_szLockFile; } const char* GetDaemonUsername() { return m_szDaemonUsername; } EOutputMode GetOutputMode() { return m_eOutputMode; } bool GetReloadQueue() { return m_bReloadQueue; } int GetUrlConnections() { return m_iUrlConnections; } int GetLogBufferSize() { return m_iLogBufferSize; } EWriteLog GetWriteLog() { return m_eWriteLog; } const char* GetLogFile() { return m_szLogFile; } int GetRotateLog() { return m_iRotateLog; } EParCheck GetParCheck() { return m_eParCheck; } bool GetParRepair() { return m_bParRepair; } EParScan GetParScan() { return m_eParScan; } bool GetParQuick() { return m_bParQuick; } bool GetParRename() { return m_bParRename; } int GetParBuffer() { return m_iParBuffer; } int GetParThreads() { return m_iParThreads; } EHealthCheck GetHealthCheck() { return m_eHealthCheck; } const char* GetScriptOrder() { return m_szScriptOrder; } const char* GetPostScript() { return m_szPostScript; } const char* GetScanScript() { return m_szScanScript; } const char* GetQueueScript() { return m_szQueueScript; } const char* GetFeedScript() { return m_szFeedScript; } int GetUMask() { return m_iUMask; } int GetUpdateInterval() {return m_iUpdateInterval; } bool GetCursesNZBName() { return m_bCursesNZBName; } bool GetCursesTime() { return m_bCursesTime; } bool GetCursesGroup() { return m_bCursesGroup; } bool GetCrcCheck() { return m_bCrcCheck; } bool GetDirectWrite() { return m_bDirectWrite; } int GetWriteBuffer() { return m_iWriteBuffer; } int GetNzbDirInterval() { return m_iNzbDirInterval; } int GetNzbDirFileAge() { return m_iNzbDirFileAge; } bool GetParCleanupQueue() { return m_bParCleanupQueue; } int GetDiskSpace() { return m_iDiskSpace; } bool GetTLS() { return m_bTLS; } bool GetDumpCore() { return m_bDumpCore; } bool GetParPauseQueue() { return m_bParPauseQueue; } bool GetScriptPauseQueue() { return m_bScriptPauseQueue; } bool GetNzbCleanupDisk() { return m_bNzbCleanupDisk; } bool GetDeleteCleanupDisk() { return m_bDeleteCleanupDisk; } int GetParTimeLimit() { return m_iParTimeLimit; } int GetKeepHistory() { return m_iKeepHistory; } bool GetAccurateRate() { return m_bAccurateRate; } bool GetUnpack() { return m_bUnpack; } bool GetUnpackCleanupDisk() { return m_bUnpackCleanupDisk; } const char* GetUnrarCmd() { return m_szUnrarCmd; } const char* GetSevenZipCmd() { return m_szSevenZipCmd; } const char* GetUnpackPassFile() { return m_szUnpackPassFile; } bool GetUnpackPauseQueue() { return m_bUnpackPauseQueue; } const char* GetExtCleanupDisk() { return m_szExtCleanupDisk; } const char* GetParIgnoreExt() { return m_szParIgnoreExt; } int GetFeedHistory() { return m_iFeedHistory; } bool GetUrlForce() { return m_bUrlForce; } int GetTimeCorrection() { return m_iTimeCorrection; } int GetPropagationDelay() { return m_iPropagationDelay; } int GetArticleCache() { return m_iArticleCache; } int GetEventInterval() { return m_iEventInterval; } Categories* GetCategories() { return &m_Categories; } Category* FindCategory(const char* szName, bool bSearchAliases) { return m_Categories.FindCategory(szName, bSearchAliases); } // Current state void SetServerMode(bool bServerMode) { m_bServerMode = bServerMode; } bool GetServerMode() { return m_bServerMode; } void SetRemoteClientMode(bool bRemoteClientMode) { m_bRemoteClientMode = bRemoteClientMode; } bool GetRemoteClientMode() { return m_bRemoteClientMode; } void SetPauseDownload(bool bPauseDownload) { m_bPauseDownload = bPauseDownload; } bool GetPauseDownload() const { return m_bPauseDownload; } void SetPausePostProcess(bool bPausePostProcess) { m_bPausePostProcess = bPausePostProcess; } bool GetPausePostProcess() const { return m_bPausePostProcess; } void SetPauseScan(bool bPauseScan) { m_bPauseScan = bPauseScan; } bool GetPauseScan() const { return m_bPauseScan; } void SetTempPauseDownload(bool bTempPauseDownload) { m_bTempPauseDownload = bTempPauseDownload; } bool GetTempPauseDownload() const { return m_bTempPauseDownload; } bool GetTempPausePostprocess() const { return m_bTempPausePostprocess; } void SetTempPausePostprocess(bool bTempPausePostprocess) { m_bTempPausePostprocess = bTempPausePostprocess; } void SetDownloadRate(int iRate) { m_iDownloadRate = iRate; } int GetDownloadRate() const { return m_iDownloadRate; } void SetResumeTime(time_t tResumeTime) { m_tResumeTime = tResumeTime; } time_t GetResumeTime() const { return m_tResumeTime; } void SetLocalTimeOffset(int iLocalTimeOffset) { m_iLocalTimeOffset = iLocalTimeOffset; } int GetLocalTimeOffset() { return m_iLocalTimeOffset; } }; extern Options* g_pOptions; #endif nzbget-16.4/daemon/main/nzbget.cpp0000644000175000017500000006056512630544544016760 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #ifdef WIN32 #include #else #include #include #include #ifdef HAVE_SYS_PRCTL_H #include #endif #include #endif #include #include #include #include #ifndef DISABLE_PARCHECK #include #endif #include "nzbget.h" #include "ServerPool.h" #include "Log.h" #include "NZBFile.h" #include "Options.h" #include "CommandLineParser.h" #include "ScriptConfig.h" #include "Thread.h" #include "ColoredFrontend.h" #include "NCursesFrontend.h" #include "QueueCoordinator.h" #include "UrlCoordinator.h" #include "RemoteServer.h" #include "WebServer.h" #include "RemoteClient.h" #include "MessageBase.h" #include "DiskState.h" #include "PrePostProcessor.h" #include "HistoryCoordinator.h" #include "DupeCoordinator.h" #include "ParChecker.h" #include "Scheduler.h" #include "Scanner.h" #include "FeedCoordinator.h" #include "Service.h" #include "DiskService.h" #include "Maintenance.h" #include "ArticleWriter.h" #include "StatMeter.h" #include "QueueScript.h" #include "Util.h" #include "StackTrace.h" #ifdef WIN32 #include "NTService.h" #include "WinConsole.h" #include "WebDownloader.h" #endif #ifdef ENABLE_TESTS #include "TestMain.h" #endif // Prototypes void RunMain(); void Run(bool bReload); void Reload(); void Cleanup(); void ProcessClientRequest(); void ProcessWebGet(); void ProcessSigVerify(); #ifndef WIN32 void Daemonize(); #endif #ifndef DISABLE_PARCHECK void DisableCout(); #endif void BootConfig(); Thread* g_pFrontend = NULL; CommandLineParser* g_pCommandLineParser = NULL; ServerPool* g_pServerPool = NULL; QueueCoordinator* g_pQueueCoordinator = NULL; UrlCoordinator* g_pUrlCoordinator = NULL; RemoteServer* g_pRemoteServer = NULL; RemoteServer* g_pRemoteSecureServer = NULL; StatMeter* g_pStatMeter = NULL; PrePostProcessor* g_pPrePostProcessor = NULL; HistoryCoordinator* g_pHistoryCoordinator = NULL; DupeCoordinator* g_pDupeCoordinator = NULL; DiskState* g_pDiskState = NULL; Scheduler* g_pScheduler = NULL; Scanner* g_pScanner = NULL; FeedCoordinator* g_pFeedCoordinator = NULL; Maintenance* g_pMaintenance = NULL; ArticleCache* g_pArticleCache = NULL; QueueScriptCoordinator* g_pQueueScriptCoordinator = NULL; ServiceCoordinator* g_pServiceCoordinator = NULL; DiskService* g_pDiskService = NULL; int g_iArgumentCount; char* (*g_szEnvironmentVariables)[] = NULL; char* (*g_szArguments)[] = NULL; bool g_bReloading = true; #ifdef WIN32 WinConsole* g_pWinConsole = NULL; #endif /* * Main loop */ int main(int argc, char *argv[], char *argp[]) { #ifdef WIN32 #ifdef _DEBUG _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF #ifdef DEBUG_CRTMEMLEAKS | _CRTDBG_CHECK_CRT_DF | _CRTDBG_CHECK_ALWAYS_DF #endif ); #endif #endif Util::Init(); g_iArgumentCount = argc; g_szArguments = (char*(*)[])argv; g_szEnvironmentVariables = (char*(*)[])argp; if (argc > 1 && (!strcmp(argv[1], "-tests") || !strcmp(argv[1], "--tests"))) { #ifdef ENABLE_TESTS return TestMain(argc, argv); #else printf("ERROR: Could not start tests, the program was compiled without tests\n"); return 1; #endif } #ifdef ENABLE_TESTS TestCleanup(); #endif #ifdef WIN32 InstallUninstallServiceCheck(argc, argv); #endif #ifndef DISABLE_PARCHECK DisableCout(); #endif srand(time(NULL)); #ifdef WIN32 for (int i=0; i < argc; i++) { if (!strcmp(argv[i], "-D")) { StartService(RunMain); return 0; } } #endif RunMain(); return 0; } void RunMain() { // we need to save and later restore current directory each time // the program is reloaded (RPC-Method "reload") in order for // config to properly load in a case relative paths are used // in command line char szCurDir[MAX_PATH + 1]; Util::GetCurrentDirectory(szCurDir, sizeof(szCurDir)); bool bReload = false; while (g_bReloading) { g_bReloading = false; Util::SetCurrentDirectory(szCurDir); Run(bReload); bReload = true; } } void Run(bool bReload) { Log::Init(); debug("nzbget %s", Util::VersionRevision()); if (!bReload) { Thread::Init(); } #ifdef WIN32 g_pWinConsole = new WinConsole(); g_pWinConsole->InitAppMode(); #endif g_pServiceCoordinator = new ServiceCoordinator(); g_pServerPool = new ServerPool(); g_pScheduler = new Scheduler(); g_pQueueCoordinator = new QueueCoordinator(); g_pStatMeter = new StatMeter(); g_pScanner = new Scanner(); g_pPrePostProcessor = new PrePostProcessor(); g_pHistoryCoordinator = new HistoryCoordinator(); g_pDupeCoordinator = new DupeCoordinator(); g_pUrlCoordinator = new UrlCoordinator(); g_pFeedCoordinator = new FeedCoordinator(); g_pArticleCache = new ArticleCache(); g_pMaintenance = new Maintenance(); g_pQueueScriptCoordinator = new QueueScriptCoordinator(); g_pDiskService = new DiskService(); BootConfig(); #ifndef WIN32 if (g_pOptions->GetUMask() < 01000) { /* set newly created file permissions */ umask(g_pOptions->GetUMask()); } #endif g_pScanner->InitOptions(); g_pQueueScriptCoordinator->InitOptions(); if (g_pCommandLineParser->GetDaemonMode()) { #ifdef WIN32 info("nzbget %s service-mode", Util::VersionRevision()); #else if (!bReload) { Daemonize(); } info("nzbget %s daemon-mode", Util::VersionRevision()); #endif } else if (g_pOptions->GetServerMode()) { info("nzbget %s server-mode", Util::VersionRevision()); } else if (g_pCommandLineParser->GetRemoteClientMode()) { info("nzbget %s remote-mode", Util::VersionRevision()); } if (!bReload) { Connection::Init(); } if (!g_pCommandLineParser->GetRemoteClientMode()) { g_pServerPool->InitConnections(); g_pStatMeter->Init(); } InstallErrorHandler(); #ifdef DEBUG if (g_pCommandLineParser->GetTestBacktrace()) { TestSegFault(); } #endif if (g_pCommandLineParser->GetWebGet()) { ProcessWebGet(); return; } if (g_pCommandLineParser->GetSigVerify()) { ProcessSigVerify(); return; } // client request if (g_pCommandLineParser->GetClientOperation() != CommandLineParser::opClientNoOperation) { ProcessClientRequest(); Cleanup(); return; } // Setup the network-server if (g_pOptions->GetServerMode()) { WebProcessor::Init(); g_pRemoteServer = new RemoteServer(false); g_pRemoteServer->Start(); if (g_pOptions->GetSecureControl()) { g_pRemoteSecureServer = new RemoteServer(true); g_pRemoteSecureServer->Start(); } } // Create the frontend if (!g_pCommandLineParser->GetDaemonMode()) { switch (g_pOptions->GetOutputMode()) { case Options::omNCurses: #ifndef DISABLE_CURSES g_pFrontend = new NCursesFrontend(); break; #endif case Options::omColored: g_pFrontend = new ColoredFrontend(); break; case Options::omLoggable: g_pFrontend = new LoggableFrontend(); break; } } // Starting a thread with the frontend if (g_pFrontend) { g_pFrontend->Start(); } // Starting QueueCoordinator and PrePostProcessor if (!g_pCommandLineParser->GetRemoteClientMode()) { // Standalone-mode if (!g_pCommandLineParser->GetServerMode()) { const char* szCategory = g_pCommandLineParser->GetAddCategory() ? g_pCommandLineParser->GetAddCategory() : ""; NZBFile* pNZBFile = new NZBFile(g_pCommandLineParser->GetArgFilename(), szCategory); if (!pNZBFile->Parse()) { printf("Parsing NZB-document %s failed\n\n", g_pCommandLineParser->GetArgFilename() ? g_pCommandLineParser->GetArgFilename() : "N/A"); delete pNZBFile; return; } g_pScanner->InitPPParameters(szCategory, pNZBFile->GetNZBInfo()->GetParameters(), false); g_pQueueCoordinator->AddNZBFileToQueue(pNZBFile, NULL, false); delete pNZBFile; } if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { g_pDiskState = new DiskState(); } #ifdef WIN32 g_pWinConsole->Start(); #endif g_pQueueCoordinator->Start(); g_pUrlCoordinator->Start(); g_pPrePostProcessor->Start(); g_pFeedCoordinator->Start(); g_pServiceCoordinator->Start(); if (g_pOptions->GetArticleCache() > 0) { g_pArticleCache->Start(); } // enter main program-loop while (g_pQueueCoordinator->IsRunning() || g_pUrlCoordinator->IsRunning() || g_pPrePostProcessor->IsRunning() || g_pFeedCoordinator->IsRunning() || g_pServiceCoordinator->IsRunning() || #ifdef WIN32 g_pWinConsole->IsRunning() || #endif g_pArticleCache->IsRunning()) { if (!g_pOptions->GetServerMode() && !g_pQueueCoordinator->HasMoreJobs() && !g_pUrlCoordinator->HasMoreJobs() && !g_pPrePostProcessor->HasMoreJobs()) { // Standalone-mode: download completed if (!g_pQueueCoordinator->IsStopped()) { g_pQueueCoordinator->Stop(); } if (!g_pUrlCoordinator->IsStopped()) { g_pUrlCoordinator->Stop(); } if (!g_pPrePostProcessor->IsStopped()) { g_pPrePostProcessor->Stop(); } if (!g_pFeedCoordinator->IsStopped()) { g_pFeedCoordinator->Stop(); } if (!g_pArticleCache->IsStopped()) { g_pArticleCache->Stop(); } if (!g_pServiceCoordinator->IsStopped()) { g_pServiceCoordinator->Stop(); } } usleep(100 * 1000); } // main program-loop is terminated debug("QueueCoordinator stopped"); debug("UrlCoordinator stopped"); debug("PrePostProcessor stopped"); debug("FeedCoordinator stopped"); debug("ServiceCoordinator stopped"); debug("ArticleCache stopped"); } ScriptController::TerminateAll(); // Stop network-server if (g_pRemoteServer) { debug("stopping RemoteServer"); g_pRemoteServer->Stop(); int iMaxWaitMSec = 1000; while (g_pRemoteServer->IsRunning() && iMaxWaitMSec > 0) { usleep(100 * 1000); iMaxWaitMSec -= 100; } if (g_pRemoteServer->IsRunning()) { debug("Killing RemoteServer"); g_pRemoteServer->Kill(); } debug("RemoteServer stopped"); } if (g_pRemoteSecureServer) { debug("stopping RemoteSecureServer"); g_pRemoteSecureServer->Stop(); int iMaxWaitMSec = 1000; while (g_pRemoteSecureServer->IsRunning() && iMaxWaitMSec > 0) { usleep(100 * 1000); iMaxWaitMSec -= 100; } if (g_pRemoteSecureServer->IsRunning()) { debug("Killing RemoteSecureServer"); g_pRemoteSecureServer->Kill(); } debug("RemoteSecureServer stopped"); } // Stop Frontend if (g_pFrontend) { if (!g_pCommandLineParser->GetRemoteClientMode()) { debug("Stopping Frontend"); g_pFrontend->Stop(); } while (g_pFrontend->IsRunning()) { usleep(50 * 1000); } debug("Frontend stopped"); } Cleanup(); } class OptionsExtender : public Options::Extender { protected: #ifdef WIN32 virtual void SetupFirstStart() { g_pWinConsole->SetupFirstStart(); } #endif virtual void AddNewsServer(int iID, bool bActive, const char* szName, const char* szHost, int iPort, const char* szUser, const char* szPass, bool bJoinGroup, bool bTLS, const char* szCipher, int iMaxConnections, int iRetention, int iLevel, int iGroup) { g_pServerPool->AddServer(new NewsServer(iID, bActive, szName, szHost, iPort, szUser, szPass, bJoinGroup, bTLS, szCipher, iMaxConnections, iRetention, iLevel, iGroup)); } virtual void AddFeed(int iID, const char* szName, const char* szUrl, int iInterval, const char* szFilter, bool bBacklog, bool bPauseNzb, const char* szCategory, int iPriority, const char* szFeedScript) { g_pFeedCoordinator->AddFeed(new FeedInfo(iID, szName, szUrl, bBacklog, iInterval, szFilter, bPauseNzb, szCategory, iPriority, szFeedScript)); } virtual void AddTask(int iID, int iHours, int iMinutes, int iWeekDaysBits, Options::ESchedulerCommand eCommand, const char* szParam) { g_pScheduler->AddTask(new Scheduler::Task(iID, iHours, iMinutes, iWeekDaysBits, (Scheduler::ECommand)eCommand, szParam)); } } g_OptionsExtender; void BootConfig() { debug("Parsing command line"); g_pCommandLineParser = new CommandLineParser(g_iArgumentCount, (const char**)(*g_szArguments)); if (g_pCommandLineParser->GetPrintVersion()) { printf("nzbget version: %s\n", Util::VersionRevision()); exit(0); } if (g_pCommandLineParser->GetPrintUsage() || g_pCommandLineParser->GetErrors() || g_iArgumentCount <= 1) { g_pCommandLineParser->PrintUsage(((const char**)(*g_szArguments))[0]); exit(0); } debug("Reading options"); g_pOptions = new Options((*g_szArguments)[0], g_pCommandLineParser->GetConfigFilename(), g_pCommandLineParser->GetNoConfig(), (Options::CmdOptList*)g_pCommandLineParser->GetOptionList(), &g_OptionsExtender); g_pOptions->SetRemoteClientMode(g_pCommandLineParser->GetRemoteClientMode()); g_pOptions->SetServerMode(g_pCommandLineParser->GetServerMode()); g_pOptions->SetPauseDownload(g_pCommandLineParser->GetPauseDownload()); g_pLog->InitOptions(); if (g_pOptions->GetFatalError()) { exit(1); } else if (g_pOptions->GetConfigErrors() && g_pCommandLineParser->GetClientOperation() == CommandLineParser::opClientNoOperation) { info("Pausing all activities due to errors in configuration"); g_pOptions->SetPauseDownload(true); g_pOptions->SetPausePostProcess(true); g_pOptions->SetPauseScan(true); } g_pServerPool->SetTimeout(g_pOptions->GetArticleTimeout()); g_pServerPool->SetRetryInterval(g_pOptions->GetRetryInterval()); g_pScriptConfig = new ScriptConfig(); } void ProcessClientRequest() { RemoteClient* Client = new RemoteClient(); switch (g_pCommandLineParser->GetClientOperation()) { case CommandLineParser::opClientRequestListFiles: Client->RequestServerList(true, false, g_pCommandLineParser->GetMatchMode() == CommandLineParser::mmRegEx ? g_pCommandLineParser->GetEditQueueText() : NULL); break; case CommandLineParser::opClientRequestListGroups: Client->RequestServerList(false, true, g_pCommandLineParser->GetMatchMode() == CommandLineParser::mmRegEx ? g_pCommandLineParser->GetEditQueueText() : NULL); break; case CommandLineParser::opClientRequestListStatus: Client->RequestServerList(false, false, NULL); break; case CommandLineParser::opClientRequestDownloadPause: Client->RequestServerPauseUnpause(true, eRemotePauseUnpauseActionDownload); break; case CommandLineParser::opClientRequestDownloadUnpause: Client->RequestServerPauseUnpause(false, eRemotePauseUnpauseActionDownload); break; case CommandLineParser::opClientRequestSetRate: Client->RequestServerSetDownloadRate(g_pCommandLineParser->GetSetRate()); break; case CommandLineParser::opClientRequestDumpDebug: Client->RequestServerDumpDebug(); break; case CommandLineParser::opClientRequestEditQueue: Client->RequestServerEditQueue((DownloadQueue::EEditAction)g_pCommandLineParser->GetEditQueueAction(), g_pCommandLineParser->GetEditQueueOffset(), g_pCommandLineParser->GetEditQueueText(), g_pCommandLineParser->GetEditQueueIDList(), g_pCommandLineParser->GetEditQueueIDCount(), g_pCommandLineParser->GetEditQueueNameList(), (eRemoteMatchMode)g_pCommandLineParser->GetMatchMode()); break; case CommandLineParser::opClientRequestLog: Client->RequestServerLog(g_pCommandLineParser->GetLogLines()); break; case CommandLineParser::opClientRequestShutdown: Client->RequestServerShutdown(); break; case CommandLineParser::opClientRequestReload: Client->RequestServerReload(); break; case CommandLineParser::opClientRequestDownload: Client->RequestServerDownload(g_pCommandLineParser->GetAddNZBFilename(), g_pCommandLineParser->GetArgFilename(), g_pCommandLineParser->GetAddCategory(), g_pCommandLineParser->GetAddTop(), g_pCommandLineParser->GetAddPaused(), g_pCommandLineParser->GetAddPriority(), g_pCommandLineParser->GetAddDupeKey(), g_pCommandLineParser->GetAddDupeMode(), g_pCommandLineParser->GetAddDupeScore()); break; case CommandLineParser::opClientRequestVersion: Client->RequestServerVersion(); break; case CommandLineParser::opClientRequestPostQueue: Client->RequestPostQueue(); break; case CommandLineParser::opClientRequestWriteLog: Client->RequestWriteLog(g_pCommandLineParser->GetWriteLogKind(), g_pCommandLineParser->GetLastArg()); break; case CommandLineParser::opClientRequestScanAsync: Client->RequestScan(false); break; case CommandLineParser::opClientRequestScanSync: Client->RequestScan(true); break; case CommandLineParser::opClientRequestPostPause: Client->RequestServerPauseUnpause(true, eRemotePauseUnpauseActionPostProcess); break; case CommandLineParser::opClientRequestPostUnpause: Client->RequestServerPauseUnpause(false, eRemotePauseUnpauseActionPostProcess); break; case CommandLineParser::opClientRequestScanPause: Client->RequestServerPauseUnpause(true, eRemotePauseUnpauseActionScan); break; case CommandLineParser::opClientRequestScanUnpause: Client->RequestServerPauseUnpause(false, eRemotePauseUnpauseActionScan); break; case CommandLineParser::opClientRequestHistory: case CommandLineParser::opClientRequestHistoryAll: Client->RequestHistory(g_pCommandLineParser->GetClientOperation() == CommandLineParser::opClientRequestHistoryAll); break; case CommandLineParser::opClientNoOperation: break; } delete Client; } void ProcessWebGet() { WebDownloader downloader; downloader.SetURL(g_pCommandLineParser->GetLastArg()); downloader.SetForce(true); downloader.SetRetry(false); downloader.SetOutputFilename(g_pCommandLineParser->GetWebGetFilename()); downloader.SetInfoName("WebGet"); WebDownloader::EStatus eStatus = downloader.DownloadWithRedirects(5); bool bOK = eStatus == WebDownloader::adFinished; exit(bOK ? 0 : 1); } void ProcessSigVerify() { #ifdef HAVE_OPENSSL bool bOK = Maintenance::VerifySignature(g_pCommandLineParser->GetLastArg(), g_pCommandLineParser->GetSigFilename(), g_pCommandLineParser->GetPubKeyFilename()); exit(bOK ? 93 : 1); #else printf("ERROR: Could not verify signature, the program was compiled without OpenSSL support\n"); exit(1); #endif } void ExitProc() { if (!g_bReloading) { info("Stopping, please wait..."); } if (g_pCommandLineParser->GetRemoteClientMode()) { if (g_pFrontend) { debug("Stopping Frontend"); g_pFrontend->Stop(); } } else { if (g_pQueueCoordinator) { debug("Stopping QueueCoordinator"); g_pServiceCoordinator->Stop(); g_pQueueCoordinator->Stop(); g_pUrlCoordinator->Stop(); g_pPrePostProcessor->Stop(); g_pFeedCoordinator->Stop(); g_pArticleCache->Stop(); g_pQueueScriptCoordinator->Stop(); #ifdef WIN32 g_pWinConsole->Stop(); #endif } } } void Reload() { g_bReloading = true; info("Reloading..."); ExitProc(); } void Cleanup() { debug("Cleaning up global objects"); debug("Deleting UrlCoordinator"); delete g_pUrlCoordinator; g_pUrlCoordinator = NULL; debug("UrlCoordinator deleted"); debug("Deleting RemoteServer"); delete g_pRemoteServer; g_pRemoteServer = NULL; debug("RemoteServer deleted"); debug("Deleting RemoteSecureServer"); delete g_pRemoteSecureServer; g_pRemoteSecureServer = NULL; debug("RemoteSecureServer deleted"); debug("Deleting PrePostProcessor"); delete g_pPrePostProcessor; g_pPrePostProcessor = NULL; delete g_pScanner; g_pScanner = NULL; debug("PrePostProcessor deleted"); debug("Deleting HistoryCoordinator"); delete g_pHistoryCoordinator; g_pHistoryCoordinator = NULL; debug("HistoryCoordinator deleted"); debug("Deleting DupeCoordinator"); delete g_pDupeCoordinator; g_pDupeCoordinator = NULL; debug("DupeCoordinator deleted"); debug("Deleting Frontend"); delete g_pFrontend; g_pFrontend = NULL; debug("Frontend deleted"); debug("Deleting QueueCoordinator"); delete g_pQueueCoordinator; g_pQueueCoordinator = NULL; debug("QueueCoordinator deleted"); debug("Deleting DiskState"); delete g_pDiskState; g_pDiskState = NULL; debug("DiskState deleted"); debug("Deleting Options"); if (g_pOptions) { if (g_pCommandLineParser->GetDaemonMode() && !g_bReloading) { info("Deleting lock file"); remove(g_pOptions->GetLockFile()); } delete g_pOptions; } debug("Options deleted"); debug("Deleting CommandLineParser"); if (g_pCommandLineParser) { delete g_pCommandLineParser; g_pCommandLineParser = NULL; } debug("CommandLineParser deleted"); debug("Deleting ScriptConfig"); if (g_pScriptConfig) { delete g_pScriptConfig; g_pScriptConfig = NULL; } debug("ScriptConfig deleted"); debug("Deleting ServerPool"); delete g_pServerPool; g_pServerPool = NULL; debug("ServerPool deleted"); debug("Deleting Scheduler"); delete g_pScheduler; g_pScheduler = NULL; debug("Scheduler deleted"); debug("Deleting FeedCoordinator"); delete g_pFeedCoordinator; g_pFeedCoordinator = NULL; debug("FeedCoordinator deleted"); debug("Deleting ArticleCache"); delete g_pArticleCache; g_pArticleCache = NULL; debug("ArticleCache deleted"); debug("Deleting QueueScriptCoordinator"); delete g_pQueueScriptCoordinator; g_pQueueScriptCoordinator = NULL; debug("QueueScriptCoordinator deleted"); debug("Deleting Maintenance"); delete g_pMaintenance; g_pMaintenance = NULL; debug("Maintenance deleted"); debug("Deleting StatMeter"); delete g_pStatMeter; g_pStatMeter = NULL; debug("StatMeter deleted"); debug("Deleting ServiceCoordinator"); delete g_pServiceCoordinator; g_pServiceCoordinator = NULL; debug("ServiceCoordinator deleted"); debug("Deleting DiskService"); delete g_pDiskService; g_pDiskService = NULL; debug("DiskService deleted"); if (!g_bReloading) { Connection::Final(); Thread::Final(); } #ifdef WIN32 delete g_pWinConsole; g_pWinConsole = NULL; #endif debug("Global objects cleaned up"); Log::Final(); } #ifndef WIN32 void Daemonize() { int f = fork(); if (f < 0) exit(1); /* fork error */ if (f > 0) exit(0); /* parent exits */ /* child (daemon) continues */ // obtain a new process group setsid(); // close all descriptors for (int i = getdtablesize(); i >= 0; --i) { close(i); } // handle standart I/O int d = open("/dev/null", O_RDWR); dup(d); dup(d); // change running directory chdir(g_pOptions->GetDestDir()); // set up lock-file int lfp = -1; if (!Util::EmptyStr(g_pOptions->GetLockFile())) { lfp = open(g_pOptions->GetLockFile(), O_RDWR | O_CREAT, 0640); if (lfp < 0) { error("Starting daemon failed: could not create lock-file %s", g_pOptions->GetLockFile()); exit(1); } if (lockf(lfp, F_TLOCK, 0) < 0) { error("Starting daemon failed: could not acquire lock on lock-file %s", g_pOptions->GetLockFile()); exit(1); } } /* Drop user if there is one, and we were run as root */ if (getuid() == 0 || geteuid() == 0) { struct passwd *pw = getpwnam(g_pOptions->GetDaemonUsername()); if (pw) { // Change owner of lock file fchown(lfp, pw->pw_uid, pw->pw_gid); // Set aux groups to null. setgroups(0, (const gid_t*)0); // Set primary group. setgid(pw->pw_gid); // Try setting aux groups correctly - not critical if this fails. initgroups(g_pOptions->GetDaemonUsername(), pw->pw_gid); // Finally, set uid. setuid(pw->pw_uid); } } // record pid to lockfile if (lfp > -1) { char str[10]; sprintf(str, "%d\n", getpid()); write(lfp, str, strlen(str)); } // ignore unwanted signals signal(SIGCHLD, SIG_IGN); signal(SIGTSTP, SIG_IGN); signal(SIGTTOU, SIG_IGN); signal(SIGTTIN, SIG_IGN); } #endif #ifndef DISABLE_PARCHECK class NullStreamBuf : public std::streambuf { public: int sputc ( char c ) { return (int) c; } } NullStreamBufInstance; void DisableCout() { // libpar2 prints messages to c++ standard output stream (std::cout). // However we do not want these messages to be printed. // Since we do not use std::cout in nzbget we just disable it. std::cout.rdbuf(&NullStreamBufInstance); } #endif nzbget-16.4/daemon/main/CommandLineParser.h0000644000175000017500000001266312630544544020473 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 COMMANDLINEPARSER_H #define COMMANDLINEPARSER_H #include #include #include class CommandLineParser { public: enum EClientOperation { opClientNoOperation, opClientRequestDownload, opClientRequestListFiles, opClientRequestListGroups, opClientRequestListStatus, opClientRequestSetRate, opClientRequestDumpDebug, opClientRequestEditQueue, opClientRequestLog, opClientRequestShutdown, opClientRequestReload, opClientRequestVersion, opClientRequestPostQueue, opClientRequestWriteLog, opClientRequestScanSync, opClientRequestScanAsync, opClientRequestDownloadPause, opClientRequestDownloadUnpause, opClientRequestPostPause, opClientRequestPostUnpause, opClientRequestScanPause, opClientRequestScanUnpause, opClientRequestHistory, opClientRequestHistoryAll }; enum EMatchMode { mmID = 1, mmName, mmRegEx }; typedef std::vector NameList; private: bool m_bNoConfig; char* m_szConfigFilename; // Parsed command-line parameters bool m_bErrors; bool m_bPrintVersion; bool m_bPrintUsage; bool m_bServerMode; bool m_bDaemonMode; bool m_bRemoteClientMode; EClientOperation m_eClientOperation; NameList m_OptionList; int m_iEditQueueAction; int m_iEditQueueOffset; int* m_pEditQueueIDList; int m_iEditQueueIDCount; NameList m_EditQueueNameList; EMatchMode m_EMatchMode; char* m_szEditQueueText; char* m_szArgFilename; char* m_szAddCategory; int m_iAddPriority; bool m_bAddPaused; char* m_szAddNZBFilename; char* m_szLastArg; bool m_bPrintOptions; bool m_bAddTop; char* m_szAddDupeKey; int m_iAddDupeScore; int m_iAddDupeMode; int m_iSetRate; int m_iLogLines; int m_iWriteLogKind; bool m_bTestBacktrace; bool m_bWebGet; char* m_szWebGetFilename; bool m_bSigVerify; char* m_szPubKeyFilename; char* m_szSigFilename; bool m_bPauseDownload; void InitCommandLine(int argc, const char* argv[]); void InitFileArg(int argc, const char* argv[]); void ParseFileIDList(int argc, const char* argv[], int optind); void ParseFileNameList(int argc, const char* argv[], int optind); bool ParseTime(const char* szTime, int* pHours, int* pMinutes); void ReportError(const char* szErrMessage); public: CommandLineParser(int argc, const char* argv[]); ~CommandLineParser(); void PrintUsage(const char* com); bool GetErrors() { return m_bErrors; } bool GetNoConfig() { return m_bNoConfig; } const char* GetConfigFilename() { return m_szConfigFilename; } bool GetServerMode() { return m_bServerMode; } bool GetDaemonMode() { return m_bDaemonMode; } bool GetRemoteClientMode() { return m_bRemoteClientMode; } EClientOperation GetClientOperation() { return m_eClientOperation; } NameList* GetOptionList() { return &m_OptionList; } int GetEditQueueAction() { return m_iEditQueueAction; } int GetEditQueueOffset() { return m_iEditQueueOffset; } int* GetEditQueueIDList() { return m_pEditQueueIDList; } int GetEditQueueIDCount() { return m_iEditQueueIDCount; } NameList* GetEditQueueNameList() { return &m_EditQueueNameList; } EMatchMode GetMatchMode() { return m_EMatchMode; } const char* GetEditQueueText() { return m_szEditQueueText; } const char* GetArgFilename() { return m_szArgFilename; } const char* GetAddCategory() { return m_szAddCategory; } bool GetAddPaused() { return m_bAddPaused; } const char* GetLastArg() { return m_szLastArg; } int GetAddPriority() { return m_iAddPriority; } char* GetAddNZBFilename() { return m_szAddNZBFilename; } bool GetAddTop() { return m_bAddTop; } const char* GetAddDupeKey() { return m_szAddDupeKey; } int GetAddDupeScore() { return m_iAddDupeScore; } int GetAddDupeMode() { return m_iAddDupeMode; } int GetSetRate() { return m_iSetRate; } int GetLogLines() { return m_iLogLines; } int GetWriteLogKind() { return m_iWriteLogKind; } bool GetTestBacktrace() { return m_bTestBacktrace; } bool GetWebGet() { return m_bWebGet; } const char* GetWebGetFilename() { return m_szWebGetFilename; } bool GetSigVerify() { return m_bSigVerify; } const char* GetPubKeyFilename() { return m_szPubKeyFilename; } const char* GetSigFilename() { return m_szSigFilename; } bool GetPrintOptions() { return m_bPrintOptions; } bool GetPrintVersion() { return m_bPrintVersion; } bool GetPrintUsage() { return m_bPrintUsage; } bool GetPauseDownload() const { return m_bPauseDownload; } }; extern CommandLineParser* g_pCommandLineParser; #endif nzbget-16.4/daemon/main/nzbget.h0000644000175000017500000000607312630544544016417 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 NZBGET_H #define NZBGET_H #ifdef WIN32 // WIN32 #define snprintf _snprintf #ifndef strdup #define strdup _strdup #endif #define fdopen _fdopen #define ctime_r(timep, buf, bufsize) ctime_s(buf, bufsize, timep) #define gmtime_r(time, tm) gmtime_s(tm, time) #define strtok_r(str, delim, saveptr) strtok_s(str, delim, saveptr) #define strerror_r(errnum, buffer, size) strerror_s(buffer, size, errnum) #if (_MSC_VER < 1600) #define int32_t __int32 #endif #define mkdir(dir, flags) _mkdir(dir) #define rmdir _rmdir #define strcasecmp(a, b) _stricmp(a, b) #define strncasecmp(a, b, c) _strnicmp(a, b, c) #define ssize_t SSIZE_T #define __S_ISTYPE(mode, mask) (((mode) & _S_IFMT) == (mask)) #define S_ISDIR(mode) __S_ISTYPE((mode), _S_IFDIR) #define S_ISREG(mode) __S_ISTYPE((mode), _S_IFREG) #define S_DIRMODE NULL #define usleep(usec) Sleep((usec) / 1000) #define socklen_t int #define SHUT_WR 0x01 #define SHUT_RDWR 0x02 #define PATH_SEPARATOR '\\' #define ALT_PATH_SEPARATOR '/' #define LINE_ENDING "\r\n" #define pid_t int #define atoll _atoi64 #define fseek _fseeki64 #define ftell _ftelli64 #if _MSC_VER < 1800 // va_copy is available in vc2013 and onwards #define va_copy(d,s) ((d) = (s)) #endif #ifndef FSCTL_SET_SPARSE #define FSCTL_SET_SPARSE 590020 #endif #define FOPEN_RB "rbN" #define FOPEN_RBP "rb+N" #define FOPEN_WB "wbN" #define FOPEN_WBP "wb+N" #define FOPEN_AB "abN" #define FOPEN_ABP "ab+N" #ifdef DEBUG // redefine "exit" to avoid printing memory leaks report when terminated because of wrong command line switches #define exit(code) ExitProcess(code) #endif #pragma warning(disable:4800) // 'type' : forcing value to bool 'true' or 'false' (performance warning) #pragma warning(disable:4267) // 'var' : conversion from 'size_t' to 'type', possible loss of data #else // POSIX #define closesocket(sock) close(sock) #define SOCKET int #define INVALID_SOCKET (-1) #define PATH_SEPARATOR '/' #define ALT_PATH_SEPARATOR '\\' #define MAX_PATH 1024 #define S_DIRMODE (S_IRWXU | S_IRWXG | S_IRWXO) #define LINE_ENDING "\n" #define FOPEN_RB "rb" #define FOPEN_RBP "rb+" #define FOPEN_WB "wb" #define FOPEN_WBP "wb+" #define FOPEN_AB "ab" #define FOPEN_ABP "ab+" #endif #ifndef SHUT_RDWR #define SHUT_RDWR 2 #endif #endif nzbget-16.4/daemon/main/Maintenance.cpp0000644000175000017500000002732012630544544017701 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2013-2015 Andrey Prygunkov * * 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 #include #include #include #ifndef WIN32 #include #endif #include #ifdef HAVE_OPENSSL #include #include #include #endif /* HAVE_OPENSSL */ #include "nzbget.h" #include "Log.h" #include "Util.h" #include "Maintenance.h" #include "Options.h" #include "CommandLineParser.h" extern void ExitProc(); extern int g_iArgumentCount; extern char* (*g_szArguments)[]; #ifdef HAVE_OPENSSL class Signature { private: const char* m_szInFilename; const char* m_szSigFilename; const char* m_szPubKeyFilename; unsigned char m_InHash[SHA256_DIGEST_LENGTH]; unsigned char m_Signature[256]; RSA* m_pPubKey; bool ReadSignature(); bool ComputeInHash(); bool ReadPubKey(); public: Signature(const char* szInFilename, const char* szSigFilename, const char* szPubKeyFilename); ~Signature(); bool Verify(); }; #endif 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); } } m_Messages.Clear(); free(m_szUpdateScript); } void Maintenance::ResetUpdateController() { m_mutexController.Lock(); m_UpdateScriptController = NULL; m_mutexController.Unlock(); } MessageList* Maintenance::LockMessages() { m_mutexLog.Lock(); return &m_Messages; } void Maintenance::UnlockMessages() { m_mutexLog.Unlock(); } void Maintenance::AddMessage(Message::EKind eKind, time_t tTime, const char * szText) { if (tTime == 0) { 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; } // make absolute path if (m_szUpdateScript[0] != PATH_SEPARATOR #ifdef WIN32 && !(strlen(m_szUpdateScript) > 2 && m_szUpdateScript[1] == ':') #endif ) { char szFilename[MAX_PATH + 100]; snprintf(szFilename, sizeof(szFilename), "%s%c%s", g_pOptions->GetAppDir(), PATH_SEPARATOR, m_szUpdateScript); free(m_szUpdateScript); m_szUpdateScript = strdup(szFilename); } m_Messages.Clear(); 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; } bool Maintenance::VerifySignature(const char* szInFilename, const char* szSigFilename, const char* szPubKeyFilename) { #ifdef HAVE_OPENSSL Signature signature(szInFilename, szSigFilename, szPubKeyFilename); return signature.Verify(); #else return false; #endif } void UpdateScriptController::Run() { // the update-script should not be automatically terminated when the program quits UnregisterRunningScript(); 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]); SetEnvVar("NZBUP_RUNMODE", g_pCommandLineParser->GetDaemonMode() ? "DAEMON" : "SERVER"); for (int i = 0; i < g_iArgumentCount; i++) { char szEnvName[40]; snprintf(szEnvName, 40, "NZBUP_CMDLINE%i", i); szInfoName[40-1] = '\0'; SetEnvVar(szEnvName, (*g_szArguments)[i]); } char szProcessID[20]; #ifdef WIN32 int pid = (int)GetCurrentProcessId(); #else int pid = (int)getpid(); #endif snprintf(szProcessID, 20, "%i", pid); szProcessID[20-1] = '\0'; SetEnvVar("NZBUP_PROCESSID", szProcessID); char szLogPrefix[100]; strncpy(szLogPrefix, Util::BaseFileName(GetScript()), 100); szLogPrefix[100-1] = '\0'; if (char* ext = strrchr(szLogPrefix, '.')) *ext = '\0'; // strip file extension SetLogPrefix(szLogPrefix); m_iPrefixLen = strlen(szLogPrefix) + 2; // 2 = strlen(": "); Execute(); g_pMaintenance->ResetUpdateController(); } void UpdateScriptController::AddMessage(Message::EKind eKind, const char* szText) { szText = szText + m_iPrefixLen; if (!strncmp(szText, "[NZB] ", 6)) { debug("Command %s detected", szText + 6); if (!strcmp(szText + 6, "QUIT")) { Detach(); ExitProc(); } else { error("Invalid command \"%s\" received", szText); } } else { g_pMaintenance->AddMessage(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); } } #ifdef HAVE_OPENSSL Signature::Signature(const char *szInFilename, const char *szSigFilename, const char *szPubKeyFilename) { m_szInFilename = szInFilename; m_szSigFilename = szSigFilename; m_szPubKeyFilename = szPubKeyFilename; m_pPubKey = NULL; } Signature::~Signature() { RSA_free(m_pPubKey); } // Calculate SHA-256 for input file (m_szInFilename) bool Signature::ComputeInHash() { FILE* infile = fopen(m_szInFilename, FOPEN_RB); if (!infile) { return false; } SHA256_CTX sha256; SHA256_Init(&sha256); const int bufSize = 32*1024; char* buffer = (char*)malloc(bufSize); while(int bytesRead = fread(buffer, 1, bufSize, infile)) { SHA256_Update(&sha256, buffer, bytesRead); } SHA256_Final(m_InHash, &sha256); free(buffer); fclose(infile); return true; } // Read signature from file (m_szSigFilename) into memory bool Signature::ReadSignature() { char szSigTitle[256]; snprintf(szSigTitle, sizeof(szSigTitle), "\"RSA-SHA256(%s)\" : \"", Util::BaseFileName(m_szInFilename)); szSigTitle[256-1] = '\0'; FILE* infile = fopen(m_szSigFilename, FOPEN_RB); if (!infile) { return false; } bool bOK = false; int iTitLen = strlen(szSigTitle); char buf[1024]; unsigned char* output = m_Signature; while (fgets(buf, sizeof(buf) - 1, infile)) { if (!strncmp(buf, szSigTitle, iTitLen)) { char* szHexSig = buf + iTitLen; int iSigLen = strlen(szHexSig); if (iSigLen > 2) { szHexSig[iSigLen - 2] = '\0'; // trim trailing ", } for (; *szHexSig && *(szHexSig+1);) { unsigned char c1 = *szHexSig++; unsigned char c2 = *szHexSig++; c1 = '0' <= c1 && c1 <= '9' ? c1 - '0' : 'A' <= c1 && c1 <= 'F' ? c1 - 'A' + 10 : 'a' <= c1 && c1 <= 'f' ? c1 - 'a' + 10 : 0; c2 = '0' <= c2 && c2 <= '9' ? c2 - '0' : 'A' <= c2 && c2 <= 'F' ? c2 - 'A' + 10 : 'a' <= c2 && c2 <= 'f' ? c2 - 'a' + 10 : 0; unsigned char ch = (c1 << 4) + c2; *output++ = (char)ch; } bOK = output == m_Signature + sizeof(m_Signature); break; } } fclose(infile); return bOK; } // Read public key from file (m_szPubKeyFilename) into memory bool Signature::ReadPubKey() { char* keybuf; int keybuflen; if (!Util::LoadFileIntoBuffer(m_szPubKeyFilename, &keybuf, &keybuflen)) { return false; } BIO* mem = BIO_new_mem_buf(keybuf, keybuflen); m_pPubKey = PEM_read_bio_RSA_PUBKEY(mem, NULL, NULL, NULL); BIO_free(mem); free(keybuf); return m_pPubKey != NULL; } bool Signature::Verify() { return ComputeInHash() && ReadSignature() && ReadPubKey() && RSA_verify(NID_sha256, m_InHash, sizeof(m_InHash), m_Signature, sizeof(m_Signature), m_pPubKey) == 1; } #endif /* HAVE_OPENSSL */ nzbget-16.4/daemon/main/Maintenance.h0000644000175000017500000000462212630544544017346 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2013-2015 Andrey Prygunkov * * 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 "Script.h" #include "Log.h" #include "Util.h" class UpdateScriptController; class Maintenance { private: MessageList m_Messages; Mutex m_mutexLog; Mutex m_mutexController; int m_iIDMessageGen; UpdateScriptController* m_UpdateScriptController; char* m_szUpdateScript; bool ReadPackageInfoStr(const char* szKey, char** pValue); public: enum EBranch { brStable, brTesting, brDevel }; Maintenance(); ~Maintenance(); void AddMessage(Message::EKind eKind, time_t tTime, const char* szText); MessageList* LockMessages(); void UnlockMessages(); bool StartUpdate(EBranch eBranch); void ResetUpdateController(); bool CheckUpdates(char** pUpdateInfo); static bool VerifySignature(const char* szInFilename, const char* szSigFilename, const char* szPubKeyFilename); }; extern Maintenance* g_pMaintenance; 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 nzbget-16.4/daemon/main/CommandLineParser.cpp0000644000175000017500000007167712630544544021040 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #include #include #include #include #include #ifdef WIN32 #include #include #else #include #include #endif #include "nzbget.h" #include "Util.h" #include "Log.h" #include "MessageBase.h" #include "DownloadInfo.h" #include "CommandLineParser.h" #ifdef HAVE_GETOPT_LONG static struct option long_options[] = { {"help", no_argument, 0, 'h'}, {"configfile", required_argument, 0, 'c'}, {"noconfigfile", no_argument, 0, 'n'}, {"printconfig", no_argument, 0, 'p'}, {"server", no_argument, 0, 's' }, {"daemon", no_argument, 0, 'D' }, {"version", no_argument, 0, 'v'}, {"serverversion", no_argument, 0, 'V'}, {"option", required_argument, 0, 'o'}, {"append", no_argument, 0, 'A'}, {"list", no_argument, 0, 'L'}, {"pause", no_argument, 0, 'P'}, {"unpause", no_argument, 0, 'U'}, {"rate", required_argument, 0, 'R'}, {"system", no_argument, 0, 'B'}, {"log", required_argument, 0, 'G'}, {"top", no_argument, 0, 'T'}, {"edit", required_argument, 0, 'E'}, {"connect", no_argument, 0, 'C'}, {"quit", no_argument, 0, 'Q'}, {"reload", no_argument, 0, 'O'}, {"write", required_argument, 0, 'W'}, {"category", required_argument, 0, 'K'}, {"scan", no_argument, 0, 'S'}, {0, 0, 0, 0} }; #endif static char short_options[] = "c:hno:psvAB:DCE:G:K:LPR:STUQOVW:"; CommandLineParser::CommandLineParser(int argc, const char* argv[]) { m_bNoConfig = false; m_szConfigFilename = NULL; m_bErrors = false; m_bPrintVersion = false; m_bPrintUsage = false; m_iEditQueueAction = 0; m_pEditQueueIDList = NULL; m_iEditQueueIDCount = 0; m_iEditQueueOffset = 0; m_szEditQueueText = NULL; m_szArgFilename = NULL; m_szLastArg = NULL; m_szAddCategory = NULL; m_iAddPriority = 0; m_szAddNZBFilename = NULL; m_bAddPaused = false; m_bServerMode = false; m_bDaemonMode = false; m_bRemoteClientMode = false; m_bPrintOptions = false; m_bAddTop = false; m_szAddDupeKey = NULL; m_iAddDupeScore = 0; m_iAddDupeMode = 0; m_iLogLines = 0; m_iWriteLogKind = 0; m_bTestBacktrace = false; m_bWebGet = false; m_szWebGetFilename = NULL; m_bSigVerify = false; m_szPubKeyFilename = NULL; m_szSigFilename = NULL; m_EMatchMode = mmID; m_bPauseDownload = false; InitCommandLine(argc, argv); if (argc == 1) { m_bPrintUsage = true; return; } if (!m_bPrintOptions && !m_bPrintUsage && !m_bPrintVersion) { InitFileArg(argc, argv); } } CommandLineParser::~CommandLineParser() { free(m_szConfigFilename); free(m_szArgFilename); free(m_szAddCategory); free(m_szEditQueueText); free(m_szLastArg); free(m_pEditQueueIDList); free(m_szAddNZBFilename); free(m_szAddDupeKey); free(m_szWebGetFilename); free(m_szPubKeyFilename); free(m_szSigFilename); for (NameList::iterator it = m_EditQueueNameList.begin(); it != m_EditQueueNameList.end(); it++) { free(*it); } m_EditQueueNameList.clear(); for (NameList::iterator it = m_OptionList.begin(); it != m_OptionList.end(); it++) { free(*it); } m_OptionList.clear(); } void CommandLineParser::InitCommandLine(int argc, const char* const_argv[]) { m_eClientOperation = opClientNoOperation; // default char** argv = (char**)malloc(sizeof(char*) * argc); for (int i = 0; i < argc; i++) { argv[i] = strdup(const_argv[i]); } // reset getopt optind = 0; while (true) { int c; #ifdef HAVE_GETOPT_LONG int option_index = 0; c = getopt_long(argc, argv, short_options, long_options, &option_index); #else c = getopt(argc, argv, short_options); #endif if (c == -1) break; switch (c) { case 'c': m_szConfigFilename = strdup(optarg); break; case 'n': m_szConfigFilename = NULL; m_bNoConfig = true; break; case 'h': m_bPrintUsage = true; return; case 'v': m_bPrintVersion = true; return; case 'p': m_bPrintOptions = true; break; case 'o': m_OptionList.push_back(strdup(optarg)); break; case 's': m_bServerMode = true; break; case 'D': m_bServerMode = true; m_bDaemonMode = true; break; case 'A': m_eClientOperation = opClientRequestDownload; while (true) { optind++; optarg = optind > argc ? NULL : argv[optind-1]; if (optarg && (!strcasecmp(optarg, "F") || !strcasecmp(optarg, "U"))) { // option ignored (but kept for compatibility) } else if (optarg && !strcasecmp(optarg, "T")) { m_bAddTop = true; } else if (optarg && !strcasecmp(optarg, "P")) { m_bAddPaused = true; } else if (optarg && !strcasecmp(optarg, "I")) { optind++; if (optind > argc) { ReportError("Could not parse value of option 'A'"); return; } m_iAddPriority = atoi(argv[optind-1]); } else if (optarg && !strcasecmp(optarg, "C")) { optind++; if (optind > argc) { ReportError("Could not parse value of option 'A'"); return; } free(m_szAddCategory); m_szAddCategory = strdup(argv[optind-1]); } else if (optarg && !strcasecmp(optarg, "N")) { optind++; if (optind > argc) { ReportError("Could not parse value of option 'A'"); return; } free(m_szAddNZBFilename); m_szAddNZBFilename = strdup(argv[optind-1]); } else if (optarg && !strcasecmp(optarg, "DK")) { optind++; if (optind > argc) { ReportError("Could not parse value of option 'A'"); return; } free(m_szAddDupeKey); m_szAddDupeKey = strdup(argv[optind-1]); } else if (optarg && !strcasecmp(optarg, "DS")) { optind++; if (optind > argc) { ReportError("Could not parse value of option 'A'"); return; } m_iAddDupeScore = atoi(argv[optind-1]); } else if (optarg && !strcasecmp(optarg, "DM")) { optind++; if (optind > argc) { ReportError("Could not parse value of option 'A'"); return; } const char* szDupeMode = argv[optind-1]; if (!strcasecmp(szDupeMode, "score")) { m_iAddDupeMode = dmScore; } else if (!strcasecmp(szDupeMode, "all")) { m_iAddDupeMode = dmAll; } else if (!strcasecmp(szDupeMode, "force")) { m_iAddDupeMode = dmForce; } else { ReportError("Could not parse value of option 'A'"); return; } } else { optind--; break; } } break; case 'L': optind++; optarg = optind > argc ? NULL : argv[optind-1]; if (!optarg || !strncmp(optarg, "-", 1)) { m_eClientOperation = opClientRequestListFiles; optind--; } else if (!strcasecmp(optarg, "F") || !strcasecmp(optarg, "FR")) { m_eClientOperation = opClientRequestListFiles; } else if (!strcasecmp(optarg, "G") || !strcasecmp(optarg, "GR")) { m_eClientOperation = opClientRequestListGroups; } else if (!strcasecmp(optarg, "O")) { m_eClientOperation = opClientRequestPostQueue; } else if (!strcasecmp(optarg, "S")) { m_eClientOperation = opClientRequestListStatus; } else if (!strcasecmp(optarg, "H")) { m_eClientOperation = opClientRequestHistory; } else if (!strcasecmp(optarg, "HA")) { m_eClientOperation = opClientRequestHistoryAll; } else { ReportError("Could not parse value of option 'L'"); return; } if (optarg && (!strcasecmp(optarg, "FR") || !strcasecmp(optarg, "GR"))) { m_EMatchMode = mmRegEx; optind++; if (optind > argc) { ReportError("Could not parse value of option 'L'"); return; } m_szEditQueueText = strdup(argv[optind-1]); } break; case 'P': case 'U': optind++; optarg = optind > argc ? NULL : argv[optind-1]; if (!optarg || !strncmp(optarg, "-", 1)) { m_eClientOperation = c == 'P' ? opClientRequestDownloadPause : opClientRequestDownloadUnpause; optind--; } else if (!strcasecmp(optarg, "D")) { m_eClientOperation = c == 'P' ? opClientRequestDownloadPause : opClientRequestDownloadUnpause; } else if (!strcasecmp(optarg, "O")) { m_eClientOperation = c == 'P' ? opClientRequestPostPause : opClientRequestPostUnpause; } else if (!strcasecmp(optarg, "S")) { m_eClientOperation = c == 'P' ? opClientRequestScanPause : opClientRequestScanUnpause; } else { ReportError(c == 'P' ? "Could not parse value of option 'P'\n" : "Could not parse value of option 'U'"); return; } break; case 'R': m_eClientOperation = opClientRequestSetRate; m_iSetRate = (int)(atof(optarg)*1024); break; case 'B': if (!strcasecmp(optarg, "dump")) { m_eClientOperation = opClientRequestDumpDebug; } else if (!strcasecmp(optarg, "trace")) { m_bTestBacktrace = true; } else if (!strcasecmp(optarg, "webget")) { m_bWebGet = true; optind++; if (optind > argc) { ReportError("Could not parse value of option 'B'"); return; } optarg = argv[optind-1]; m_szWebGetFilename = strdup(optarg); } else if (!strcasecmp(optarg, "verify")) { m_bSigVerify = true; optind++; if (optind > argc) { ReportError("Could not parse value of option 'B'"); return; } optarg = argv[optind-1]; m_szPubKeyFilename = strdup(optarg); optind++; if (optind > argc) { ReportError("Could not parse value of option 'B'"); return; } optarg = argv[optind-1]; m_szSigFilename = strdup(optarg); } else { ReportError("Could not parse value of option 'B'"); return; } break; case 'G': m_eClientOperation = opClientRequestLog; m_iLogLines = atoi(optarg); if (m_iLogLines == 0) { ReportError("Could not parse value of option 'G'"); return; } break; case 'T': m_bAddTop = true; break; case 'C': m_bRemoteClientMode = true; break; case 'E': { m_eClientOperation = opClientRequestEditQueue; bool bGroup = !strcasecmp(optarg, "G") || !strcasecmp(optarg, "GN") || !strcasecmp(optarg, "GR"); bool bFile = !strcasecmp(optarg, "F") || !strcasecmp(optarg, "FN") || !strcasecmp(optarg, "FR"); if (!strcasecmp(optarg, "GN") || !strcasecmp(optarg, "FN")) { m_EMatchMode = mmName; } else if (!strcasecmp(optarg, "GR") || !strcasecmp(optarg, "FR")) { m_EMatchMode = mmRegEx; } else { m_EMatchMode = mmID; }; bool bPost = !strcasecmp(optarg, "O"); bool bHistory = !strcasecmp(optarg, "H"); if (bGroup || bFile || bPost || bHistory) { optind++; if (optind > argc) { ReportError("Could not parse value of option 'E'"); return; } optarg = argv[optind-1]; } if (bPost) { // edit-commands for post-processor-queue if (!strcasecmp(optarg, "D")) { m_iEditQueueAction = DownloadQueue::eaPostDelete; } else { ReportError("Could not parse value of option 'E'"); return; } } else if (bHistory) { // edit-commands for history if (!strcasecmp(optarg, "D")) { m_iEditQueueAction = DownloadQueue::eaHistoryDelete; } else if (!strcasecmp(optarg, "R")) { m_iEditQueueAction = DownloadQueue::eaHistoryReturn; } else if (!strcasecmp(optarg, "P")) { m_iEditQueueAction = DownloadQueue::eaHistoryProcess; } else if (!strcasecmp(optarg, "A")) { m_iEditQueueAction = DownloadQueue::eaHistoryRedownload; } else if (!strcasecmp(optarg, "O")) { m_iEditQueueAction = DownloadQueue::eaHistorySetParameter; optind++; if (optind > argc) { ReportError("Could not parse value of option 'E'"); return; } m_szEditQueueText = strdup(argv[optind-1]); if (!strchr(m_szEditQueueText, '=')) { ReportError("Could not parse value of option 'E'"); return; } } else if (!strcasecmp(optarg, "B")) { m_iEditQueueAction = DownloadQueue::eaHistoryMarkBad; } else if (!strcasecmp(optarg, "G")) { m_iEditQueueAction = DownloadQueue::eaHistoryMarkGood; } else if (!strcasecmp(optarg, "S")) { m_iEditQueueAction = DownloadQueue::eaHistoryMarkSuccess; } else { ReportError("Could not parse value of option 'E'"); return; } } else { // edit-commands for download-queue if (!strcasecmp(optarg, "T")) { m_iEditQueueAction = bGroup ? DownloadQueue::eaGroupMoveTop : DownloadQueue::eaFileMoveTop; } else if (!strcasecmp(optarg, "B")) { m_iEditQueueAction = bGroup ? DownloadQueue::eaGroupMoveBottom : DownloadQueue::eaFileMoveBottom; } else if (!strcasecmp(optarg, "P")) { m_iEditQueueAction = bGroup ? DownloadQueue::eaGroupPause : DownloadQueue::eaFilePause; } else if (!strcasecmp(optarg, "A")) { m_iEditQueueAction = bGroup ? DownloadQueue::eaGroupPauseAllPars : DownloadQueue::eaFilePauseAllPars; } else if (!strcasecmp(optarg, "R")) { m_iEditQueueAction = bGroup ? DownloadQueue::eaGroupPauseExtraPars : DownloadQueue::eaFilePauseExtraPars; } else if (!strcasecmp(optarg, "U")) { m_iEditQueueAction = bGroup ? DownloadQueue::eaGroupResume : DownloadQueue::eaFileResume; } else if (!strcasecmp(optarg, "D")) { m_iEditQueueAction = bGroup ? DownloadQueue::eaGroupDelete : DownloadQueue::eaFileDelete; } else if (!strcasecmp(optarg, "C") || !strcasecmp(optarg, "K") || !strcasecmp(optarg, "CP")) { // switch "K" is provided for compatibility with v. 0.8.0 and can be removed in future versions if (!bGroup) { ReportError("Category can be set only for groups"); return; } m_iEditQueueAction = !strcasecmp(optarg, "CP") ? DownloadQueue::eaGroupApplyCategory : DownloadQueue::eaGroupSetCategory; optind++; if (optind > argc) { ReportError("Could not parse value of option 'E'"); return; } m_szEditQueueText = strdup(argv[optind-1]); } else if (!strcasecmp(optarg, "N")) { if (!bGroup) { ReportError("Only groups can be renamed"); return; } m_iEditQueueAction = DownloadQueue::eaGroupSetName; optind++; if (optind > argc) { ReportError("Could not parse value of option 'E'"); return; } m_szEditQueueText = strdup(argv[optind-1]); } else if (!strcasecmp(optarg, "M")) { if (!bGroup) { ReportError("Only groups can be merged"); return; } m_iEditQueueAction = DownloadQueue::eaGroupMerge; } else if (!strcasecmp(optarg, "S")) { m_iEditQueueAction = DownloadQueue::eaFileSplit; optind++; if (optind > argc) { ReportError("Could not parse value of option 'E'"); return; } m_szEditQueueText = strdup(argv[optind-1]); } else if (!strcasecmp(optarg, "O")) { if (!bGroup) { ReportError("Post-process parameter can be set only for groups"); return; } m_iEditQueueAction = DownloadQueue::eaGroupSetParameter; optind++; if (optind > argc) { ReportError("Could not parse value of option 'E'"); return; } m_szEditQueueText = strdup(argv[optind-1]); if (!strchr(m_szEditQueueText, '=')) { ReportError("Could not parse value of option 'E'"); return; } } else if (!strcasecmp(optarg, "I")) { if (!bGroup) { ReportError("Priority can be set only for groups"); return; } m_iEditQueueAction = DownloadQueue::eaGroupSetPriority; optind++; if (optind > argc) { ReportError("Could not parse value of option 'E'"); return; } m_szEditQueueText = strdup(argv[optind-1]); if (atoi(m_szEditQueueText) == 0 && strcmp("0", m_szEditQueueText)) { ReportError("Could not parse value of option 'E'"); return; } } else { m_iEditQueueOffset = atoi(optarg); if (m_iEditQueueOffset == 0) { ReportError("Could not parse value of option 'E'"); return; } m_iEditQueueAction = bGroup ? DownloadQueue::eaGroupMoveOffset : DownloadQueue::eaFileMoveOffset; } } break; } case 'Q': m_eClientOperation = opClientRequestShutdown; break; case 'O': m_eClientOperation = opClientRequestReload; break; case 'V': m_eClientOperation = opClientRequestVersion; break; case 'W': m_eClientOperation = opClientRequestWriteLog; if (!strcasecmp(optarg, "I")) { m_iWriteLogKind = (int)Message::mkInfo; } else if (!strcasecmp(optarg, "W")) { m_iWriteLogKind = (int)Message::mkWarning; } else if (!strcasecmp(optarg, "E")) { m_iWriteLogKind = (int)Message::mkError; } else if (!strcasecmp(optarg, "D")) { m_iWriteLogKind = (int)Message::mkDetail; } else if (!strcasecmp(optarg, "G")) { m_iWriteLogKind = (int)Message::mkDebug; } else { ReportError("Could not parse value of option 'W'"); return; } break; case 'K': // switch "K" is provided for compatibility with v. 0.8.0 and can be removed in future versions free(m_szAddCategory); m_szAddCategory = strdup(optarg); break; case 'S': optind++; optarg = optind > argc ? NULL : argv[optind-1]; if (!optarg || !strncmp(optarg, "-", 1)) { m_eClientOperation = opClientRequestScanAsync; optind--; } else if (!strcasecmp(optarg, "W")) { m_eClientOperation = opClientRequestScanSync; } else { ReportError("Could not parse value of option 'S'"); return; } break; case '?': m_bErrors = true; return; } } for (int i = 0; i < argc; i++) { free(argv[i]); } free(argv); if (m_bServerMode && m_eClientOperation == opClientRequestDownloadPause) { m_bPauseDownload = true; m_eClientOperation = opClientNoOperation; } } void CommandLineParser::PrintUsage(const char* com) { printf("Usage:\n" " %s [switches]\n\n" "Switches:\n" " -h, --help Print this help-message\n" " -v, --version Print version and exit\n" " -c, --configfile Filename of configuration-file\n" " -n, --noconfigfile Prevent loading of configuration-file\n" " (required options must be passed with --option)\n" " -p, --printconfig Print configuration and exit\n" " -o, --option Set or override option in configuration-file\n" " -s, --server Start nzbget as a server in console-mode\n" #ifndef WIN32 " -D, --daemon Start nzbget as a server in daemon-mode\n" #endif " -V, --serverversion Print server's version and exit\n" " -Q, --quit Shutdown server\n" " -O, --reload Reload config and restart all services\n" " -A, --append [] Send file/url to server's\n" " download queue\n" " are (multiple options must be separated with space):\n" " T Add file to the top (beginning) of queue\n" " P Pause added files\n" " C Assign category to nzb-file\n" " N Use this name as nzb-filename\n" " I Set priority (signed integer)\n" " DK Set duplicate key (string)\n" " DS Set duplicate score (signed integer)\n" " DM (score|all|force) Set duplicate mode\n" " -C, --connect Attach client to server\n" " -L, --list [F|FR|G|GR|O|H|S] [RegEx] Request list of items from server\n" " F List individual files and server status (default)\n" " FR Like \"F\" but apply regular expression filter\n" " G List groups (nzb-files) and server status\n" " GR Like \"G\" but apply regular expression filter\n" " O List post-processor-queue\n" " H List history\n" " HA List history, all records (incl. hidden)\n" " S Print only server status\n" " Regular expression (only with options \"FR\", \"GR\")\n" " using POSIX Extended Regular Expression Syntax\n" " -P, --pause [D|O|S] Pause server\n" " D Pause download queue (default)\n" " O Pause post-processor queue\n" " S Pause scan of incoming nzb-directory\n" " -U, --unpause [D|O|S] Unpause server\n" " D Unpause download queue (default)\n" " O Unpause post-processor queue\n" " S Unpause scan of incoming nzb-directory\n" " -R, --rate Set download rate on server, in KB/s\n" " -G, --log Request last lines from server's screen-log\n" " -W, --write \"Text\" Send text to server's log\n" " -S, --scan [W] Scan incoming nzb-directory on server\n" " W Wait until scan completes (synchronous mode)\n" " -E, --edit [F|FN|FR|G|GN|GR|O|H] Edit items\n" " on server\n" " F Edit individual files (default)\n" " FN Like \"F\" but uses names (as \"group/file\")\n" " instead of IDs\n" " FR Like \"FN\" but with regular expressions\n" " G Edit all files in the group (same nzb-file)\n" " GN Like \"G\" but uses group names instead of IDs\n" " GR Like \"GN\" but with regular expressions\n" " O Edit post-processor-queue\n" " H Edit history\n" " is one of:\n" " - for files (F) and groups (G):\n" " <+offset|-offset> Move in queue relative to current position,\n" " offset is an integer value\n" " T Move to top of queue\n" " B Move to bottom of queue\n" " D Delete\n" " - for files (F) and groups (G):\n" " P Pause\n" " U Resume (unpause)\n" " - for groups (G):\n" " A Pause all pars\n" " R Pause extra pars\n" " I Set priority (signed integer)\n" " C Set category\n" " CP Set category and apply post-process parameters\n" " N Rename\n" " M Merge\n" " S Split - create new group from selected files\n" " O = Set post-process parameter\n" " - for post-jobs (O):\n" " D Delete (cancel post-processing)\n" " - for history (H):\n" " D Delete\n" " P Post-process again\n" " R Download remaining files\n" " A Download again\n" " O = Set post-process parameter\n" " B Mark as bad\n" " G Mark as good\n" " S Mark as success\n" " Comma-separated list of file- or group- ids or\n" " ranges of file-ids, e. g.: 1-5,3,10-22\n" " List of names (with options \"FN\" and \"GN\"),\n" " e. g.: \"my nzb download%cmyfile.nfo\" \"another nzb\"\n" " List of regular expressions (options \"FR\", \"GR\")\n" " using POSIX Extended Regular Expression Syntax\n", Util::BaseFileName(com), PATH_SEPARATOR); } void CommandLineParser::InitFileArg(int argc, const char* argv[]) { if (optind >= argc) { // no nzb-file passed if (!m_bServerMode && !m_bRemoteClientMode && (m_eClientOperation == opClientNoOperation || m_eClientOperation == opClientRequestDownload || m_eClientOperation == opClientRequestWriteLog)) { if (m_eClientOperation == opClientRequestWriteLog) { ReportError("Log-text not specified"); } else { ReportError("Nzb-file or Url not specified"); } } } else if (m_eClientOperation == opClientRequestEditQueue) { if (m_EMatchMode == mmID) { ParseFileIDList(argc, argv, optind); } else { ParseFileNameList(argc, argv, optind); } } else { m_szLastArg = strdup(argv[optind]); // Check if the file-name is a relative path or an absolute path // If the path starts with '/' its an absolute, else relative const char* szFileName = argv[optind]; #ifdef WIN32 m_szArgFilename = strdup(szFileName); #else if (szFileName[0] == '/' || !strncasecmp(szFileName, "http://", 6) || !strncasecmp(szFileName, "https://", 7)) { m_szArgFilename = strdup(szFileName); } else { // TEST char szFileNameWithPath[1024]; getcwd(szFileNameWithPath, 1024); strcat(szFileNameWithPath, "/"); strcat(szFileNameWithPath, szFileName); m_szArgFilename = strdup(szFileNameWithPath); } #endif if (m_bServerMode || m_bRemoteClientMode || !(m_eClientOperation == opClientNoOperation || m_eClientOperation == opClientRequestDownload || m_eClientOperation == opClientRequestWriteLog)) { ReportError("Too many arguments"); } } } void CommandLineParser::ParseFileIDList(int argc, const char* argv[], int optind) { std::vector IDs; IDs.clear(); while (optind < argc) { char* szWritableFileIDList = strdup(argv[optind++]); char* optarg = strtok(szWritableFileIDList, ", "); while (optarg) { int iEditQueueIDFrom = 0; int iEditQueueIDTo = 0; const char* p = strchr(optarg, '-'); if (p) { char buf[101]; int maxlen = (int)(p - optarg < 100 ? p - optarg : 100); strncpy(buf, optarg, maxlen); buf[maxlen] = '\0'; iEditQueueIDFrom = atoi(buf); iEditQueueIDTo = atoi(p + 1); if (iEditQueueIDFrom <= 0 || iEditQueueIDTo <= 0) { ReportError("invalid list of file IDs"); return; } } else { iEditQueueIDFrom = atoi(optarg); if (iEditQueueIDFrom <= 0) { ReportError("invalid list of file IDs"); return; } iEditQueueIDTo = iEditQueueIDFrom; } int iEditQueueIDCount = 0; if (iEditQueueIDTo != 0) { if (iEditQueueIDFrom < iEditQueueIDTo) { iEditQueueIDCount = iEditQueueIDTo - iEditQueueIDFrom + 1; } else { iEditQueueIDCount = iEditQueueIDFrom - iEditQueueIDTo + 1; } } else { iEditQueueIDCount = 1; } for (int i = 0; i < iEditQueueIDCount; i++) { if (iEditQueueIDFrom < iEditQueueIDTo || iEditQueueIDTo == 0) { IDs.push_back(iEditQueueIDFrom + i); } else { IDs.push_back(iEditQueueIDFrom - i); } } optarg = strtok(NULL, ", "); } free(szWritableFileIDList); } m_iEditQueueIDCount = IDs.size(); m_pEditQueueIDList = (int*)malloc(sizeof(int) * m_iEditQueueIDCount); for (int i = 0; i < m_iEditQueueIDCount; i++) { m_pEditQueueIDList[i] = IDs[i]; } } void CommandLineParser::ParseFileNameList(int argc, const char* argv[], int optind) { while (optind < argc) { m_EditQueueNameList.push_back(strdup(argv[optind++])); } } void CommandLineParser::ReportError(const char* szErrMessage) { m_bErrors = true; printf("%s\n", szErrMessage); } nzbget-16.4/daemon/main/DiskService.h0000644000175000017500000000235712630544544017342 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2015 Andrey Prygunkov * * 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 DISKSERVICE_H #define DISKSERVICE_H #include "Service.h" class DiskService : public Service { private: int m_iInterval; bool m_bWaitingRequiredDir; bool m_bWaitingReported; void CheckDiskSpace(); void CheckRequiredDir(); protected: virtual int ServiceInterval() { return 200; } virtual void ServiceWork(); public: DiskService(); }; #endif nzbget-16.4/daemon/main/StackTrace.cpp0000644000175000017500000001603312630544544017502 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #include #ifdef WIN32 #include #else #include #include #include #endif #ifdef HAVE_SYS_PRCTL_H #include #endif #ifdef HAVE_BACKTRACE #include #endif #include "nzbget.h" #include "Log.h" #include "Options.h" #include "StackTrace.h" extern void ExitProc(); #ifdef WIN32 #ifdef DEBUG void PrintBacktrace(PCONTEXT pContext) { HANDLE hProcess = GetCurrentProcess(); HANDLE hThread = GetCurrentThread(); char szAppDir[MAX_PATH + 1]; GetModuleFileName(NULL, szAppDir, sizeof(szAppDir)); char* end = strrchr(szAppDir, PATH_SEPARATOR); if (end) *end = '\0'; SymSetOptions(SymGetOptions() | SYMOPT_LOAD_LINES | SYMOPT_FAIL_CRITICAL_ERRORS); if (!SymInitialize(hProcess, szAppDir, TRUE)) { warn("Could not obtain detailed exception information: SymInitialize failed"); return; } const int MAX_NAMELEN = 1024; IMAGEHLP_SYMBOL64* pSym = (IMAGEHLP_SYMBOL64 *) malloc(sizeof(IMAGEHLP_SYMBOL64) + MAX_NAMELEN); memset(pSym, 0, sizeof(IMAGEHLP_SYMBOL64) + MAX_NAMELEN); pSym->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); pSym->MaxNameLength = MAX_NAMELEN; IMAGEHLP_LINE64 ilLine; memset(&ilLine, 0, sizeof(ilLine)); ilLine.SizeOfStruct = sizeof(ilLine); STACKFRAME64 sfStackFrame; memset(&sfStackFrame, 0, sizeof(sfStackFrame)); DWORD imageType; #ifdef _M_IX86 imageType = IMAGE_FILE_MACHINE_I386; sfStackFrame.AddrPC.Offset = pContext->Eip; sfStackFrame.AddrPC.Mode = AddrModeFlat; sfStackFrame.AddrFrame.Offset = pContext->Ebp; sfStackFrame.AddrFrame.Mode = AddrModeFlat; sfStackFrame.AddrStack.Offset = pContext->Esp; sfStackFrame.AddrStack.Mode = AddrModeFlat; #elif _M_X64 imageType = IMAGE_FILE_MACHINE_AMD64; sfStackFrame.AddrPC.Offset = pContext->Rip; sfStackFrame.AddrPC.Mode = AddrModeFlat; sfStackFrame.AddrFrame.Offset = pContext->Rsp; sfStackFrame.AddrFrame.Mode = AddrModeFlat; sfStackFrame.AddrStack.Offset = pContext->Rsp; sfStackFrame.AddrStack.Mode = AddrModeFlat; #else warn("Could not obtain detailed exception information: platform not supported"); return; #endif for (int frameNum = 0; ; frameNum++) { if (frameNum > 1000) { warn("Endless stack, abort tracing"); return; } if (!StackWalk64(imageType, hProcess, hThread, &sfStackFrame, pContext, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)) { warn("Could not obtain detailed exception information: StackWalk64 failed"); return; } DWORD64 dwAddr = sfStackFrame.AddrPC.Offset; char szSymName[1024]; char szSrcFileName[1024]; int iLineNumber = 0; DWORD64 dwSymbolDisplacement; if (SymGetSymFromAddr64(hProcess, dwAddr, &dwSymbolDisplacement, pSym)) { UnDecorateSymbolName(pSym->Name, szSymName, sizeof(szSymName), UNDNAME_COMPLETE); szSymName[sizeof(szSymName) - 1] = '\0'; } else { strncpy(szSymName, "", sizeof(szSymName)); } DWORD dwLineDisplacement; if (SymGetLineFromAddr64(hProcess, dwAddr, &dwLineDisplacement, &ilLine)) { iLineNumber = ilLine.LineNumber; char* szUseFileName = ilLine.FileName; char* szRoot = strstr(szUseFileName, "\\daemon\\"); if (szRoot) { szUseFileName = szRoot; } strncpy(szSrcFileName, szUseFileName, sizeof(szSrcFileName)); szSrcFileName[sizeof(szSrcFileName) - 1] = '\0'; } else { strncpy(szSrcFileName, "", sizeof(szSymName)); } info("%s (%i) : %s", szSrcFileName, iLineNumber, szSymName); if (sfStackFrame.AddrReturn.Offset == 0) { break; } } } #endif LONG __stdcall ExceptionFilter(EXCEPTION_POINTERS* pExPtrs) { error("Unhandled Exception: code: 0x%8.8X, flags: %d, address: 0x%8.8X", pExPtrs->ExceptionRecord->ExceptionCode, pExPtrs->ExceptionRecord->ExceptionFlags, pExPtrs->ExceptionRecord->ExceptionAddress); #ifdef DEBUG PrintBacktrace(pExPtrs->ContextRecord); #else info("Detailed exception information can be printed by debug version of NZBGet (available from download page)"); #endif ExitProcess(-1); return EXCEPTION_CONTINUE_SEARCH; } void InstallErrorHandler() { SetUnhandledExceptionFilter(ExceptionFilter); } #else #ifdef DEBUG typedef void(*sighandler)(int); std::vector SignalProcList; #endif #ifdef HAVE_SYS_PRCTL_H /** * activates the creation of core-files */ void EnableDumpCore() { rlimit rlim; rlim.rlim_cur= RLIM_INFINITY; rlim.rlim_max= RLIM_INFINITY; setrlimit(RLIMIT_CORE, &rlim); prctl(PR_SET_DUMPABLE, 1); } #endif void PrintBacktrace() { #ifdef HAVE_BACKTRACE printf("Segmentation fault, tracing...\n"); void *array[100]; size_t size; char **strings; size_t i; size = backtrace(array, 100); strings = backtrace_symbols(array, size); // first trace to screen printf("Obtained %zd stack frames\n", size); for (i = 0; i < size; i++) { printf("%s\n", strings[i]); } // then trace to log error("Segmentation fault, tracing..."); error("Obtained %zd stack frames", size); for (i = 0; i < size; i++) { error("%s", strings[i]); } free(strings); #else error("Segmentation fault"); #endif } /* * Signal handler */ void SignalProc(int iSignal) { switch (iSignal) { case SIGINT: signal(SIGINT, SIG_DFL); // Reset the signal handler ExitProc(); break; case SIGTERM: signal(SIGTERM, SIG_DFL); // Reset the signal handler ExitProc(); break; case SIGCHLD: // ignoring break; #ifdef DEBUG case SIGSEGV: signal(SIGSEGV, SIG_DFL); // Reset the signal handler PrintBacktrace(); break; #endif } } void InstallErrorHandler() { #ifdef HAVE_SYS_PRCTL_H if (g_pOptions->GetDumpCore()) { EnableDumpCore(); } #endif signal(SIGINT, SignalProc); signal(SIGTERM, SignalProc); signal(SIGPIPE, SIG_IGN); #ifdef DEBUG signal(SIGSEGV, SignalProc); #endif #ifdef SIGCHLD_HANDLER // it could be necessary on some systems to activate a handler for SIGCHLD // however it make troubles on other systems and is deactivated by default signal(SIGCHLD, SignalProc); #endif } #endif #ifdef DEBUG class SegFault { public: void DoSegFault() { char* N = NULL; strcpy(N, ""); } }; void TestSegFault() { SegFault s; s.DoSegFault(); } #endif nzbget-16.4/daemon/main/DiskService.cpp0000644000175000017500000000577712630544544017706 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2015 Andrey Prygunkov * * 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 #include #include #ifdef WIN32 #include #else #include #endif #include "nzbget.h" #include "DiskService.h" #include "Options.h" #include "StatMeter.h" #include "Log.h" #include "Util.h" DiskService::DiskService() { m_iInterval = 0; m_bWaitingReported = false; m_bWaitingRequiredDir = true; } void DiskService::ServiceWork() { m_iInterval++; if (m_iInterval == 5) { if (!g_pOptions->GetPauseDownload() && g_pOptions->GetDiskSpace() > 0 && !g_pStatMeter->GetStandBy()) { // check free disk space every 1 second CheckDiskSpace(); } m_iInterval = 0; } if (m_bWaitingRequiredDir) { CheckRequiredDir(); } } void DiskService::CheckDiskSpace() { long long lFreeSpace = Util::FreeDiskSize(g_pOptions->GetDestDir()); if (lFreeSpace > -1 && lFreeSpace / 1024 / 1024 < g_pOptions->GetDiskSpace()) { warn("Low disk space on %s. Pausing download", g_pOptions->GetDestDir()); g_pOptions->SetPauseDownload(true); } if (!Util::EmptyStr(g_pOptions->GetInterDir())) { lFreeSpace = Util::FreeDiskSize(g_pOptions->GetInterDir()); if (lFreeSpace > -1 && lFreeSpace / 1024 / 1024 < g_pOptions->GetDiskSpace()) { warn("Low disk space on %s. Pausing download", g_pOptions->GetInterDir()); g_pOptions->SetPauseDownload(true); } } } void DiskService::CheckRequiredDir() { if (!Util::EmptyStr(g_pOptions->GetRequiredDir())) { bool bAllExist = true; bool bWasWaitingReported = m_bWaitingReported; // split RequiredDir into tokens Tokenizer tok(g_pOptions->GetRequiredDir(), ",;"); while (const char* szDir = tok.Next()) { if (!Util::FileExists(szDir) && !Util::DirectoryExists(szDir)) { if (!bWasWaitingReported) { info("Waiting for required directory %s", szDir); m_bWaitingReported = true; } bAllExist = false; } } if (!bAllExist) { return; } } if (m_bWaitingReported) { info("All required directories available"); } g_pOptions->SetTempPauseDownload(false); g_pOptions->SetTempPausePostprocess(false); m_bWaitingRequiredDir = false; } nzbget-16.4/daemon/main/Scheduler.cpp0000644000175000017500000002227312630544544017377 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2008-2015 Andrey Prygunkov * * 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" #else #include #endif #include #include #include #include "nzbget.h" #include "Scheduler.h" #include "Options.h" #include "Log.h" #include "NewsServer.h" #include "ServerPool.h" #include "FeedInfo.h" #include "FeedCoordinator.h" #include "SchedulerScript.h" Scheduler::Task::Task(int iID, int iHours, int iMinutes, int iWeekDaysBits, ECommand eCommand, const char* szParam) { m_iID = iID; m_iHours = iHours; m_iMinutes = iMinutes; m_iWeekDaysBits = iWeekDaysBits; m_eCommand = eCommand; m_szParam = szParam ? strdup(szParam) : NULL; m_tLastExecuted = 0; } Scheduler::Task::~Task() { free(m_szParam); } Scheduler::Scheduler() { debug("Creating Scheduler"); m_bFirstChecked = false; m_tLastCheck = 0; m_TaskList.clear(); } Scheduler::~Scheduler() { debug("Destroying Scheduler"); for (TaskList::iterator it = m_TaskList.begin(); it != m_TaskList.end(); it++) { delete *it; } } void Scheduler::AddTask(Task* pTask) { m_mutexTaskList.Lock(); m_TaskList.push_back(pTask); m_mutexTaskList.Unlock(); } bool Scheduler::CompareTasks(Scheduler::Task* pTask1, Scheduler::Task* pTask2) { return (pTask1->m_iHours < pTask2->m_iHours) || ((pTask1->m_iHours == pTask2->m_iHours) && (pTask1->m_iMinutes < pTask2->m_iMinutes)); } void Scheduler::FirstCheck() { m_mutexTaskList.Lock(); m_TaskList.sort(CompareTasks); m_mutexTaskList.Unlock(); // check all tasks for the last week CheckTasks(); } void Scheduler::ServiceWork() { if (!DownloadQueue::IsLoaded()) { return; } if (!m_bFirstChecked) { FirstCheck(); m_bFirstChecked = true; return; } m_bExecuteProcess = true; CheckTasks(); CheckScheduledResume(); } void Scheduler::CheckTasks() { PrepareLog(); m_mutexTaskList.Lock(); time_t tCurrent = time(NULL); if (!m_TaskList.empty()) { // Detect large step changes of system time time_t tDiff = tCurrent - m_tLastCheck; if (tDiff > 60*90 || tDiff < 0) { debug("Reset scheduled tasks (detected clock change greater than 90 minutes or negative)"); // check all tasks for the last week m_tLastCheck = tCurrent - 60*60*24*7; m_bExecuteProcess = false; for (TaskList::iterator it = m_TaskList.begin(); it != m_TaskList.end(); it++) { Task* pTask = *it; pTask->m_tLastExecuted = 0; } } time_t tLocalCurrent = tCurrent + g_pOptions->GetLocalTimeOffset(); time_t tLocalLastCheck = m_tLastCheck + g_pOptions->GetLocalTimeOffset(); tm tmCurrent; gmtime_r(&tLocalCurrent, &tmCurrent); tm tmLastCheck; gmtime_r(&tLocalLastCheck, &tmLastCheck); tm tmLoop; memcpy(&tmLoop, &tmLastCheck, sizeof(tmLastCheck)); tmLoop.tm_hour = tmCurrent.tm_hour; tmLoop.tm_min = tmCurrent.tm_min; tmLoop.tm_sec = tmCurrent.tm_sec; time_t tLoop = Util::Timegm(&tmLoop); while (tLoop <= tLocalCurrent) { for (TaskList::iterator it = m_TaskList.begin(); it != m_TaskList.end(); it++) { Task* pTask = *it; if (pTask->m_tLastExecuted != tLoop) { tm tmAppoint; memcpy(&tmAppoint, &tmLoop, sizeof(tmLoop)); tmAppoint.tm_hour = pTask->m_iHours; tmAppoint.tm_min = pTask->m_iMinutes; tmAppoint.tm_sec = 0; time_t tAppoint = Util::Timegm(&tmAppoint); int iWeekDay = tmAppoint.tm_wday; if (iWeekDay == 0) { iWeekDay = 7; } bool bWeekDayOK = pTask->m_iWeekDaysBits == 0 || (pTask->m_iWeekDaysBits & (1 << (iWeekDay - 1))); bool bDoTask = bWeekDayOK && tLocalLastCheck < tAppoint && tAppoint <= tLocalCurrent; //debug("TEMP: 1) m_tLastCheck=%i, tLocalCurrent=%i, tLoop=%i, tAppoint=%i, bWeekDayOK=%i, bDoTask=%i", m_tLastCheck, tLocalCurrent, tLoop, tAppoint, (int)bWeekDayOK, (int)bDoTask); if (bDoTask) { ExecuteTask(pTask); pTask->m_tLastExecuted = tLoop; } } } tLoop += 60*60*24; // inc day gmtime_r(&tLoop, &tmLoop); } } m_tLastCheck = tCurrent; m_mutexTaskList.Unlock(); PrintLog(); } void Scheduler::ExecuteTask(Task* pTask) { const char* szCommandName[] = { "Pause", "Unpause", "Pause Post-processing", "Unpause Post-processing", "Set download rate", "Execute process", "Execute script", "Pause Scan", "Unpause Scan", "Enable Server", "Disable Server", "Fetch Feed" }; debug("Executing scheduled command: %s", szCommandName[pTask->m_eCommand]); switch (pTask->m_eCommand) { case scDownloadRate: if (!Util::EmptyStr(pTask->m_szParam)) { g_pOptions->SetDownloadRate(atoi(pTask->m_szParam) * 1024); m_bDownloadRateChanged = true; } break; case scPauseDownload: case scUnpauseDownload: g_pOptions->SetPauseDownload(pTask->m_eCommand == scPauseDownload); m_bPauseDownloadChanged = true; break; case scPausePostProcess: case scUnpausePostProcess: g_pOptions->SetPausePostProcess(pTask->m_eCommand == scPausePostProcess); m_bPausePostProcessChanged = true; break; case scPauseScan: case scUnpauseScan: g_pOptions->SetPauseScan(pTask->m_eCommand == scPauseScan); m_bPauseScanChanged = true; break; case scScript: case scProcess: if (m_bExecuteProcess) { SchedulerScriptController::StartScript(pTask->m_szParam, pTask->m_eCommand == scProcess, pTask->m_iID); } 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_bPausePostProcessChanged = 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_bPauseDownloadChanged) { info("Scheduler: %s download", g_pOptions->GetPauseDownload() ? "pausing" : "unpausing"); } if (m_bPausePostProcessChanged) { info("Scheduler: %s post-processing", g_pOptions->GetPausePostProcess() ? "pausing" : "unpausing"); } 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) { Tokenizer tok(szServerList, ",;"); while (const char* szServer = tok.Next()) { 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; } } } } void Scheduler::FetchFeed(const char* szFeedList) { Tokenizer tok(szFeedList, ",;"); while (const char* szFeed = tok.Next()) { 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(!strcasecmp("0", szFeed) ? 0 : pFeed->GetID()); break; } } } } void Scheduler::CheckScheduledResume() { time_t tResumeTime = g_pOptions->GetResumeTime(); time_t tCurrentTime = time(NULL); if (tResumeTime > 0 && tCurrentTime >= tResumeTime) { info("Autoresume"); g_pOptions->SetResumeTime(0); g_pOptions->SetPauseDownload(false); g_pOptions->SetPausePostProcess(false); g_pOptions->SetPauseScan(false); } } nzbget-16.4/daemon/main/StackTrace.h0000644000175000017500000000175412630544544017153 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2014 Andrey Prygunkov * * 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 STACKTRACE_H #define STACKTRACE_H void InstallErrorHandler(); #ifdef DEBUG void TestSegFault(); #endif #endif nzbget-16.4/daemon/main/Options.cpp0000644000175000017500000016727512630544544017130 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #include #include #include #include #include #ifdef WIN32 #include #else #include #endif #include "nzbget.h" #include "Util.h" #include "Options.h" #include "Log.h" #include "MessageBase.h" #include "DownloadInfo.h" // Program options static const char* OPTION_CONFIGFILE = "ConfigFile"; static const char* OPTION_APPBIN = "AppBin"; static const char* OPTION_APPDIR = "AppDir"; static const char* OPTION_VERSION = "Version"; static const char* OPTION_MAINDIR = "MainDir"; static const char* OPTION_DESTDIR = "DestDir"; static const char* OPTION_INTERDIR = "InterDir"; static const char* OPTION_TEMPDIR = "TempDir"; static const char* OPTION_QUEUEDIR = "QueueDir"; static const char* OPTION_NZBDIR = "NzbDir"; static const char* OPTION_WEBDIR = "WebDir"; static const char* OPTION_CONFIGTEMPLATE = "ConfigTemplate"; static const char* OPTION_SCRIPTDIR = "ScriptDir"; static const char* OPTION_REQUIREDDIR = "RequiredDir"; static const char* OPTION_LOGFILE = "LogFile"; static const char* OPTION_WRITELOG = "WriteLog"; static const char* OPTION_ROTATELOG = "RotateLog"; static const char* OPTION_APPENDCATEGORYDIR = "AppendCategoryDir"; static const char* OPTION_LOCKFILE = "LockFile"; static const char* OPTION_DAEMONUSERNAME = "DaemonUsername"; static const char* OPTION_OUTPUTMODE = "OutputMode"; static const char* OPTION_DUPECHECK = "DupeCheck"; static const char* OPTION_DOWNLOADRATE = "DownloadRate"; static const char* OPTION_CONTROLIP = "ControlIp"; static const char* OPTION_CONTROLPORT = "ControlPort"; static const char* OPTION_CONTROLUSERNAME = "ControlUsername"; static const char* OPTION_CONTROLPASSWORD = "ControlPassword"; static const char* OPTION_RESTRICTEDUSERNAME = "RestrictedUsername"; static const char* OPTION_RESTRICTEDPASSWORD = "RestrictedPassword"; static const char* OPTION_ADDUSERNAME = "AddUsername"; static const char* OPTION_ADDPASSWORD = "AddPassword"; static const char* OPTION_SECURECONTROL = "SecureControl"; static const char* OPTION_SECUREPORT = "SecurePort"; static const char* OPTION_SECURECERT = "SecureCert"; static const char* OPTION_SECUREKEY = "SecureKey"; static const char* OPTION_AUTHORIZEDIP = "AuthorizedIP"; static const char* OPTION_ARTICLETIMEOUT = "ArticleTimeout"; static const char* OPTION_URLTIMEOUT = "UrlTimeout"; static const char* OPTION_SAVEQUEUE = "SaveQueue"; static const char* OPTION_FLUSHQUEUE = "FlushQueue"; static const char* OPTION_RELOADQUEUE = "ReloadQueue"; static const char* OPTION_BROKENLOG = "BrokenLog"; static const char* OPTION_NZBLOG = "NzbLog"; static const char* OPTION_DECODE = "Decode"; static const char* OPTION_RETRIES = "Retries"; static const char* OPTION_RETRYINTERVAL = "RetryInterval"; static const char* OPTION_TERMINATETIMEOUT = "TerminateTimeout"; static const char* OPTION_CONTINUEPARTIAL = "ContinuePartial"; static const char* OPTION_URLCONNECTIONS = "UrlConnections"; static const char* OPTION_LOGBUFFERSIZE = "LogBufferSize"; static const char* OPTION_INFOTARGET = "InfoTarget"; static const char* OPTION_WARNINGTARGET = "WarningTarget"; static const char* OPTION_ERRORTARGET = "ErrorTarget"; static const char* OPTION_DEBUGTARGET = "DebugTarget"; static const char* OPTION_DETAILTARGET = "DetailTarget"; static const char* OPTION_PARCHECK = "ParCheck"; static const char* OPTION_PARREPAIR = "ParRepair"; static const char* OPTION_PARSCAN = "ParScan"; static const char* OPTION_PARQUICK = "ParQuick"; static const char* OPTION_PARRENAME = "ParRename"; static const char* OPTION_PARBUFFER = "ParBuffer"; static const char* OPTION_PARTHREADS = "ParThreads"; static const char* OPTION_HEALTHCHECK = "HealthCheck"; static const char* OPTION_SCANSCRIPT = "ScanScript"; static const char* OPTION_QUEUESCRIPT = "QueueScript"; static const char* OPTION_FEEDSCRIPT = "FeedScript"; static const char* OPTION_UMASK = "UMask"; static const char* OPTION_UPDATEINTERVAL = "UpdateInterval"; static const char* OPTION_CURSESNZBNAME = "CursesNzbName"; static const char* OPTION_CURSESTIME = "CursesTime"; static const char* OPTION_CURSESGROUP = "CursesGroup"; static const char* OPTION_CRCCHECK = "CrcCheck"; static const char* OPTION_DIRECTWRITE = "DirectWrite"; static const char* OPTION_WRITEBUFFER = "WriteBuffer"; static const char* OPTION_NZBDIRINTERVAL = "NzbDirInterval"; static const char* OPTION_NZBDIRFILEAGE = "NzbDirFileAge"; static const char* OPTION_PARCLEANUPQUEUE = "ParCleanupQueue"; static const char* OPTION_DISKSPACE = "DiskSpace"; static const char* OPTION_DUMPCORE = "DumpCore"; static const char* OPTION_PARPAUSEQUEUE = "ParPauseQueue"; static const char* OPTION_SCRIPTPAUSEQUEUE = "ScriptPauseQueue"; static const char* OPTION_NZBCLEANUPDISK = "NzbCleanupDisk"; static const char* OPTION_DELETECLEANUPDISK = "DeleteCleanupDisk"; static const char* OPTION_PARTIMELIMIT = "ParTimeLimit"; static const char* OPTION_KEEPHISTORY = "KeepHistory"; static const char* OPTION_ACCURATERATE = "AccurateRate"; static const char* OPTION_UNPACK = "Unpack"; static const char* OPTION_UNPACKCLEANUPDISK = "UnpackCleanupDisk"; static const char* OPTION_UNRARCMD = "UnrarCmd"; static const char* OPTION_SEVENZIPCMD = "SevenZipCmd"; static const char* OPTION_UNPACKPASSFILE = "UnpackPassFile"; static const char* OPTION_UNPACKPAUSEQUEUE = "UnpackPauseQueue"; static const char* OPTION_SCRIPTORDER = "ScriptOrder"; static const char* OPTION_POSTSCRIPT = "PostScript"; static const char* OPTION_EXTCLEANUPDISK = "ExtCleanupDisk"; static const char* OPTION_PARIGNOREEXT = "ParIgnoreExt"; static const char* OPTION_FEEDHISTORY = "FeedHistory"; static const char* OPTION_URLFORCE = "UrlForce"; static const char* OPTION_TIMECORRECTION = "TimeCorrection"; static const char* OPTION_PROPAGATIONDELAY = "PropagationDelay"; static const char* OPTION_ARTICLECACHE = "ArticleCache"; static const char* OPTION_EVENTINTERVAL = "EventInterval"; // obsolete options static const char* OPTION_POSTLOGKIND = "PostLogKind"; static const char* OPTION_NZBLOGKIND = "NZBLogKind"; static const char* OPTION_RETRYONCRCERROR = "RetryOnCrcError"; static const char* OPTION_ALLOWREPROCESS = "AllowReProcess"; static const char* OPTION_POSTPROCESS = "PostProcess"; static const char* OPTION_LOADPARS = "LoadPars"; static const char* OPTION_THREADLIMIT = "ThreadLimit"; static const char* OPTION_PROCESSLOGKIND = "ProcessLogKind"; static const char* OPTION_APPENDNZBDIR = "AppendNzbDir"; static const char* OPTION_RENAMEBROKEN = "RenameBroken"; static const char* OPTION_MERGENZB = "MergeNzb"; static const char* OPTION_STRICTPARNAME = "StrictParName"; static const char* OPTION_RELOADURLQUEUE = "ReloadUrlQueue"; static const char* OPTION_RELOADPOSTQUEUE = "ReloadPostQueue"; static const char* OPTION_NZBPROCESS = "NZBProcess"; static const char* OPTION_NZBADDEDPROCESS = "NZBAddedProcess"; static const char* OPTION_CREATELOG = "CreateLog"; static const char* OPTION_RESETLOG = "ResetLog"; const char* BoolNames[] = { "yes", "no", "true", "false", "1", "0", "on", "off", "enable", "disable", "enabled", "disabled" }; const int BoolValues[] = { 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 }; const int BoolCount = 12; #ifndef WIN32 const char* PossibleConfigLocations[] = { "~/.nzbget", "/etc/nzbget.conf", "/usr/etc/nzbget.conf", "/usr/local/etc/nzbget.conf", "/opt/etc/nzbget.conf", NULL }; #endif Options* g_pOptions = NULL; Options::OptEntry::OptEntry() { m_szName = NULL; m_szValue = NULL; m_szDefValue = NULL; m_iLineNo = 0; } Options::OptEntry::OptEntry(const char* szName, const char* szValue) { m_szName = strdup(szName); m_szValue = strdup(szValue); m_szDefValue = NULL; m_iLineNo = 0; } Options::OptEntry::~OptEntry() { free(m_szName); free(m_szValue); free(m_szDefValue); } void Options::OptEntry::SetName(const char* szName) { free(m_szName); m_szName = strdup(szName); } void Options::OptEntry::SetValue(const char* szValue) { free(m_szValue); m_szValue = strdup(szValue); if (!m_szDefValue) { m_szDefValue = strdup(szValue); } } bool Options::OptEntry::Restricted() { char szLoName[256]; strncpy(szLoName, m_szName, sizeof(szLoName)); szLoName[256-1] = '\0'; for (char* p = szLoName; *p; p++) *p = tolower(*p); // convert string to lowercase bool bRestricted = !strcasecmp(m_szName, OPTION_CONTROLIP) || !strcasecmp(m_szName, OPTION_CONTROLPORT) || !strcasecmp(m_szName, OPTION_SECURECONTROL) || !strcasecmp(m_szName, OPTION_SECUREPORT) || !strcasecmp(m_szName, OPTION_SECURECERT) || !strcasecmp(m_szName, OPTION_SECUREKEY) || !strcasecmp(m_szName, OPTION_AUTHORIZEDIP) || !strcasecmp(m_szName, OPTION_DAEMONUSERNAME) || !strcasecmp(m_szName, OPTION_UMASK) || strchr(m_szName, ':') || // All extension script options strstr(szLoName, "username") || // ServerX.Username, ControlUsername, etc. strstr(szLoName, "password"); // ServerX.Password, ControlPassword, etc. return bRestricted; } Options::OptEntries::~OptEntries() { for (iterator it = begin(); it != end(); it++) { delete *it; } } Options::OptEntry* Options::OptEntries::FindOption(const char* szName) { if (!szName) { return NULL; } for (iterator it = begin(); it != end(); it++) { OptEntry* pOptEntry = *it; if (!strcasecmp(pOptEntry->GetName(), szName)) { return pOptEntry; } } return NULL; } Options::Category::Category(const char* szName, const char* szDestDir, bool bUnpack, const char* szPostScript) { m_szName = strdup(szName); m_szDestDir = szDestDir ? strdup(szDestDir) : NULL; m_bUnpack = bUnpack; m_szPostScript = szPostScript ? strdup(szPostScript) : NULL; } Options::Category::~Category() { free(m_szName); free(m_szDestDir); free(m_szPostScript); for (NameList::iterator it = m_Aliases.begin(); it != m_Aliases.end(); it++) { free(*it); } } Options::Categories::~Categories() { for (iterator it = begin(); it != end(); it++) { delete *it; } } Options::Category* Options::Categories::FindCategory(const char* szName, bool bSearchAliases) { if (!szName) { return NULL; } for (iterator it = begin(); it != end(); it++) { Category* pCategory = *it; if (!strcasecmp(pCategory->GetName(), szName)) { return pCategory; } } if (bSearchAliases) { for (iterator it = begin(); it != end(); it++) { Category* pCategory = *it; for (NameList::iterator it2 = pCategory->GetAliases()->begin(); it2 != pCategory->GetAliases()->end(); it2++) { const char* szAlias = *it2; WildMask mask(szAlias); if (mask.Match(szName)) { return pCategory; } } } } return NULL; } Options::Options(const char* szExeName, const char* szConfigFilename, bool bNoConfig, CmdOptList* pCommandLineOptions, Extender* pExtender) { Init(szExeName, szConfigFilename, bNoConfig, pCommandLineOptions, false, pExtender); } Options::Options(CmdOptList* pCommandLineOptions, Extender* pExtender) { Init("nzbget/nzbget", NULL, true, pCommandLineOptions, true, pExtender); } void Options::Init(const char* szExeName, const char* szConfigFilename, bool bNoConfig, CmdOptList* pCommandLineOptions, bool bNoDiskAccess, Extender* pExtender) { g_pOptions = this; m_pExtender = pExtender; m_bNoDiskAccess = bNoDiskAccess; m_bNoConfig = bNoConfig; m_bConfigErrors = false; m_iConfigLine = 0; m_bServerMode = false; m_bRemoteClientMode = false; m_bFatalError = false; // initialize options with default values m_szConfigFilename = NULL; m_szAppDir = NULL; m_szDestDir = NULL; m_szInterDir = NULL; m_szTempDir = NULL; m_szQueueDir = NULL; m_szNzbDir = NULL; m_szWebDir = NULL; m_szConfigTemplate = NULL; m_szScriptDir = NULL; m_szRequiredDir = NULL; m_eInfoTarget = mtScreen; m_eWarningTarget = mtScreen; m_eErrorTarget = mtScreen; m_eDebugTarget = mtScreen; m_eDetailTarget = mtScreen; m_bDecode = true; m_bPauseDownload = false; m_bPausePostProcess = false; m_bPauseScan = false; m_bTempPauseDownload = true; m_bTempPausePostprocess = true; m_bBrokenLog = false; m_bNzbLog = false; m_iDownloadRate = 0; m_iArticleTimeout = 0; m_iUrlTimeout = 0; m_iTerminateTimeout = 0; m_bAppendCategoryDir = false; m_bContinuePartial = false; m_bSaveQueue = false; m_bFlushQueue = false; m_bDupeCheck = false; m_iRetries = 0; m_iRetryInterval = 0; m_iControlPort = 0; m_szControlIP = NULL; m_szControlUsername = NULL; m_szControlPassword = NULL; m_szRestrictedUsername = NULL; m_szRestrictedPassword = NULL; m_szAddUsername = NULL; m_szAddPassword = NULL; m_bSecureControl = false; m_iSecurePort = 0; m_szSecureCert = NULL; m_szSecureKey = NULL; m_szAuthorizedIP = NULL; m_szLockFile = NULL; m_szDaemonUsername = NULL; m_eOutputMode = omLoggable; m_bReloadQueue = false; m_iUrlConnections = 0; m_iLogBufferSize = 0; m_eWriteLog = wlAppend; m_iRotateLog = 0; m_szLogFile = NULL; m_eParCheck = pcManual; m_bParRepair = false; m_eParScan = psLimited; m_bParQuick = true; m_bParRename = false; m_iParBuffer = 0; m_iParThreads = 0; m_eHealthCheck = hcNone; m_szScriptOrder = NULL; m_szPostScript = NULL; m_szScanScript = NULL; m_szQueueScript = NULL; m_szFeedScript = NULL; m_iUMask = 0; m_iUpdateInterval = 0; m_bCursesNZBName = false; m_bCursesTime = false; m_bCursesGroup = false; m_bCrcCheck = false; m_bDirectWrite = false; m_iWriteBuffer = 0; m_iNzbDirInterval = 0; m_iNzbDirFileAge = 0; m_bParCleanupQueue = false; m_iDiskSpace = 0; m_bTLS = false; m_bDumpCore = false; m_bParPauseQueue = false; m_bScriptPauseQueue = false; m_bNzbCleanupDisk = false; m_bDeleteCleanupDisk = false; m_iParTimeLimit = 0; m_iKeepHistory = 0; m_bAccurateRate = false; m_tResumeTime = 0; m_bUnpack = false; m_bUnpackCleanupDisk = false; m_szUnrarCmd = NULL; m_szSevenZipCmd = NULL; m_szUnpackPassFile = NULL; m_bUnpackPauseQueue = false; m_szExtCleanupDisk = NULL; m_szParIgnoreExt = NULL; m_iFeedHistory = 0; m_bUrlForce = false; m_iTimeCorrection = 0; m_iLocalTimeOffset = 0; m_iPropagationDelay = 0; m_iArticleCache = 0; m_iEventInterval = 0; m_bNoDiskAccess = bNoDiskAccess; m_szConfigFilename = szConfigFilename ? strdup(szConfigFilename) : NULL; SetOption(OPTION_CONFIGFILE, ""); char szFilename[MAX_PATH + 1]; if (m_bNoDiskAccess) { strncpy(szFilename, szExeName, sizeof(szFilename)); szFilename[sizeof(szFilename)-1] = '\0'; } else { Util::GetExeFileName(szExeName, szFilename, sizeof(szFilename)); } Util::NormalizePathSeparators(szFilename); SetOption(OPTION_APPBIN, szFilename); char* end = strrchr(szFilename, PATH_SEPARATOR); if (end) *end = '\0'; SetOption(OPTION_APPDIR, szFilename); m_szAppDir = strdup(szFilename); SetOption(OPTION_VERSION, Util::VersionRevision()); InitDefaults(); InitOptFile(); if (m_bFatalError) { return; } if (pCommandLineOptions) { InitCommandLineOptions(pCommandLineOptions); } if (!m_szConfigFilename && !bNoConfig) { printf("No configuration-file found\n"); #ifdef WIN32 printf("Please put configuration-file \"nzbget.conf\" into the directory with exe-file\n"); #else printf("Please use option \"-c\" or put configuration-file in one of the following locations:\n"); int p = 0; while (const char* szFilename = PossibleConfigLocations[p++]) { printf("%s\n", szFilename); } #endif m_bFatalError = true; return; } InitOptions(); CheckOptions(); InitServers(); InitCategories(); InitScheduler(); InitFeeds(); } Options::~Options() { g_pOptions = NULL; free(m_szConfigFilename); free(m_szAppDir); free(m_szDestDir); free(m_szInterDir); free(m_szTempDir); free(m_szQueueDir); free(m_szNzbDir); free(m_szWebDir); free(m_szConfigTemplate); free(m_szScriptDir); free(m_szRequiredDir); free(m_szControlIP); free(m_szControlUsername); free(m_szControlPassword); free(m_szRestrictedUsername); free(m_szRestrictedPassword); free(m_szAddUsername); free(m_szAddPassword); free(m_szSecureCert); free(m_szSecureKey); free(m_szAuthorizedIP); free(m_szLogFile); free(m_szLockFile); free(m_szDaemonUsername); free(m_szScriptOrder); free(m_szPostScript); free(m_szScanScript); free(m_szQueueScript); free(m_szFeedScript); free(m_szUnrarCmd); free(m_szSevenZipCmd); free(m_szUnpackPassFile); free(m_szExtCleanupDisk); free(m_szParIgnoreExt); } void Options::Dump() { for (OptEntries::iterator it = m_OptEntries.begin(); it != m_OptEntries.end(); it++) { OptEntry* pOptEntry = *it; printf("%s = \"%s\"\n", pOptEntry->GetName(), pOptEntry->GetValue()); } } void Options::ConfigError(const char* msg, ...) { char tmp2[1024]; va_list ap; va_start(ap, msg); vsnprintf(tmp2, 1024, msg, ap); tmp2[1024-1] = '\0'; va_end(ap); printf("%s(%i): %s\n", m_szConfigFilename ? Util::BaseFileName(m_szConfigFilename) : "", m_iConfigLine, tmp2); error("%s(%i): %s", m_szConfigFilename ? Util::BaseFileName(m_szConfigFilename) : "", m_iConfigLine, tmp2); m_bConfigErrors = true; } void Options::ConfigWarn(const char* msg, ...) { char tmp2[1024]; va_list ap; va_start(ap, msg); vsnprintf(tmp2, 1024, msg, ap); tmp2[1024-1] = '\0'; va_end(ap); printf("%s(%i): %s\n", Util::BaseFileName(m_szConfigFilename), m_iConfigLine, tmp2); warn("%s(%i): %s", Util::BaseFileName(m_szConfigFilename), m_iConfigLine, tmp2); } void Options::LocateOptionSrcPos(const char *szOptionName) { OptEntry* pOptEntry = FindOption(szOptionName); if (pOptEntry) { m_iConfigLine = pOptEntry->GetLineNo(); } else { m_iConfigLine = 0; } } void Options::InitDefaults() { #ifdef WIN32 SetOption(OPTION_MAINDIR, "${AppDir}"); SetOption(OPTION_WEBDIR, "${AppDir}\\webui"); SetOption(OPTION_CONFIGTEMPLATE, "${AppDir}\\nzbget.conf.template"); #else SetOption(OPTION_MAINDIR, "~/downloads"); SetOption(OPTION_WEBDIR, ""); SetOption(OPTION_CONFIGTEMPLATE, ""); #endif SetOption(OPTION_TEMPDIR, "${MainDir}/tmp"); SetOption(OPTION_DESTDIR, "${MainDir}/dst"); SetOption(OPTION_INTERDIR, ""); SetOption(OPTION_QUEUEDIR, "${MainDir}/queue"); SetOption(OPTION_NZBDIR, "${MainDir}/nzb"); SetOption(OPTION_LOCKFILE, "${MainDir}/nzbget.lock"); SetOption(OPTION_LOGFILE, "${DestDir}/nzbget.log"); SetOption(OPTION_SCRIPTDIR, "${MainDir}/scripts"); SetOption(OPTION_REQUIREDDIR, ""); SetOption(OPTION_WRITELOG, "append"); SetOption(OPTION_ROTATELOG, "3"); SetOption(OPTION_APPENDCATEGORYDIR, "yes"); SetOption(OPTION_OUTPUTMODE, "curses"); SetOption(OPTION_DUPECHECK, "yes"); SetOption(OPTION_DOWNLOADRATE, "0"); SetOption(OPTION_CONTROLIP, "0.0.0.0"); SetOption(OPTION_CONTROLUSERNAME, "nzbget"); SetOption(OPTION_CONTROLPASSWORD, "tegbzn6789"); SetOption(OPTION_RESTRICTEDUSERNAME, ""); SetOption(OPTION_RESTRICTEDPASSWORD, ""); SetOption(OPTION_ADDUSERNAME, ""); SetOption(OPTION_ADDPASSWORD, ""); SetOption(OPTION_CONTROLPORT, "6789"); SetOption(OPTION_SECURECONTROL, "no"); SetOption(OPTION_SECUREPORT, "6791"); SetOption(OPTION_SECURECERT, ""); SetOption(OPTION_SECUREKEY, ""); SetOption(OPTION_AUTHORIZEDIP, ""); SetOption(OPTION_ARTICLETIMEOUT, "60"); SetOption(OPTION_URLTIMEOUT, "60"); SetOption(OPTION_SAVEQUEUE, "yes"); SetOption(OPTION_FLUSHQUEUE, "yes"); SetOption(OPTION_RELOADQUEUE, "yes"); SetOption(OPTION_BROKENLOG, "yes"); SetOption(OPTION_NZBLOG, "yes"); SetOption(OPTION_DECODE, "yes"); SetOption(OPTION_RETRIES, "3"); SetOption(OPTION_RETRYINTERVAL, "10"); SetOption(OPTION_TERMINATETIMEOUT, "600"); SetOption(OPTION_CONTINUEPARTIAL, "no"); SetOption(OPTION_URLCONNECTIONS, "4"); SetOption(OPTION_LOGBUFFERSIZE, "1000"); SetOption(OPTION_INFOTARGET, "both"); SetOption(OPTION_WARNINGTARGET, "both"); SetOption(OPTION_ERRORTARGET, "both"); SetOption(OPTION_DEBUGTARGET, "none"); SetOption(OPTION_DETAILTARGET, "both"); SetOption(OPTION_PARCHECK, "auto"); SetOption(OPTION_PARREPAIR, "yes"); SetOption(OPTION_PARSCAN, "extended"); SetOption(OPTION_PARQUICK, "yes"); SetOption(OPTION_PARRENAME, "yes"); SetOption(OPTION_PARBUFFER, "16"); SetOption(OPTION_PARTHREADS, "1"); SetOption(OPTION_HEALTHCHECK, "none"); SetOption(OPTION_SCRIPTORDER, ""); SetOption(OPTION_POSTSCRIPT, ""); SetOption(OPTION_SCANSCRIPT, ""); SetOption(OPTION_QUEUESCRIPT, ""); SetOption(OPTION_FEEDSCRIPT, ""); SetOption(OPTION_DAEMONUSERNAME, "root"); SetOption(OPTION_UMASK, "1000"); SetOption(OPTION_UPDATEINTERVAL, "200"); SetOption(OPTION_CURSESNZBNAME, "yes"); SetOption(OPTION_CURSESTIME, "no"); SetOption(OPTION_CURSESGROUP, "no"); SetOption(OPTION_CRCCHECK, "yes"); SetOption(OPTION_DIRECTWRITE, "yes"); SetOption(OPTION_WRITEBUFFER, "0"); SetOption(OPTION_NZBDIRINTERVAL, "5"); SetOption(OPTION_NZBDIRFILEAGE, "60"); SetOption(OPTION_PARCLEANUPQUEUE, "yes"); SetOption(OPTION_DISKSPACE, "250"); SetOption(OPTION_DUMPCORE, "no"); SetOption(OPTION_PARPAUSEQUEUE, "no"); SetOption(OPTION_SCRIPTPAUSEQUEUE, "no"); SetOption(OPTION_NZBCLEANUPDISK, "no"); SetOption(OPTION_DELETECLEANUPDISK, "no"); SetOption(OPTION_PARTIMELIMIT, "0"); SetOption(OPTION_KEEPHISTORY, "7"); SetOption(OPTION_ACCURATERATE, "no"); SetOption(OPTION_UNPACK, "no"); SetOption(OPTION_UNPACKCLEANUPDISK, "no"); #ifdef WIN32 SetOption(OPTION_UNRARCMD, "unrar.exe"); SetOption(OPTION_SEVENZIPCMD, "7z.exe"); #else SetOption(OPTION_UNRARCMD, "unrar"); SetOption(OPTION_SEVENZIPCMD, "7z"); #endif SetOption(OPTION_UNPACKPASSFILE, ""); SetOption(OPTION_UNPACKPAUSEQUEUE, "no"); SetOption(OPTION_EXTCLEANUPDISK, ""); SetOption(OPTION_PARIGNOREEXT, ""); SetOption(OPTION_FEEDHISTORY, "7"); SetOption(OPTION_URLFORCE, "yes"); SetOption(OPTION_TIMECORRECTION, "0"); SetOption(OPTION_PROPAGATIONDELAY, "0"); SetOption(OPTION_ARTICLECACHE, "0"); SetOption(OPTION_EVENTINTERVAL, "0"); } void Options::InitOptFile() { if (!m_szConfigFilename && !m_bNoConfig) { // search for config file in default locations #ifdef WIN32 char szFilename[MAX_PATH + 20]; snprintf(szFilename, sizeof(szFilename), "%s\\nzbget.conf", m_szAppDir); if (!Util::FileExists(szFilename)) { char szAppDataPath[MAX_PATH]; SHGetFolderPath(NULL, CSIDL_COMMON_APPDATA, NULL, 0, szAppDataPath); snprintf(szFilename, sizeof(szFilename), "%s\\NZBGet\\nzbget.conf", szAppDataPath); szFilename[sizeof(szFilename)-1] = '\0'; if (m_pExtender && !Util::FileExists(szFilename)) { m_pExtender->SetupFirstStart(); } } if (Util::FileExists(szFilename)) { m_szConfigFilename = strdup(szFilename); } #else // look in the exe-directory first char szFilename[1024]; snprintf(szFilename, sizeof(szFilename), "%s/nzbget.conf", m_szAppDir); szFilename[1024-1] = '\0'; if (Util::FileExists(szFilename)) { m_szConfigFilename = strdup(szFilename); } else { int p = 0; while (const char* szFilename = PossibleConfigLocations[p++]) { // substitute HOME-variable char szExpandedFilename[1024]; if (Util::ExpandHomePath(szFilename, szExpandedFilename, sizeof(szExpandedFilename))) { szFilename = szExpandedFilename; } if (Util::FileExists(szFilename)) { m_szConfigFilename = strdup(szFilename); break; } } } #endif } if (m_szConfigFilename) { // normalize path in filename char szFilename[MAX_PATH + 1]; Util::ExpandFileName(m_szConfigFilename, szFilename, sizeof(szFilename)); szFilename[MAX_PATH] = '\0'; #ifndef WIN32 // substitute HOME-variable char szExpandedFilename[1024]; if (Util::ExpandHomePath(szFilename, szExpandedFilename, sizeof(szExpandedFilename))) { strncpy(szFilename, szExpandedFilename, sizeof(szFilename)); } #endif free(m_szConfigFilename); m_szConfigFilename = strdup(szFilename); SetOption(OPTION_CONFIGFILE, m_szConfigFilename); LoadConfigFile(); } } void Options::CheckDir(char** dir, const char* szOptionName, const char* szParentDir, bool bAllowEmpty, bool bCreate) { char* usedir = NULL; const char* tempdir = GetOption(szOptionName); if (m_bNoDiskAccess) { *dir = strdup(tempdir); return; } if (Util::EmptyStr(tempdir)) { if (!bAllowEmpty) { ConfigError("Invalid value for option \"%s\": ", szOptionName); } *dir = strdup(""); return; } int len = strlen(tempdir); usedir = (char*)malloc(len + 2); strcpy(usedir, tempdir); char ch = usedir[len-1]; Util::NormalizePathSeparators(usedir); if (ch != PATH_SEPARATOR) { usedir[len] = PATH_SEPARATOR; usedir[len + 1] = '\0'; } if (!(usedir[0] == PATH_SEPARATOR || usedir[0] == ALT_PATH_SEPARATOR || (usedir[0] && usedir[1] == ':')) && !Util::EmptyStr(szParentDir)) { // convert relative path to absolute path int plen = strlen(szParentDir); int len2 = len + plen + 4; char* usedir2 = (char*)malloc(len2); if (szParentDir[plen-1] == PATH_SEPARATOR || szParentDir[plen-1] == ALT_PATH_SEPARATOR) { snprintf(usedir2, len2, "%s%s", szParentDir, usedir); } else { snprintf(usedir2, len2, "%s%c%s", szParentDir, PATH_SEPARATOR, usedir); } usedir2[len2-1] = '\0'; free(usedir); usedir = usedir2; Util::NormalizePathSeparators(usedir); int ulen = strlen(usedir); usedir[ulen-1] = '\0'; SetOption(szOptionName, usedir); usedir[ulen-1] = PATH_SEPARATOR; } // Ensure the dir is created char szErrBuf[1024]; if (bCreate && !Util::ForceDirectories(usedir, szErrBuf, sizeof(szErrBuf))) { ConfigError("Invalid value for option \"%s\" (%s): %s", szOptionName, usedir, szErrBuf); } *dir = usedir; } void Options::InitOptions() { const char* szMainDir = GetOption(OPTION_MAINDIR); CheckDir(&m_szDestDir, OPTION_DESTDIR, szMainDir, false, false); CheckDir(&m_szInterDir, OPTION_INTERDIR, szMainDir, true, false); CheckDir(&m_szTempDir, OPTION_TEMPDIR, szMainDir, false, true); CheckDir(&m_szQueueDir, OPTION_QUEUEDIR, szMainDir, false, true); CheckDir(&m_szWebDir, OPTION_WEBDIR, NULL, true, false); CheckDir(&m_szScriptDir, OPTION_SCRIPTDIR, szMainDir, true, false); CheckDir(&m_szNzbDir, OPTION_NZBDIR, szMainDir, false, true); m_szRequiredDir = strdup(GetOption(OPTION_REQUIREDDIR)); m_szConfigTemplate = strdup(GetOption(OPTION_CONFIGTEMPLATE)); m_szScriptOrder = strdup(GetOption(OPTION_SCRIPTORDER)); m_szPostScript = strdup(GetOption(OPTION_POSTSCRIPT)); m_szScanScript = strdup(GetOption(OPTION_SCANSCRIPT)); m_szQueueScript = strdup(GetOption(OPTION_QUEUESCRIPT)); m_szFeedScript = strdup(GetOption(OPTION_FEEDSCRIPT)); m_szControlIP = strdup(GetOption(OPTION_CONTROLIP)); m_szControlUsername = strdup(GetOption(OPTION_CONTROLUSERNAME)); m_szControlPassword = strdup(GetOption(OPTION_CONTROLPASSWORD)); m_szRestrictedUsername = strdup(GetOption(OPTION_RESTRICTEDUSERNAME)); m_szRestrictedPassword = strdup(GetOption(OPTION_RESTRICTEDPASSWORD)); m_szAddUsername = strdup(GetOption(OPTION_ADDUSERNAME)); m_szAddPassword = strdup(GetOption(OPTION_ADDPASSWORD)); m_szSecureCert = strdup(GetOption(OPTION_SECURECERT)); m_szSecureKey = strdup(GetOption(OPTION_SECUREKEY)); m_szAuthorizedIP = strdup(GetOption(OPTION_AUTHORIZEDIP)); m_szLockFile = strdup(GetOption(OPTION_LOCKFILE)); m_szDaemonUsername = strdup(GetOption(OPTION_DAEMONUSERNAME)); m_szLogFile = strdup(GetOption(OPTION_LOGFILE)); m_szUnrarCmd = strdup(GetOption(OPTION_UNRARCMD)); m_szSevenZipCmd = strdup(GetOption(OPTION_SEVENZIPCMD)); m_szUnpackPassFile = strdup(GetOption(OPTION_UNPACKPASSFILE)); m_szExtCleanupDisk = strdup(GetOption(OPTION_EXTCLEANUPDISK)); m_szParIgnoreExt = strdup(GetOption(OPTION_PARIGNOREEXT)); m_iDownloadRate = ParseIntValue(OPTION_DOWNLOADRATE, 10) * 1024; m_iArticleTimeout = ParseIntValue(OPTION_ARTICLETIMEOUT, 10); m_iUrlTimeout = ParseIntValue(OPTION_URLTIMEOUT, 10); m_iTerminateTimeout = ParseIntValue(OPTION_TERMINATETIMEOUT, 10); m_iRetries = ParseIntValue(OPTION_RETRIES, 10); m_iRetryInterval = ParseIntValue(OPTION_RETRYINTERVAL, 10); m_iControlPort = ParseIntValue(OPTION_CONTROLPORT, 10); m_iSecurePort = ParseIntValue(OPTION_SECUREPORT, 10); m_iUrlConnections = ParseIntValue(OPTION_URLCONNECTIONS, 10); m_iLogBufferSize = ParseIntValue(OPTION_LOGBUFFERSIZE, 10); m_iRotateLog = ParseIntValue(OPTION_ROTATELOG, 10); m_iUMask = ParseIntValue(OPTION_UMASK, 8); m_iUpdateInterval = ParseIntValue(OPTION_UPDATEINTERVAL, 10); m_iWriteBuffer = ParseIntValue(OPTION_WRITEBUFFER, 10); m_iNzbDirInterval = ParseIntValue(OPTION_NZBDIRINTERVAL, 10); m_iNzbDirFileAge = ParseIntValue(OPTION_NZBDIRFILEAGE, 10); m_iDiskSpace = ParseIntValue(OPTION_DISKSPACE, 10); m_iParTimeLimit = ParseIntValue(OPTION_PARTIMELIMIT, 10); m_iKeepHistory = ParseIntValue(OPTION_KEEPHISTORY, 10); m_iFeedHistory = ParseIntValue(OPTION_FEEDHISTORY, 10); m_iTimeCorrection = ParseIntValue(OPTION_TIMECORRECTION, 10); if (-24 <= m_iTimeCorrection && m_iTimeCorrection <= 24) { m_iTimeCorrection *= 60; } m_iTimeCorrection *= 60; m_iPropagationDelay = ParseIntValue(OPTION_PROPAGATIONDELAY, 10) * 60; m_iArticleCache = ParseIntValue(OPTION_ARTICLECACHE, 10); m_iEventInterval = ParseIntValue(OPTION_EVENTINTERVAL, 10); m_iParBuffer = ParseIntValue(OPTION_PARBUFFER, 10); m_iParThreads = ParseIntValue(OPTION_PARTHREADS, 10); m_bBrokenLog = (bool)ParseEnumValue(OPTION_BROKENLOG, BoolCount, BoolNames, BoolValues); m_bNzbLog = (bool)ParseEnumValue(OPTION_NZBLOG, BoolCount, BoolNames, BoolValues); m_bAppendCategoryDir = (bool)ParseEnumValue(OPTION_APPENDCATEGORYDIR, BoolCount, BoolNames, BoolValues); m_bContinuePartial = (bool)ParseEnumValue(OPTION_CONTINUEPARTIAL, BoolCount, BoolNames, BoolValues); m_bSaveQueue = (bool)ParseEnumValue(OPTION_SAVEQUEUE, BoolCount, BoolNames, BoolValues); m_bFlushQueue = (bool)ParseEnumValue(OPTION_FLUSHQUEUE, BoolCount, BoolNames, BoolValues); m_bDupeCheck = (bool)ParseEnumValue(OPTION_DUPECHECK, BoolCount, BoolNames, BoolValues); m_bParRepair = (bool)ParseEnumValue(OPTION_PARREPAIR, BoolCount, BoolNames, BoolValues); m_bParQuick = (bool)ParseEnumValue(OPTION_PARQUICK, BoolCount, BoolNames, BoolValues); m_bParRename = (bool)ParseEnumValue(OPTION_PARRENAME, BoolCount, BoolNames, BoolValues); m_bReloadQueue = (bool)ParseEnumValue(OPTION_RELOADQUEUE, BoolCount, BoolNames, BoolValues); m_bCursesNZBName = (bool)ParseEnumValue(OPTION_CURSESNZBNAME, BoolCount, BoolNames, BoolValues); m_bCursesTime = (bool)ParseEnumValue(OPTION_CURSESTIME, BoolCount, BoolNames, BoolValues); m_bCursesGroup = (bool)ParseEnumValue(OPTION_CURSESGROUP, BoolCount, BoolNames, BoolValues); m_bCrcCheck = (bool)ParseEnumValue(OPTION_CRCCHECK, BoolCount, BoolNames, BoolValues); m_bDirectWrite = (bool)ParseEnumValue(OPTION_DIRECTWRITE, BoolCount, BoolNames, BoolValues); m_bParCleanupQueue = (bool)ParseEnumValue(OPTION_PARCLEANUPQUEUE, BoolCount, BoolNames, BoolValues); m_bDecode = (bool)ParseEnumValue(OPTION_DECODE, BoolCount, BoolNames, BoolValues); m_bDumpCore = (bool)ParseEnumValue(OPTION_DUMPCORE, BoolCount, BoolNames, BoolValues); m_bParPauseQueue = (bool)ParseEnumValue(OPTION_PARPAUSEQUEUE, BoolCount, BoolNames, BoolValues); m_bScriptPauseQueue = (bool)ParseEnumValue(OPTION_SCRIPTPAUSEQUEUE, BoolCount, BoolNames, BoolValues); m_bNzbCleanupDisk = (bool)ParseEnumValue(OPTION_NZBCLEANUPDISK, BoolCount, BoolNames, BoolValues); m_bDeleteCleanupDisk = (bool)ParseEnumValue(OPTION_DELETECLEANUPDISK, BoolCount, BoolNames, BoolValues); m_bAccurateRate = (bool)ParseEnumValue(OPTION_ACCURATERATE, BoolCount, BoolNames, BoolValues); m_bSecureControl = (bool)ParseEnumValue(OPTION_SECURECONTROL, BoolCount, BoolNames, BoolValues); m_bUnpack = (bool)ParseEnumValue(OPTION_UNPACK, BoolCount, BoolNames, BoolValues); m_bUnpackCleanupDisk = (bool)ParseEnumValue(OPTION_UNPACKCLEANUPDISK, BoolCount, BoolNames, BoolValues); m_bUnpackPauseQueue = (bool)ParseEnumValue(OPTION_UNPACKPAUSEQUEUE, BoolCount, BoolNames, BoolValues); m_bUrlForce = (bool)ParseEnumValue(OPTION_URLFORCE, BoolCount, BoolNames, BoolValues); const char* OutputModeNames[] = { "loggable", "logable", "log", "colored", "color", "ncurses", "curses" }; const int OutputModeValues[] = { omLoggable, omLoggable, omLoggable, omColored, omColored, omNCurses, omNCurses }; const int OutputModeCount = 7; m_eOutputMode = (EOutputMode)ParseEnumValue(OPTION_OUTPUTMODE, OutputModeCount, OutputModeNames, OutputModeValues); const char* ParCheckNames[] = { "auto", "always", "force", "manual" }; const int ParCheckValues[] = { pcAuto, pcAlways, pcForce, pcManual }; const int ParCheckCount = 6; m_eParCheck = (EParCheck)ParseEnumValue(OPTION_PARCHECK, ParCheckCount, ParCheckNames, ParCheckValues); const char* ParScanNames[] = { "limited", "extended", "full", "dupe" }; const int ParScanValues[] = { psLimited, psExtended, psFull, psDupe }; const int ParScanCount = 4; m_eParScan = (EParScan)ParseEnumValue(OPTION_PARSCAN, ParScanCount, ParScanNames, ParScanValues); const char* HealthCheckNames[] = { "pause", "delete", "none" }; const int HealthCheckValues[] = { hcPause, hcDelete, hcNone }; const int HealthCheckCount = 3; m_eHealthCheck = (EHealthCheck)ParseEnumValue(OPTION_HEALTHCHECK, HealthCheckCount, HealthCheckNames, HealthCheckValues); const char* TargetNames[] = { "screen", "log", "both", "none" }; const int TargetValues[] = { mtScreen, mtLog, mtBoth, mtNone }; const int TargetCount = 4; m_eInfoTarget = (EMessageTarget)ParseEnumValue(OPTION_INFOTARGET, TargetCount, TargetNames, TargetValues); m_eWarningTarget = (EMessageTarget)ParseEnumValue(OPTION_WARNINGTARGET, TargetCount, TargetNames, TargetValues); m_eErrorTarget = (EMessageTarget)ParseEnumValue(OPTION_ERRORTARGET, TargetCount, TargetNames, TargetValues); m_eDebugTarget = (EMessageTarget)ParseEnumValue(OPTION_DEBUGTARGET, TargetCount, TargetNames, TargetValues); m_eDetailTarget = (EMessageTarget)ParseEnumValue(OPTION_DETAILTARGET, TargetCount, TargetNames, TargetValues); const char* WriteLogNames[] = { "none", "append", "reset", "rotate" }; const int WriteLogValues[] = { wlNone, wlAppend, wlReset, wlRotate }; const int WriteLogCount = 4; m_eWriteLog = (EWriteLog)ParseEnumValue(OPTION_WRITELOG, WriteLogCount, WriteLogNames, WriteLogValues); } int Options::ParseEnumValue(const char* OptName, int argc, const char * argn[], const int argv[]) { OptEntry* pOptEntry = FindOption(OptName); if (!pOptEntry) { ConfigError("Undefined value for option \"%s\"", OptName); return argv[0]; } int iDefNum = 0; for (int i = 0; i < argc; i++) { if (!strcasecmp(pOptEntry->GetValue(), argn[i])) { // normalizing option value in option list, for example "NO" -> "no" for (int j = 0; j < argc; j++) { if (argv[j] == argv[i]) { if (strcmp(argn[j], pOptEntry->GetValue())) { pOptEntry->SetValue(argn[j]); } break; } } return argv[i]; } if (!strcasecmp(pOptEntry->GetDefValue(), argn[i])) { iDefNum = i; } } m_iConfigLine = pOptEntry->GetLineNo(); ConfigError("Invalid value for option \"%s\": \"%s\"", OptName, pOptEntry->GetValue()); pOptEntry->SetValue(argn[iDefNum]); return argv[iDefNum]; } int Options::ParseIntValue(const char* OptName, int iBase) { OptEntry* pOptEntry = FindOption(OptName); if (!pOptEntry) { ConfigError("Undefined value for option \"%s\"", OptName); return 0; } char *endptr; int val = strtol(pOptEntry->GetValue(), &endptr, iBase); if (endptr && *endptr != '\0') { m_iConfigLine = pOptEntry->GetLineNo(); ConfigError("Invalid value for option \"%s\": \"%s\"", OptName, pOptEntry->GetValue()); pOptEntry->SetValue(pOptEntry->GetDefValue()); val = strtol(pOptEntry->GetDefValue(), NULL, iBase); } return val; } void Options::SetOption(const char* optname, const char* value) { OptEntry* pOptEntry = FindOption(optname); if (!pOptEntry) { pOptEntry = new OptEntry(); pOptEntry->SetName(optname); m_OptEntries.push_back(pOptEntry); } char* curvalue = NULL; #ifndef WIN32 if (value && (value[0] == '~') && (value[1] == '/')) { char szExpandedPath[1024]; if (m_bNoDiskAccess) { strncpy(szExpandedPath, value, 1024); szExpandedPath[1024-1] = '\0'; } else if (!Util::ExpandHomePath(value, szExpandedPath, sizeof(szExpandedPath))) { ConfigError("Invalid value for option\"%s\": unable to determine home-directory", optname); szExpandedPath[0] = '\0'; } curvalue = strdup(szExpandedPath); } else #endif { curvalue = strdup(value); } pOptEntry->SetLineNo(m_iConfigLine); // expand variables while (char* dollar = strstr(curvalue, "${")) { char* end = strchr(dollar, '}'); if (end) { int varlen = (int)(end - dollar - 2); char variable[101]; int maxlen = varlen < 100 ? varlen : 100; strncpy(variable, dollar + 2, maxlen); variable[maxlen] = '\0'; const char* varvalue = GetOption(variable); if (varvalue) { int newlen = strlen(varvalue); char* newvalue = (char*)malloc(strlen(curvalue) - varlen - 3 + newlen + 1); strncpy(newvalue, curvalue, dollar - curvalue); strncpy(newvalue + (dollar - curvalue), varvalue, newlen); strcpy(newvalue + (dollar - curvalue) + newlen, end + 1); free(curvalue); curvalue = newvalue; } else { break; } } else { break; } } pOptEntry->SetValue(curvalue); free(curvalue); } Options::OptEntry* Options::FindOption(const char* optname) { OptEntry* pOptEntry = m_OptEntries.FindOption(optname); // normalize option name in option list; for example "server1.joingroup" -> "Server1.JoinGroup" if (pOptEntry && strcmp(pOptEntry->GetName(), optname)) { pOptEntry->SetName(optname); } return pOptEntry; } const char* Options::GetOption(const char* optname) { OptEntry* pOptEntry = FindOption(optname); if (pOptEntry) { if (pOptEntry->GetLineNo() > 0) { m_iConfigLine = pOptEntry->GetLineNo(); } return pOptEntry->GetValue(); } return NULL; } void Options::InitServers() { int n = 1; while (true) { char optname[128]; sprintf(optname, "Server%i.Active", n); const char* nactive = GetOption(optname); bool bActive = true; if (nactive) { bActive = (bool)ParseEnumValue(optname, BoolCount, BoolNames, BoolValues); } sprintf(optname, "Server%i.Name", n); const char* nname = GetOption(optname); sprintf(optname, "Server%i.Level", n); const char* nlevel = GetOption(optname); sprintf(optname, "Server%i.Group", n); const char* ngroup = GetOption(optname); sprintf(optname, "Server%i.Host", n); const char* nhost = GetOption(optname); sprintf(optname, "Server%i.Port", n); const char* nport = GetOption(optname); sprintf(optname, "Server%i.Username", n); const char* nusername = GetOption(optname); sprintf(optname, "Server%i.Password", n); const char* npassword = GetOption(optname); sprintf(optname, "Server%i.JoinGroup", n); const char* njoingroup = GetOption(optname); bool bJoinGroup = false; if (njoingroup) { bJoinGroup = (bool)ParseEnumValue(optname, BoolCount, BoolNames, BoolValues); } sprintf(optname, "Server%i.Encryption", n); const char* ntls = GetOption(optname); bool bTLS = false; if (ntls) { bTLS = (bool)ParseEnumValue(optname, BoolCount, BoolNames, BoolValues); #ifdef DISABLE_TLS if (bTLS) { ConfigError("Invalid value for option \"%s\": program was compiled without TLS/SSL-support", optname); bTLS = false; } #endif m_bTLS |= bTLS; } sprintf(optname, "Server%i.Cipher", n); const char* ncipher = GetOption(optname); sprintf(optname, "Server%i.Connections", n); const char* nconnections = GetOption(optname); sprintf(optname, "Server%i.Retention", n); const char* nretention = GetOption(optname); bool definition = nactive || nname || nlevel || ngroup || nhost || nport || nusername || npassword || nconnections || njoingroup || ntls || ncipher || nretention; bool completed = nhost && nport && nconnections; if (!definition) { break; } if (completed) { if (m_pExtender) { m_pExtender->AddNewsServer(n, bActive, nname, nhost, nport ? atoi(nport) : 119, nusername, npassword, bJoinGroup, bTLS, ncipher, nconnections ? atoi(nconnections) : 1, nretention ? atoi(nretention) : 0, nlevel ? atoi(nlevel) : 0, ngroup ? atoi(ngroup) : 0); } } else { ConfigError("Server definition not complete for \"Server%i\"", n); } n++; } } void Options::InitCategories() { int n = 1; while (true) { char optname[128]; sprintf(optname, "Category%i.Name", n); const char* nname = GetOption(optname); char destdiroptname[128]; sprintf(destdiroptname, "Category%i.DestDir", n); const char* ndestdir = GetOption(destdiroptname); sprintf(optname, "Category%i.Unpack", n); const char* nunpack = GetOption(optname); bool bUnpack = true; if (nunpack) { bUnpack = (bool)ParseEnumValue(optname, BoolCount, BoolNames, BoolValues); } sprintf(optname, "Category%i.PostScript", n); const char* npostscript = GetOption(optname); sprintf(optname, "Category%i.Aliases", n); const char* naliases = GetOption(optname); bool definition = nname || ndestdir || nunpack || npostscript || naliases; bool completed = nname && strlen(nname) > 0; if (!definition) { break; } if (completed) { char* szDestDir = NULL; if (ndestdir && ndestdir[0] != '\0') { CheckDir(&szDestDir, destdiroptname, m_szDestDir, false, false); } Category* pCategory = new Category(nname, szDestDir, bUnpack, npostscript); m_Categories.push_back(pCategory); free(szDestDir); // split Aliases into tokens and create items for each token if (naliases) { Tokenizer tok(naliases, ",;"); while (const char* szAliasName = tok.Next()) { pCategory->GetAliases()->push_back(strdup(szAliasName)); } } } else { ConfigError("Category definition not complete for \"Category%i\"", n); } n++; } } void Options::InitFeeds() { int n = 1; while (true) { char optname[128]; sprintf(optname, "Feed%i.Name", n); const char* nname = GetOption(optname); sprintf(optname, "Feed%i.URL", n); const char* nurl = GetOption(optname); sprintf(optname, "Feed%i.Filter", n); const char* nfilter = GetOption(optname); sprintf(optname, "Feed%i.Category", n); const char* ncategory = GetOption(optname); sprintf(optname, "Feed%i.FeedScript", n); const char* nfeedscript = GetOption(optname); sprintf(optname, "Feed%i.Backlog", n); const char* nbacklog = GetOption(optname); bool bBacklog = true; if (nbacklog) { bBacklog = (bool)ParseEnumValue(optname, BoolCount, BoolNames, BoolValues); } sprintf(optname, "Feed%i.PauseNzb", n); const char* npausenzb = GetOption(optname); bool bPauseNzb = false; if (npausenzb) { bPauseNzb = (bool)ParseEnumValue(optname, BoolCount, BoolNames, BoolValues); } sprintf(optname, "Feed%i.Interval", n); const char* ninterval = GetOption(optname); sprintf(optname, "Feed%i.Priority", n); const char* npriority = GetOption(optname); bool definition = nname || nurl || nfilter || ncategory || nbacklog || npausenzb || ninterval || npriority || nfeedscript; bool completed = nurl; if (!definition) { break; } if (completed) { if (m_pExtender) { m_pExtender->AddFeed(n, nname, nurl, ninterval ? atoi(ninterval) : 0, nfilter, bBacklog, bPauseNzb, ncategory, npriority ? atoi(npriority) : 0, nfeedscript); } } else { ConfigError("Feed definition not complete for \"Feed%i\"", n); } n++; } } void Options::InitScheduler() { for (int n = 1; ; n++) { char optname[128]; sprintf(optname, "Task%i.Time", n); const char* szTime = GetOption(optname); sprintf(optname, "Task%i.WeekDays", n); const char* szWeekDays = GetOption(optname); sprintf(optname, "Task%i.Command", n); const char* szCommand = GetOption(optname); sprintf(optname, "Task%i.DownloadRate", n); const char* szDownloadRate = GetOption(optname); sprintf(optname, "Task%i.Process", n); const char* szProcess = GetOption(optname); sprintf(optname, "Task%i.Param", n); const char* szParam = GetOption(optname); if (Util::EmptyStr(szParam) && !Util::EmptyStr(szProcess)) { szParam = szProcess; } if (Util::EmptyStr(szParam) && !Util::EmptyStr(szDownloadRate)) { szParam = szDownloadRate; } bool definition = szTime || szWeekDays || szCommand || szDownloadRate || szParam; bool completed = szTime && szCommand; if (!definition) { break; } if (!completed) { ConfigError("Task definition not complete for \"Task%i\"", n); continue; } snprintf(optname, sizeof(optname), "Task%i.Command", n); optname[sizeof(optname)-1] = '\0'; const char* CommandNames[] = { "pausedownload", "pause", "unpausedownload", "resumedownload", "unpause", "resume", "pausepostprocess", "unpausepostprocess", "resumepostprocess", "pausepost", "unpausepost", "resumepost", "downloadrate", "setdownloadrate", "rate", "speed", "script", "process", "pausescan", "unpausescan", "resumescan", "activateserver", "activateservers", "deactivateserver", "deactivateservers", "fetchfeed", "fetchfeeds" }; const int CommandValues[] = { scPauseDownload, scPauseDownload, scUnpauseDownload, scUnpauseDownload, scUnpauseDownload, scUnpauseDownload, scPausePostProcess, scUnpausePostProcess, scUnpausePostProcess, scPausePostProcess, scUnpausePostProcess, scUnpausePostProcess, scDownloadRate, scDownloadRate, scDownloadRate, scDownloadRate, scScript, scProcess, scPauseScan, scUnpauseScan, scUnpauseScan, scActivateServer, scActivateServer, scDeactivateServer, scDeactivateServer, scFetchFeed, scFetchFeed }; const int CommandCount = 27; ESchedulerCommand eCommand = (ESchedulerCommand)ParseEnumValue(optname, CommandCount, CommandNames, CommandValues); if (szParam && strlen(szParam) > 0 && eCommand == scProcess && !Util::SplitCommandLine(szParam, NULL)) { ConfigError("Invalid value for option \"Task%i.Param\"", n); continue; } int iWeekDays = 0; if (szWeekDays && !ParseWeekDays(szWeekDays, &iWeekDays)) { ConfigError("Invalid value for option \"Task%i.WeekDays\": \"%s\"", n, szWeekDays); continue; } if (eCommand == scDownloadRate) { if (szParam) { char* szErr; int iDownloadRate = strtol(szParam, &szErr, 10); if (!szErr || *szErr != '\0' || iDownloadRate < 0) { ConfigError("Invalid value for option \"Task%i.Param\": \"%s\"", n, szDownloadRate); continue; } } else { ConfigError("Task definition not complete for \"Task%i\". Option \"Task%i.Param\" is missing", n, n); continue; } } if ((eCommand == scScript || eCommand == scProcess || eCommand == scActivateServer || eCommand == scDeactivateServer || eCommand == scFetchFeed) && Util::EmptyStr(szParam)) { ConfigError("Task definition not complete for \"Task%i\". Option \"Task%i.Param\" is missing", n, n); continue; } int iHours, iMinutes; Tokenizer tok(szTime, ";,"); while (const char* szOneTime = tok.Next()) { if (!ParseTime(szOneTime, &iHours, &iMinutes)) { ConfigError("Invalid value for option \"Task%i.Time\": \"%s\"", n, szOneTime); break; } if (m_pExtender) { if (iHours == -1) { for (int iEveryHour = 0; iEveryHour < 24; iEveryHour++) { m_pExtender->AddTask(n, iEveryHour, iMinutes, iWeekDays, eCommand, szParam); } } else { m_pExtender->AddTask(n, iHours, iMinutes, iWeekDays, eCommand, szParam); } } } } } bool Options::ParseTime(const char* szTime, int* pHours, int* pMinutes) { int iColons = 0; const char* p = szTime; while (*p) { if (!strchr("0123456789: *", *p)) { return false; } if (*p == ':') { iColons++; } p++; } if (iColons != 1) { return false; } const char* szColon = strchr(szTime, ':'); if (!szColon) { return false; } if (szTime[0] == '*') { *pHours = -1; } else { *pHours = atoi(szTime); if (*pHours < 0 || *pHours > 23) { return false; } } if (szColon[1] == '*') { return false; } *pMinutes = atoi(szColon + 1); if (*pMinutes < 0 || *pMinutes > 59) { return false; } return true; } bool Options::ParseWeekDays(const char* szWeekDays, int* pWeekDaysBits) { *pWeekDaysBits = 0; const char* p = szWeekDays; int iFirstDay = 0; bool bRange = false; while (*p) { if (strchr("1234567", *p)) { int iDay = *p - '0'; if (bRange) { if (iDay <= iFirstDay || iFirstDay == 0) { return false; } for (int i = iFirstDay; i <= iDay; i++) { *pWeekDaysBits |= 1 << (i - 1); } iFirstDay = 0; } else { *pWeekDaysBits |= 1 << (iDay - 1); iFirstDay = iDay; } bRange = false; } else if (*p == ',') { bRange = false; } else if (*p == '-') { bRange = true; } else if (*p == ' ') { // skip spaces } else { return false; } p++; } return true; } void Options::LoadConfigFile() { SetOption(OPTION_CONFIGFILE, m_szConfigFilename); FILE* infile = fopen(m_szConfigFilename, FOPEN_RB); if (!infile) { ConfigError("Could not open file %s", m_szConfigFilename); m_bFatalError = true; return; } m_iConfigLine = 0; int iBufLen = (int)Util::FileSize(m_szConfigFilename) + 1; char* buf = (char*)malloc(iBufLen); int iLine = 0; while (fgets(buf, iBufLen - 1, infile)) { m_iConfigLine = ++iLine; if (buf[0] != 0 && buf[strlen(buf)-1] == '\n') { buf[strlen(buf)-1] = 0; // remove traling '\n' } if (buf[0] != 0 && buf[strlen(buf)-1] == '\r') { buf[strlen(buf)-1] = 0; // remove traling '\r' (for windows line endings) } if (buf[0] == 0 || buf[0] == '#' || strspn(buf, " ") == strlen(buf)) { continue; } SetOptionString(buf); } fclose(infile); free(buf); m_iConfigLine = 0; } void Options::InitCommandLineOptions(CmdOptList* pCommandLineOptions) { for (CmdOptList::iterator it = pCommandLineOptions->begin(); it != pCommandLineOptions->end(); it++) { const char* option = *it; SetOptionString(option); } } bool Options::SetOptionString(const char* option) { char* optname; char* optvalue; if (!SplitOptionString(option, &optname, &optvalue)) { ConfigError("Invalid option \"%s\"", option); return false; } bool bOK = ValidateOptionName(optname, optvalue); if (bOK) { SetOption(optname, optvalue); } else { ConfigError("Invalid option \"%s\"", optname); } free(optname); free(optvalue); return bOK; } /* * Splits option string into name and value; * Converts old names and values if necessary; * Allocates buffers for name and value; * Returns true if the option string has name and value; * If "true" is returned the caller is responsible for freeing optname and optvalue. */ bool Options::SplitOptionString(const char* option, char** pOptName, char** pOptValue) { const char* eq = strchr(option, '='); if (!eq) { return false; } const char* value = eq + 1; char optname[1001]; char optvalue[1001]; int maxlen = (int)(eq - option < 1000 ? eq - option : 1000); strncpy(optname, option, maxlen); optname[maxlen] = '\0'; strncpy(optvalue, eq + 1, 1000); optvalue[1000] = '\0'; if (strlen(optname) == 0) { return false; } ConvertOldOption(optname, sizeof(optname), optvalue, sizeof(optvalue)); // if value was (old-)converted use the new value, which is linited to 1000 characters, // otherwise use original (length-unlimited) value if (strncmp(value, optvalue, 1000)) { value = optvalue; } *pOptName = strdup(optname); *pOptValue = strdup(value); return true; } bool Options::ValidateOptionName(const char* optname, const char* optvalue) { if (!strcasecmp(optname, OPTION_CONFIGFILE) || !strcasecmp(optname, OPTION_APPBIN) || !strcasecmp(optname, OPTION_APPDIR) || !strcasecmp(optname, OPTION_VERSION)) { // read-only options return false; } const char* v = GetOption(optname); if (v) { // it's predefined option, OK return true; } if (!strncasecmp(optname, "server", 6)) { char* p = (char*)optname + 6; while (*p >= '0' && *p <= '9') p++; if (p && (!strcasecmp(p, ".active") || !strcasecmp(p, ".name") || !strcasecmp(p, ".level") || !strcasecmp(p, ".host") || !strcasecmp(p, ".port") || !strcasecmp(p, ".username") || !strcasecmp(p, ".password") || !strcasecmp(p, ".joingroup") || !strcasecmp(p, ".encryption") || !strcasecmp(p, ".connections") || !strcasecmp(p, ".cipher") || !strcasecmp(p, ".group") || !strcasecmp(p, ".retention"))) { return true; } } if (!strncasecmp(optname, "task", 4)) { char* p = (char*)optname + 4; while (*p >= '0' && *p <= '9') p++; if (p && (!strcasecmp(p, ".time") || !strcasecmp(p, ".weekdays") || !strcasecmp(p, ".command") || !strcasecmp(p, ".param") || !strcasecmp(p, ".downloadrate") || !strcasecmp(p, ".process"))) { return true; } } if (!strncasecmp(optname, "category", 8)) { char* p = (char*)optname + 8; while (*p >= '0' && *p <= '9') p++; if (p && (!strcasecmp(p, ".name") || !strcasecmp(p, ".destdir") || !strcasecmp(p, ".postscript") || !strcasecmp(p, ".unpack") || !strcasecmp(p, ".aliases"))) { return true; } } if (!strncasecmp(optname, "feed", 4)) { char* p = (char*)optname + 4; while (*p >= '0' && *p <= '9') p++; if (p && (!strcasecmp(p, ".name") || !strcasecmp(p, ".url") || !strcasecmp(p, ".interval") || !strcasecmp(p, ".filter") || !strcasecmp(p, ".backlog") || !strcasecmp(p, ".pausenzb") || !strcasecmp(p, ".category") || !strcasecmp(p, ".priority") || !strcasecmp(p, ".feedscript"))) { return true; } } // scripts options if (strchr(optname, ':')) { return true; } // print warning messages for obsolete options if (!strcasecmp(optname, OPTION_RETRYONCRCERROR) || !strcasecmp(optname, OPTION_ALLOWREPROCESS) || !strcasecmp(optname, OPTION_LOADPARS) || !strcasecmp(optname, OPTION_THREADLIMIT) || !strcasecmp(optname, OPTION_POSTLOGKIND) || !strcasecmp(optname, OPTION_NZBLOGKIND) || !strcasecmp(optname, OPTION_PROCESSLOGKIND) || !strcasecmp(optname, OPTION_APPENDNZBDIR) || !strcasecmp(optname, OPTION_RENAMEBROKEN) || !strcasecmp(optname, OPTION_MERGENZB) || !strcasecmp(optname, OPTION_STRICTPARNAME) || !strcasecmp(optname, OPTION_RELOADURLQUEUE) || !strcasecmp(optname, OPTION_RELOADPOSTQUEUE)) { ConfigWarn("Option \"%s\" is obsolete, ignored", optname); return true; } if (!strcasecmp(optname, OPTION_POSTPROCESS) || !strcasecmp(optname, OPTION_NZBPROCESS) || !strcasecmp(optname, OPTION_NZBADDEDPROCESS)) { if (optvalue && strlen(optvalue) > 0) { ConfigError("Option \"%s\" is obsolete, ignored, use \"%s\" and \"%s\" instead", optname, OPTION_SCRIPTDIR, !strcasecmp(optname, OPTION_POSTPROCESS) ? OPTION_POSTSCRIPT : !strcasecmp(optname, OPTION_NZBPROCESS) ? OPTION_SCANSCRIPT : !strcasecmp(optname, OPTION_NZBADDEDPROCESS) ? OPTION_QUEUESCRIPT : "ERROR"); } return true; } if (!strcasecmp(optname, OPTION_CREATELOG) || !strcasecmp(optname, OPTION_RESETLOG)) { ConfigWarn("Option \"%s\" is obsolete, ignored, use \"%s\" instead", optname, OPTION_WRITELOG); return true; } return false; } void Options::ConvertOldOption(char *szOption, int iOptionBufLen, char *szValue, int iValueBufLen) { // for compatibility with older versions accept old option names if (!strcasecmp(szOption, "$MAINDIR")) { strncpy(szOption, "MainDir", iOptionBufLen); } if (!strcasecmp(szOption, "ServerIP")) { strncpy(szOption, "ControlIP", iOptionBufLen); } if (!strcasecmp(szOption, "ServerPort")) { strncpy(szOption, "ControlPort", iOptionBufLen); } if (!strcasecmp(szOption, "ServerPassword")) { strncpy(szOption, "ControlPassword", iOptionBufLen); } if (!strcasecmp(szOption, "PostPauseQueue")) { strncpy(szOption, "ScriptPauseQueue", iOptionBufLen); } if (!strcasecmp(szOption, "ParCheck") && !strcasecmp(szValue, "yes")) { strncpy(szValue, "always", iValueBufLen); } if (!strcasecmp(szOption, "ParCheck") && !strcasecmp(szValue, "no")) { strncpy(szValue, "auto", iValueBufLen); } if (!strcasecmp(szOption, "ParScan") && !strcasecmp(szValue, "auto")) { strncpy(szValue, "extended", iValueBufLen); } if (!strcasecmp(szOption, "DefScript")) { strncpy(szOption, "PostScript", iOptionBufLen); } int iNameLen = strlen(szOption); if (!strncasecmp(szOption, "Category", 8) && iNameLen > 10 && !strcasecmp(szOption + iNameLen - 10, ".DefScript")) { strncpy(szOption + iNameLen - 10, ".PostScript", iOptionBufLen - 9 /* strlen("Category.") */); } if (!strcasecmp(szOption, "WriteBufferSize")) { strncpy(szOption, "WriteBuffer", iOptionBufLen); int val = strtol(szValue, NULL, 10); val = val == -1 ? 1024 : val / 1024; snprintf(szValue, iValueBufLen, "%i", val); } if (!strcasecmp(szOption, "ConnectionTimeout")) { strncpy(szOption, "ArticleTimeout", iOptionBufLen); } if (!strcasecmp(szOption, "CreateBrokenLog")) { strncpy(szOption, "BrokenLog", iOptionBufLen); } szOption[iOptionBufLen-1] = '\0'; szOption[iValueBufLen-1] = '\0'; } void Options::CheckOptions() { #ifdef DISABLE_PARCHECK if (m_eParCheck != pcManual) { LocateOptionSrcPos(OPTION_PARCHECK); ConfigError("Invalid value for option \"%s\": program was compiled without parcheck-support", OPTION_PARCHECK); } if (m_bParRename) { LocateOptionSrcPos(OPTION_PARRENAME); ConfigError("Invalid value for option \"%s\": program was compiled without parcheck-support", OPTION_PARRENAME); } #endif #ifdef DISABLE_CURSES if (m_eOutputMode == omNCurses) { LocateOptionSrcPos(OPTION_OUTPUTMODE); ConfigError("Invalid value for option \"%s\": program was compiled without curses-support", OPTION_OUTPUTMODE); } #endif #ifdef DISABLE_TLS if (m_bSecureControl) { LocateOptionSrcPos(OPTION_SECURECONTROL); ConfigError("Invalid value for option \"%s\": program was compiled without TLS/SSL-support", OPTION_SECURECONTROL); } #endif if (!m_bDecode) { m_bDirectWrite = false; } // if option "ConfigTemplate" is not set, use "WebDir" as default location for template // (for compatibility with versions 9 and 10). if (Util::EmptyStr(m_szConfigTemplate) && !m_bNoDiskAccess) { free(m_szConfigTemplate); int iLen = strlen(m_szWebDir) + 15; m_szConfigTemplate = (char*)malloc(iLen); snprintf(m_szConfigTemplate, iLen, "%s%s", m_szWebDir, "nzbget.conf"); m_szConfigTemplate[iLen-1] = '\0'; if (!Util::FileExists(m_szConfigTemplate)) { free(m_szConfigTemplate); m_szConfigTemplate = strdup(""); } } if (m_iArticleCache < 0) { m_iArticleCache = 0; } else if (sizeof(void*) == 4 && m_iArticleCache > 1900) { ConfigError("Invalid value for option \"ArticleCache\": %i. Changed to 1900", m_iArticleCache); m_iArticleCache = 1900; } else if (sizeof(void*) == 4 && m_iParBuffer > 1900) { ConfigError("Invalid value for option \"ParBuffer\": %i. Changed to 1900", m_iParBuffer); m_iParBuffer = 1900; } if (sizeof(void*) == 4 && m_iParBuffer + m_iArticleCache > 1900) { ConfigError("Options \"ArticleCache\" and \"ParBuffer\" in total cannot use more than 1900MB of memory in 32-Bit mode. Changed to 1500 and 400"); m_iArticleCache = 1900; m_iParBuffer = 400; } if (!Util::EmptyStr(m_szUnpackPassFile) && !Util::FileExists(m_szUnpackPassFile)) { ConfigError("Invalid value for option \"UnpackPassFile\": %s. File not found", m_szUnpackPassFile); } } Options::OptEntries* Options::LockOptEntries() { m_mutexOptEntries.Lock(); return &m_OptEntries; } void Options::UnlockOptEntries() { m_mutexOptEntries.Unlock(); } nzbget-16.4/daemon/main/Scheduler.h0000644000175000017500000000511112630544544017034 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2008-2015 Andrey Prygunkov * * 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 SCHEDULER_H #define SCHEDULER_H #include #include #include #include "Thread.h" #include "Service.h" class Scheduler : public Service { public: enum ECommand { scPauseDownload, scUnpauseDownload, scPausePostProcess, scUnpausePostProcess, scDownloadRate, scScript, scProcess, scPauseScan, scUnpauseScan, scActivateServer, scDeactivateServer, scFetchFeed }; class Task { private: int m_iID; int m_iHours; int m_iMinutes; int m_iWeekDaysBits; ECommand m_eCommand; char* m_szParam; time_t m_tLastExecuted; public: Task(int iID, int iHours, int iMinutes, int iWeekDaysBits, ECommand eCommand, const char* szParam); ~Task(); friend class Scheduler; }; private: typedef std::list TaskList; typedef std::vector ServerStatusList; TaskList m_TaskList; Mutex m_mutexTaskList; time_t m_tLastCheck; bool m_bDownloadRateChanged; bool m_bExecuteProcess; bool m_bPauseDownloadChanged; bool m_bPausePostProcessChanged; bool m_bPauseScanChanged; bool m_bServerChanged; ServerStatusList m_ServerStatusList; bool m_bFirstChecked; void ExecuteTask(Task* pTask); void CheckTasks(); static bool CompareTasks(Scheduler::Task* pTask1, Scheduler::Task* pTask2); void PrepareLog(); void PrintLog(); void EditServer(bool bActive, const char* szServerList); void FetchFeed(const char* szFeedList); void CheckScheduledResume(); void FirstCheck(); protected: virtual int ServiceInterval() { return 1000; } virtual void ServiceWork(); public: Scheduler(); ~Scheduler(); void AddTask(Task* pTask); }; extern Scheduler* g_pScheduler; #endif nzbget-16.4/daemon/util/0000755000175000017500000000000012630544544015000 5ustar andreasandreasnzbget-16.4/daemon/util/Script.h0000644000175000017500000000623712630544544016425 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 SCRIPT_H #define SCRIPT_H #include #include "Thread.h" #include "Log.h" class EnvironmentStrings { private: typedef std::vector Strings; Strings m_strings; public: EnvironmentStrings(); ~EnvironmentStrings(); void Clear(); void InitFromCurrentProcess(); void Append(char* szString); #ifdef WIN32 char* GetStrings(); #else char** GetStrings(); #endif }; class ScriptController { private: const char* m_szScript; const char* m_szWorkingDir; const char** m_szArgs; bool m_bFreeArgs; const char* m_szStdArgs[2]; const char* m_szInfoName; const char* m_szLogPrefix; EnvironmentStrings m_environmentStrings; bool m_bTerminated; bool m_bDetached; FILE* m_pReadpipe; #ifdef WIN32 HANDLE m_hProcess; char m_szCmdLine[2048]; #else pid_t m_hProcess; #endif typedef std::vector RunningScripts; static RunningScripts m_RunningScripts; static Mutex m_mutexRunning; 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, const char* szText); bool GetTerminated() { return m_bTerminated; } void ResetEnv(); void PrepareEnvOptions(const char* szStripPrefix); void PrepareArgs(); void UnregisterRunningScript(); public: ScriptController(); virtual ~ScriptController(); int Execute(); void Terminate(); void Resume(); void Detach(); static void TerminateAll(); void SetScript(const char* szScript) { m_szScript = szScript; } const char* GetScript() { return m_szScript; } void SetWorkingDir(const char* szWorkingDir) { m_szWorkingDir = szWorkingDir; } 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 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); }; #endif nzbget-16.4/daemon/util/Observer.cpp0000644000175000017500000000277112630544544017302 0ustar andreasandreas/* * This file if part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2014 Andrey Prygunkov * * 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 "Observer.h" #include "Log.h" Subject::Subject() { m_Observers.clear(); } void Subject::Attach(Observer* pObserver) { m_Observers.push_back(pObserver); } void Subject::Detach(Observer* pObserver) { m_Observers.remove(pObserver); } void Subject::Notify(void* pAspect) { debug("Notifying observers"); for (std::list::iterator it = m_Observers.begin(); it != m_Observers.end(); it++) { Observer* Observer = *it; Observer->Update(this, pAspect); } } nzbget-16.4/daemon/util/Service.h0000644000175000017500000000274312630544544016557 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2015 Andrey Prygunkov * * 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 SERVICE_H #define SERVICE_H #include #include "Thread.h" class Service { private: int m_iLastTick; protected: virtual int ServiceInterval() = 0; virtual void ServiceWork() = 0; friend class ServiceCoordinator; public: Service(); }; class ServiceCoordinator : public Thread { public: typedef std::vector ServiceList; private: ServiceList m_Services; void RegisterService(Service* pService); friend class Service; public: ServiceCoordinator(); virtual ~ServiceCoordinator(); virtual void Run(); }; extern ServiceCoordinator* g_pServiceCoordinator; #endif nzbget-16.4/daemon/util/Script.cpp0000644000175000017500000004407412630544544016761 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #include #ifndef WIN32 #include #include #include #else #include #endif #include #include #include #include #include #include #include "nzbget.h" #include "Script.h" #include "Log.h" #include "Util.h" #include "Options.h" // System global variable holding environments variables extern char** environ; extern char* (*g_szEnvironmentVariables)[]; ScriptController::RunningScripts ScriptController::m_RunningScripts; Mutex ScriptController::m_mutexRunning; #ifndef WIN32 #define CHILD_WATCHDOG 1 #endif #ifdef CHILD_WATCHDOG /** * Sometimes the forked child process doesn't start properly and hangs * just during the starting. I didn't find any explanation about what * could cause that problem except of a general advice, that * "a forking in a multithread application is not recommended". * * Workaround: * 1) child process prints a line into stdout directly after the start; * 2) parent process waits for a line for 60 seconds. If it didn't receive it * the cild process assumed to hang and will be killed. Another attempt * will be made. */ class ChildWatchDog : public Thread { private: pid_t m_hProcessID; protected: virtual void Run(); public: void SetProcessID(pid_t hProcessID) { m_hProcessID = hProcessID; } }; void ChildWatchDog::Run() { static const int WAIT_SECONDS = 60; time_t tStart = time(NULL); while (!IsStopped() && (time(NULL) - tStart) < WAIT_SECONDS) { usleep(10 * 1000); } if (!IsStopped()) { info("Restarting hanging child process"); kill(m_hProcessID, SIGKILL); } } #endif EnvironmentStrings::EnvironmentStrings() { } EnvironmentStrings::~EnvironmentStrings() { Clear(); } void EnvironmentStrings::Clear() { for (Strings::iterator it = m_strings.begin(); it != m_strings.end(); it++) { free(*it); } m_strings.clear(); } void EnvironmentStrings::InitFromCurrentProcess() { for (int i = 0; (*g_szEnvironmentVariables)[i]; i++) { char* szVar = (*g_szEnvironmentVariables)[i]; // Ignore all env vars set by NZBGet. // This is to avoid the passing of env vars after program update (when NZBGet is // started from a script which was started by a previous instance of NZBGet). // Format: NZBXX_YYYY (XX are any two characters, YYYY are any number of any characters). if (!(!strncmp(szVar, "NZB", 3) && strlen(szVar) > 5 && szVar[5] == '_')) { Append(strdup(szVar)); } } } void EnvironmentStrings::Append(char* szString) { m_strings.push_back(szString); } #ifdef WIN32 /* * Returns environment block in format suitable for using with CreateProcess. * The allocated memory must be freed by caller using "free()". */ char* EnvironmentStrings::GetStrings() { int iSize = 1; for (Strings::iterator it = m_strings.begin(); it != m_strings.end(); it++) { char* szVar = *it; iSize += strlen(szVar) + 1; } char* szStrings = (char*)malloc(iSize); char* szPtr = szStrings; for (Strings::iterator it = m_strings.begin(); it != m_strings.end(); it++) { char* szVar = *it; strcpy(szPtr, szVar); szPtr += strlen(szVar) + 1; } *szPtr = '\0'; return szStrings; } #else /* * Returns environment block in format suitable for using with execve * The allocated memory must be freed by caller using "free()". */ char** EnvironmentStrings::GetStrings() { char** pStrings = (char**)malloc((m_strings.size() + 1) * sizeof(char*)); char** pPtr = pStrings; for (Strings::iterator it = m_strings.begin(); it != m_strings.end(); it++) { char* szVar = *it; *pPtr = szVar; pPtr++; } *pPtr = NULL; return pStrings; } #endif ScriptController::ScriptController() { m_szScript = NULL; m_szWorkingDir = NULL; m_szArgs = NULL; m_bFreeArgs = false; m_szInfoName = NULL; m_szLogPrefix = NULL; m_bTerminated = false; m_bDetached = false; m_hProcess = 0; ResetEnv(); m_mutexRunning.Lock(); m_RunningScripts.push_back(this); m_mutexRunning.Unlock(); } ScriptController::~ScriptController() { if (m_bFreeArgs) { for (const char** szArgPtr = m_szArgs; *szArgPtr; szArgPtr++) { free((char*)*szArgPtr); } free(m_szArgs); } UnregisterRunningScript(); } void ScriptController::UnregisterRunningScript() { m_mutexRunning.Lock(); RunningScripts::iterator it = std::find(m_RunningScripts.begin(), m_RunningScripts.end(), this); if (it != m_RunningScripts.end()) { m_RunningScripts.erase(it); } m_mutexRunning.Unlock(); } void ScriptController::ResetEnv() { m_environmentStrings.Clear(); m_environmentStrings.InitFromCurrentProcess(); } void ScriptController::SetEnvVar(const char* szName, const char* szValue) { int iLen = strlen(szName) + strlen(szValue) + 2; char* szVar = (char*)malloc(iLen); snprintf(szVar, iLen, "%s=%s", szName, szValue); m_environmentStrings.Append(szVar); } void ScriptController::SetIntEnvVar(const char* szName, int iValue) { char szValue[1024]; snprintf(szValue, 10, "%i", iValue); szValue[1024-1] = '\0'; SetEnvVar(szName, szValue); } /** * If szStripPrefix is not NULL, only options, whose names start with the prefix * are processed. The prefix is then stripped from the names. * If szStripPrefix is NULL, all options are processed; without stripping. */ void ScriptController::PrepareEnvOptions(const char* szStripPrefix) { int iPrefixLen = szStripPrefix ? strlen(szStripPrefix) : 0; Options::OptEntries* pOptEntries = g_pOptions->LockOptEntries(); for (Options::OptEntries::iterator it = pOptEntries->begin(); it != pOptEntries->end(); it++) { Options::OptEntry* pOptEntry = *it; if (szStripPrefix && !strncmp(pOptEntry->GetName(), szStripPrefix, iPrefixLen) && (int)strlen(pOptEntry->GetName()) > iPrefixLen) { SetEnvVarSpecial("NZBPO", pOptEntry->GetName() + iPrefixLen, pOptEntry->GetValue()); } else if (!szStripPrefix) { SetEnvVarSpecial("NZBOP", pOptEntry->GetName(), pOptEntry->GetValue()); } } g_pOptions->UnlockOptEntries(); } void ScriptController::SetEnvVarSpecial(const char* szPrefix, const char* szName, const char* szValue) { char szVarname[1024]; snprintf(szVarname, sizeof(szVarname), "%s_%s", szPrefix, szName); szVarname[1024-1] = '\0'; // Original name SetEnvVar(szVarname, szValue); char szNormVarname[1024]; strncpy(szNormVarname, szVarname, sizeof(szVarname)); szNormVarname[1024-1] = '\0'; // Replace special characters with "_" and convert to upper case for (char* szPtr = szNormVarname; *szPtr; szPtr++) { if (strchr(".:*!\"$%&/()=`+~#'{}[]@- ", *szPtr)) *szPtr = '_'; *szPtr = toupper(*szPtr); } // Another env var with normalized name (replaced special chars and converted to upper case) if (strcmp(szVarname, szNormVarname)) { SetEnvVar(szNormVarname, szValue); } } void ScriptController::PrepareArgs() { #ifdef WIN32 if (!m_szArgs) { // Special support for script languages: // automatically find the app registered for this extension and run it const char* szExtension = strrchr(GetScript(), '.'); if (szExtension && strcasecmp(szExtension, ".exe") && strcasecmp(szExtension, ".bat") && strcasecmp(szExtension, ".cmd")) { debug("Looking for associated program for %s", szExtension); char szCommand[512]; int iBufLen = 512-1; if (Util::RegReadStr(HKEY_CLASSES_ROOT, szExtension, NULL, szCommand, &iBufLen)) { szCommand[iBufLen] = '\0'; debug("Extension: %s", szCommand); char szRegPath[512]; snprintf(szRegPath, 512, "%s\\shell\\open\\command", szCommand); szRegPath[512-1] = '\0'; iBufLen = 512-1; if (Util::RegReadStr(HKEY_CLASSES_ROOT, szRegPath, NULL, szCommand, &iBufLen)) { szCommand[iBufLen] = '\0'; debug("Command: %s", szCommand); DWORD_PTR pArgs[] = { (DWORD_PTR)GetScript(), (DWORD_PTR)0 }; if (FormatMessage(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY, szCommand, 0, 0, m_szCmdLine, sizeof(m_szCmdLine), (va_list*)pArgs)) { debug("CmdLine: %s", m_szCmdLine); return; } } } warn("Could not found associated program for %s. Trying to execute %s directly", szExtension, Util::BaseFileName(GetScript())); } } #endif if (!m_szArgs) { m_szStdArgs[0] = GetScript(); m_szStdArgs[1] = NULL; SetArgs(m_szStdArgs, false); } } int ScriptController::Execute() { PrepareEnvOptions(NULL); PrepareArgs(); int iExitCode = 0; int pipein; #ifdef CHILD_WATCHDOG bool bChildConfirmed = false; while (!bChildConfirmed && !m_bTerminated) { #endif #ifdef WIN32 // build command line char* szCmdLine = NULL; if (m_szArgs) { char szCmdLineBuf[2048]; int iUsedLen = 0; for (const char** szArgPtr = m_szArgs; *szArgPtr; szArgPtr++) { snprintf(szCmdLineBuf + iUsedLen, 2048 - iUsedLen, "\"%s\" ", *szArgPtr); iUsedLen += strlen(*szArgPtr) + 3; } szCmdLineBuf[iUsedLen < 2048 ? iUsedLen - 1 : 2048 - 1] = '\0'; szCmdLine = szCmdLineBuf; } else { szCmdLine = m_szCmdLine; } // create pipes to write and read data HANDLE hReadPipe, hWritePipe; SECURITY_ATTRIBUTES SecurityAttributes; memset(&SecurityAttributes, 0, sizeof(SecurityAttributes)); SecurityAttributes.nLength = sizeof(SecurityAttributes); SecurityAttributes.bInheritHandle = TRUE; CreatePipe(&hReadPipe, &hWritePipe, &SecurityAttributes, 0); SetHandleInformation(hReadPipe, HANDLE_FLAG_INHERIT, 0); STARTUPINFO StartupInfo; memset(&StartupInfo, 0, sizeof(StartupInfo)); StartupInfo.cb = sizeof(StartupInfo); StartupInfo.dwFlags = STARTF_USESTDHANDLES; StartupInfo.hStdInput = 0; StartupInfo.hStdOutput = hWritePipe; StartupInfo.hStdError = hWritePipe; PROCESS_INFORMATION ProcessInfo; memset(&ProcessInfo, 0, sizeof(ProcessInfo)); char* szEnvironmentStrings = m_environmentStrings.GetStrings(); BOOL bOK = CreateProcess(NULL, szCmdLine, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW, szEnvironmentStrings, m_szWorkingDir, &StartupInfo, &ProcessInfo); if (!bOK) { DWORD dwErrCode = GetLastError(); char szErrMsg[255]; szErrMsg[255-1] = '\0'; if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwErrCode, 0, szErrMsg, 255, NULL)) { PrintMessage(Message::mkError, "Could not start %s: %s", m_szInfoName, szErrMsg); } else { PrintMessage(Message::mkError, "Could not start %s: error %i", m_szInfoName, dwErrCode); } if (!Util::FileExists(m_szScript)) { PrintMessage(Message::mkError, "Could not find file %s", m_szScript); } free(szEnvironmentStrings); return -1; } free(szEnvironmentStrings); debug("Child Process-ID: %i", (int)ProcessInfo.dwProcessId); m_hProcess = ProcessInfo.hProcess; // close unused "write" end CloseHandle(hWritePipe); pipein = _open_osfhandle((intptr_t)hReadPipe, _O_RDONLY); #else int p[2]; int pipeout; // create the pipe if (pipe(p)) { PrintMessage(Message::mkError, "Could not open pipe: errno %i", errno); return -1; } char** pEnvironmentStrings = m_environmentStrings.GetStrings(); pipein = p[0]; pipeout = p[1]; debug("forking"); pid_t pid = fork(); if (pid == -1) { PrintMessage(Message::mkError, "Could not start %s: errno %i", m_szInfoName, errno); free(pEnvironmentStrings); return -1; } else if (pid == 0) { // here goes the second instance // create new process group (see Terminate() where it is used) setsid(); // close up the "read" end close(pipein); // make the pipeout to be the same as stdout and stderr dup2(pipeout, 1); dup2(pipeout, 2); close(pipeout); #ifdef CHILD_WATCHDOG fwrite("\n", 1, 1, stdout); fflush(stdout); #endif chdir(m_szWorkingDir); environ = pEnvironmentStrings; execvp(m_szScript, (char* const*)m_szArgs); if (errno == EACCES) { fprintf(stdout, "[WARNING] Fixing permissions for %s\n", m_szScript); fflush(stdout); Util::FixExecPermission(m_szScript); execvp(m_szScript, (char* const*)m_szArgs); } // NOTE: the text "[ERROR] Could not start " is checked later, // by changing adjust the dependent code below. fprintf(stdout, "[ERROR] Could not start %s: %s", m_szScript, strerror(errno)); fflush(stdout); _exit(254); } // continue the first instance debug("forked"); debug("Child Process-ID: %i", (int)pid); free(pEnvironmentStrings); m_hProcess = pid; // close unused "write" end close(pipeout); #endif // open the read end m_pReadpipe = fdopen(pipein, "r"); if (!m_pReadpipe) { PrintMessage(Message::mkError, "Could not open pipe to %s", m_szInfoName); return -1; } #ifdef CHILD_WATCHDOG debug("Creating child watchdog"); ChildWatchDog* pWatchDog = new ChildWatchDog(); pWatchDog->SetAutoDestroy(false); pWatchDog->SetProcessID(pid); pWatchDog->Start(); #endif char* buf = (char*)malloc(10240); debug("Entering pipe-loop"); bool bFirstLine = true; bool bStartError = false; while (!m_bTerminated && !m_bDetached && !feof(m_pReadpipe)) { if (ReadLine(buf, 10240, m_pReadpipe) && m_pReadpipe) { #ifdef CHILD_WATCHDOG if (!bChildConfirmed) { bChildConfirmed = true; pWatchDog->Stop(); debug("Child confirmed"); continue; } #endif if (bFirstLine && !strncmp(buf, "[ERROR] Could not start ", 24)) { bStartError = true; } ProcessOutput(buf); bFirstLine = false; } } debug("Exited pipe-loop"); #ifdef CHILD_WATCHDOG debug("Destroying WatchDog"); if (!bChildConfirmed) { pWatchDog->Stop(); } while (pWatchDog->IsRunning()) { usleep(5 * 1000); } delete pWatchDog; #endif free(buf); if (m_pReadpipe) { fclose(m_pReadpipe); } if (m_bTerminated && m_szInfoName) { warn("Interrupted %s", m_szInfoName); } iExitCode = 0; if (!m_bTerminated && !m_bDetached) { #ifdef WIN32 WaitForSingleObject(m_hProcess, INFINITE); DWORD dExitCode = 0; GetExitCodeProcess(m_hProcess, &dExitCode); iExitCode = dExitCode; #else int iStatus = 0; waitpid(m_hProcess, &iStatus, 0); if (WIFEXITED(iStatus)) { iExitCode = WEXITSTATUS(iStatus); if (iExitCode == 254 && bStartError) { iExitCode = -1; } } #endif } #ifdef CHILD_WATCHDOG } // while (!bChildConfirmed && !m_bTerminated) #endif debug("Exit code %i", iExitCode); return iExitCode; } void ScriptController::Terminate() { debug("Stopping %s", m_szInfoName); m_bTerminated = true; #ifdef WIN32 BOOL bOK = TerminateProcess(m_hProcess, -1); if (bOK) { // wait 60 seconds for process to terminate WaitForSingleObject(m_hProcess, 60 * 1000); } else { DWORD dExitCode = 0; GetExitCodeProcess(m_hProcess, &dExitCode); bOK = dExitCode != STILL_ACTIVE; } #else pid_t hKillProcess = m_hProcess; if (getpgid(hKillProcess) == hKillProcess) { // if the child process has its own group (setsid() was successful), kill the whole group hKillProcess = -hKillProcess; } bool bOK = hKillProcess && kill(hKillProcess, SIGKILL) == 0; #endif if (bOK) { debug("Terminated %s", m_szInfoName); } else { error("Could not terminate %s", m_szInfoName); } debug("Stopped %s", m_szInfoName); } void ScriptController::TerminateAll() { m_mutexRunning.Lock(); for (RunningScripts::iterator it = m_RunningScripts.begin(); it != m_RunningScripts.end(); it++) { ScriptController* pScript = *it; if (pScript->m_hProcess && !pScript->m_bDetached) { pScript->Terminate(); } } m_mutexRunning.Unlock(); } void ScriptController::Detach() { debug("Detaching %s", m_szInfoName); m_bDetached = true; FILE* pReadpipe = m_pReadpipe; m_pReadpipe = NULL; fclose(pReadpipe); } void ScriptController::Resume() { m_bTerminated = false; m_bDetached = false; m_hProcess = 0; } bool ScriptController::ReadLine(char* szBuf, int iBufSize, FILE* pStream) { return fgets(szBuf, iBufSize, pStream); } void ScriptController::ProcessOutput(char* szText) { debug("Processing output received from script"); for (char* pend = szText + strlen(szText) - 1; pend >= szText && (*pend == '\n' || *pend == '\r' || *pend == ' '); pend--) *pend = '\0'; if (szText[0] == '\0') { // skip empty lines return; } if (!strncmp(szText, "[INFO] ", 7)) { PrintMessage(Message::mkInfo, "%s", szText + 7); } else if (!strncmp(szText, "[WARNING] ", 10)) { PrintMessage(Message::mkWarning, "%s", szText + 10); } else if (!strncmp(szText, "[ERROR] ", 8)) { PrintMessage(Message::mkError, "%s", szText + 8); } else if (!strncmp(szText, "[DETAIL] ", 9)) { PrintMessage(Message::mkDetail, "%s", szText + 9); } else if (!strncmp(szText, "[DEBUG] ", 8)) { PrintMessage(Message::mkDebug, "%s", szText + 8); } else { PrintMessage(Message::mkInfo, "%s", szText); } debug("Processing output received from script - completed"); } void ScriptController::AddMessage(Message::EKind eKind, const char* 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; } } void ScriptController::PrintMessage(Message::EKind eKind, const char* szFormat, ...) { char tmp2[1024]; va_list ap; va_start(ap, szFormat); vsnprintf(tmp2, 1024, szFormat, ap); tmp2[1024-1] = '\0'; va_end(ap); char tmp3[1024]; if (m_szLogPrefix) { snprintf(tmp3, 1024, "%s: %s", m_szLogPrefix, tmp2); } else { strncpy(tmp3, tmp2, 1024); } tmp3[1024-1] = '\0'; AddMessage(eKind, tmp3); } nzbget-16.4/daemon/util/Log.h0000644000175000017500000000663712630544544015706 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 LOG_H #define LOG_H #include #include #include #include "Thread.h" void error(const char* msg, ...); void warn(const char* msg, ...); void info(const char* msg, ...); void detail(const char* msg, ...); #ifdef DEBUG #ifdef HAVE_VARIADIC_MACROS void debug(const char* szFilename, const char* szFuncname, int iLineNr, const char* msg, ...); #else void debug(const char* msg, ...); #endif #endif class Message { public: enum EKind { mkInfo, mkWarning, mkError, mkDebug, mkDetail }; private: unsigned int m_iID; EKind m_eKind; time_t m_tTime; char* m_szText; friend class Log; public: Message(unsigned int iID, EKind eKind, time_t tTime, const char* szText); ~Message(); unsigned int GetID() { return m_iID; } EKind GetKind() { return m_eKind; } time_t GetTime() { return m_tTime; } const char* GetText() { return m_szText; } }; typedef std::deque MessageListBase; class MessageList: public MessageListBase { public: ~MessageList(); void Clear(); }; class Debuggable { protected: virtual void LogDebugInfo() = 0; friend class Log; }; class Log { public: typedef std::list Debuggables; private: Mutex m_mutexLog; MessageList m_Messages; Debuggables m_Debuggables; Mutex m_mutexDebug; char* m_szLogFilename; unsigned int m_iIDGen; time_t m_tLastWritten; bool m_bOptInit; #ifdef DEBUG bool m_bExtraDebug; #endif Log(); ~Log(); void Filelog(const char* msg, ...); void AddMessage(Message::EKind eKind, const char* szText); void RotateLog(); friend void error(const char* msg, ...); friend void warn(const char* msg, ...); friend void info(const char* msg, ...); friend void detail(const char* msg, ...); #ifdef DEBUG #ifdef HAVE_VARIADIC_MACROS friend void debug(const char* szFilename, const char* szFuncname, int iLineNr, const char* msg, ...); #else friend void debug(const char* msg, ...); #endif #endif public: static void Init(); static void Final(); MessageList* LockMessages(); void UnlockMessages(); void Clear(); void ResetLog(); void InitOptions(); void RegisterDebuggable(Debuggable* pDebuggable); void UnregisterDebuggable(Debuggable* pDebuggable); void LogDebugInfo(); }; #ifdef DEBUG #ifdef HAVE_VARIADIC_MACROS #define debug(...) debug(__FILE__, FUNCTION_MACRO_NAME, __LINE__, __VA_ARGS__) #endif #else #define debug(...) do { } while(0) #endif extern Log* g_pLog; #endif nzbget-16.4/daemon/util/Thread.cpp0000644000175000017500000001216612630544544016721 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #include #ifdef WIN32 #include #else #include #include #endif #include "Log.h" #include "Thread.h" int Thread::m_iThreadCount = 1; // take the main program thread into account Mutex* Thread::m_pMutexThread; Mutex::Mutex() { #ifdef WIN32 m_pMutexObj = (CRITICAL_SECTION*)malloc(sizeof(CRITICAL_SECTION)); InitializeCriticalSection((CRITICAL_SECTION*)m_pMutexObj); #else m_pMutexObj = (pthread_mutex_t*)malloc(sizeof(pthread_mutex_t)); pthread_mutex_init((pthread_mutex_t*)m_pMutexObj, NULL); #endif } Mutex::~ Mutex() { #ifdef WIN32 DeleteCriticalSection((CRITICAL_SECTION*)m_pMutexObj); #else pthread_mutex_destroy((pthread_mutex_t*)m_pMutexObj); #endif free(m_pMutexObj); } void Mutex::Lock() { #ifdef WIN32 EnterCriticalSection((CRITICAL_SECTION*)m_pMutexObj); #ifdef DEBUG // CriticalSections on Windows can be locked many times from the same thread, // but we do not want this and must treat such situations as errors and detect them. if (((CRITICAL_SECTION*)m_pMutexObj)->RecursionCount > 1) { error("Internal program error: inconsistent thread-lock detected"); } #endif #else pthread_mutex_lock((pthread_mutex_t*)m_pMutexObj); #endif } void Mutex::Unlock() { #ifdef WIN32 LeaveCriticalSection((CRITICAL_SECTION*)m_pMutexObj); #else pthread_mutex_unlock((pthread_mutex_t*)m_pMutexObj); #endif } void Thread::Init() { debug("Initializing global thread data"); m_pMutexThread = new Mutex(); } void Thread::Final() { debug("Finalizing global thread data"); delete m_pMutexThread; } Thread::Thread() { debug("Creating Thread"); #ifdef WIN32 m_pThreadObj = NULL; #else m_pThreadObj = (pthread_t*)malloc(sizeof(pthread_t)); *((pthread_t*)m_pThreadObj) = 0; #endif m_bRunning = false; m_bStopped = false; m_bAutoDestroy = false; } Thread::~Thread() { debug("Destroying Thread"); #ifndef WIN32 free(m_pThreadObj); #endif } void Thread::Start() { debug("Starting Thread"); m_bRunning = true; // NOTE: we must guarantee, that in a time we set m_bRunning // to value returned from pthread_create, the thread-object still exists. // This is not obviously! // pthread_create could wait long enough before returning result // back to allow the started thread to complete its job and terminate. // We lock mutex m_pMutexThread on calling pthread_create; the started thread // then also try to lock the mutex (see thread_handler) and therefore // must wait until we unlock it m_pMutexThread->Lock(); #ifdef WIN32 m_pThreadObj = (HANDLE)_beginthread(Thread::thread_handler, 0, (void *)this); m_bRunning = m_pThreadObj != NULL; #else pthread_attr_t m_Attr; pthread_attr_init(&m_Attr); pthread_attr_setdetachstate(&m_Attr, PTHREAD_CREATE_DETACHED); pthread_attr_setinheritsched(&m_Attr , PTHREAD_INHERIT_SCHED); m_bRunning = !pthread_create((pthread_t*)m_pThreadObj, &m_Attr, Thread::thread_handler, (void *) this); pthread_attr_destroy(&m_Attr); #endif m_pMutexThread->Unlock(); } void Thread::Stop() { debug("Stopping Thread"); m_bStopped = true; } void Thread::Resume() { debug("Resuming Thread"); m_bStopped = false; } bool Thread::Kill() { debug("Killing Thread"); m_pMutexThread->Lock(); #ifdef WIN32 bool terminated = TerminateThread((HANDLE)m_pThreadObj, 0) != 0; #else bool terminated = pthread_cancel(*(pthread_t*)m_pThreadObj) == 0; #endif if (terminated) { m_iThreadCount--; } m_pMutexThread->Unlock(); return terminated; } #ifdef WIN32 void __cdecl Thread::thread_handler(void* pObject) #else void* Thread::thread_handler(void* pObject) #endif { m_pMutexThread->Lock(); m_iThreadCount++; m_pMutexThread->Unlock(); debug("Entering Thread-func"); Thread* pThread = (Thread*)pObject; pThread->Run(); debug("Thread-func exited"); pThread->m_bRunning = false; if (pThread->m_bAutoDestroy) { debug("Autodestroying Thread-object"); delete pThread; } m_pMutexThread->Lock(); m_iThreadCount--; m_pMutexThread->Unlock(); #ifndef WIN32 return NULL; #endif } int Thread::GetThreadCount() { m_pMutexThread->Lock(); int iThreadCount = m_iThreadCount; m_pMutexThread->Unlock(); return iThreadCount; } nzbget-16.4/daemon/util/Util.h0000644000175000017500000003213112630544544016066 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 UTIL_H #define UTIL_H #ifdef DIRBROWSER_SNAPSHOT #include #endif #include #include #ifdef WIN32 extern int optind, opterr; extern char *optarg; int getopt(int argc, char *argv[], char *optstring); #endif class DirBrowser { private: #ifdef WIN32 WIN32_FIND_DATA m_FindData; HANDLE m_hFile; bool m_bFirst; #else void* m_pDir; // DIR*, declared as void* to avoid including of struct dirent* m_pFindData; #endif #ifdef DIRBROWSER_SNAPSHOT bool m_bSnapshot; typedef std::deque FileList; FileList m_Snapshot; FileList::iterator m_itSnapshot; #endif public: #ifdef DIRBROWSER_SNAPSHOT DirBrowser(const char* szPath, bool bSnapshot = true); #else DirBrowser(const char* szPath); #endif ~DirBrowser(); const char* Next(); }; class StringBuilder { private: char* m_szBuffer; int m_iBufferSize; int m_iUsedSize; int m_iGrowSize; void Reserve(int iSize); public: StringBuilder(); ~StringBuilder(); void Append(const char* szStr); void AppendFmt(const char* szFormat, ...); void AppendFmtV(const char* szFormat, va_list ap); const char* GetBuffer() { return m_szBuffer; } void SetGrowSize(int iGrowSize) { m_iGrowSize = iGrowSize; } int GetUsedSize() { return m_iUsedSize; } void Clear(); }; class Util { 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, long long 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 CopyFile(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, char* szErrBuf, int iBufSize); 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); static long long FreeDiskSize(const char* szPath); static bool DirEmpty(const char* szDirFilename); 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 GetExeFileName(const char* argv0, char* szBuffer, int iBufSize); static char* FormatSpeed(char* szBuffer, int iBufSize, int iBytesPerSecond); static char* FormatSize(char* szBuffer, int iBufLen, long long lFileSize); static bool SameFilename(const char* szFilename1, const char* szFilename2); static bool MatchFileExt(const char* szFilename, const char* szExtensionList, const char* szListSeparator); static char* GetLastErrorMessage(char* szBuffer, int iBufLen); static long long GetCurrentTicks(); /* Flush disk buffers for file with given descriptor */ static bool FlushFileBuffers(int iFileDescriptor, char* szErrBuf, int iBufSize); /* Flush disk buffers for file metadata (after file renaming) */ static bool FlushDirBuffers(const char* szFilename, char* szErrBuf, int iBufSize); /* * Split command line int arguments. * Uses spaces and single quotation marks as separators. * Returns bool if sucessful or false if bad escaping was detected. * Parameter "argv" may be NULL if only a syntax check is needed. * Parsed parameters returned in Array "argv", which contains at least one element. * The last element in array is NULL. * Restrictions: the number of arguments is limited to 100 and each arguments must * be maximum 1024 chars long. * If these restrictions are exceeded, only first 100 arguments and only first 1024 * for each argument are returned (the functions still returns "true"). */ static bool SplitCommandLine(const char* szCommandLine, char*** argv); static long long JoinInt64(unsigned long Hi, unsigned long Lo); static void SplitInt64(long long Int64, unsigned long* Hi, unsigned long* Lo); 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 static void SetStandByMode(bool bStandBy); /* cross platform version of GNU timegm, which is similar to mktime but takes an UTC time as parameter */ static time_t Timegm(tm const *t); /* * Returns program version and revision number as string formatted like "0.7.0-r295". * If revision number is not available only version is returned ("0.7.0"). */ static const char* VersionRevision() { return VersionRevisionBuf; }; static char VersionRevisionBuf[100]; static void Init(); static unsigned long Crc32(unsigned char *block, unsigned long length); static unsigned long Crc32m(unsigned long startCrc, unsigned char *block, unsigned long length); static unsigned long Crc32Combine(unsigned long crc1, unsigned long crc2, unsigned long len2); /* * Returns number of available CPU cores or -1 if it could not be determined */ static int NumberOfCpuCores(); }; class WebUtil { public: static unsigned int DecodeBase64(char* szInputBuffer, int iInputBufferLength, char* szOutputBuffer); /* * Encodes string to be used as content of xml-tag. * Returns new string allocated with malloc, it need to be freed by caller. */ static char* XmlEncode(const char* raw); /* * Decodes string from xml. * The string is decoded on the place overwriting the content of raw-data. */ static void XmlDecode(char* raw); /* * Returns pointer to tag-content and length of content in iValueLength * The returned pointer points to the part of source-string, no additional strings are allocated. */ static const char* XmlFindTag(const char* szXml, const char* szTag, int* pValueLength); /* * Parses tag-content into szValueBuf. */ static bool XmlParseTagValue(const char* szXml, const char* szTag, char* szValueBuf, int iValueBufSize, const char** pTagEnd); /* * Replaces all tags with spaces effectively providing the text content only. * The string is transformed in-place overwriting the previous content. */ static void XmlStripTags(char* szXml); /* * Replaces all entities with spaces. * The string is transformed in-place overwriting the previous content. */ static void XmlRemoveEntities(char* raw); /* * Creates JSON-string by replace the certain characters with escape-sequences. * Returns new string allocated with malloc, it need to be freed by caller. */ static char* JsonEncode(const char* raw); /* * Decodes JSON-string. * The string is decoded on the place overwriting the content of raw-data. */ static void JsonDecode(char* raw); /* * Returns pointer to field-content and length of content in iValueLength * The returned pointer points to the part of source-string, no additional strings are allocated. */ static const char* JsonFindField(const char* szJsonText, const char* szFieldName, int* pValueLength); /* * Returns pointer to field-content and length of content in iValueLength * The returned pointer points to the part of source-string, no additional strings are allocated. */ static const char* JsonNextValue(const char* szJsonText, int* pValueLength); /* * Unquote http quoted string. * The string is decoded on the place overwriting the content of raw-data. */ static void HttpUnquote(char* raw); /* * Decodes URL-string. * The string is decoded on the place overwriting the content of raw-data. */ static void URLDecode(char* raw); /* * Makes valid URL by replacing of spaces with "%20". * Returns new string allocated with malloc, it need to be freed by caller. */ static char* URLEncode(const 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 { private: char* m_szAddress; char* m_szProtocol; char* m_szUser; char* m_szPassword; char* m_szHost; char* m_szResource; int m_iPort; bool m_bTLS; bool m_bValid; void ParseURL(); public: URL(const char* szAddress); ~URL(); bool IsValid() { return m_bValid; } const char* GetAddress() { return m_szAddress; } const char* GetProtocol() { return m_szProtocol; } const char* GetUser() { return m_szUser; } const char* GetPassword() { return m_szPassword; } const char* GetHost() { return m_szHost; } const char* GetResource() { return m_szResource; } int GetPort() { return m_iPort; } bool GetTLS() { return m_bTLS; } }; class RegEx { private: void* m_pContext; bool m_bValid; void* m_pMatches; int m_iMatchBufSize; public: 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 class ZLib { public: /* * calculates the size required for output buffer */ static unsigned int GZipLen(int iInputBufferLength); /* * returns the size of bytes written to szOutputBuffer or 0 if the buffer is too small or an error occured. */ static unsigned int GZip(const void* szInputBuffer, int iInputBufferLength, void* szOutputBuffer, int iOutputBufferLength); }; class GUnzipStream { public: enum EStatus { zlError, zlFinished, zlOK }; private: void* m_pZStream; void* m_pOutputBuffer; int m_iBufferSize; public: GUnzipStream(int BufferSize); ~GUnzipStream(); /* * set next memory block for uncompression */ void Write(const void *pInputBuffer, int iInputBufferLength); /* * get next uncompressed memory block. * iOutputBufferLength - the size of uncompressed block. if it is "0" the next compressed block must be provided via "Write". */ EStatus Read(const void **pOutputBuffer, int *iOutputBufferLength); }; #endif class Tokenizer { private: char m_szDefaultBuf[2014]; char* m_szDataString; bool m_bInplaceBuf; const char* m_szSeparators; char* m_szSavePtr; bool m_bWorking; public: Tokenizer(const char* szDataString, const char* szSeparators); Tokenizer(char* szDataString, const char* szSeparators, bool bInplaceBuf); ~Tokenizer(); char* Next(); }; #endif nzbget-16.4/daemon/util/Observer.h0000644000175000017500000000247712630544544016752 0ustar andreasandreas/* * This file if part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2014 Andrey Prygunkov * * 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 OBSERVER_H #define OBSERVER_H #include class Observer; class Subject { private: std::list m_Observers; public: Subject(); void Attach(Observer* pObserver); void Detach(Observer* pObserver); void Notify(void* pAspect); }; class Observer { protected: virtual void Update(Subject* pCaller, void* pAspect) = 0; friend class Subject; }; #endif nzbget-16.4/daemon/util/Util.cpp0000644000175000017500000017764412630544544016444 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #include #include #include #include #include #ifdef WIN32 #include #include #include #else #include #include #include #include #include #include #endif #ifdef HAVE_REGEX_H #include #endif #ifndef DISABLE_GZIP #include #endif #include #include "nzbget.h" #include "Util.h" #ifndef WIN32 // function "code_revision" is automatically generated in file "code_revision.cpp" on each build const char* code_revision(void); #endif #ifdef WIN32 // getopt for WIN32: // from http://www.codeproject.com/cpp/xgetopt.asp // Original Author: Hans Dietrich (hdietrich2@hotmail.com) // Released to public domain from author (thanks) // Slightly modified by Andrey Prygunkov char *optarg; // global argument pointer int optind = 0; // global argv index int getopt(int argc, char *argv[], char *optstring) { static char *next = NULL; if (optind == 0) next = NULL; optarg = NULL; if (next == NULL || *next == '\0') { if (optind == 0) optind++; if (optind >= argc || argv[optind][0] != '-' || argv[optind][1] == '\0') { optarg = NULL; if (optind < argc) optarg = argv[optind]; return -1; } if (strcmp(argv[optind], "--") == 0) { optind++; optarg = NULL; if (optind < argc) optarg = argv[optind]; return -1; } next = argv[optind]; next++; // skip past - optind++; } char c = *next++; char *cp = strchr(optstring, c); if (cp == NULL || c == ':') { fprintf(stderr, "Invalid option %c", c); return '?'; } cp++; if (*cp == ':') { if (*next != '\0') { optarg = next; next = NULL; } else if (optind < argc) { optarg = argv[optind]; optind++; } else { fprintf(stderr, "Option %c needs an argument", c); return '?'; } } return c; } DirBrowser::DirBrowser(const char* szPath) { char szMask[MAX_PATH + 1]; snprintf(szMask, MAX_PATH + 1, "%s%c*.*", szPath, (int)PATH_SEPARATOR); szMask[MAX_PATH] = '\0'; m_hFile = FindFirstFile(szMask, &m_FindData); m_bFirst = true; } DirBrowser::~DirBrowser() { if (m_hFile != INVALID_HANDLE_VALUE) { FindClose(m_hFile); } } const char* DirBrowser::Next() { bool bOK = false; if (m_bFirst) { bOK = m_hFile != INVALID_HANDLE_VALUE; m_bFirst = false; } else { bOK = FindNextFile(m_hFile, &m_FindData) != 0; } if (bOK) { return m_FindData.cFileName; } return NULL; } #else #ifdef DIRBROWSER_SNAPSHOT DirBrowser::DirBrowser(const char* szPath, bool bSnapshot) #else DirBrowser::DirBrowser(const char* szPath) #endif { #ifdef DIRBROWSER_SNAPSHOT m_bSnapshot = bSnapshot; if (m_bSnapshot) { DirBrowser dir(szPath, false); while (const char* filename = dir.Next()) { m_Snapshot.push_back(strdup(filename)); } m_itSnapshot = m_Snapshot.begin(); } else #endif { m_pDir = opendir(szPath); } } DirBrowser::~DirBrowser() { #ifdef DIRBROWSER_SNAPSHOT if (m_bSnapshot) { for (FileList::iterator it = m_Snapshot.begin(); it != m_Snapshot.end(); it++) { delete *it; } } else #endif { if (m_pDir) { closedir((DIR*)m_pDir); } } } const char* DirBrowser::Next() { #ifdef DIRBROWSER_SNAPSHOT if (m_bSnapshot) { return m_itSnapshot == m_Snapshot.end() ? NULL : *m_itSnapshot++; } else #endif { if (m_pDir) { m_pFindData = readdir((DIR*)m_pDir); if (m_pFindData) { return m_pFindData->d_name; } } return NULL; } } #endif StringBuilder::StringBuilder() { m_szBuffer = NULL; m_iBufferSize = 0; m_iUsedSize = 0; m_iGrowSize = 10240; } StringBuilder::~StringBuilder() { free(m_szBuffer); } void StringBuilder::Clear() { free(m_szBuffer); m_szBuffer = NULL; m_iBufferSize = 0; m_iUsedSize = 0; } void StringBuilder::Append(const char* szStr) { int iPartLen = strlen(szStr); Reserve(iPartLen + 1); strcpy(m_szBuffer + m_iUsedSize, szStr); m_iUsedSize += iPartLen; m_szBuffer[m_iUsedSize] = '\0'; } void StringBuilder::AppendFmt(const char* szFormat, ...) { va_list args; va_start(args, szFormat); AppendFmtV(szFormat, args); va_end(args); } void StringBuilder::AppendFmtV(const char* szFormat, va_list ap) { va_list ap2; va_copy(ap2, ap); int iRemainingSize = m_iBufferSize - m_iUsedSize; int m = vsnprintf(m_szBuffer + m_iUsedSize, iRemainingSize, szFormat, ap); #ifdef WIN32 if (m == -1) { m = _vscprintf(szFormat, ap); } #endif if (m + 1 > iRemainingSize) { Reserve(m - iRemainingSize + m_iGrowSize); iRemainingSize = m_iBufferSize - m_iUsedSize; m = vsnprintf(m_szBuffer + m_iUsedSize, iRemainingSize, szFormat, ap2); } if (m >= 0) { m_szBuffer[m_iUsedSize += m] = '\0'; } va_end(ap2); } void StringBuilder::Reserve(int iSize) { if (m_iUsedSize + iSize > m_iBufferSize) { m_iBufferSize += iSize + m_iGrowSize; m_szBuffer = (char*)realloc(m_szBuffer, m_iBufferSize); } } char Util::VersionRevisionBuf[100]; char* Util::BaseFileName(const char* filename) { char* p = (char*)strrchr(filename, PATH_SEPARATOR); char* p1 = (char*)strrchr(filename, ALT_PATH_SEPARATOR); if (p1) { if ((p && p < p1) || !p) { p = p1; } } if (p) { return p + 1; } else { return (char*)filename; } } void Util::NormalizePathSeparators(char* szPath) { for (char* p = szPath; *p; p++) { if (*p == ALT_PATH_SEPARATOR) { *p = PATH_SEPARATOR; } } } bool Util::ForceDirectories(const char* szPath, char* szErrBuf, int iBufSize) { *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 #ifdef WIN32 && iLen > 3 #endif ) { szNormPath[iLen-1] = '\0'; } struct stat buffer; 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[1024]; strncpy(szParentPath, szNormPath, 1024); szParentPath[1024-1] = '\0'; char* p = (char*)strrchr(szParentPath, PATH_SEPARATOR); if (p) { #ifdef WIN32 if (p - szParentPath == 2 && szParentPath[1] == ':' && strlen(szParentPath) > 2) { szParentPath[3] = '\0'; } else #endif { *p = '\0'; } if (strlen(szParentPath) != strlen(szPath) && !ForceDirectories(szParentPath, szErrBuf, iBufSize)) { return false; } } if (mkdir(szNormPath, S_DIRMODE) != 0 && errno != EEXIST) { 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; } } return true; } bool Util::GetCurrentDirectory(char* szBuffer, int iBufSize) { #ifdef WIN32 return ::GetCurrentDirectory(iBufSize, szBuffer) != NULL; #else return getcwd(szBuffer, iBufSize) != NULL; #endif } bool Util::SetCurrentDirectory(const char* szDirFilename) { #ifdef WIN32 return ::SetCurrentDirectory(szDirFilename); #else return chdir(szDirFilename) == 0; #endif } bool Util::DirEmpty(const char* szDirFilename) { DirBrowser dir(szDirFilename); while (const char* filename = dir.Next()) { if (strcmp(filename, ".") && strcmp(filename, "..")) { return false; } } return true; } bool Util::LoadFileIntoBuffer(const char* szFileName, char** pBuffer, int* pBufferLength) { FILE* pFile = fopen(szFileName, FOPEN_RB); if (!pFile) { return false; } // obtain file size. fseek(pFile , 0 , SEEK_END); int iSize = (int)ftell(pFile); rewind(pFile); // allocate memory to contain the whole file. *pBuffer = (char*) malloc(iSize + 1); if (!*pBuffer) { return false; } // copy the file into the buffer. fread(*pBuffer, 1, iSize, pFile); fclose(pFile); (*pBuffer)[iSize] = 0; *pBufferLength = iSize + 1; return true; } bool Util::SaveBufferIntoFile(const char* szFileName, const char* szBuffer, int iBufLen) { FILE* pFile = fopen(szFileName, FOPEN_WB); if (!pFile) { return false; } int iWrittenBytes = fwrite(szBuffer, 1, iBufLen, pFile); fclose(pFile); return iWrittenBytes == iBufLen; } bool Util::CreateSparseFile(const char* szFilename, long long iSize) { bool bOK = false; #ifdef WIN32 HANDLE hFile = CreateFile(szFilename, GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_NEW, 0, NULL); if (hFile != INVALID_HANDLE_VALUE) { // first try to create sparse file (supported only on NTFS partitions), // it may fail but that's OK. DWORD dwBytesReturned; DeviceIoControl(hFile, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, &dwBytesReturned, NULL); LARGE_INTEGER iSize64; iSize64.QuadPart = iSize; SetFilePointerEx(hFile, iSize64, NULL, FILE_END); SetEndOfFile(hFile); CloseHandle(hFile); bOK = true; } #else // create file FILE* pFile = fopen(szFilename, FOPEN_AB); if (pFile) { fclose(pFile); } // there are no reliable function to expand file on POSIX, so we must try different approaches, // starting with the fastest one and hoping it will work // 1) set file size using function "truncate" (it is fast, if it works) truncate(szFilename, iSize); // check if it worked bOK = FileSize(szFilename) == iSize; if (!bOK) { // 2) truncate did not work, expanding the file by writing in it (it is slow) truncate(szFilename, 0); pFile = fopen(szFilename, FOPEN_AB); char c = '0'; fwrite(&c, 1, iSize, pFile); fclose(pFile); bOK = FileSize(szFilename) == iSize; } #endif return bOK; } bool Util::TruncateFile(const char* szFilename, int iSize) { bool bOK = false; #ifdef WIN32 FILE *file = fopen(szFilename, FOPEN_RBP); fseek(file, iSize, SEEK_SET); bOK = SetEndOfFile((HANDLE)_get_osfhandle(_fileno(file))) != 0; fclose(file); #else bOK = truncate(szFilename, iSize) == 0; #endif return bOK; } //replace bad chars in filename void Util::MakeValidFilename(char* szFilename, char cReplaceChar, bool bAllowSlashes) { const char* szReplaceChars = bAllowSlashes ? ":*?\"><\n\r\t" : "\\/:*?\"><\n\r\t"; char* p = szFilename; while (*p) { if (strchr(szReplaceChars, *p)) { *p = cReplaceChar; } if (bAllowSlashes && *p == ALT_PATH_SEPARATOR) { *p = PATH_SEPARATOR; } p++; } // remove trailing dots and spaces. they are not allowed in directory names on windows, // but we remove them on posix also, in a case the directory is accessed from windows via samba. for (int iLen = strlen(szFilename); iLen > 0 && (szFilename[iLen - 1] == '.' || szFilename[iLen - 1] == ' '); iLen--) { szFilename[iLen - 1] = '\0'; } } // 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; } void Util::SplitInt64(long long Int64, unsigned long* Hi, unsigned long* Lo) { *Hi = (unsigned long)(Int64 >> 32); *Lo = (unsigned long)(Int64 & 0xFFFFFFFF); } /* Base64 decryption is taken from * Article "BASE 64 Decoding and Encoding Class 2003" by Jan Raddatz * http://www.codeguru.com/cpp/cpp/algorithms/article.php/c5099/ */ const static char BASE64_DEALPHABET [128] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 9 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 10 - 19 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20 - 29 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 30 - 39 0, 0, 0, 62, 0, 0, 0, 63, 52, 53, // 40 - 49 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, // 50 - 59 0, 61, 0, 0, 0, 0, 1, 2, 3, 4, // 60 - 69 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 70 - 79 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 80 - 89 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, // 90 - 99 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // 100 - 109 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, // 110 - 119 49, 50, 51, 0, 0, 0, 0, 0 // 120 - 127 }; unsigned int DecodeByteQuartet(char* szInputBuffer, char* szOutputBuffer) { unsigned int buffer = 0; if (szInputBuffer[3] == '=') { if (szInputBuffer[2] == '=') { buffer = (buffer | BASE64_DEALPHABET [(int)szInputBuffer[0]]) << 6; buffer = (buffer | BASE64_DEALPHABET [(int)szInputBuffer[1]]) << 6; buffer = buffer << 14; szOutputBuffer [0] = (char)(buffer >> 24); return 1; } else { buffer = (buffer | BASE64_DEALPHABET [(int)szInputBuffer[0]]) << 6; buffer = (buffer | BASE64_DEALPHABET [(int)szInputBuffer[1]]) << 6; buffer = (buffer | BASE64_DEALPHABET [(int)szInputBuffer[2]]) << 6; buffer = buffer << 8; szOutputBuffer [0] = (char)(buffer >> 24); szOutputBuffer [1] = (char)(buffer >> 16); return 2; } } else { buffer = (buffer | BASE64_DEALPHABET [(int)szInputBuffer[0]]) << 6; buffer = (buffer | BASE64_DEALPHABET [(int)szInputBuffer[1]]) << 6; buffer = (buffer | BASE64_DEALPHABET [(int)szInputBuffer[2]]) << 6; buffer = (buffer | BASE64_DEALPHABET [(int)szInputBuffer[3]]) << 6; buffer = buffer << 2; szOutputBuffer [0] = (char)(buffer >> 24); szOutputBuffer [1] = (char)(buffer >> 16); szOutputBuffer [2] = (char)(buffer >> 8); return 3; } return 0; } bool Util::MoveFile(const char* szSrcFilename, const char* szDstFilename) { bool bOK = rename(szSrcFilename, szDstFilename) == 0; #ifndef WIN32 if (!bOK && errno == EXDEV) { bOK = CopyFile(szSrcFilename, szDstFilename) && remove(szSrcFilename) == 0; } #endif return bOK; } bool Util::CopyFile(const char* szSrcFilename, const char* szDstFilename) { FILE* infile = fopen(szSrcFilename, FOPEN_RB); if (!infile) { return false; } FILE* outfile = fopen(szDstFilename, FOPEN_WBP); if (!outfile) { fclose(infile); return false; } static const int BUFFER_SIZE = 1024 * 50; char* buffer = (char*)malloc(BUFFER_SIZE); int cnt = BUFFER_SIZE; while (cnt == BUFFER_SIZE) { cnt = (int)fread(buffer, 1, BUFFER_SIZE, infile); fwrite(buffer, 1, cnt, outfile); } fclose(infile); fclose(outfile); free(buffer); return true; } bool Util::FileExists(const char* szFilename) { #ifdef WIN32 // we use a native windows call because c-lib function "stat" fails on windows if file date is invalid WIN32_FIND_DATA findData; HANDLE handle = FindFirstFile(szFilename, &findData); if (handle != INVALID_HANDLE_VALUE) { bool bExists = (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0; FindClose(handle); return bExists; } return false; #else struct stat buffer; bool bExists = !stat(szFilename, &buffer) && S_ISREG(buffer.st_mode); return bExists; #endif } 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) { #ifdef WIN32 // we use a native windows call because c-lib function "stat" fails on windows if file date is invalid WIN32_FIND_DATA findData; HANDLE handle = FindFirstFile(szDirFilename, &findData); if (handle != INVALID_HANDLE_VALUE) { bool bExists = (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; FindClose(handle); return bExists; } return false; #else struct stat buffer; bool bExists = !stat(szDirFilename, &buffer) && S_ISDIR(buffer.st_mode); return bExists; #endif } bool Util::CreateDirectory(const char* szDirFilename) { mkdir(szDirFilename, S_DIRMODE); return DirectoryExists(szDirFilename); } bool Util::RemoveDirectory(const char* szDirFilename) { #ifdef WIN32 return _rmdir(szDirFilename) == 0; #else return remove(szDirFilename) == 0; #endif } bool Util::DeleteDirectoryWithContent(const char* szDirFilename, char* szErrBuf, int iBufSize) { *szErrBuf = '\0'; char szSysErrStr[256]; bool bDel = false; bool bOK = true; DirBrowser dir(szDirFilename); while (const char* filename = dir.Next()) { char szFullFilename[1024]; snprintf(szFullFilename, 1024, "%s%c%s", szDirFilename, PATH_SEPARATOR, filename); szFullFilename[1024-1] = '\0'; if (strcmp(filename, ".") && strcmp(filename, "..")) { if (Util::DirectoryExists(szFullFilename)) { bDel = DeleteDirectoryWithContent(szFullFilename, szSysErrStr, sizeof(szSysErrStr)); } else { bDel = remove(szFullFilename) == 0; } bOK &= bDel; if (!bDel && !*szErrBuf) { snprintf(szErrBuf, iBufSize, "could not delete %s: %s", szFullFilename, GetLastErrorMessage(szSysErrStr, sizeof(szSysErrStr))); } } } bDel = RemoveDirectory(szDirFilename); bOK &= bDel; if (!bDel && !*szErrBuf) { GetLastErrorMessage(szErrBuf, iBufSize); } return bOK; } long long Util::FileSize(const char* szFilename) { #ifdef WIN32 struct _stat32i64 buffer; _stat32i64(szFilename, &buffer); #else struct stat buffer; stat(szFilename, &buffer); #endif return buffer.st_size; } long long Util::FreeDiskSize(const char* szPath) { #ifdef WIN32 ULARGE_INTEGER lFree, lDummy; if (GetDiskFreeSpaceEx(szPath, &lFree, &lDummy, &lDummy)) { return lFree.QuadPart; } #else struct statvfs diskdata; if (!statvfs(szPath, &diskdata)) { return (long long)diskdata.f_frsize * (long long)diskdata.f_bavail; } #endif return -1; } bool Util::RenameBak(const char* szFilename, const char* szBakPart, bool bRemoveOldExtension, char* szNewNameBuf, int iNewNameBufSize) { char szChangedFilename[1024]; if (bRemoveOldExtension) { strncpy(szChangedFilename, szFilename, 1024); szChangedFilename[1024-1] = '\0'; char* szExtension = strrchr(szChangedFilename, '.'); if (szExtension) { *szExtension = '\0'; } } char bakname[1024]; snprintf(bakname, 1024, "%s.%s", bRemoveOldExtension ? szChangedFilename : szFilename, szBakPart); bakname[1024-1] = '\0'; int i = 2; struct stat buffer; while (!stat(bakname, &buffer)) { snprintf(bakname, 1024, "%s.%i.%s", bRemoveOldExtension ? szChangedFilename : szFilename, i++, szBakPart); bakname[1024-1] = '\0'; } if (szNewNameBuf) { strncpy(szNewNameBuf, bakname, iNewNameBufSize); } bool bOK = !rename(szFilename, bakname); return bOK; } #ifndef WIN32 bool Util::ExpandHomePath(const char* szFilename, char* szBuffer, int iBufSize) { if (szFilename && (szFilename[0] == '~') && (szFilename[1] == '/')) { // expand home-dir char* home = getenv("HOME"); if (!home) { struct passwd *pw = getpwuid(getuid()); if (pw) { home = pw->pw_dir; } } if (!home) { return false; } if (home[strlen(home)-1] == '/') { snprintf(szBuffer, iBufSize, "%s%s", home, szFilename + 2); } else { snprintf(szBuffer, iBufSize, "%s/%s", home, szFilename + 2); } szBuffer[iBufSize - 1] = '\0'; } else { strncpy(szBuffer, szFilename ? szFilename : "", iBufSize); szBuffer[iBufSize - 1] = '\0'; } return true; } #endif void Util::ExpandFileName(const char* szFilename, char* szBuffer, int iBufSize) { #ifdef WIN32 _fullpath(szBuffer, szFilename, iBufSize); #else if (szFilename[0] != '\0' && szFilename[0] != '/') { char szCurDir[MAX_PATH + 1]; getcwd(szCurDir, sizeof(szCurDir) - 1); // 1 char reserved for adding backslash int iOffset = 0; if (szFilename[0] == '.' && szFilename[1] == '/') { iOffset += 2; } snprintf(szBuffer, iBufSize, "%s/%s", szCurDir, szFilename + iOffset); } else { strncpy(szBuffer, szFilename, iBufSize); szBuffer[iBufSize - 1] = '\0'; } #endif } void Util::GetExeFileName(const char* argv0, char* szBuffer, int iBufSize) { #ifdef WIN32 GetModuleFileName(NULL, szBuffer, iBufSize); #else // Linux int r = readlink("/proc/self/exe", szBuffer, iBufSize-1); if (r > 0) { szBuffer[r] = '\0'; return; } // FreeBSD r = readlink("/proc/curproc/file", szBuffer, iBufSize-1); if (r > 0) { szBuffer[r] = '\0'; return; } ExpandFileName(argv0, szBuffer, iBufSize); #endif } char* Util::FormatSize(char * szBuffer, int iBufLen, long long lFileSize) { if (lFileSize > 1024 * 1024 * 1000) { snprintf(szBuffer, iBufLen, "%.2f GB", (float)((float)lFileSize / 1024 / 1024 / 1024)); } else if (lFileSize > 1024 * 1000) { snprintf(szBuffer, iBufLen, "%.2f MB", (float)((float)lFileSize / 1024 / 1024)); } else if (lFileSize > 1000) { snprintf(szBuffer, iBufLen, "%.2f KB", (float)((float)lFileSize / 1024)); } else if (lFileSize == 0) { strncpy(szBuffer, "0 MB", iBufLen); } else { snprintf(szBuffer, iBufLen, "%i B", (int)lFileSize); } szBuffer[iBufLen - 1] = '\0'; return szBuffer; } char* Util::FormatSpeed(char* szBuffer, int iBufSize, int iBytesPerSecond) { if (iBytesPerSecond >= 100 * 1024 * 1024) { snprintf(szBuffer, iBufSize, "%i MB/s", iBytesPerSecond / 1024 / 1024); } else if (iBytesPerSecond >= 10 * 1024 * 1024) { snprintf(szBuffer, iBufSize, "%0.1f MB/s", (float)iBytesPerSecond / 1024.0 / 1024.0); } else if (iBytesPerSecond >= 1024 * 1000) { snprintf(szBuffer, iBufSize, "%0.2f MB/s", (float)iBytesPerSecond / 1024.0 / 1024.0); } else { snprintf(szBuffer, iBufSize, "%i KB/s", iBytesPerSecond / 1024); } szBuffer[iBufSize - 1] = '\0'; return szBuffer; } bool Util::SameFilename(const char* szFilename1, const char* szFilename2) { #ifdef WIN32 return strcasecmp(szFilename1, szFilename2) == 0; #else return strcmp(szFilename1, szFilename2) == 0; #endif } bool Util::MatchFileExt(const char* szFilename, const char* szExtensionList, const char* szListSeparator) { int iFilenameLen = strlen(szFilename); Tokenizer tok(szExtensionList, szListSeparator); while (const char* szExt = tok.Next()) { int iExtLen = strlen(szExt); if (iFilenameLen >= iExtLen && !strcasecmp(szExt, szFilename + iFilenameLen - iExtLen)) { return true; } if (strchr(szExt, '*') || strchr(szExt, '?')) { WildMask mask(szExt); if (mask.Match(szFilename)) { return true; } } } return false; } #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::Init() { #ifndef WIN32 if ((strlen(code_revision()) > 0) && strstr(VERSION, "testing")) { snprintf(VersionRevisionBuf, sizeof(VersionRevisionBuf), "%s-r%s", VERSION, code_revision()); } else #endif { snprintf(VersionRevisionBuf, sizeof(VersionRevisionBuf), "%s", VERSION); } // init static vars there GetCurrentTicks(); } bool Util::SplitCommandLine(const char* szCommandLine, char*** argv) { int iArgCount = 0; char szBuf[1024]; char* pszArgList[100]; unsigned int iLen = 0; bool bEscaping = false; bool bSpace = true; for (const char* p = szCommandLine; ; p++) { if (*p) { const char c = *p; if (bEscaping) { if (c == '\'') { if (p[1] == '\'' && iLen < sizeof(szBuf) - 1) { szBuf[iLen++] = c; p++; } else { bEscaping = false; bSpace = true; } } else if (iLen < sizeof(szBuf) - 1) { szBuf[iLen++] = c; } } else { if (c == ' ') { bSpace = true; } else if (c == '\'' && bSpace) { bEscaping = true; bSpace = false; } else if (iLen < sizeof(szBuf) - 1) { szBuf[iLen++] = c; bSpace = false; } } } if ((bSpace || !*p) && iLen > 0 && iArgCount < 100) { //add token szBuf[iLen] = '\0'; if (argv) { pszArgList[iArgCount] = strdup(szBuf); } (iArgCount)++; iLen = 0; } if (!*p) { break; } } if (argv) { pszArgList[iArgCount] = NULL; *argv = (char**)malloc((iArgCount + 1) * sizeof(char*)); memcpy(*argv, pszArgList, sizeof(char*) * (iArgCount + 1)); } return iArgCount > 0; } void Util::TrimRight(char* szStr) { char* szEnd = szStr + strlen(szStr) - 1; while (szEnd >= szStr && (*szEnd == '\n' || *szEnd == '\r' || *szEnd == ' ' || *szEnd == '\t')) { *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 /* From boost */ inline int is_leap(int year) { if(year % 400 == 0) return 1; if(year % 100 == 0) return 0; if(year % 4 == 0) return 1; return 0; } inline int days_from_0(int year) { year--; return 365 * year + (year / 400) - (year/100) + (year / 4); } inline int days_from_1970(int year) { static const int days_from_0_to_1970 = 719162; // days_from_0(1970); return days_from_0(year) - days_from_0_to_1970; } inline int days_from_1jan(int year,int month,int day) { static const int days[2][12] = { { 0,31,59,90,120,151,181,212,243,273,304,334}, { 0,31,60,91,121,152,182,213,244,274,305,335} }; return days[is_leap(year)][month-1] + day - 1; } inline time_t internal_timegm(tm const *t) { int year = t->tm_year + 1900; int month = t->tm_mon; if(month > 11) { year += month/12; month %= 12; } else if(month < 0) { int years_diff = (-month + 11)/12; year -= years_diff; month+=12 * years_diff; } month++; int day = t->tm_mday; int day_of_year = days_from_1jan(year,month,day); int days_since_epoch = days_from_1970(year) + day_of_year; time_t seconds_in_day = 3600 * 24; time_t result = seconds_in_day * days_since_epoch + 3600 * t->tm_hour + 60 * t->tm_min + t->tm_sec; return result; } time_t Util::Timegm(tm const *t) { return internal_timegm(t); } // prevent PC from going to sleep void Util::SetStandByMode(bool bStandBy) { #ifdef WIN32 SetThreadExecutionState((bStandBy ? 0 : ES_SYSTEM_REQUIRED) | ES_CONTINUOUS); #endif } static unsigned long crc32_tab[] = { 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; /* This is a modified version of chksum_crc() from * crc32.c (http://www.koders.com/c/fid699AFE0A656F0022C9D6B9D1743E697B69CE5815.aspx) * (c) 1999,2000 Krzysztof Dabrowski * (c) 1999,2000 ElysiuM deeZine * * chksum_crc() -- to a given block, this one calculates the * crc32-checksum until the length is * reached. the crc32-checksum will be * the result. */ unsigned long Util::Crc32m(unsigned long startCrc, unsigned char *block, unsigned long length) { register unsigned long crc = startCrc; for (unsigned long i = 0; i < length; i++) { crc = ((crc >> 8) & 0x00FFFFFF) ^ crc32_tab[(crc ^ *block++) & 0xFF]; } return crc; } unsigned long Util::Crc32(unsigned char *block, unsigned long length) { return Util::Crc32m(0xFFFFFFFF, block, length) ^ 0xFFFFFFFF; } /* From zlib/crc32.c (http://www.zlib.net/) * Copyright (C) 1995-2006, 2010, 2011, 2012 Mark Adler */ #define GF2_DIM 32 /* dimension of GF(2) vectors (length of CRC) */ unsigned long gf2_matrix_times(unsigned long *mat, unsigned long vec) { unsigned long sum; sum = 0; while (vec) { if (vec & 1) sum ^= *mat; vec >>= 1; mat++; } return sum; } void gf2_matrix_square(unsigned long *square, unsigned long *mat) { int n; for (n = 0; n < GF2_DIM; n++) square[n] = gf2_matrix_times(mat, mat[n]); } unsigned long Util::Crc32Combine(unsigned long crc1, unsigned long crc2, unsigned long len2) { int n; unsigned long row; unsigned long even[GF2_DIM]; /* even-power-of-two zeros operator */ unsigned long odd[GF2_DIM]; /* odd-power-of-two zeros operator */ /* degenerate case (also disallow negative lengths) */ if (len2 <= 0) return crc1; /* put operator for one zero bit in odd */ odd[0] = 0xedb88320UL; /* CRC-32 polynomial */ row = 1; for (n = 1; n < GF2_DIM; n++) { odd[n] = row; row <<= 1; } /* put operator for two zero bits in even */ gf2_matrix_square(even, odd); /* put operator for four zero bits in odd */ gf2_matrix_square(odd, even); /* apply len2 zeros to crc1 (first square will put the operator for one zero byte, eight zero bits, in even) */ do { /* apply zeros operator for this bit of len2 */ gf2_matrix_square(even, odd); if (len2 & 1) crc1 = gf2_matrix_times(even, crc1); len2 >>= 1; /* if no more bits set, then done */ if (len2 == 0) break; /* another iteration of the loop with odd and even swapped */ gf2_matrix_square(odd, even); if (len2 & 1) crc1 = gf2_matrix_times(odd, crc1); len2 >>= 1; /* if no more bits set, then done */ } while (len2 != 0); /* return combined crc */ crc1 ^= crc2; return crc1; } int Util::NumberOfCpuCores() { #ifdef WIN32 SYSTEM_INFO sysinfo; GetSystemInfo(&sysinfo); return sysinfo.dwNumberOfProcessors; #elif HAVE_SC_NPROCESSORS_ONLN return sysconf(_SC_NPROCESSORS_ONLN); #endif return -1; } bool Util::FlushFileBuffers(int iFileDescriptor, char* szErrBuf, int iBufSize) { #ifdef WIN32 BOOL bOK = ::FlushFileBuffers((HANDLE)_get_osfhandle(iFileDescriptor)); if (!bOK) { FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), szErrBuf, iBufSize, NULL); } return bOK; #else #ifdef HAVE_FULLFSYNC int ret = fcntl(iFileDescriptor, F_FULLFSYNC) == -1 ? 1 : 0; #elif HAVE_FDATASYNC int ret = fdatasync(iFileDescriptor); #else int ret = fsync(iFileDescriptor); #endif if (ret != 0) { GetLastErrorMessage(szErrBuf, iBufSize); } return ret == 0; #endif } bool Util::FlushDirBuffers(const char* szFilename, char* szErrBuf, int iBufSize) { char szParentPath[1024]; strncpy(szParentPath, szFilename, 1024); szParentPath[1024-1] = '\0'; const char* szFileMode = FOPEN_RBP; #ifndef WIN32 char* p = (char*)strrchr(szParentPath, PATH_SEPARATOR); if (p) { *p = '\0'; } szFileMode = FOPEN_RB; #endif FILE* pFile = fopen(szParentPath, szFileMode); if (!pFile) { GetLastErrorMessage(szErrBuf, iBufSize); return false; } bool bOK = FlushFileBuffers(fileno(pFile), szErrBuf, iBufSize); fclose(pFile); return bOK; } long long Util::GetCurrentTicks() { #ifdef WIN32 static long long hz=0, hzo=0; if (!hz) { QueryPerformanceFrequency((LARGE_INTEGER*)&hz); QueryPerformanceCounter((LARGE_INTEGER*)&hzo); } long long t; QueryPerformanceCounter((LARGE_INTEGER*)&t); return ((t-hzo)*1000000)/hz; #else timeval t; gettimeofday(&t, NULL); return (long long)(t.tv_sec) * 1000000ll + (long long)(t.tv_usec); #endif } unsigned int WebUtil::DecodeBase64(char* szInputBuffer, int iInputBufferLength, char* szOutputBuffer) { unsigned int InputBufferIndex = 0; unsigned int OutputBufferIndex = 0; unsigned int InputBufferLength = iInputBufferLength > 0 ? iInputBufferLength : strlen(szInputBuffer); char ByteQuartet [4]; int i = 0; while (InputBufferIndex < InputBufferLength) { // Ignore all characters except the ones in BASE64_ALPHABET if ((szInputBuffer [InputBufferIndex] >= 48 && szInputBuffer [InputBufferIndex] <= 57) || (szInputBuffer [InputBufferIndex] >= 65 && szInputBuffer [InputBufferIndex] <= 90) || (szInputBuffer [InputBufferIndex] >= 97 && szInputBuffer [InputBufferIndex] <= 122) || szInputBuffer [InputBufferIndex] == '+' || szInputBuffer [InputBufferIndex] == '/' || szInputBuffer [InputBufferIndex] == '=') { ByteQuartet [i] = szInputBuffer [InputBufferIndex]; i++; } InputBufferIndex++; if (i == 4) { OutputBufferIndex += DecodeByteQuartet(ByteQuartet, szOutputBuffer + OutputBufferIndex); i = 0; } } // OutputBufferIndex gives us the next position of the next decoded character // inside our output buffer and thus represents the number of decoded characters // in our buffer. return OutputBufferIndex; } /* END - Base64 */ char* WebUtil::XmlEncode(const char* raw) { // calculate the required outputstring-size based on number of xml-entities and their sizes int iReqSize = strlen(raw); for (const char* p = raw; *p; p++) { unsigned char ch = *p; switch (ch) { case '>': case '<': iReqSize += 4; break; case '&': iReqSize += 5; break; case '\'': case '\"': iReqSize += 6; break; default: if (ch < 0x20 || ch >= 0x80) { iReqSize += 10; break; } } } char* result = (char*)malloc(iReqSize + 1); // copy string char* output = result; for (const char* p = raw; ; p++) { unsigned char ch = *p; switch (ch) { case '\0': goto BreakLoop; case '<': strcpy(output, "<"); output += 4; break; case '>': strcpy(output, ">"); output += 4; break; case '&': strcpy(output, "&"); output += 5; break; case '\'': strcpy(output, "'"); output += 6; break; case '\"': strcpy(output, """); output += 6; break; default: if (ch < 0x20 || ch > 0x80) { 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 { *output++ = ch; } break; } } BreakLoop: *output = '\0'; return result; } void WebUtil::XmlDecode(char* raw) { char* output = raw; for (char* p = raw;;) { switch (*p) { case '\0': goto BreakLoop; case '&': { p++; if (!strncmp(p, "lt;", 3)) { *output++ = '<'; p += 3; } else if (!strncmp(p, "gt;", 3)) { *output++ = '>'; p += 3; } else if (!strncmp(p, "amp;", 4)) { *output++ = '&'; p += 4; } else if (!strncmp(p, "apos;", 5)) { *output++ = '\''; p += 5; } else if (!strncmp(p, "quot;", 5)) { *output++ = '\"'; p += 5; } else if (*p == '#') { int code = atoi((p++)+1); while (strchr("0123456789;", *p)) p++; *output++ = (char)code; } else { // unknown entity, keep as is *output++ = *(p-1); *output++ = *p++; } break; } default: *output++ = *p++; break; } } BreakLoop: *output = '\0'; } const char* WebUtil::XmlFindTag(const char* szXml, const char* szTag, int* pValueLength) { char szOpenTag[100]; snprintf(szOpenTag, 100, "<%s>", szTag); szOpenTag[100-1] = '\0'; char szCloseTag[100]; snprintf(szCloseTag, 100, "", szTag); szCloseTag[100-1] = '\0'; char szOpenCloseTag[100]; snprintf(szOpenCloseTag, 100, "<%s/>", szTag); szOpenCloseTag[100-1] = '\0'; const char* pstart = strstr(szXml, szOpenTag); const char* pstartend = strstr(szXml, szOpenCloseTag); if (!pstart && !pstartend) return NULL; if (pstartend && (!pstart || pstartend < pstart)) { *pValueLength = 0; return pstartend; } const char* pend = strstr(pstart, szCloseTag); if (!pend) return NULL; int iTagLen = strlen(szOpenTag); *pValueLength = (int)(pend - pstart - iTagLen); return pstart + iTagLen; } bool WebUtil::XmlParseTagValue(const char* szXml, const char* szTag, char* szValueBuf, int iValueBufSize, const char** pTagEnd) { int iValueLen = 0; const char* szValue = XmlFindTag(szXml, szTag, &iValueLen); if (!szValue) { return false; } int iLen = iValueLen < iValueBufSize ? iValueLen : iValueBufSize - 1; strncpy(szValueBuf, szValue, iLen); szValueBuf[iLen] = '\0'; if (pTagEnd) { *pTagEnd = szValue + iValueLen; } return true; } void WebUtil::XmlStripTags(char* szXml) { while (char *start = strchr(szXml, '<')) { char *end = strchr(start, '>'); if (!end) { break; } memset(start, ' ', end - start + 1); szXml = end + 1; } } void WebUtil::XmlRemoveEntities(char* raw) { char* output = raw; for (char* p = raw;;) { switch (*p) { case '\0': goto BreakLoop; case '&': { char* p2 = p+1; while (isalpha(*p2) || strchr("0123456789#", *p2)) p2++; if (*p2 == ';') { *output++ = ' '; p = p2+1; } else { *output++ = *p++; } break; } default: *output++ = *p++; break; } } BreakLoop: *output = '\0'; } char* WebUtil::JsonEncode(const char* raw) { // calculate the required outputstring-size based on number of escape-entities and their sizes int iReqSize = strlen(raw); for (const char* p = raw; *p; p++) { unsigned char ch = *p; switch (ch) { case '\"': case '\\': case '/': case '\b': case '\f': case '\n': case '\r': case '\t': iReqSize++; break; default: if (ch < 0x20 || ch >= 0x80) { iReqSize += 6; break; } } } char* result = (char*)malloc(iReqSize + 1); // copy string char* output = result; for (const char* p = raw; ; p++) { unsigned char ch = *p; switch (ch) { case '\0': goto BreakLoop; case '"': strcpy(output, "\\\""); output += 2; break; case '\\': strcpy(output, "\\\\"); output += 2; break; case '/': strcpy(output, "\\/"); output += 2; break; case '\b': strcpy(output, "\\b"); output += 2; break; case '\f': strcpy(output, "\\f"); output += 2; break; case '\n': strcpy(output, "\\n"); output += 2; break; case '\r': strcpy(output, "\\r"); output += 2; break; case '\t': strcpy(output, "\\t"); output += 2; break; default: if (ch < 0x20 || ch > 0x80) { 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 { *output++ = ch; } break; } } BreakLoop: *output = '\0'; return result; } void WebUtil::JsonDecode(char* raw) { char* output = raw; for (char* p = raw;;) { switch (*p) { case '\0': goto BreakLoop; case '\\': { p++; switch (*p) { case '"': *output++ = '"'; break; case '\\': *output++ = '\\'; break; case '/': *output++ = '/'; break; case 'b': *output++ = '\b'; break; case 'f': *output++ = '\f'; break; case 'n': *output++ = '\n'; break; case 'r': *output++ = '\r'; break; case 't': *output++ = '\t'; break; case 'u': *output++ = (char)strtol(p + 1, NULL, 16); p += 4; break; default: // unknown escape-sequence, should never occur *output++ = *p; break; } p++; break; } default: *output++ = *p++; break; } } BreakLoop: *output = '\0'; } const char* WebUtil::JsonFindField(const char* szJsonText, const char* szFieldName, int* pValueLength) { char szOpenTag[100]; snprintf(szOpenTag, 100, "\"%s\"", szFieldName); szOpenTag[100-1] = '\0'; const char* pstart = strstr(szJsonText, szOpenTag); if (!pstart) return NULL; pstart += strlen(szOpenTag); return JsonNextValue(pstart, pValueLength); } const char* WebUtil::JsonNextValue(const char* szJsonText, int* pValueLength) { const char* pstart = szJsonText; while (*pstart && strchr(" ,[{:\r\n\t\f", *pstart)) pstart++; if (!*pstart) return NULL; const char* pend = pstart; char ch = *pend; bool bStr = ch == '"'; if (bStr) { ch = *++pend; } while (ch) { if (ch == '\\') { if (!*++pend || !*++pend) return NULL; ch = *pend; } if (bStr && ch == '"') { pend++; break; } else if (!bStr && strchr(" ,]}\r\n\t\f", ch)) { break; } ch = *++pend; } *pValueLength = (int)(pend - pstart); return pstart; } void WebUtil::HttpUnquote(char* raw) { if (*raw != '"') { return; } char *output = raw; for (char *p = raw+1;;) { switch (*p) { case '\0': case '"': goto BreakLoop; case '\\': p++; *output++ = *p; break; default: *output++ = *p++; break; } } BreakLoop: *output = '\0'; } void WebUtil::URLDecode(char* raw) { char* output = raw; for (char* p = raw;;) { switch (*p) { case '\0': goto BreakLoop; case '%': { p++; unsigned char c1 = *p++; unsigned char c2 = *p++; c1 = '0' <= c1 && c1 <= '9' ? c1 - '0' : 'A' <= c1 && c1 <= 'F' ? c1 - 'A' + 10 : 'a' <= c1 && c1 <= 'f' ? c1 - 'a' + 10 : 0; c2 = '0' <= c2 && c2 <= '9' ? c2 - '0' : 'A' <= c2 && c2 <= 'F' ? c2 - 'A' + 10 : 'a' <= c2 && c2 <= 'f' ? c2 - 'a' + 10 : 0; unsigned char ch = (c1 << 4) + c2; *output++ = (char)ch; break; } default: *output++ = *p++; break; } } BreakLoop: *output = '\0'; } char* WebUtil::URLEncode(const char* raw) { // calculate the required outputstring-size based on number of spaces int iReqSize = strlen(raw); for (const char* p = raw; *p; p++) { if (*p == ' ') { iReqSize += 3; // length of "%20" } } char* result = (char*)malloc(iReqSize + 1); // copy string char* output = result; for (const char* p = raw; ; p++) { unsigned char ch = *p; switch (ch) { case '\0': goto BreakLoop; case ' ': strcpy(output, "%20"); output += 3; break; default: *output++ = ch; } } BreakLoop: *output = '\0'; return result; } #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 = Util::Timegm(&rawtime); enctime -= (zonehours * 60 + (zonehours > 0 ? zoneminutes : -zoneminutes)) * 60; return enctime; } URL::URL(const char* szAddress) { m_szAddress = NULL; m_szProtocol = NULL; m_szUser = NULL; m_szPassword = NULL; m_szHost = NULL; m_szResource = NULL; m_iPort = 0; m_bTLS = false; m_bValid = false; if (szAddress) { m_szAddress = strdup(szAddress); ParseURL(); } } URL::~URL() { free(m_szAddress); free(m_szProtocol); free(m_szUser); free(m_szPassword); free(m_szHost); free(m_szResource); } void URL::ParseURL() { // Examples: // http://user:password@host:port/path/to/resource?param // http://user@host:port/path/to/resource?param // http://host:port/path/to/resource?param // http://host/path/to/resource?param // http://host char* protEnd = strstr(m_szAddress, "://"); if (!protEnd) { // Bad URL return; } m_szProtocol = (char*)malloc(protEnd - m_szAddress + 1); strncpy(m_szProtocol, m_szAddress, protEnd - m_szAddress); m_szProtocol[protEnd - m_szAddress] = 0; char* hostStart = protEnd + 3; char* slash = strchr(hostStart, '/'); char* hostEnd = NULL; char* amp = strchr(hostStart, '@'); if (amp && (!slash || amp < slash)) { // parse user/password char* userend = amp - 1; char* pass = strchr(hostStart, ':'); if (pass && pass < amp) { int iLen = (int)(amp - pass - 1); if (iLen > 0) { m_szPassword = (char*)malloc(iLen + 1); strncpy(m_szPassword, pass + 1, iLen); m_szPassword[iLen] = 0; } userend = pass - 1; } int iLen = (int)(userend - hostStart + 1); if (iLen > 0) { m_szUser = (char*)malloc(iLen + 1); strncpy(m_szUser, hostStart, iLen); m_szUser[iLen] = 0; } hostStart = amp + 1; } if (slash) { char* resEnd = m_szAddress + strlen(m_szAddress); m_szResource = (char*)malloc(resEnd - slash + 1 + 1); strncpy(m_szResource, slash, resEnd - slash + 1); m_szResource[resEnd - slash + 1] = 0; hostEnd = slash - 1; } else { m_szResource = strdup("/"); hostEnd = m_szAddress + strlen(m_szAddress); } char* colon = strchr(hostStart, ':'); if (colon && colon < hostEnd) { hostEnd = colon - 1; m_iPort = atoi(colon + 1); } m_szHost = (char*)malloc(hostEnd - hostStart + 1 + 1); strncpy(m_szHost, hostStart, hostEnd - hostStart + 1); m_szHost[hostEnd - hostStart + 1] = 0; m_bValid = true; } 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 | (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 } 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, 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) { z_stream zstr; memset(&zstr, 0, sizeof(zstr)); return (unsigned int)deflateBound(&zstr, iInputBufferLength); } unsigned int ZLib::GZip(const void* szInputBuffer, int iInputBufferLength, void* szOutputBuffer, int iOutputBufferLength) { z_stream zstr; zstr.zalloc = Z_NULL; zstr.zfree = Z_NULL; zstr.opaque = Z_NULL; zstr.next_in = (Bytef*)szInputBuffer; zstr.avail_in = iInputBufferLength; zstr.next_out = (Bytef*)szOutputBuffer; zstr.avail_out = iOutputBufferLength; /* add 16 to MAX_WBITS to enforce gzip format */ if (Z_OK != deflateInit2(&zstr, Z_DEFAULT_COMPRESSION, Z_DEFLATED, MAX_WBITS + 16, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY)) { return 0; } unsigned int total_out = 0; if (deflate(&zstr, Z_FINISH) == Z_STREAM_END) { total_out = (unsigned int)zstr.total_out; } deflateEnd(&zstr); return total_out; } GUnzipStream::GUnzipStream(int BufferSize) { m_iBufferSize = BufferSize; m_pZStream = malloc(sizeof(z_stream)); m_pOutputBuffer = malloc(BufferSize); memset(m_pZStream, 0, sizeof(z_stream)); /* add 16 to MAX_WBITS to enforce gzip format */ int ret = inflateInit2(((z_stream*)m_pZStream), MAX_WBITS + 16); if (ret != Z_OK) { free(m_pZStream); m_pZStream = NULL; } } GUnzipStream::~GUnzipStream() { if (m_pZStream) { inflateEnd(((z_stream*)m_pZStream)); free(m_pZStream); } free(m_pOutputBuffer); } void GUnzipStream::Write(const void *pInputBuffer, int iInputBufferLength) { ((z_stream*)m_pZStream)->next_in = (Bytef*)pInputBuffer; ((z_stream*)m_pZStream)->avail_in = iInputBufferLength; } GUnzipStream::EStatus GUnzipStream::Read(const void **pOutputBuffer, int *iOutputBufferLength) { ((z_stream*)m_pZStream)->next_out = (Bytef*)m_pOutputBuffer; ((z_stream*)m_pZStream)->avail_out = m_iBufferSize; *iOutputBufferLength = 0; if (!m_pZStream) { return zlError; } int ret = inflate(((z_stream*)m_pZStream), Z_NO_FLUSH); switch (ret) { case Z_STREAM_END: case Z_OK: *iOutputBufferLength = m_iBufferSize - ((z_stream*)m_pZStream)->avail_out; *pOutputBuffer = m_pOutputBuffer; return ret == Z_STREAM_END ? zlFinished : zlOK; case Z_BUF_ERROR: return zlOK; } return zlError; } #endif Tokenizer::Tokenizer(const char* szDataString, const char* szSeparators) { // an optimization to avoid memory allocation for short data string (shorten than 1024 chars) int iLen = strlen(szDataString); if (iLen < sizeof(m_szDefaultBuf) - 1) { strncpy(m_szDefaultBuf, szDataString, sizeof(m_szDefaultBuf)); m_szDefaultBuf[1024- 1] = '\0'; m_szDataString = m_szDefaultBuf; m_bInplaceBuf = true; } else { m_szDataString = strdup(szDataString); m_bInplaceBuf = false; } m_szSeparators = szSeparators; m_szSavePtr = NULL; m_bWorking = false; } Tokenizer::Tokenizer(char* szDataString, const char* szSeparators, bool bInplaceBuf) { m_szDataString = bInplaceBuf ? szDataString : strdup(szDataString); m_szSeparators = szSeparators; m_szSavePtr = NULL; m_bWorking = false; m_bInplaceBuf = bInplaceBuf; } Tokenizer::~Tokenizer() { if (!m_bInplaceBuf) { free(m_szDataString); } } char* Tokenizer::Next() { char* szToken = NULL; while (!szToken || !*szToken) { szToken = strtok_r(m_bWorking ? NULL : m_szDataString, m_szSeparators, &m_szSavePtr); m_bWorking = true; if (!szToken) { return NULL; } szToken = Util::Trim(szToken); } return szToken; } nzbget-16.4/daemon/util/Service.cpp0000644000175000017500000000430012630544544017101 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2015 Andrey Prygunkov * * 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 #include #include #ifdef WIN32 #include #else #include #endif #include "nzbget.h" #include "Service.h" #include "DownloadInfo.h" #include "Options.h" #include "Log.h" #include "Util.h" Service::Service() { m_iLastTick = 0; g_pServiceCoordinator->RegisterService(this); } ServiceCoordinator::ServiceCoordinator() { debug("Creating ServiceCoordinator"); } ServiceCoordinator::~ServiceCoordinator() { debug("Destroying ServiceCoordinator"); } void ServiceCoordinator::Run() { debug("Entering ServiceCoordinator-loop"); const int iStepMSec = 100; int iCurTick = 0; while (!IsStopped()) { for (ServiceList::iterator it = m_Services.begin(); it != m_Services.end(); it++) { Service* pService = *it; if (iCurTick >= pService->m_iLastTick + pService->ServiceInterval() || // interval expired iCurTick == 0 || // first start iCurTick + 10000 < pService->m_iLastTick) // int overflow { pService->ServiceWork(); pService->m_iLastTick = iCurTick; } } iCurTick += iStepMSec; usleep(iStepMSec * 1000); } debug("Exiting ServiceCoordinator-loop"); } void ServiceCoordinator::RegisterService(Service* pService) { m_Services.push_back(pService); } nzbget-16.4/daemon/util/Log.cpp0000644000175000017500000002735612630544544016242 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2015 Andrey Prygunkov * * 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" #else #include #include #endif #include #include #include #include #include #include "nzbget.h" #include "Options.h" #include "Log.h" #include "Util.h" Log* g_pLog = NULL; void Log::Init() { g_pLog = new Log(); } void Log::Final() { delete g_pLog; g_pLog = NULL; } Log::Log() { m_Messages.clear(); m_iIDGen = 0; m_bOptInit = false; m_szLogFilename = NULL; m_tLastWritten = 0; #ifdef DEBUG m_bExtraDebug = Util::FileExists("extradebug"); #endif } Log::~Log() { Clear(); free(m_szLogFilename); } void Log::LogDebugInfo() { info("--------------------------------------------"); info("Dumping debug info to log"); info("--------------------------------------------"); m_mutexDebug.Lock(); for (Debuggables::iterator it = m_Debuggables.begin(); it != m_Debuggables.end(); it++) { Debuggable* pDebuggable = *it; pDebuggable->LogDebugInfo(); } m_mutexDebug.Unlock(); info("--------------------------------------------"); } void Log::Filelog(const char* msg, ...) { if (!m_szLogFilename) { return; } char tmp2[1024]; va_list ap; va_start(ap, msg); vsnprintf(tmp2, 1024, msg, ap); tmp2[1024-1] = '\0'; va_end(ap); time_t rawtime = time(NULL) + g_pOptions->GetTimeCorrection(); char szTime[50]; #ifdef HAVE_CTIME_R_3 ctime_r(&rawtime, szTime, 50); #else ctime_r(&rawtime, szTime); #endif szTime[50-1] = '\0'; szTime[strlen(szTime) - 1] = '\0'; // trim LF if ((int)rawtime/86400 != (int)m_tLastWritten/86400 && g_pOptions->GetWriteLog() == Options::wlRotate) { RotateLog(); } m_tLastWritten = rawtime; FILE* file = fopen(m_szLogFilename, FOPEN_ABP); if (file) { #ifdef WIN32 unsigned long iProcessId = GetCurrentProcessId(); unsigned long iThreadId = GetCurrentThreadId(); #else unsigned long iProcessId = (unsigned long)getpid(); unsigned long iThreadId = (unsigned long)pthread_self(); #endif #ifdef DEBUG fprintf(file, "%s\t%lu\t%lu\t%s%s", szTime, iProcessId, iThreadId, tmp2, LINE_ENDING); #else fprintf(file, "%s\t%s%s", szTime, tmp2, LINE_ENDING); #endif fclose(file); } else { perror(m_szLogFilename); } } #ifdef DEBUG #undef debug #ifdef HAVE_VARIADIC_MACROS void debug(const char* szFilename, const char* szFuncname, int iLineNr, const char* msg, ...) #else void debug(const char* msg, ...) #endif { char tmp1[1024]; va_list ap; va_start(ap, msg); vsnprintf(tmp1, 1024, msg, ap); tmp1[1024-1] = '\0'; va_end(ap); char tmp2[1024]; #ifdef HAVE_VARIADIC_MACROS if (szFuncname) { snprintf(tmp2, 1024, "%s (%s:%i:%s)", tmp1, Util::BaseFileName(szFilename), iLineNr, szFuncname); } else { snprintf(tmp2, 1024, "%s (%s:%i)", tmp1, Util::BaseFileName(szFilename), iLineNr); } #else snprintf(tmp2, 1024, "%s", tmp1); #endif tmp2[1024-1] = '\0'; g_pLog->m_mutexLog.Lock(); if (!g_pOptions && g_pLog->m_bExtraDebug) { printf("%s\n", tmp2); } Options::EMessageTarget eMessageTarget = g_pOptions ? g_pOptions->GetDebugTarget() : Options::mtScreen; if (eMessageTarget == Options::mtScreen || eMessageTarget == Options::mtBoth) { g_pLog->AddMessage(Message::mkDebug, tmp2); } if (eMessageTarget == Options::mtLog || eMessageTarget == Options::mtBoth) { g_pLog->Filelog("DEBUG\t%s", tmp2); } g_pLog->m_mutexLog.Unlock(); } #endif void error(const char* msg, ...) { char tmp2[1024]; va_list ap; va_start(ap, msg); vsnprintf(tmp2, 1024, msg, ap); tmp2[1024-1] = '\0'; va_end(ap); g_pLog->m_mutexLog.Lock(); Options::EMessageTarget eMessageTarget = g_pOptions ? g_pOptions->GetErrorTarget() : Options::mtBoth; if (eMessageTarget == Options::mtScreen || eMessageTarget == Options::mtBoth) { g_pLog->AddMessage(Message::mkError, tmp2); } if (eMessageTarget == Options::mtLog || eMessageTarget == Options::mtBoth) { g_pLog->Filelog("ERROR\t%s", tmp2); } g_pLog->m_mutexLog.Unlock(); } void warn(const char* msg, ...) { char tmp2[1024]; va_list ap; va_start(ap, msg); vsnprintf(tmp2, 1024, msg, ap); tmp2[1024-1] = '\0'; va_end(ap); g_pLog->m_mutexLog.Lock(); Options::EMessageTarget eMessageTarget = g_pOptions ? g_pOptions->GetWarningTarget() : Options::mtScreen; if (eMessageTarget == Options::mtScreen || eMessageTarget == Options::mtBoth) { g_pLog->AddMessage(Message::mkWarning, tmp2); } if (eMessageTarget == Options::mtLog || eMessageTarget == Options::mtBoth) { g_pLog->Filelog("WARNING\t%s", tmp2); } g_pLog->m_mutexLog.Unlock(); } void info(const char* msg, ...) { char tmp2[1024]; va_list ap; va_start(ap, msg); vsnprintf(tmp2, 1024, msg, ap); tmp2[1024-1] = '\0'; va_end(ap); g_pLog->m_mutexLog.Lock(); Options::EMessageTarget eMessageTarget = g_pOptions ? g_pOptions->GetInfoTarget() : Options::mtScreen; if (eMessageTarget == Options::mtScreen || eMessageTarget == Options::mtBoth) { g_pLog->AddMessage(Message::mkInfo, tmp2); } if (eMessageTarget == Options::mtLog || eMessageTarget == Options::mtBoth) { g_pLog->Filelog("INFO\t%s", tmp2); } g_pLog->m_mutexLog.Unlock(); } void detail(const char* msg, ...) { char tmp2[1024]; va_list ap; va_start(ap, msg); vsnprintf(tmp2, 1024, msg, ap); tmp2[1024-1] = '\0'; va_end(ap); g_pLog->m_mutexLog.Lock(); Options::EMessageTarget eMessageTarget = g_pOptions ? g_pOptions->GetDetailTarget() : Options::mtScreen; if (eMessageTarget == Options::mtScreen || eMessageTarget == Options::mtBoth) { g_pLog->AddMessage(Message::mkDetail, tmp2); } if (eMessageTarget == Options::mtLog || eMessageTarget == Options::mtBoth) { g_pLog->Filelog("DETAIL\t%s", tmp2); } g_pLog->m_mutexLog.Unlock(); } //************************************************************ // Message Message::Message(unsigned int iID, EKind eKind, time_t tTime, const char* szText) { m_iID = iID; m_eKind = eKind; m_tTime = tTime; if (szText) { m_szText = strdup(szText); } else { m_szText = NULL; } } Message::~ Message() { free(m_szText); } MessageList::~MessageList() { Clear(); } void MessageList::Clear() { for (iterator it = begin(); it != end(); it++) { delete *it; } clear(); } void Log::Clear() { m_mutexLog.Lock(); m_Messages.Clear(); m_mutexLog.Unlock(); } void Log::AddMessage(Message::EKind eKind, const char * szText) { Message* pMessage = new Message(++m_iIDGen, eKind, time(NULL), szText); m_Messages.push_back(pMessage); if (m_bOptInit && g_pOptions) { while (m_Messages.size() > (unsigned int)g_pOptions->GetLogBufferSize()) { Message* pMessage = m_Messages.front(); delete pMessage; m_Messages.pop_front(); } } } MessageList* Log::LockMessages() { m_mutexLog.Lock(); return &m_Messages; } void Log::UnlockMessages() { m_mutexLog.Unlock(); } void Log::ResetLog() { remove(g_pOptions->GetLogFile()); } void Log::RotateLog() { char szDirectory[1024]; strncpy(szDirectory, g_pOptions->GetLogFile(), 1024); szDirectory[1024-1] = '\0'; // split the full filename into path, basename and extension char* szBaseName = Util::BaseFileName(szDirectory); if (szBaseName > szDirectory) { szBaseName[-1] = '\0'; } char szBaseExt[250]; char* szExt = strrchr(szBaseName, '.'); if (szExt && szExt > szBaseName) { strncpy(szBaseExt, szExt, 250); szBaseExt[250-1] = '\0'; szExt[0] = '\0'; } else { szBaseExt[0] = '\0'; } char szFileMask[1024]; snprintf(szFileMask, 1024, "%s-####-##-##%s", szBaseName, szBaseExt); szFileMask[1024-1] = '\0'; time_t tCurTime = time(NULL) + g_pOptions->GetTimeCorrection(); int iCurDay = (int)tCurTime / 86400; char szFullFilename[1024]; WildMask mask(szFileMask, true); DirBrowser dir(szDirectory); while (const char* filename = dir.Next()) { if (mask.Match(filename)) { snprintf(szFullFilename, 1024, "%s%c%s", szDirectory, PATH_SEPARATOR, filename); szFullFilename[1024-1] = '\0'; struct tm tm; memset(&tm, 0, sizeof(tm)); tm.tm_year = atoi(filename + mask.GetMatchStart(0)) - 1900; tm.tm_mon = atoi(filename + mask.GetMatchStart(1)) - 1; tm.tm_mday = atoi(filename + mask.GetMatchStart(2)); time_t tFileTime = Util::Timegm(&tm); int iFileDay = (int)tFileTime / 86400; if (iFileDay <= iCurDay - g_pOptions->GetRotateLog()) { char szMessage[1024]; snprintf(szMessage, 1024, "Deleting old log-file %s\n", filename); szMessage[1024-1] = '\0'; g_pLog->AddMessage(Message::mkInfo, szMessage); remove(szFullFilename); } } } struct tm tm; gmtime_r(&tCurTime, &tm); snprintf(szFullFilename, 1024, "%s%c%s-%i-%.2i-%.2i%s", szDirectory, PATH_SEPARATOR, szBaseName, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, szBaseExt); szFullFilename[1024-1] = '\0'; free(m_szLogFilename); m_szLogFilename = strdup(szFullFilename); #ifdef WIN32 WebUtil::Utf8ToAnsi(m_szLogFilename, strlen(m_szLogFilename) + 1); #endif } /* * During intializing stage (when options were not read yet) all messages * are saved in screen log, even if they shouldn't (according to options). * Method "InitOptions()" check all messages added to screen log during * intializing stage and does three things: * 1) save the messages to log-file (if they should according to options); * 2) delete messages from screen log (if they should not be saved in screen log). * 3) renumerate IDs */ void Log::InitOptions() { const char* szMessageType[] = { "INFO", "WARNING", "ERROR", "DEBUG", "DETAIL"}; if (g_pOptions->GetWriteLog() != Options::wlNone && g_pOptions->GetLogFile()) { m_szLogFilename = strdup(g_pOptions->GetLogFile()); #ifdef WIN32 WebUtil::Utf8ToAnsi(m_szLogFilename, strlen(m_szLogFilename) + 1); #endif if (g_pOptions->GetServerMode() && g_pOptions->GetWriteLog() == Options::wlReset) { g_pLog->ResetLog(); } } m_iIDGen = 0; for (unsigned int i = 0; i < m_Messages.size(); ) { Message* pMessage = m_Messages.at(i); Options::EMessageTarget eTarget = Options::mtNone; switch (pMessage->GetKind()) { case Message::mkDebug: eTarget = g_pOptions->GetDebugTarget(); break; case Message::mkDetail: eTarget = g_pOptions->GetDetailTarget(); break; case Message::mkInfo: eTarget = g_pOptions->GetInfoTarget(); break; case Message::mkWarning: eTarget = g_pOptions->GetWarningTarget(); break; case Message::mkError: eTarget = g_pOptions->GetErrorTarget(); break; } if (eTarget == Options::mtLog || eTarget == Options::mtBoth) { Filelog("%s\t%s", szMessageType[pMessage->GetKind()], pMessage->GetText()); } if (eTarget == Options::mtLog || eTarget == Options::mtNone) { delete pMessage; m_Messages.erase(m_Messages.begin() + i); } else { pMessage->m_iID = ++m_iIDGen; i++; } } m_bOptInit = true; } void Log::RegisterDebuggable(Debuggable* pDebuggable) { m_mutexDebug.Lock(); m_Debuggables.push_back(pDebuggable); m_mutexDebug.Unlock(); } void Log::UnregisterDebuggable(Debuggable* pDebuggable) { m_mutexDebug.Lock(); m_Debuggables.remove(pDebuggable); m_mutexDebug.Unlock(); } nzbget-16.4/daemon/util/Thread.h0000644000175000017500000000377712630544544016376 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 THREAD_H #define THREAD_H class Mutex { private: void* m_pMutexObj; public: Mutex(); ~Mutex(); void Lock(); void Unlock(); }; class Thread { private: static Mutex* m_pMutexThread; static int m_iThreadCount; void* m_pThreadObj; bool m_bRunning; bool m_bStopped; bool m_bAutoDestroy; #ifdef WIN32 static void __cdecl thread_handler(void* pObject); #else static void *thread_handler(void* pObject); #endif public: Thread(); virtual ~Thread(); static void Init(); static void Final(); virtual void Start(); virtual void Stop(); virtual void Resume(); bool Kill(); bool IsStopped() { return m_bStopped; }; bool IsRunning() const { return m_bRunning; } void SetRunning(bool bOnOff) { m_bRunning = bOnOff; } bool GetAutoDestroy() { return m_bAutoDestroy; } void SetAutoDestroy(bool bAutoDestroy) { m_bAutoDestroy = bAutoDestroy; } static int GetThreadCount(); protected: virtual void Run() {}; // Virtual function - override in derivatives }; #endif nzbget-16.4/daemon/nntp/0000755000175000017500000000000012630544544015002 5ustar andreasandreasnzbget-16.4/daemon/nntp/ServerPool.cpp0000644000175000017500000002670012630544544017613 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #ifndef WIN32 #include #include #endif #include #include "nzbget.h" #include "ServerPool.h" static const int CONNECTION_HOLD_SECODNS = 5; ServerPool::PooledConnection::PooledConnection(NewsServer* server) : NNTPConnection(server) { m_bInUse = false; m_tFreeTime = 0; } ServerPool::ServerPool() { debug("Creating ServerPool"); m_iMaxNormLevel = 0; m_iTimeout = 60; m_iGeneration = 0; m_iRetryInterval = 0; g_pLog->RegisterDebuggable(this); } ServerPool::~ ServerPool() { debug("Destroying ServerPool"); g_pLog->UnregisterDebuggable(this); m_Levels.clear(); for (Servers::iterator it = m_Servers.begin(); it != m_Servers.end(); it++) { delete *it; } m_Servers.clear(); m_SortedServers.clear(); for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++) { delete *it; } m_Connections.clear(); } void ServerPool::AddServer(NewsServer* pNewsServer) { debug("Adding server to ServerPool"); 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()) { return; } std::sort(m_SortedServers.begin(), m_SortedServers.end(), CompareServers); // 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() < iMinLevel) { iMinLevel = pNewsServer->GetLevel(); } } 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); } } } bool ServerPool::CompareServers(NewsServer* pServer1, NewsServer* pServer2) { return pServer1->GetLevel() < pServer2->GetLevel(); } void ServerPool::InitConnections() { debug("Initializing connections in ServerPool"); m_mutexConnections.Lock(); NormalizeLevels(); m_Levels.clear(); for (Servers::iterator it = m_SortedServers.begin(); it != m_SortedServers.end(); it++) { NewsServer* pNewsServer = *it; pNewsServer->SetBlockTime(0); int iNormLevel = pNewsServer->GetNormLevel(); if (pNewsServer->GetNormLevel() > -1) { 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; } } } m_iGeneration++; m_mutexConnections.Unlock(); } NNTPConnection* ServerPool::GetConnection(int iLevel, NewsServer* pWantServer, Servers* pIgnoreServers) { PooledConnection* pConnection = NULL; m_mutexConnections.Lock(); time_t tCurTime = time(NULL); if (iLevel < (int)m_Levels.size() && m_Levels[iLevel] > 0) { Connections candidates; candidates.reserve(m_Connections.size()); for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++) { PooledConnection* pCandidateConnection = *it; NewsServer* pCandidateServer = pCandidateConnection->GetNewsServer(); if (!pCandidateConnection->GetInUse() && pCandidateServer->GetActive() && pCandidateServer->GetNormLevel() == iLevel && (!pWantServer || pCandidateServer == pWantServer || (pWantServer->GetGroup() > 0 && pWantServer->GetGroup() == pCandidateServer->GetGroup())) && (pCandidateConnection->GetStatus() == Connection::csConnected || !pCandidateServer->GetBlockTime() || pCandidateServer->GetBlockTime() + m_iRetryInterval <= tCurTime || pCandidateServer->GetBlockTime() > tCurTime)) { // free connection found, check if it's not from the server which should be ignored bool bUseConnection = true; if (pIgnoreServers && !pWantServer) { for (Servers::iterator it = pIgnoreServers->begin(); it != pIgnoreServers->end(); it++) { NewsServer* pIgnoreServer = *it; if (pIgnoreServer == pCandidateServer || (pIgnoreServer->GetGroup() > 0 && pIgnoreServer->GetGroup() == pCandidateServer->GetGroup() && pIgnoreServer->GetNormLevel() == pCandidateServer->GetNormLevel())) { bUseConnection = false; break; } } } pCandidateServer->SetBlockTime(0); if (bUseConnection) { candidates.push_back(pCandidateConnection); } } } if (!candidates.empty()) { // Peeking a random free connection. This is better than taking the first // available connection because provides better distribution across news servers, // especially when one of servers becomes unavailable or doesn't have requested articles. int iRandomIndex = rand() % candidates.size(); pConnection = candidates[iRandomIndex]; pConnection->SetInUse(true); } if (pConnection) { m_Levels[iLevel]--; } } m_mutexConnections.Unlock(); return pConnection; } void ServerPool::FreeConnection(NNTPConnection* pConnection, bool bUsed) { if (bUsed) { debug("Freeing used connection"); } m_mutexConnections.Lock(); ((PooledConnection*)pConnection)->SetInUse(false); if (bUsed) { ((PooledConnection*)pConnection)->SetFreeTimeNow(); } if (pConnection->GetNewsServer()->GetNormLevel() > -1 && pConnection->GetNewsServer()->GetActive()) { m_Levels[pConnection->GetNewsServer()->GetNormLevel()]++; } m_mutexConnections.Unlock(); } void ServerPool::BlockServer(NewsServer* pNewsServer) { m_mutexConnections.Lock(); time_t tCurTime = time(NULL); bool bNewBlock = pNewsServer->GetBlockTime() != tCurTime; pNewsServer->SetBlockTime(tCurTime); m_mutexConnections.Unlock(); if (bNewBlock && m_iRetryInterval > 0) { warn("Blocking %s (%s) for %i sec", pNewsServer->GetName(), pNewsServer->GetHost(), m_iRetryInterval); } } void ServerPool::CloseUnusedConnections() { m_mutexConnections.Lock(); time_t curtime = ::time(NULL); // close and free all connections of servers which were disabled since the last check int i = 0; for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); ) { PooledConnection* pConnection = *it; bool bDeleted = false; if (!pConnection->GetInUse() && (pConnection->GetNewsServer()->GetNormLevel() == -1 || !pConnection->GetNewsServer()->GetActive())) { 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) { it++; i++; } } // close all opened connections on levels not having any in-use connections for (int iLevel = 0; iLevel <= m_iMaxNormLevel; iLevel++) { // check if we have in-use connections on the level bool bHasInUseConnections = false; int iInactiveTime = 0; for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++) { PooledConnection* pConnection = *it; if (pConnection->GetNewsServer()->GetNormLevel() == iLevel) { if (pConnection->GetInUse()) { bHasInUseConnections = true; break; } else { int tdiff = (int)(curtime - pConnection->GetFreeTime()); if (tdiff > iInactiveTime) { iInactiveTime = tdiff; } } } } // if there are no in-use connections on the level and the hold time out has // expired - close all connections of the level. if (!bHasInUseConnections && iInactiveTime > CONNECTION_HOLD_SECODNS) { for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++) { PooledConnection* pConnection = *it; if (pConnection->GetNewsServer()->GetNormLevel() == iLevel && pConnection->GetStatus() == Connection::csConnected) { debug("Closing (and keeping) unused connection to server%i", pConnection->GetNewsServer()->GetID()); pConnection->Disconnect(); } } } } m_mutexConnections.Unlock(); } void ServerPool::Changed() { debug("Server config has been changed"); InitConnections(); CloseUnusedConnections(); } void ServerPool::LogDebugInfo() { info(" ---------- ServerPool"); info(" Max-Level: %i", m_iMaxNormLevel); m_mutexConnections.Lock(); time_t tCurTime = time(NULL); info(" Servers: %i", m_Servers.size()); for (Servers::iterator it = m_Servers.begin(); it != m_Servers.end(); it++) { NewsServer* pNewsServer = *it; info(" %i) %s (%s): Level=%i, NormLevel=%i, BlockSec=%i", pNewsServer->GetID(), pNewsServer->GetName(), pNewsServer->GetHost(), pNewsServer->GetLevel(), pNewsServer->GetNormLevel(), pNewsServer->GetBlockTime() && pNewsServer->GetBlockTime() + m_iRetryInterval > tCurTime ? pNewsServer->GetBlockTime() + m_iRetryInterval - tCurTime : 0); } info(" Levels: %i", m_Levels.size()); int index = 0; for (Levels::iterator it = m_Levels.begin(); it != m_Levels.end(); it++, index++) { int iSize = *it; info(" %i: Free connections=%i", index, iSize); } info(" Connections: %i", m_Connections.size()); for (Connections::iterator it = m_Connections.begin(); it != m_Connections.end(); it++) { PooledConnection* pConnection = *it; info(" %i) %s (%s): Level=%i, NormLevel=%i, InUse:%i", pConnection->GetNewsServer()->GetID(), pConnection->GetNewsServer()->GetName(), pConnection->GetNewsServer()->GetHost(), pConnection->GetNewsServer()->GetLevel(), pConnection->GetNewsServer()->GetNormLevel(), (int)pConnection->GetInUse()); } m_mutexConnections.Unlock(); } nzbget-16.4/daemon/nntp/ArticleWriter.h0000644000175000017500000000601112630544544017731 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2014-2015 Andrey Prygunkov * * 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 ARTICLEWRITER_H #define ARTICLEWRITER_H #include "DownloadInfo.h" #include "Decoder.h" class ArticleWriter { private: FileInfo* m_pFileInfo; ArticleInfo* m_pArticleInfo; FILE* m_pOutFile; char* m_szTempFilename; char* m_szOutputFilename; const char* m_szResultFilename; Decoder::EFormat m_eFormat; char* m_pArticleData; long long m_iArticleOffset; int m_iArticleSize; int m_iArticlePtr; bool m_bFlushing; bool m_bDuplicate; char* m_szInfoName; bool PrepareFile(char* szLine); bool CreateOutputFile(long long iSize); void BuildOutputFilename(); bool IsFileCached(); void SetWriteBuffer(FILE* pOutFile, int iRecSize); protected: virtual void SetLastUpdateTimeNow() {} public: ArticleWriter(); ~ArticleWriter(); void SetInfoName(const char* szInfoName); void SetFileInfo(FileInfo* pFileInfo) { m_pFileInfo = pFileInfo; } void SetArticleInfo(ArticleInfo* pArticleInfo) { m_pArticleInfo = pArticleInfo; } void Prepare(); bool Start(Decoder::EFormat eFormat, const char* szFilename, long long iFileSize, long long iArticleOffset, int iArticleSize); bool Write(char* szBufffer, int iLen); void Finish(bool bSuccess); bool GetDuplicate() { return m_bDuplicate; } void CompleteFileParts(); static bool MoveCompletedFiles(NZBInfo* pNZBInfo, const char* szOldDestDir); void FlushCache(); }; class ArticleCache : public Thread { private: size_t m_iAllocated; bool m_bFlushing; Mutex m_mutexAlloc; Mutex m_mutexFlush; Mutex m_mutexContent; FileInfo* m_pFileInfo; bool CheckFlush(bool bFlushEverything); public: ArticleCache(); virtual void Run(); void* Alloc(int iSize); void* Realloc(void* buf, int iOldSize, int iNewSize); void Free(int iSize); void LockFlush(); void UnlockFlush(); void LockContent() { m_mutexContent.Lock(); } void UnlockContent() { m_mutexContent.Unlock(); } bool GetFlushing() { return m_bFlushing; } size_t GetAllocated() { return m_iAllocated; } bool FileBusy(FileInfo* pFileInfo) { return pFileInfo == m_pFileInfo; } }; extern ArticleCache* g_pArticleCache; #endif nzbget-16.4/daemon/nntp/Decoder.h0000644000175000017500000000472712630544544016532 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2014 Andrey Prygunkov * * 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 DECODER_H #define DECODER_H class Decoder { public: enum EStatus { eUnknownError, eFinished, eArticleIncomplete, eCrcError, eInvalidSize, eNoBinaryData }; enum EFormat { efUnknown, efYenc, efUx, }; static const char* FormatNames[]; protected: char* m_szArticleFilename; public: Decoder(); virtual ~Decoder(); virtual EStatus Check() = 0; virtual void Clear(); virtual int DecodeBuffer(char* buffer, int len) = 0; const char* GetArticleFilename() { return m_szArticleFilename; } static EFormat DetectFormat(const char* buffer, int len, bool inBody); }; class YDecoder: public Decoder { protected: bool m_bBegin; bool m_bPart; bool m_bBody; bool m_bEnd; bool m_bCrc; unsigned long m_lExpectedCRC; unsigned long m_lCalculatedCRC; long long m_iBegin; long long m_iEnd; long long m_iSize; long long m_iEndSize; bool m_bCrcCheck; public: YDecoder(); virtual EStatus Check(); virtual void Clear(); virtual int DecodeBuffer(char* buffer, int len); void SetCrcCheck(bool bCrcCheck) { m_bCrcCheck = bCrcCheck; } long long GetBegin() { return m_iBegin; } long long GetEnd() { return m_iEnd; } long long GetSize() { return m_iSize; } unsigned long GetExpectedCrc() { return m_lExpectedCRC; } unsigned long GetCalculatedCrc() { return m_lCalculatedCRC; } }; class UDecoder: public Decoder { private: bool m_bBody; bool m_bEnd; public: UDecoder(); virtual EStatus Check(); virtual void Clear(); virtual int DecodeBuffer(char* buffer, int len); }; #endif nzbget-16.4/daemon/nntp/ArticleDownloader.h0000644000175000017500000000675412630544544020571 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2014 Andrey Prygunkov * * 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 ARTICLEDOWNLOADER_H #define ARTICLEDOWNLOADER_H #include #include "Observer.h" #include "DownloadInfo.h" #include "Thread.h" #include "NNTPConnection.h" #include "Decoder.h" #include "ArticleWriter.h" class ArticleDownloader : public Thread, public Subject { public: enum EStatus { adUndefined, adRunning, adWaiting, adFinished, adFailed, adRetry, adCrcError, adNotFound, adConnectError, adFatalError }; class ArticleWriterImpl : public ArticleWriter { private: ArticleDownloader* m_pOwner; protected: virtual void SetLastUpdateTimeNow() { m_pOwner->SetLastUpdateTimeNow(); } public: void SetOwner(ArticleDownloader* pOwner) { m_pOwner = pOwner; } }; private: FileInfo* m_pFileInfo; ArticleInfo* m_pArticleInfo; NNTPConnection* m_pConnection; EStatus m_eStatus; Mutex m_mutexConnection; char* m_szInfoName; char m_szConnectionName[250]; char* m_szArticleFilename; time_t m_tLastUpdateTime; Decoder::EFormat m_eFormat; YDecoder m_YDecoder; UDecoder m_UDecoder; ArticleWriterImpl m_ArticleWriter; ServerStatList m_ServerStats; bool m_bWritingStarted; int m_iDownloadedSize; EStatus Download(); EStatus DecodeCheck(); void FreeConnection(bool bKeepConnected); EStatus CheckResponse(const char* szResponse, const char* szComment); void SetStatus(EStatus eStatus) { m_eStatus = eStatus; } bool Write(char* szLine, int iLen); void AddServerData(); public: ArticleDownloader(); virtual ~ArticleDownloader(); void SetFileInfo(FileInfo* pFileInfo) { m_pFileInfo = pFileInfo; } FileInfo* GetFileInfo() { return m_pFileInfo; } void SetArticleInfo(ArticleInfo* pArticleInfo) { m_pArticleInfo = pArticleInfo; } ArticleInfo* GetArticleInfo() { return m_pArticleInfo; } EStatus GetStatus() { return m_eStatus; } ServerStatList* GetServerStats() { return &m_ServerStats; } virtual void Run(); virtual void Stop(); bool Terminate(); time_t GetLastUpdateTime() { return m_tLastUpdateTime; } void SetLastUpdateTimeNow() { m_tLastUpdateTime = ::time(NULL); } const char* GetArticleFilename() { return m_szArticleFilename; } void SetInfoName(const char* szInfoName); const char* GetInfoName() { return m_szInfoName; } const char* GetConnectionName() { return m_szConnectionName; } void SetConnection(NNTPConnection* pConnection) { m_pConnection = pConnection; } void CompleteFileParts() { m_ArticleWriter.CompleteFileParts(); } int GetDownloadedSize() { return m_iDownloadedSize; } void LogDebugInfo(); }; #endif nzbget-16.4/daemon/nntp/StatMeter.cpp0000644000175000017500000003131612630544544017422 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2014-2015 Andrey Prygunkov * * 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 #include #include #include "nzbget.h" #include "StatMeter.h" #include "Options.h" #include "ServerPool.h" #include "DiskState.h" #include "Util.h" static const int DAYS_UP_TO_2013_JAN_1 = 15706; static const int DAYS_IN_TWENTY_YEARS = 366*20; ServerVolume::ServerVolume() { m_BytesPerSeconds.resize(60); m_BytesPerMinutes.resize(60); m_BytesPerHours.resize(24); m_BytesPerDays.resize(0); m_iFirstDay = 0; m_tDataTime = 0; m_lTotalBytes = 0; m_lCustomBytes = 0; m_tCustomTime = time(NULL); m_iSecSlot = 0; m_iMinSlot = 0; m_iHourSlot = 0; m_iDaySlot = 0; } void ServerVolume::CalcSlots(time_t tLocCurTime) { m_iSecSlot = (int)tLocCurTime % 60; m_iMinSlot = ((int)tLocCurTime / 60) % 60; m_iHourSlot = ((int)tLocCurTime % 86400) / 3600; int iDaysSince1970 = (int)tLocCurTime / 86400; m_iDaySlot = iDaysSince1970 - DAYS_UP_TO_2013_JAN_1 + 1; if (0 <= m_iDaySlot && m_iDaySlot < DAYS_IN_TWENTY_YEARS) { int iCurDay = iDaysSince1970; if (m_iFirstDay == 0 || m_iFirstDay > iCurDay) { m_iFirstDay = iCurDay; } m_iDaySlot = iCurDay - m_iFirstDay; if (m_iDaySlot + 1 > (int)m_BytesPerDays.size()) { m_BytesPerDays.resize(m_iDaySlot + 1); } } else { m_iDaySlot = -1; } } void ServerVolume::AddData(int iBytes) { time_t tCurTime = time(NULL); time_t tLocCurTime = tCurTime + g_pOptions->GetLocalTimeOffset(); time_t tLocDataTime = m_tDataTime + g_pOptions->GetLocalTimeOffset(); int iLastMinSlot = m_iMinSlot; int iLastHourSlot = m_iHourSlot; CalcSlots(tLocCurTime); if (tLocCurTime != tLocDataTime) { // clear seconds/minutes/hours slots if necessary // also handle the backwards changes of system clock int iTotalDelta = (int)(tLocCurTime - tLocDataTime); int iDeltaSign = iTotalDelta >= 0 ? 1 : -1; iTotalDelta = abs(iTotalDelta); int iSecDelta = iTotalDelta; if (iDeltaSign < 0) iSecDelta++; if (iSecDelta >= 60) iSecDelta = 60; for (int i = 0; i < iSecDelta; i++) { int iNulSlot = m_iSecSlot - i * iDeltaSign; if (iNulSlot < 0) iNulSlot += 60; if (iNulSlot >= 60) iNulSlot -= 60; m_BytesPerSeconds[iNulSlot] = 0; } int iMinDelta = iTotalDelta / 60; if (iDeltaSign < 0) iMinDelta++; if (abs(iMinDelta) >= 60) iMinDelta = 60; if (iMinDelta == 0 && m_iMinSlot != iLastMinSlot) iMinDelta = 1; for (int i = 0; i < iMinDelta; i++) { int iNulSlot = m_iMinSlot - i * iDeltaSign; if (iNulSlot < 0) iNulSlot += 60; if (iNulSlot >= 60) iNulSlot -= 60; m_BytesPerMinutes[iNulSlot] = 0; } int iHourDelta = iTotalDelta / (60 * 60); if (iDeltaSign < 0) iHourDelta++; if (iHourDelta >= 24) iHourDelta = 24; if (iHourDelta == 0 && m_iHourSlot != iLastHourSlot) iHourDelta = 1; for (int i = 0; i < iHourDelta; i++) { int iNulSlot = m_iHourSlot - i * iDeltaSign; if (iNulSlot < 0) iNulSlot += 24; if (iNulSlot >= 24) iNulSlot -= 24; m_BytesPerHours[iNulSlot] = 0; } } // add bytes to every slot m_BytesPerSeconds[m_iSecSlot] += iBytes; m_BytesPerMinutes[m_iMinSlot] += iBytes; m_BytesPerHours[m_iHourSlot] += iBytes; if (m_iDaySlot >= 0) { m_BytesPerDays[m_iDaySlot] += iBytes; } m_lTotalBytes += iBytes; m_lCustomBytes += iBytes; m_tDataTime = tCurTime; } void ServerVolume::ResetCustom() { m_lCustomBytes = 0; m_tCustomTime = time(NULL); } void ServerVolume::LogDebugInfo() { info(" ---------- ServerVolume"); StringBuilder msg; for (int i = 0; i < 60; i++) { char szNum[30]; snprintf(szNum, 30, "[%i]=%lli ", i, m_BytesPerSeconds[i]); msg.Append(szNum); } info("Secs: %s", msg.GetBuffer()); msg.Clear(); for (int i = 0; i < 60; i++) { char szNum[30]; snprintf(szNum, 30, "[%i]=%lli ", i, m_BytesPerMinutes[i]); msg.Append(szNum); } info("Mins: %s", msg.GetBuffer()); msg.Clear(); for (int i = 0; i < 24; i++) { char szNum[30]; snprintf(szNum, 30, "[%i]=%lli ", i, m_BytesPerHours[i]); msg.Append(szNum); } info("Hours: %s", msg.GetBuffer()); msg.Clear(); for (int i = 0; i < (int)m_BytesPerDays.size(); i++) { char szNum[30]; snprintf(szNum, 30, "[%i]=%lli ", m_iFirstDay + i, m_BytesPerDays[i]); msg.Append(szNum); } info("Days: %s", msg.GetBuffer()); } StatMeter::StatMeter() { debug("Creating StatMeter"); ResetSpeedStat(); m_iAllBytes = 0; m_tStartDownload = 0; m_tPausedFrom = 0; m_bStandBy = true; m_tStartServer = 0; m_tLastCheck = 0; m_tLastTimeOffset = 0; m_bStatChanged = false; g_pLog->RegisterDebuggable(this); } StatMeter::~StatMeter() { debug("Destroying StatMeter"); // Cleanup g_pLog->UnregisterDebuggable(this); for (ServerVolumes::iterator it = m_ServerVolumes.begin(); it != m_ServerVolumes.end(); it++) { delete *it; } debug("StatMeter destroyed"); } void StatMeter::Init() { m_tStartServer = time(NULL); m_tLastCheck = m_tStartServer; AdjustTimeOffset(); m_ServerVolumes.resize(1 + g_pServerPool->GetServers()->size()); m_ServerVolumes[0] = new ServerVolume(); for (Servers::iterator it = g_pServerPool->GetServers()->begin(); it != g_pServerPool->GetServers()->end(); it++) { NewsServer* pServer = *it; m_ServerVolumes[pServer->GetID()] = new ServerVolume(); } } void StatMeter::AdjustTimeOffset() { time_t tUtcTime = time(NULL); tm tmSplittedTime; gmtime_r(&tUtcTime, &tmSplittedTime); tmSplittedTime.tm_isdst = -1; time_t tLocTime = mktime(&tmSplittedTime); time_t tLocalTimeDelta = tUtcTime - tLocTime; g_pOptions->SetLocalTimeOffset((int)tLocalTimeDelta + g_pOptions->GetTimeCorrection()); m_tLastTimeOffset = tUtcTime; debug("UTC delta: %i (%i+%i)", g_pOptions->GetLocalTimeOffset(), (int)tLocalTimeDelta, g_pOptions->GetTimeCorrection()); } /* * Called once per second. * - detect large step changes of system time and adjust statistics; * - save volume stats (if changed). */ void StatMeter::IntervalCheck() { time_t m_tCurTime = time(NULL); time_t tDiff = m_tCurTime - m_tLastCheck; if (tDiff > 60 || tDiff < 0) { m_tStartServer += tDiff + 1; // "1" because the method is called once per second if (m_tStartDownload != 0 && !m_bStandBy) { m_tStartDownload += tDiff + 1; } AdjustTimeOffset(); } else if (m_tLastTimeOffset > m_tCurTime || m_tCurTime - m_tLastTimeOffset > 60 * 60 * 3 || (m_tCurTime - m_tLastTimeOffset > 60 && !m_bStandBy)) { // checking time zone settings may prevent the device from entering sleep/hibernate mode // check every minute if not in standby // check at least every 3 hours even in standby AdjustTimeOffset(); } m_tLastCheck = m_tCurTime; if (m_bStatChanged) { Save(); } } void StatMeter::EnterLeaveStandBy(bool bEnter) { m_mutexStat.Lock(); m_bStandBy = bEnter; if (bEnter) { m_tPausedFrom = time(NULL); } else { if (m_tStartDownload == 0) { m_tStartDownload = time(NULL); } else { m_tStartDownload += time(NULL) - m_tPausedFrom; } m_tPausedFrom = 0; ResetSpeedStat(); } m_mutexStat.Unlock(); } void StatMeter::CalcTotalStat(int* iUpTimeSec, int* iDnTimeSec, long long* iAllBytes, bool* bStandBy) { m_mutexStat.Lock(); if (m_tStartServer > 0) { *iUpTimeSec = (int)(time(NULL) - m_tStartServer); } else { *iUpTimeSec = 0; } *bStandBy = m_bStandBy; if (m_bStandBy) { *iDnTimeSec = (int)(m_tPausedFrom - m_tStartDownload); } else { *iDnTimeSec = (int)(time(NULL) - m_tStartDownload); } *iAllBytes = m_iAllBytes; m_mutexStat.Unlock(); } // Average speed in last 30 seconds int StatMeter::CalcCurrentDownloadSpeed() { if (m_bStandBy) { return 0; } int iTimeDiff = (int)time(NULL) - m_iSpeedStartTime * SPEEDMETER_SLOTSIZE; if (iTimeDiff == 0) { return 0; } return (int)(m_iSpeedTotalBytes / iTimeDiff); } // Amount of data downloaded in current second int StatMeter::CalcMomentaryDownloadSpeed() { time_t tCurTime = time(NULL); int iSpeed = tCurTime == m_tCurSecTime ? m_iCurSecBytes : 0; return iSpeed; } void StatMeter::AddSpeedReading(int iBytes) { time_t tCurTime = time(NULL); int iNowSlot = (int)tCurTime / SPEEDMETER_SLOTSIZE; if (g_pOptions->GetAccurateRate()) { m_mutexSpeed.Lock(); } if (tCurTime != m_tCurSecTime) { m_tCurSecTime = tCurTime; m_iCurSecBytes = 0; } m_iCurSecBytes += iBytes; while (iNowSlot > m_iSpeedTime[m_iSpeedBytesIndex]) { //record bytes in next slot m_iSpeedBytesIndex++; if (m_iSpeedBytesIndex >= SPEEDMETER_SLOTS) { m_iSpeedBytesIndex = 0; } //Adjust counters with outgoing information. m_iSpeedTotalBytes = m_iSpeedTotalBytes - (long long)m_iSpeedBytes[m_iSpeedBytesIndex]; //Note we should really use the start time of the next slot //but its easier to just use the outgoing slot time. This //will result in a small error. m_iSpeedStartTime = m_iSpeedTime[m_iSpeedBytesIndex]; //Now reset. m_iSpeedBytes[m_iSpeedBytesIndex] = 0; m_iSpeedTime[m_iSpeedBytesIndex] = iNowSlot; } // Once per second recalculate summary field "m_iSpeedTotalBytes" to recover from possible synchronisation errors if (tCurTime > m_tSpeedCorrection) { long long iSpeedTotalBytes = 0; for (int i = 0; i < SPEEDMETER_SLOTS; i++) { iSpeedTotalBytes += m_iSpeedBytes[i]; } m_iSpeedTotalBytes = iSpeedTotalBytes; m_tSpeedCorrection = tCurTime; } if (m_iSpeedTotalBytes == 0) { m_iSpeedStartTime = iNowSlot; } m_iSpeedBytes[m_iSpeedBytesIndex] += iBytes; m_iSpeedTotalBytes += iBytes; m_iAllBytes += iBytes; if (g_pOptions->GetAccurateRate()) { m_mutexSpeed.Unlock(); } } void StatMeter::ResetSpeedStat() { time_t tCurTime = time(NULL); m_iSpeedStartTime = (int)tCurTime / SPEEDMETER_SLOTSIZE; for (int i = 0; i < SPEEDMETER_SLOTS; i++) { m_iSpeedBytes[i] = 0; m_iSpeedTime[i] = m_iSpeedStartTime; } m_iSpeedBytesIndex = 0; m_iSpeedTotalBytes = 0; m_tSpeedCorrection = tCurTime; m_tCurSecTime = 0; m_iCurSecBytes = 0; } void StatMeter::LogDebugInfo() { info(" ---------- SpeedMeter"); int iSpeed = CalcCurrentDownloadSpeed() / 1024; int iTimeDiff = (int)time(NULL) - m_iSpeedStartTime * SPEEDMETER_SLOTSIZE; info(" Speed: %i", iSpeed); info(" SpeedStartTime: %i", m_iSpeedStartTime); info(" SpeedTotalBytes: %i", m_iSpeedTotalBytes); info(" SpeedBytesIndex: %i", m_iSpeedBytesIndex); info(" AllBytes: %i", m_iAllBytes); info(" Time: %i", (int)time(NULL)); info(" TimeDiff: %i", iTimeDiff); for (int i=0; i < SPEEDMETER_SLOTS; i++) { info(" Bytes[%i]: %i, Time[%i]: %i", i, m_iSpeedBytes[i], i, m_iSpeedTime[i]); } m_mutexVolume.Lock(); int index = 0; for (ServerVolumes::iterator it = m_ServerVolumes.begin(); it != m_ServerVolumes.end(); it++, index++) { ServerVolume* pServerVolume = *it; info(" ServerVolume %i", index); pServerVolume->LogDebugInfo(); } m_mutexVolume.Unlock(); } void StatMeter::AddServerData(int iBytes, int iServerID) { if (iBytes == 0) { return; } m_mutexVolume.Lock(); m_ServerVolumes[0]->AddData(iBytes); m_ServerVolumes[iServerID]->AddData(iBytes); m_bStatChanged = true; m_mutexVolume.Unlock(); } ServerVolumes* StatMeter::LockServerVolumes() { m_mutexVolume.Lock(); // update slots for (ServerVolumes::iterator it = m_ServerVolumes.begin(); it != m_ServerVolumes.end(); it++) { ServerVolume* pServerVolume = *it; pServerVolume->AddData(0); } return &m_ServerVolumes; } void StatMeter::UnlockServerVolumes() { m_mutexVolume.Unlock(); } void StatMeter::Save() { if (!g_pOptions->GetServerMode()) { return; } m_mutexVolume.Lock(); g_pDiskState->SaveStats(g_pServerPool->GetServers(), &m_ServerVolumes); m_bStatChanged = false; m_mutexVolume.Unlock(); } bool StatMeter::Load(bool* pPerfectServerMatch) { m_mutexVolume.Lock(); bool bOK = g_pDiskState->LoadStats(g_pServerPool->GetServers(), &m_ServerVolumes, pPerfectServerMatch); for (ServerVolumes::iterator it = m_ServerVolumes.begin(); it != m_ServerVolumes.end(); it++) { ServerVolume* pServerVolume = *it; pServerVolume->CalcSlots(pServerVolume->GetDataTime() + g_pOptions->GetLocalTimeOffset()); } m_mutexVolume.Unlock(); return bOK; } nzbget-16.4/daemon/nntp/ServerPool.h0000644000175000017500000000532512630544544017260 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 SERVERPOOL_H #define SERVERPOOL_H #include #include #include "Log.h" #include "Thread.h" #include "NewsServer.h" #include "NNTPConnection.h" class ServerPool : public Debuggable { private: class PooledConnection : public NNTPConnection { private: bool m_bInUse; time_t m_tFreeTime; public: PooledConnection(NewsServer* server); bool GetInUse() { return m_bInUse; } void SetInUse(bool bInUse) { m_bInUse = bInUse; } time_t GetFreeTime() { return m_tFreeTime; } void SetFreeTimeNow() { m_tFreeTime = ::time(NULL); } }; typedef std::vector Levels; typedef std::vector Connections; Servers m_Servers; Servers m_SortedServers; Connections m_Connections; Levels m_Levels; int m_iMaxNormLevel; Mutex m_mutexConnections; int m_iTimeout; int m_iRetryInterval; int m_iGeneration; void NormalizeLevels(); static bool CompareServers(NewsServer* pServer1, NewsServer* pServer2); protected: virtual void LogDebugInfo(); public: ServerPool(); ~ServerPool(); void SetTimeout(int iTimeout) { m_iTimeout = iTimeout; } void SetRetryInterval(int iRetryInterval) { m_iRetryInterval = iRetryInterval; } void AddServer(NewsServer* pNewsServer); void InitConnections(); int GetMaxNormLevel() { return m_iMaxNormLevel; } Servers* GetServers() { return &m_Servers; } // Only for read access (no lockings) NNTPConnection* GetConnection(int iLevel, NewsServer* pWantServer, Servers* pIgnoreServers); void FreeConnection(NNTPConnection* pConnection, bool bUsed); void CloseUnusedConnections(); void Changed(); int GetGeneration() { return m_iGeneration; } void BlockServer(NewsServer* pNewsServer); }; extern ServerPool* g_pServerPool; #endif nzbget-16.4/daemon/nntp/NewsServer.h0000644000175000017500000000530112630544544017255 0ustar andreasandreas/* * This file if part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 NEWSSERVER_H #define NEWSSERVER_H #include #include class NewsServer { private: int m_iID; int m_iStateID; bool m_bActive; char* m_szName; int m_iGroup; char* m_szHost; int m_iPort; char* m_szUser; char* m_szPassword; int m_iMaxConnections; int m_iLevel; int m_iNormLevel; bool m_bJoinGroup; bool m_bTLS; char* m_szCipher; int m_iRetention; time_t m_tBlockTime; public: NewsServer(int iID, bool bActive, const char* szName, const char* szHost, int iPort, const char* szUser, const char* szPass, bool bJoinGroup, bool bTLS, const char* szCipher, int iMaxConnections, int iRetention, int iLevel, int iGroup); ~NewsServer(); int GetID() { return m_iID; } int GetStateID() { return m_iStateID; } void SetStateID(int iStateID) { m_iStateID = iStateID; } bool GetActive() { return m_bActive; } void SetActive(bool bActive) { m_bActive = bActive; } const char* GetName() { return m_szName; } int GetGroup() { return m_iGroup; } const char* GetHost() { return m_szHost; } int GetPort() { return m_iPort; } const char* GetUser() { return m_szUser; } const char* GetPassword() { return m_szPassword; } int GetMaxConnections() { return m_iMaxConnections; } int GetLevel() { return m_iLevel; } int GetNormLevel() { return m_iNormLevel; } void SetNormLevel(int iLevel) { m_iNormLevel = iLevel; } int GetJoinGroup() { return m_bJoinGroup; } bool GetTLS() { return m_bTLS; } const char* GetCipher() { return m_szCipher; } int GetRetention() { return m_iRetention; } time_t GetBlockTime() { return m_tBlockTime; } void SetBlockTime(time_t tBlockTime) { m_tBlockTime = tBlockTime; } }; typedef std::vector Servers; #endif nzbget-16.4/daemon/nntp/Decoder.cpp0000644000175000017500000001571512630544544017064 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2007-2014 Andrey Prygunkov * * 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 #include #include #ifndef WIN32 #include #endif #include "nzbget.h" #include "Decoder.h" #include "Log.h" #include "Util.h" const char* Decoder::FormatNames[] = { "Unknown", "yEnc", "UU" }; Decoder::Decoder() { debug("Creating Decoder"); m_szArticleFilename = NULL; } Decoder::~ Decoder() { debug("Destroying Decoder"); free(m_szArticleFilename); } void Decoder::Clear() { free(m_szArticleFilename); m_szArticleFilename = NULL; } Decoder::EFormat Decoder::DetectFormat(const char* buffer, int len, bool inBody) { if (!strncmp(buffer, "=ybegin ", 8)) { return efYenc; } if (inBody && (len == 62 || len == 63) && (buffer[62] == '\n' || buffer[62] == '\r') && *buffer == 'M') { return efUx; } if (!strncmp(buffer, "begin ", 6)) { bool bOK = true; buffer += 6; //strlen("begin ") while (*buffer && *buffer != ' ') { char ch = *buffer++; if (ch < '0' || ch > '7') { bOK = false; break; } } if (bOK) { return efUx; } } return efUnknown; } /** * YDecoder: fast implementation of yEnc-Decoder */ YDecoder::YDecoder() { Clear(); } void YDecoder::Clear() { Decoder::Clear(); m_bBody = false; m_bBegin = false; m_bPart = false; m_bEnd = false; m_bCrc = false; m_lExpectedCRC = 0; m_lCalculatedCRC = 0xFFFFFFFF; m_iBegin = 0; m_iEnd = 0; m_iSize = 0; m_iEndSize = 0; m_bCrcCheck = false; } int YDecoder::DecodeBuffer(char* buffer, int len) { if (m_bBody && !m_bEnd) { if (!strncmp(buffer, "=yend ", 6)) { m_bEnd = true; char* pb = strstr(buffer, m_bPart ? " pcrc32=" : " crc32="); if (pb) { m_bCrc = true; pb += 7 + (int)m_bPart; //=strlen(" crc32=") or strlen(" pcrc32=") m_lExpectedCRC = strtoul(pb, NULL, 16); } pb = strstr(buffer, " size="); if (pb) { pb += 6; //=strlen(" size=") m_iEndSize = (long long)atoll(pb); } return 0; } char* iptr = buffer; char* optr = buffer; while (true) { switch (*iptr) { case '=': //escape-sequence iptr++; *optr = *iptr - 64 - 42; optr++; break; case '\n': // ignored char case '\r': // ignored char break; case '\0': goto BreakLoop; default: // normal char *optr = *iptr - 42; optr++; break; } iptr++; } BreakLoop: if (m_bCrcCheck) { m_lCalculatedCRC = Util::Crc32m(m_lCalculatedCRC, (unsigned char *)buffer, (unsigned int)(optr - buffer)); } return optr - buffer; } else { if (!m_bPart && !strncmp(buffer, "=ybegin ", 8)) { m_bBegin = true; char* pb = strstr(buffer, " name="); if (pb) { pb += 6; //=strlen(" name=") char* pe; for (pe = pb; *pe != '\0' && *pe != '\n' && *pe != '\r'; pe++) ; free(m_szArticleFilename); m_szArticleFilename = (char*)malloc(pe - pb + 1); strncpy(m_szArticleFilename, pb, pe - pb); m_szArticleFilename[pe - pb] = '\0'; } pb = strstr(buffer, " size="); if (pb) { pb += 6; //=strlen(" size=") m_iSize = (long long)atoll(pb); } m_bPart = strstr(buffer, " part="); if (!m_bPart) { m_bBody = true; m_iBegin = 1; m_iEnd = m_iSize; } } else if (m_bPart && !strncmp(buffer, "=ypart ", 7)) { m_bPart = true; m_bBody = true; char* pb = strstr(buffer, " begin="); if (pb) { pb += 7; //=strlen(" begin=") m_iBegin = (long long)atoll(pb); } pb = strstr(buffer, " end="); if (pb) { pb += 5; //=strlen(" end=") m_iEnd = (long long)atoll(pb); } } } return 0; } Decoder::EStatus YDecoder::Check() { m_lCalculatedCRC ^= 0xFFFFFFFF; debug("Expected crc32=%x", m_lExpectedCRC); debug("Calculated crc32=%x", m_lCalculatedCRC); if (!m_bBegin) { return eNoBinaryData; } else if (!m_bEnd) { return eArticleIncomplete; } else if (!m_bPart && m_iSize != m_iEndSize) { return eInvalidSize; } else if (m_bCrcCheck && m_bCrc && (m_lExpectedCRC != m_lCalculatedCRC)) { return eCrcError; } return eFinished; } /** * UDecoder: supports UU encoding formats */ UDecoder::UDecoder() { } void UDecoder::Clear() { Decoder::Clear(); m_bBody = false; m_bEnd = false; } /* DecodeBuffer-function uses portions of code from tool UUDECODE by Clem Dye * UUDECODE.c (http://www.bastet.com/uue.zip) * Copyright (C) 1998 Clem Dye * * Released under GPL (thanks) */ #define UU_DECODE_CHAR(c) (c == '`' ? 0 : (((c) - ' ') & 077)) int UDecoder::DecodeBuffer(char* buffer, int len) { if (!m_bBody) { if (!strncmp(buffer, "begin ", 6)) { char* pb = buffer; pb += 6; //strlen("begin ") // skip file-permissions for (; *pb != ' ' && *pb != '\0' && *pb != '\n' && *pb != '\r'; pb++) ; pb++; // extracting filename char* pe; for (pe = pb; *pe != '\0' && *pe != '\n' && *pe != '\r'; pe++) ; free(m_szArticleFilename); m_szArticleFilename = (char*)malloc(pe - pb + 1); strncpy(m_szArticleFilename, pb, pe - pb); m_szArticleFilename[pe - pb] = '\0'; m_bBody = true; return 0; } else if ((len == 62 || len == 63) && (buffer[62] == '\n' || buffer[62] == '\r') && *buffer == 'M') { m_bBody = true; } } if (m_bBody && (!strncmp(buffer, "end ", 4) || *buffer == '`')) { m_bEnd = true; } if (m_bBody && !m_bEnd) { int iEffLen = UU_DECODE_CHAR(buffer[0]); if (iEffLen > len) { // error; return 0; } char* iptr = buffer; char* optr = buffer; for (++iptr; iEffLen > 0; iptr += 4, iEffLen -= 3) { if (iEffLen >= 3) { *optr++ = UU_DECODE_CHAR (iptr[0]) << 2 | UU_DECODE_CHAR (iptr[1]) >> 4; *optr++ = UU_DECODE_CHAR (iptr[1]) << 4 | UU_DECODE_CHAR (iptr[2]) >> 2; *optr++ = UU_DECODE_CHAR (iptr[2]) << 6 | UU_DECODE_CHAR (iptr[3]); } else { if (iEffLen >= 1) { *optr++ = UU_DECODE_CHAR (iptr[0]) << 2 | UU_DECODE_CHAR (iptr[1]) >> 4; } if (iEffLen >= 2) { *optr++ = UU_DECODE_CHAR (iptr[1]) << 4 | UU_DECODE_CHAR (iptr[2]) >> 2; } } } return optr - buffer; } return 0; } Decoder::EStatus UDecoder::Check() { if (!m_bBody) { return eNoBinaryData; } return eFinished; } nzbget-16.4/daemon/nntp/NNTPConnection.h0000644000175000017500000000331712630544544017756 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2015 Andrey Prygunkov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Revision$ * $Date$ * */ #ifndef NNTPCONNECTION_H #define NNTPCONNECTION_H #include "NewsServer.h" #include "Connection.h" class NNTPConnection : public Connection { private: NewsServer* m_pNewsServer; char* m_szActiveGroup; char* m_szLineBuf; bool m_bAuthError; void Clear(); void ReportErrorAnswer(const char* szMsgPrefix, const char* szAnswer); bool Authenticate(); bool AuthInfoUser(int iRecur); bool AuthInfoPass(int iRecur); public: NNTPConnection(NewsServer* pNewsServer); virtual ~NNTPConnection(); virtual bool Connect(); virtual bool Disconnect(); NewsServer* GetNewsServer() { return m_pNewsServer; } const char* Request(const char* req); const char* JoinGroup(const char* grp); bool GetAuthError() { return m_bAuthError; } }; #endif nzbget-16.4/daemon/nntp/StatMeter.h0000644000175000017500000001015012630544544017060 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2014-2015 Andrey Prygunkov * * 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 STATMETER_H #define STATMETER_H #include #include #include "Log.h" #include "Thread.h" class ServerVolume { public: typedef std::vector VolumeArray; private: VolumeArray m_BytesPerSeconds; VolumeArray m_BytesPerMinutes; VolumeArray m_BytesPerHours; VolumeArray m_BytesPerDays; int m_iFirstDay; long long m_lTotalBytes; long long m_lCustomBytes; time_t m_tDataTime; time_t m_tCustomTime; int m_iSecSlot; int m_iMinSlot; int m_iHourSlot; int m_iDaySlot; public: ServerVolume(); VolumeArray* BytesPerSeconds() { return &m_BytesPerSeconds; } VolumeArray* BytesPerMinutes() { return &m_BytesPerMinutes; } VolumeArray* BytesPerHours() { return &m_BytesPerHours; } VolumeArray* BytesPerDays() { return &m_BytesPerDays; } void SetFirstDay(int iFirstDay) { m_iFirstDay = iFirstDay; } int GetFirstDay() { return m_iFirstDay; } void SetTotalBytes(long long lTotalBytes) { m_lTotalBytes = lTotalBytes; } long long GetTotalBytes() { return m_lTotalBytes; } void SetCustomBytes(long long lCustomBytes) { m_lCustomBytes = lCustomBytes; } long long GetCustomBytes() { return m_lCustomBytes; } int GetSecSlot() { return m_iSecSlot; } int GetMinSlot() { return m_iMinSlot; } int GetHourSlot() { return m_iHourSlot; } int GetDaySlot() { return m_iDaySlot; } time_t GetDataTime() { return m_tDataTime; } void SetDataTime(time_t tDataTime) { m_tDataTime = tDataTime; } time_t GetCustomTime() { return m_tCustomTime; } void SetCustomTime(time_t tCustomTime) { m_tCustomTime = tCustomTime; } void AddData(int iBytes); void CalcSlots(time_t tLocCurTime); void ResetCustom(); void LogDebugInfo(); }; typedef std::vector ServerVolumes; class StatMeter : public Debuggable { private: // speed meter static const int SPEEDMETER_SLOTS = 30; static const int SPEEDMETER_SLOTSIZE = 1; //Split elapsed time into this number of secs. int m_iSpeedBytes[SPEEDMETER_SLOTS]; long long m_iSpeedTotalBytes; int m_iSpeedTime[SPEEDMETER_SLOTS]; int m_iSpeedStartTime; time_t m_tSpeedCorrection; int m_iSpeedBytesIndex; int m_iCurSecBytes; time_t m_tCurSecTime; Mutex m_mutexSpeed; // time long long m_iAllBytes; time_t m_tStartServer; time_t m_tLastCheck; time_t m_tLastTimeOffset; time_t m_tStartDownload; time_t m_tPausedFrom; bool m_bStandBy; Mutex m_mutexStat; // data volume bool m_bStatChanged; ServerVolumes m_ServerVolumes; Mutex m_mutexVolume; void ResetSpeedStat(); void AdjustTimeOffset(); protected: virtual void LogDebugInfo(); public: StatMeter(); ~StatMeter(); void Init(); int CalcCurrentDownloadSpeed(); int CalcMomentaryDownloadSpeed(); void AddSpeedReading(int iBytes); void AddServerData(int iBytes, int iServerID); void CalcTotalStat(int* iUpTimeSec, int* iDnTimeSec, long long* iAllBytes, bool* bStandBy); bool GetStandBy() { return m_bStandBy; } void IntervalCheck(); void EnterLeaveStandBy(bool bEnter); ServerVolumes* LockServerVolumes(); void UnlockServerVolumes(); void Save(); bool Load(bool* pPerfectServerMatch); }; extern StatMeter* g_pStatMeter; #endif nzbget-16.4/daemon/nntp/ArticleDownloader.cpp0000644000175000017500000004376612630544544021130 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #include #ifdef WIN32 #include #else #include #include #endif #include #include #include "nzbget.h" #include "ArticleDownloader.h" #include "ArticleWriter.h" #include "Decoder.h" #include "Log.h" #include "Options.h" #include "ServerPool.h" #include "StatMeter.h" #include "Util.h" ArticleDownloader::ArticleDownloader() { debug("Creating ArticleDownloader"); m_szInfoName = NULL; m_szConnectionName[0] = '\0'; m_pConnection = NULL; m_eStatus = adUndefined; m_eFormat = Decoder::efUnknown; m_szArticleFilename = NULL; m_iDownloadedSize = 0; m_ArticleWriter.SetOwner(this); SetLastUpdateTimeNow(); } ArticleDownloader::~ArticleDownloader() { debug("Destroying ArticleDownloader"); free(m_szInfoName); free(m_szArticleFilename); } void ArticleDownloader::SetInfoName(const char* szInfoName) { m_szInfoName = strdup(szInfoName); m_ArticleWriter.SetInfoName(m_szInfoName); } /* * How server management (for one particular article) works: - there is a list of failed servers which is initially empty; - level is initially 0; - request a connection from server pool for current level; Exception: this step is skipped for the very first download attempt, because a level-0 connection is initially passed from queue manager; - try to download from server; - if connection to server cannot be established or download fails due to interrupted connection, try again (as many times as needed without limit) the same server until connection is OK; - if download fails with error "Not-Found" (article or group not found) or with CRC error, add the server to failed server list; - if download fails with general failure error (article incomplete, other unknown error codes), try the same server again as many times as defined by option ; if all attempts fail, add the server to failed server list; - if all servers from current level were tried, increase level; - if all servers from all levels were tried, break the loop with failure status. */ void ArticleDownloader::Run() { debug("Entering ArticleDownloader-loop"); SetStatus(adRunning); m_ArticleWriter.SetFileInfo(m_pFileInfo); m_ArticleWriter.SetArticleInfo(m_pArticleInfo); m_ArticleWriter.Prepare(); EStatus Status = adFailed; int iRetries = g_pOptions->GetRetries() > 0 ? g_pOptions->GetRetries() : 1; int iRemainedRetries = iRetries; Servers failedServers; failedServers.reserve(g_pServerPool->GetServers()->size()); NewsServer* pWantServer = NULL; NewsServer* pLastServer = NULL; int iLevel = 0; int iServerConfigGeneration = g_pServerPool->GetGeneration(); bool bForce = m_pFileInfo->GetNZBInfo()->GetForcePriority(); while (!IsStopped()) { Status = adFailed; SetStatus(adWaiting); while (!m_pConnection && !(IsStopped() || iServerConfigGeneration != g_pServerPool->GetGeneration())) { m_pConnection = g_pServerPool->GetConnection(iLevel, pWantServer, &failedServers); usleep(5 * 1000); } SetLastUpdateTimeNow(); SetStatus(adRunning); if (IsStopped() || (g_pOptions->GetPauseDownload() && !bForce) || (g_pOptions->GetTempPauseDownload() && !m_pFileInfo->GetExtraPriority()) || iServerConfigGeneration != g_pServerPool->GetGeneration()) { Status = adRetry; break; } pLastServer = m_pConnection->GetNewsServer(); m_pConnection->SetSuppressErrors(false); snprintf(m_szConnectionName, sizeof(m_szConnectionName), "%s (%s)", m_pConnection->GetNewsServer()->GetName(), m_pConnection->GetHost()); m_szConnectionName[sizeof(m_szConnectionName) - 1] = '\0'; // check server retention bool bRetentionFailure = m_pConnection->GetNewsServer()->GetRetention() > 0 && (time(NULL) - m_pFileInfo->GetTime()) / 86400 > m_pConnection->GetNewsServer()->GetRetention(); if (bRetentionFailure) { detail("Article %s @ %s failed: out of server retention (file age: %i, configured retention: %i)", m_szInfoName, m_szConnectionName, (time(NULL) - m_pFileInfo->GetTime()) / 86400, m_pConnection->GetNewsServer()->GetRetention()); Status = adFailed; FreeConnection(true); } if (m_pConnection && !IsStopped()) { detail("Downloading %s @ %s", m_szInfoName, m_szConnectionName); } // test connection bool bConnected = m_pConnection && m_pConnection->Connect(); if (bConnected && !IsStopped()) { NewsServer* pNewsServer = m_pConnection->GetNewsServer(); // Download article Status = Download(); if (Status == adFinished || Status == adFailed || Status == adNotFound || Status == adCrcError) { m_ServerStats.StatOp(pNewsServer->GetID(), Status == adFinished ? 1 : 0, Status == adFinished ? 0 : 1, ServerStatList::soSet); } } if (m_pConnection) { AddServerData(); } if (!bConnected && m_pConnection) { detail("Article %s @ %s failed: could not establish connection", m_szInfoName, m_szConnectionName); } if (Status == adConnectError) { bConnected = false; Status = adFailed; } if (bConnected && Status == adFailed) { iRemainedRetries--; } if (!bConnected && m_pConnection && !IsStopped()) { g_pServerPool->BlockServer(pLastServer); } pWantServer = NULL; if (bConnected && Status == adFailed && iRemainedRetries > 0 && !bRetentionFailure) { pWantServer = pLastServer; } else { FreeConnection(Status == adFinished || Status == adNotFound); } if (Status == adFinished || Status == adFatalError) { break; } if (IsStopped() || (g_pOptions->GetPauseDownload() && !bForce) || (g_pOptions->GetTempPauseDownload() && !m_pFileInfo->GetExtraPriority()) || iServerConfigGeneration != g_pServerPool->GetGeneration()) { Status = adRetry; break; } if (!pWantServer && (bConnected || bRetentionFailure)) { 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 (Servers::iterator it = g_pServerPool->GetServers()->begin(); it != g_pServerPool->GetServers()->end(); it++) { NewsServer* pCandidateServer = *it; if (pCandidateServer->GetNormLevel() == iLevel) { bool bServerFailed = !pCandidateServer->GetActive() || pCandidateServer->GetMaxConnections() == 0; if (!bServerFailed) { for (Servers::iterator it = failedServers.begin(); it != failedServers.end(); it++) { NewsServer* pIgnoreServer = *it; if (pIgnoreServer == pCandidateServer || (pIgnoreServer->GetGroup() > 0 && pIgnoreServer->GetGroup() == pCandidateServer->GetGroup() && pIgnoreServer->GetNormLevel() == pCandidateServer->GetNormLevel())) { bServerFailed = true; break; } } } if (!bServerFailed) { bAllServersOnLevelFailed = false; break; } } } if (bAllServersOnLevelFailed) { if (iLevel < g_pServerPool->GetMaxNormLevel()) { detail("Article %s @ all level %i servers failed, increasing level", m_szInfoName, iLevel); iLevel++; } else { detail("Article %s @ all servers failed", m_szInfoName); Status = adFailed; break; } } iRemainedRetries = iRetries; } } FreeConnection(Status == adFinished); if (m_ArticleWriter.GetDuplicate()) { Status = adFinished; } if (Status != adFinished && Status != adRetry) { Status = adFailed; } if (IsStopped()) { detail("Download %s cancelled", m_szInfoName); Status = adRetry; } if (Status == adFailed) { detail("Download %s failed", m_szInfoName); } SetStatus(Status); Notify(NULL); debug("Exiting ArticleDownloader-loop"); } ArticleDownloader::EStatus ArticleDownloader::Download() { const char* szResponse = NULL; EStatus Status = adRunning; m_bWritingStarted = false; m_pArticleInfo->SetCrc(0); if (m_pConnection->GetNewsServer()->GetJoinGroup()) { // change group for (FileInfo::Groups::iterator it = m_pFileInfo->GetGroups()->begin(); it != m_pFileInfo->GetGroups()->end(); it++) { szResponse = m_pConnection->JoinGroup(*it); if (szResponse && !strncmp(szResponse, "2", 1)) { break; } } Status = CheckResponse(szResponse, "could not join group"); if (Status != adFinished) { return Status; } } // retrieve article char tmp[1024]; snprintf(tmp, 1024, "ARTICLE %s\r\n", m_pArticleInfo->GetMessageID()); tmp[1024-1] = '\0'; for (int retry = 3; retry > 0; retry--) { szResponse = m_pConnection->Request(tmp); if ((szResponse && !strncmp(szResponse, "2", 1)) || m_pConnection->GetAuthError()) { break; } } Status = CheckResponse(szResponse, "could not fetch article"); if (Status != adFinished) { return Status; } if (g_pOptions->GetDecode()) { m_YDecoder.Clear(); m_YDecoder.SetCrcCheck(g_pOptions->GetCrcCheck()); m_UDecoder.Clear(); } bool bBody = false; bool bEnd = false; const int LineBufSize = 1024*10; char* szLineBuf = (char*)malloc(LineBufSize); Status = adRunning; while (!IsStopped()) { time_t tOldTime = m_tLastUpdateTime; SetLastUpdateTimeNow(); if (tOldTime != m_tLastUpdateTime) { AddServerData(); } // Throttle the bandwidth while (!IsStopped() && (g_pOptions->GetDownloadRate() > 0.0f) && (g_pStatMeter->CalcCurrentDownloadSpeed() > g_pOptions->GetDownloadRate() || g_pStatMeter->CalcMomentaryDownloadSpeed() > g_pOptions->GetDownloadRate())) { SetLastUpdateTimeNow(); usleep(10 * 1000); } int iLen = 0; char* line = m_pConnection->ReadLine(szLineBuf, LineBufSize, &iLen); g_pStatMeter->AddSpeedReading(iLen); if (g_pOptions->GetAccurateRate()) { AddServerData(); } // Have we encountered a timeout? if (!line) { if (!IsStopped()) { detail("Article %s @ %s failed: Unexpected end of article", m_szInfoName, m_szConnectionName); } Status = adFailed; break; } //detect end of article if (!strcmp(line, ".\r\n") || !strcmp(line, ".\n")) { bEnd = true; break; } //detect lines starting with "." (marked as "..") if (!strncmp(line, "..", 2)) { line++; iLen--; } if (!bBody) { // detect body of article if (*line == '\r' || *line == '\n') { bBody = true; } // check id of returned article else if (!strncmp(line, "Message-ID: ", 12)) { char* p = line + 12; if (strncmp(p, m_pArticleInfo->GetMessageID(), strlen(m_pArticleInfo->GetMessageID()))) { if (char* e = strrchr(p, '\r')) *e = '\0'; // remove trailing CR-character detail("Article %s @ %s failed: Wrong message-id, expected %s, returned %s", m_szInfoName, m_szConnectionName, m_pArticleInfo->GetMessageID(), p); Status = adFailed; break; } } } if (m_eFormat == Decoder::efUnknown && g_pOptions->GetDecode()) { m_eFormat = Decoder::DetectFormat(line, iLen, bBody); if (m_eFormat != Decoder::efUnknown) { // sometimes news servers misbehave and send article body without new line separator between headers and body // if we found decoder signature we know the body is already arrived bBody = true; } } // write to output file if (((bBody && m_eFormat != Decoder::efUnknown) || !g_pOptions->GetDecode()) && !Write(line, iLen)) { Status = adFatalError; break; } } free(szLineBuf); if (!bEnd && Status == adRunning && !IsStopped()) { detail("Article %s @ %s failed: article incomplete", m_szInfoName, m_szConnectionName); Status = adFailed; } if (IsStopped()) { Status = adFailed; } if (Status == adRunning) { FreeConnection(true); Status = DecodeCheck(); } if (m_bWritingStarted) { m_ArticleWriter.Finish(Status == adFinished); } if (Status == adFinished) { detail("Successfully downloaded %s", m_szInfoName); } return Status; } ArticleDownloader::EStatus ArticleDownloader::CheckResponse(const char* szResponse, const char* szComment) { if (!szResponse) { if (!IsStopped()) { detail("Article %s @ %s failed, %s: Connection closed by remote host", m_szInfoName, m_szConnectionName, szComment); } return adConnectError; } else if (m_pConnection->GetAuthError() || !strncmp(szResponse, "400", 3) || !strncmp(szResponse, "499", 3)) { detail("Article %s @ %s failed, %s: %s", m_szInfoName, m_szConnectionName, szComment, szResponse); return adConnectError; } else if (!strncmp(szResponse, "41", 2) || !strncmp(szResponse, "42", 2) || !strncmp(szResponse, "43", 2)) { detail("Article %s @ %s failed, %s: %s", m_szInfoName, m_szConnectionName, szComment, szResponse); return adNotFound; } else if (!strncmp(szResponse, "2", 1)) { // OK return adFinished; } else { // unknown error, no special handling detail("Article %s @ %s failed, %s: %s", m_szInfoName, m_szConnectionName, szComment, szResponse); return adFailed; } } bool ArticleDownloader::Write(char* szLine, int iLen) { const char* szArticleFilename = NULL; long long iArticleFileSize = 0; long long iArticleOffset = 0; int iArticleSize = 0; if (g_pOptions->GetDecode()) { if (m_eFormat == Decoder::efYenc) { iLen = m_YDecoder.DecodeBuffer(szLine, iLen); szArticleFilename = m_YDecoder.GetArticleFilename(); iArticleFileSize = m_YDecoder.GetSize(); } else if (m_eFormat == Decoder::efUx) { iLen = m_UDecoder.DecodeBuffer(szLine, iLen); szArticleFilename = m_UDecoder.GetArticleFilename(); } else { detail("Decoding %s failed: unsupported encoding", m_szInfoName); return false; } if (iLen > 0 && m_eFormat == Decoder::efYenc) { if (m_YDecoder.GetBegin() == 0 || m_YDecoder.GetEnd() == 0) { return false; } iArticleOffset = m_YDecoder.GetBegin() - 1; iArticleSize = (int)(m_YDecoder.GetEnd() - m_YDecoder.GetBegin() + 1); } } if (!m_bWritingStarted && iLen > 0) { if (!m_ArticleWriter.Start(m_eFormat, szArticleFilename, iArticleFileSize, iArticleOffset, iArticleSize)) { return false; } m_bWritingStarted = true; } bool bOK = iLen == 0 || m_ArticleWriter.Write(szLine, iLen); return bOK; } ArticleDownloader::EStatus ArticleDownloader::DecodeCheck() { if (g_pOptions->GetDecode()) { Decoder* pDecoder = NULL; if (m_eFormat == Decoder::efYenc) { pDecoder = &m_YDecoder; } else if (m_eFormat == Decoder::efUx) { pDecoder = &m_UDecoder; } else { detail("Decoding %s failed: no binary data or unsupported encoding format", m_szInfoName); return adFailed; } Decoder::EStatus eStatus = pDecoder->Check(); if (eStatus == Decoder::eFinished) { if (pDecoder->GetArticleFilename()) { free(m_szArticleFilename); m_szArticleFilename = strdup(pDecoder->GetArticleFilename()); } if (m_eFormat == Decoder::efYenc) { m_pArticleInfo->SetCrc(g_pOptions->GetCrcCheck() ? m_YDecoder.GetCalculatedCrc() : m_YDecoder.GetExpectedCrc()); } return adFinished; } else if (eStatus == Decoder::eCrcError) { detail("Decoding %s failed: CRC-Error", m_szInfoName); return adCrcError; } else if (eStatus == Decoder::eArticleIncomplete) { detail("Decoding %s failed: article incomplete", m_szInfoName); return adFailed; } else if (eStatus == Decoder::eInvalidSize) { detail("Decoding %s failed: size mismatch", m_szInfoName); return adFailed; } else if (eStatus == Decoder::eNoBinaryData) { detail("Decoding %s failed: no binary data found", m_szInfoName); return adFailed; } else { detail("Decoding %s failed", m_szInfoName); return adFailed; } } else { return adFinished; } } void ArticleDownloader::LogDebugInfo() { char szTime[50]; #ifdef HAVE_CTIME_R_3 ctime_r(&m_tLastUpdateTime, szTime, 50); #else ctime_r(&m_tLastUpdateTime, szTime); #endif info(" Download: Status=%i, LastUpdateTime=%s, InfoName=%s", m_eStatus, szTime, m_szInfoName); } void ArticleDownloader::Stop() { debug("Trying to stop ArticleDownloader"); Thread::Stop(); m_mutexConnection.Lock(); if (m_pConnection) { m_pConnection->SetSuppressErrors(true); m_pConnection->Cancel(); } m_mutexConnection.Unlock(); debug("ArticleDownloader stopped successfully"); } bool ArticleDownloader::Terminate() { NNTPConnection* pConnection = m_pConnection; bool terminated = Kill(); if (terminated && pConnection) { debug("Terminating connection"); pConnection->SetSuppressErrors(true); pConnection->Cancel(); pConnection->Disconnect(); g_pStatMeter->AddServerData(pConnection->FetchTotalBytesRead(), pConnection->GetNewsServer()->GetID()); g_pServerPool->FreeConnection(pConnection, true); } return terminated; } void ArticleDownloader::FreeConnection(bool bKeepConnected) { if (m_pConnection) { debug("Releasing connection"); m_mutexConnection.Lock(); if (!bKeepConnected || m_pConnection->GetStatus() == Connection::csCancelled) { m_pConnection->Disconnect(); } AddServerData(); g_pServerPool->FreeConnection(m_pConnection, true); m_pConnection = NULL; m_mutexConnection.Unlock(); } } void ArticleDownloader::AddServerData() { int iBytesRead = m_pConnection->FetchTotalBytesRead(); g_pStatMeter->AddServerData(iBytesRead, m_pConnection->GetNewsServer()->GetID()); m_iDownloadedSize += iBytesRead; } nzbget-16.4/daemon/nntp/NNTPConnection.cpp0000644000175000017500000001376112630544544020315 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2015 Andrey Prygunkov * * 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 #include #include #include #include "nzbget.h" #include "Log.h" #include "NNTPConnection.h" #include "Connection.h" #include "NewsServer.h" static const int CONNECTION_LINEBUFFER_SIZE = 1024*10; NNTPConnection::NNTPConnection(NewsServer* pNewsServer) : Connection(pNewsServer->GetHost(), pNewsServer->GetPort(), pNewsServer->GetTLS()) { m_pNewsServer = pNewsServer; m_szActiveGroup = NULL; m_szLineBuf = (char*)malloc(CONNECTION_LINEBUFFER_SIZE); m_bAuthError = false; SetCipher(pNewsServer->GetCipher()); } NNTPConnection::~NNTPConnection() { free(m_szActiveGroup); free(m_szLineBuf); } const char* NNTPConnection::Request(const char* req) { if (!req) { return NULL; } m_bAuthError = false; WriteLine(req); char* answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL); if (!answer) { return NULL; } if (!strncmp(answer, "480", 3)) { debug("%s requested authorization", GetHost()); if (!Authenticate()) { return NULL; } //try again WriteLine(req); answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL); } return answer; } bool NNTPConnection::Authenticate() { if (strlen(m_pNewsServer->GetUser()) == 0 || strlen(m_pNewsServer->GetPassword()) == 0) { ReportError("Could not connect to %s: server requested authorization but username/password are not set in settings", m_pNewsServer->GetHost(), false, 0); m_bAuthError = true; return false; } m_bAuthError = !AuthInfoUser(0); return !m_bAuthError; } bool NNTPConnection::AuthInfoUser(int iRecur) { if (iRecur > 10) { return false; } char tmp[1024]; snprintf(tmp, 1024, "AUTHINFO USER %s\r\n", m_pNewsServer->GetUser()); tmp[1024-1] = '\0'; WriteLine(tmp); char* answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL); if (!answer) { ReportErrorAnswer("Authorization for %s (%s) failed: Connection closed by remote host", NULL); return false; } if (!strncmp(answer, "281", 3)) { debug("Authorization for %s successful", GetHost()); return true; } else if (!strncmp(answer, "381", 3)) { return AuthInfoPass(++iRecur); } else if (!strncmp(answer, "480", 3)) { return AuthInfoUser(++iRecur); } if (char* p = strrchr(answer, '\r')) *p = '\0'; // remove last CRLF from error message if (GetStatus() != csCancelled) { ReportErrorAnswer("Authorization for %s (%s) failed: %s", answer); } return false; } bool NNTPConnection::AuthInfoPass(int iRecur) { if (iRecur > 10) { return false; } char tmp[1024]; snprintf(tmp, 1024, "AUTHINFO PASS %s\r\n", m_pNewsServer->GetPassword()); tmp[1024-1] = '\0'; WriteLine(tmp); char* answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL); if (!answer) { ReportErrorAnswer("Authorization failed for %s (%s): Connection closed by remote host", NULL); return false; } else if (!strncmp(answer, "2", 1)) { debug("Authorization for %s successful", GetHost()); return true; } else if (!strncmp(answer, "381", 3)) { return AuthInfoPass(++iRecur); } if (char* p = strrchr(answer, '\r')) *p = '\0'; // remove last CRLF from error message if (GetStatus() != csCancelled) { ReportErrorAnswer("Authorization for %s (%s) failed: %s", answer); } return false; } const char* NNTPConnection::JoinGroup(const char* grp) { if (m_szActiveGroup && !strcmp(m_szActiveGroup, grp)) { // already in group strcpy(m_szLineBuf, "211 "); return m_szLineBuf; } char tmp[1024]; snprintf(tmp, 1024, "GROUP %s\r\n", grp); tmp[1024-1] = '\0'; const char* answer = Request(tmp); if (answer && !strncmp(answer, "2", 1)) { debug("Changed group to %s on %s", grp, GetHost()); free(m_szActiveGroup); m_szActiveGroup = strdup(grp); } else { debug("Error changing group on %s to %s: %s.", GetHost(), grp, answer); } return answer; } bool NNTPConnection::Connect() { debug("Opening connection to %s", GetHost()); if (m_eStatus == csConnected) { return true; } if (!Connection::Connect()) { return false; } char* answer = ReadLine(m_szLineBuf, CONNECTION_LINEBUFFER_SIZE, NULL); if (!answer) { ReportErrorAnswer("Connection to %s (%s) failed: Connection closed by remote host", NULL); Disconnect(); return false; } if (strncmp(answer, "2", 1)) { ReportErrorAnswer("Connection to %s (%s) failed: %s", answer); Disconnect(); return false; } if ((strlen(m_pNewsServer->GetUser()) > 0 && strlen(m_pNewsServer->GetPassword()) > 0) && !Authenticate()) { return false; } debug("Connection to %s established", GetHost()); return true; } bool NNTPConnection::Disconnect() { if (m_eStatus == csConnected) { if (!m_bBroken) { Request("quit\r\n"); } free(m_szActiveGroup); m_szActiveGroup = NULL; } return Connection::Disconnect(); } void NNTPConnection::ReportErrorAnswer(const char* szMsgPrefix, const char* szAnswer) { char szErrStr[1024]; snprintf(szErrStr, 1024, szMsgPrefix, m_pNewsServer->GetName(), m_pNewsServer->GetHost(), szAnswer); szErrStr[1024-1] = '\0'; ReportError(szErrStr, NULL, false, 0); } nzbget-16.4/daemon/nntp/NewsServer.cpp0000644000175000017500000000414012630544544017610 0ustar andreasandreas/* * This file if part of nzbget * * Copyright (C) 2004 Sven Henkel * Copyright (C) 2007-2014 Andrey Prygunkov * * 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 #include #include #include "nzbget.h" #include "NewsServer.h" NewsServer::NewsServer(int iID, bool bActive, const char* szName, const char* szHost, int iPort, const char* szUser, const char* szPass, bool bJoinGroup, bool bTLS, const char* szCipher, int iMaxConnections, int iRetention, int iLevel, int iGroup) { m_iID = iID; m_iStateID = 0; m_bActive = bActive; m_iPort = iPort; m_iLevel = iLevel; m_iNormLevel = iLevel; m_iGroup = iGroup; m_iMaxConnections = iMaxConnections; m_bJoinGroup = bJoinGroup; m_bTLS = bTLS; m_szHost = strdup(szHost ? szHost : ""); m_szUser = strdup(szUser ? szUser : ""); m_szPassword = strdup(szPass ? szPass : ""); m_szCipher = strdup(szCipher ? szCipher : ""); m_iRetention = iRetention; m_tBlockTime = 0; if (szName && strlen(szName) > 0) { m_szName = strdup(szName); } else { m_szName = (char*)malloc(20); snprintf(m_szName, 20, "server%i", iID); m_szName[20-1] = '\0'; } } NewsServer::~NewsServer() { free(m_szName); free(m_szHost); free(m_szUser); free(m_szPassword); free(m_szCipher); } nzbget-16.4/daemon/nntp/ArticleWriter.cpp0000644000175000017500000006560512630544544020302 0ustar andreasandreas/* * This file is part of nzbget * * Copyright (C) 2014-2015 Andrey Prygunkov * * 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 #include #include #ifdef WIN32 #include #else #include #include #endif #include #include #include #include "nzbget.h" #include "ArticleWriter.h" #include "DiskState.h" #include "Options.h" #include "Log.h" #include "Util.h" ArticleWriter::ArticleWriter() { debug("Creating ArticleWriter"); m_szTempFilename = NULL; m_szOutputFilename = NULL; m_szResultFilename = NULL; m_szInfoName = NULL; m_eFormat = Decoder::efUnknown; m_pArticleData = NULL; m_bDuplicate = false; m_bFlushing = false; } ArticleWriter::~ArticleWriter() { debug("Destroying ArticleWriter"); free(m_szOutputFilename); free(m_szTempFilename); free(m_szInfoName); if (m_pArticleData) { free(m_pArticleData); g_pArticleCache->Free(m_iArticleSize); } if (m_bFlushing) { g_pArticleCache->UnlockFlush(); } } void ArticleWriter::SetInfoName(const char* szInfoName) { m_szInfoName = strdup(szInfoName); } void ArticleWriter::SetWriteBuffer(FILE* pOutFile, int iRecSize) { if (g_pOptions->GetWriteBuffer() > 0) { setvbuf(pOutFile, NULL, _IOFBF, iRecSize > 0 && iRecSize < g_pOptions->GetWriteBuffer() * 1024 ? iRecSize : g_pOptions->GetWriteBuffer() * 1024); } } void ArticleWriter::Prepare() { BuildOutputFilename(); m_szResultFilename = m_pArticleInfo->GetResultFilename(); } bool ArticleWriter::Start(Decoder::EFormat eFormat, const char* szFilename, long long iFileSize, long long iArticleOffset, int iArticleSize) { char szErrBuf[256]; m_pOutFile = NULL; m_eFormat = eFormat; m_iArticleOffset = iArticleOffset; m_iArticleSize = iArticleSize ? iArticleSize : m_pArticleInfo->GetSize(); m_iArticlePtr = 0; // prepare file for writing if (m_eFormat == Decoder::efYenc) { if (g_pOptions->GetDupeCheck() && m_pFileInfo->GetNZBInfo()->GetDupeMode() != dmForce && !m_pFileInfo->GetNZBInfo()->GetManyDupeFiles()) { m_pFileInfo->LockOutputFile(); bool bOutputInitialized = m_pFileInfo->GetOutputInitialized(); if (!g_pOptions->GetDirectWrite()) { m_pFileInfo->SetOutputInitialized(true); } m_pFileInfo->UnlockOutputFile(); if (!bOutputInitialized && szFilename && Util::FileExists(m_pFileInfo->GetNZBInfo()->GetDestDir(), szFilename)) { m_bDuplicate = true; return false; } } if (g_pOptions->GetDirectWrite()) { m_pFileInfo->LockOutputFile(); if (!m_pFileInfo->GetOutputInitialized()) { if (!CreateOutputFile(iFileSize)) { m_pFileInfo->UnlockOutputFile(); return false; } m_pFileInfo->SetOutputInitialized(true); } m_pFileInfo->UnlockOutputFile(); } } // allocate cache buffer if (g_pOptions->GetArticleCache() > 0 && g_pOptions->GetDecode() && (!g_pOptions->GetDirectWrite() || m_eFormat == Decoder::efYenc)) { if (m_pArticleData) { free(m_pArticleData); g_pArticleCache->Free(m_iArticleSize); } m_pArticleData = (char*)g_pArticleCache->Alloc(m_iArticleSize); while (!m_pArticleData && g_pArticleCache->GetFlushing()) { usleep(5 * 1000); m_pArticleData = (char*)g_pArticleCache->Alloc(m_iArticleSize); } if (!m_pArticleData) { detail("Article cache is full, using disk for %s", m_szInfoName); } } if (!m_pArticleData) { bool bDirectWrite = g_pOptions->GetDirectWrite() && m_eFormat == Decoder::efYenc; const char* szFilename = bDirectWrite ? m_szOutputFilename : m_szTempFilename; m_pOutFile = fopen(szFilename, bDirectWrite ? FOPEN_RBP : FOPEN_WB); if (!m_pOutFile) { m_pFileInfo->GetNZBInfo()->PrintMessage(Message::mkError, "Could not %s file %s: %s", bDirectWrite ? "open" : "create", szFilename, Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf))); return false; } SetWriteBuffer(m_pOutFile, m_pArticleInfo->GetSize()); if (g_pOptions->GetDirectWrite() && m_eFormat == Decoder::efYenc) { fseek(m_pOutFile, m_iArticleOffset, SEEK_SET); } } return true; } bool ArticleWriter::Write(char* szBufffer, int iLen) { if (g_pOptions->GetDecode()) { m_iArticlePtr += iLen; } if (g_pOptions->GetDecode() && m_pArticleData) { if (m_iArticlePtr > m_iArticleSize) { detail("Decoding %s failed: article size mismatch", m_szInfoName); return false; } memcpy(m_pArticleData + m_iArticlePtr - iLen, szBufffer, iLen); return true; } return fwrite(szBufffer, 1, iLen, m_pOutFile) > 0; } void ArticleWriter::Finish(bool bSuccess) { char szErrBuf[256]; if (m_pOutFile) { fclose(m_pOutFile); m_pOutFile = NULL; } if (!bSuccess) { remove(m_szTempFilename); remove(m_szResultFilename); return; } bool bDirectWrite = g_pOptions->GetDirectWrite() && m_eFormat == Decoder::efYenc; if (g_pOptions->GetDecode()) { if (!bDirectWrite && !m_pArticleData) { if (!Util::MoveFile(m_szTempFilename, m_szResultFilename)) { m_pFileInfo->GetNZBInfo()->PrintMessage(Message::mkError, "Could not rename file %s to %s: %s", m_szTempFilename, m_szResultFilename, Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf))); } } remove(m_szTempFilename); if (m_pArticleData) { if (m_iArticleSize != m_iArticlePtr) { m_pArticleData = (char*)g_pArticleCache->Realloc(m_pArticleData, m_iArticleSize, m_iArticlePtr); } g_pArticleCache->LockContent(); m_pArticleInfo->AttachSegment(m_pArticleData, m_iArticleOffset, m_iArticlePtr); m_pFileInfo->SetCachedArticles(m_pFileInfo->GetCachedArticles() + 1); g_pArticleCache->UnlockContent(); m_pArticleData = NULL; } else { m_pArticleInfo->SetSegmentOffset(m_iArticleOffset); m_pArticleInfo->SetSegmentSize(m_iArticlePtr); } } else { // rawmode if (!Util::MoveFile(m_szTempFilename, m_szResultFilename)) { m_pFileInfo->GetNZBInfo()->PrintMessage(Message::mkError, "Could not move file %s to %s: %s", m_szTempFilename, m_szResultFilename, Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf))); } } } /* creates output file and subdirectores */ bool ArticleWriter::CreateOutputFile(long long 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; if (iMaxlen > 1024-1) iMaxlen = 1024-1; strncpy(szDestDir, m_szOutputFilename, iMaxlen); szDestDir[iMaxlen] = '\0'; char szErrBuf[1024]; if (!Util::ForceDirectories(szDestDir, szErrBuf, sizeof(szErrBuf))) { m_pFileInfo->GetNZBInfo()->PrintMessage(Message::mkError, "Could not create directory %s: %s", szDestDir, szErrBuf); return false; } if (!Util::CreateSparseFile(m_szOutputFilename, iSize)) { m_pFileInfo->GetNZBInfo()->PrintMessage(Message::mkError, "Could not create file %s", m_szOutputFilename); return false; } return true; } void ArticleWriter::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'; m_szTempFilename = strdup(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(); m_szOutputFilename = strdup(szFilename); } } void ArticleWriter::CompleteFileParts() { debug("Completing file parts"); debug("ArticleFilename: %s", m_pFileInfo->GetFilename()); bool bDirectWrite = g_pOptions->GetDirectWrite() && m_pFileInfo->GetOutputInitialized(); char szErrBuf[256]; char szNZBName[1024]; char szNZBDestDir[1024]; // the locking is needed for accessing the members of NZBInfo DownloadQueue::Lock(); strncpy(szNZBName, m_pFileInfo->GetNZBInfo()->GetName(), 1024); strncpy(szNZBDestDir, m_pFileInfo->GetNZBInfo()->GetDestDir(), 1024); DownloadQueue::Unlock(); szNZBName[1024-1] = '\0'; szNZBDestDir[1024-1] = '\0'; char szInfoFilename[1024]; snprintf(szInfoFilename, 1024, "%s%c%s", szNZBName, (int)PATH_SEPARATOR, m_pFileInfo->GetFilename()); szInfoFilename[1024-1] = '\0'; bool bCached = m_pFileInfo->GetCachedArticles() > 0; if (!g_pOptions->GetDecode()) { detail("Moving articles for %s", szInfoFilename); } else if (bDirectWrite && bCached) { detail("Writing articles for %s", szInfoFilename); } else if (bDirectWrite) { detail("Checking articles for %s", szInfoFilename); } else { detail("Joining articles for %s", szInfoFilename); } // Ensure the DstDir is created if (!Util::ForceDirectories(szNZBDestDir, szErrBuf, sizeof(szErrBuf))) { m_pFileInfo->GetNZBInfo()->PrintMessage(Message::mkError, "Could not create directory %s: %s", szNZBDestDir, szErrBuf); return; } char ofn[1024]; Util::MakeUniqueFilename(ofn, 1024, szNZBDestDir, m_pFileInfo->GetFilename()); FILE* outfile = NULL; char tmpdestfile[1024]; snprintf(tmpdestfile, 1024, "%s.tmp", ofn); tmpdestfile[1024-1] = '\0'; if (g_pOptions->GetDecode() && !bDirectWrite) { remove(tmpdestfile); outfile = fopen(tmpdestfile, FOPEN_WBP); if (!outfile) { m_pFileInfo->GetNZBInfo()->PrintMessage(Message::mkError, "Could not create file %s: %s", tmpdestfile, Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf))); return; } } else if (bDirectWrite && bCached) { outfile = fopen(m_szOutputFilename, FOPEN_RBP); if (!outfile) { m_pFileInfo->GetNZBInfo()->PrintMessage(Message::mkError, "Could not open file %s: %s", m_szOutputFilename, Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf))); return; } strncpy(tmpdestfile, m_szOutputFilename, 1024); tmpdestfile[1024-1] = '\0'; } else if (!g_pOptions->GetDecode()) { remove(tmpdestfile); if (!Util::CreateDirectory(ofn)) { m_pFileInfo->GetNZBInfo()->PrintMessage(Message::mkError, "Could not create directory %s: %s", ofn, Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf))); return; } } if (outfile) { SetWriteBuffer(outfile, 0); } if (bCached) { g_pArticleCache->LockFlush(); m_bFlushing = true; } static const int BUFFER_SIZE = 1024 * 64; char* buffer = NULL; bool bFirstArticle = true; unsigned long lCrc = 0; if (g_pOptions->GetDecode() && !bDirectWrite) { buffer = (char*)malloc(BUFFER_SIZE); } for (FileInfo::Articles::iterator it = m_pFileInfo->GetArticles()->begin(); it != m_pFileInfo->GetArticles()->end(); it++) { ArticleInfo* pa = *it; if (pa->GetStatus() != ArticleInfo::aiFinished) { continue; } if (g_pOptions->GetDecode() && !bDirectWrite && pa->GetSegmentOffset() > -1 && pa->GetSegmentOffset() > ftell(outfile) && ftell(outfile) > -1) { memset(buffer, 0, BUFFER_SIZE); while (pa->GetSegmentOffset() > ftell(outfile) && ftell(outfile) > -1 && fwrite(buffer, 1, (std::min)((int)(pa->GetSegmentOffset() - ftell(outfile)), BUFFER_SIZE), outfile)) ; } if (pa->GetSegmentContent()) { fseek(outfile, pa->GetSegmentOffset(), SEEK_SET); fwrite(pa->GetSegmentContent(), 1, pa->GetSegmentSize(), outfile); pa->DiscardSegment(); SetLastUpdateTimeNow(); } else if (g_pOptions->GetDecode() && !bDirectWrite) { FILE* infile = pa->GetResultFilename() ? fopen(pa->GetResultFilename(), FOPEN_RB) : NULL; if (infile) { int cnt = BUFFER_SIZE; while (cnt == BUFFER_SIZE) { cnt = (int)fread(buffer, 1, BUFFER_SIZE, infile); fwrite(buffer, 1, cnt, outfile); SetLastUpdateTimeNow(); } fclose(infile); } else { m_pFileInfo->SetFailedArticles(m_pFileInfo->GetFailedArticles() + 1); m_pFileInfo->SetSuccessArticles(m_pFileInfo->GetSuccessArticles() - 1); m_pFileInfo->GetNZBInfo()->PrintMessage(Message::mkError, "Could not find file %s for %s%c%s [%i/%i]", pa->GetResultFilename(), szNZBName, (int)PATH_SEPARATOR, m_pFileInfo->GetFilename(), pa->GetPartNumber(), (int)m_pFileInfo->GetArticles()->size()); } } else if (!g_pOptions->GetDecode()) { char dstFileName[1024]; snprintf(dstFileName, 1024, "%s%c%03i", ofn, (int)PATH_SEPARATOR, pa->GetPartNumber()); dstFileName[1024-1] = '\0'; if (!Util::MoveFile(pa->GetResultFilename(), dstFileName)) { m_pFileInfo->GetNZBInfo()->PrintMessage(Message::mkError, "Could not move file %s to %s: %s", pa->GetResultFilename(), dstFileName, Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf))); } } if (m_eFormat == Decoder::efYenc) { lCrc = bFirstArticle ? pa->GetCrc() : Util::Crc32Combine(lCrc, pa->GetCrc(), pa->GetSegmentSize()); bFirstArticle = false; } } free(buffer); if (bCached) { g_pArticleCache->UnlockFlush(); m_bFlushing = false; } if (outfile) { fclose(outfile); if (!bDirectWrite && !Util::MoveFile(tmpdestfile, ofn)) { m_pFileInfo->GetNZBInfo()->PrintMessage(Message::mkError, "Could not move file %s to %s: %s", tmpdestfile, ofn, Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf))); } } if (bDirectWrite) { if (!Util::MoveFile(m_szOutputFilename, ofn)) { m_pFileInfo->GetNZBInfo()->PrintMessage(Message::mkError, "Could not move file %s to %s: %s", m_szOutputFilename, ofn, Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf))); } // if destination directory was changed delete the old directory (if empty) int iLen = strlen(szNZBDestDir); if (!(!strncmp(szNZBDestDir, m_szOutputFilename, iLen) && (m_szOutputFilename[iLen] == PATH_SEPARATOR || m_szOutputFilename[iLen] == ALT_PATH_SEPARATOR))) { debug("Checking old dir for: %s", m_szOutputFilename); char szOldDestDir[1024]; int iMaxlen = Util::BaseFileName(m_szOutputFilename) - m_szOutputFilename; if (iMaxlen > 1024-1) iMaxlen = 1024-1; strncpy(szOldDestDir, m_szOutputFilename, iMaxlen); szOldDestDir[iMaxlen] = '\0'; if (Util::DirEmpty(szOldDestDir)) { debug("Deleting old dir: %s", szOldDestDir); rmdir(szOldDestDir); } } } if (!bDirectWrite) { for (FileInfo::Articles::iterator it = m_pFileInfo->GetArticles()->begin(); it != m_pFileInfo->GetArticles()->end(); it++) { ArticleInfo* pa = *it; remove(pa->GetResultFilename()); } } if (m_pFileInfo->GetMissedArticles() == 0 && m_pFileInfo->GetFailedArticles() == 0) { m_pFileInfo->GetNZBInfo()->PrintMessage(Message::mkInfo, "Successfully downloaded %s", szInfoFilename); } else { m_pFileInfo->GetNZBInfo()->PrintMessage(Message::mkWarning, "%i of %i article downloads failed for \"%s\"", m_pFileInfo->GetMissedArticles() + m_pFileInfo->GetFailedArticles(), m_pFileInfo->GetTotalArticles(), szInfoFilename); if (g_pOptions->GetBrokenLog()) { char szBrokenLogName[1024]; snprintf(szBrokenLogName, 1024, "%s%c_brokenlog.txt", szNZBDestDir, (int)PATH_SEPARATOR); szBrokenLogName[1024-1] = '\0'; FILE* file = fopen(szBrokenLogName, FOPEN_AB); fprintf(file, "%s (%i/%i)%s", m_pFileInfo->GetFilename(), m_pFileInfo->GetSuccessArticles(), m_pFileInfo->GetTotalArticles(), LINE_ENDING); fclose(file); } lCrc = 0; if (g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode()) { g_pDiskState->DiscardFile(m_pFileInfo, false, true, false); g_pDiskState->SaveFileState(m_pFileInfo, true); } } CompletedFile::EStatus eFileStatus = m_pFileInfo->GetMissedArticles() == 0 && m_pFileInfo->GetFailedArticles() == 0 ? CompletedFile::cfSuccess : m_pFileInfo->GetSuccessArticles() > 0 ? CompletedFile::cfPartial : CompletedFile::cfFailure; // the locking is needed for accessing the members of NZBInfo DownloadQueue::Lock(); m_pFileInfo->GetNZBInfo()->GetCompletedFiles()->push_back(new CompletedFile( m_pFileInfo->GetID(), Util::BaseFileName(ofn), eFileStatus, lCrc)); if (strcmp(m_pFileInfo->GetNZBInfo()->GetDestDir(), szNZBDestDir)) { // destination directory was changed during completion, need to move the file MoveCompletedFiles(m_pFileInfo->GetNZBInfo(), szNZBDestDir); } DownloadQueue::Unlock(); } void ArticleWriter::FlushCache() { detail("Flushing cache for %s", m_szInfoName); bool bDirectWrite = g_pOptions->GetDirectWrite() && m_pFileInfo->GetOutputInitialized(); FILE* outfile = NULL; bool bNeedBufFile = false; char szDestFile[1024]; char szErrBuf[256]; int iFlushedArticles = 0; long long iFlushedSize = 0; g_pArticleCache->LockFlush(); FileInfo::Articles cachedArticles; cachedArticles.reserve(m_pFileInfo->GetArticles()->size()); g_pArticleCache->LockContent(); for (FileInfo::Articles::iterator it = m_pFileInfo->GetArticles()->begin(); it != m_pFileInfo->GetArticles()->end(); it++) { ArticleInfo* pa = *it; if (pa->GetSegmentContent()) { cachedArticles.push_back(pa); } } g_pArticleCache->UnlockContent(); for (FileInfo::Articles::iterator it = cachedArticles.begin(); it != cachedArticles.end(); it++) { if (m_pFileInfo->GetDeleted()) { // the file was deleted during flushing: stop flushing immediately break; } ArticleInfo* pa = *it; if (bDirectWrite && !outfile) { outfile = fopen(m_pFileInfo->GetOutputFilename(), FOPEN_RBP); if (!outfile) { m_pFileInfo->GetNZBInfo()->PrintMessage(Message::mkError, "Could not open file %s: %s", m_pFileInfo->GetOutputFilename(), Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf))); break; } bNeedBufFile = true; } if (!bDirectWrite) { snprintf(szDestFile, 1024, "%s.tmp", pa->GetResultFilename()); szDestFile[1024-1] = '\0'; outfile = fopen(szDestFile, FOPEN_WB); if (!outfile) { m_pFileInfo->GetNZBInfo()->PrintMessage(Message::mkError, "Could not create file %s: %s", "create", szDestFile, Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf))); break; } bNeedBufFile = true; } if (outfile && bNeedBufFile) { SetWriteBuffer(outfile, 0); bNeedBufFile = false; } if (bDirectWrite) { fseek(outfile, pa->GetSegmentOffset(), SEEK_SET); } fwrite(pa->GetSegmentContent(), 1, pa->GetSegmentSize(), outfile); iFlushedSize += pa->GetSegmentSize(); iFlushedArticles++; pa->DiscardSegment(); if (!bDirectWrite) { fclose(outfile); outfile = NULL; if (!Util::MoveFile(szDestFile, pa->GetResultFilename())) { m_pFileInfo->GetNZBInfo()->PrintMessage(Message::mkError, "Could not rename file %s to %s: %s", szDestFile, pa->GetResultFilename(), Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf))); } } } if (outfile) { fclose(outfile); } g_pArticleCache->LockContent(); m_pFileInfo->SetCachedArticles(m_pFileInfo->GetCachedArticles() - iFlushedArticles); g_pArticleCache->UnlockContent(); g_pArticleCache->UnlockFlush(); detail("Saved %i articles (%.2f MB) from cache into disk for %s", iFlushedArticles, (float)(iFlushedSize / 1024.0 / 1024.0), m_szInfoName); } bool ArticleWriter::MoveCompletedFiles(NZBInfo* pNZBInfo, const char* szOldDestDir) { if (pNZBInfo->GetCompletedFiles()->empty()) { return true; } // Ensure the DstDir is created char szErrBuf[1024]; if (!Util::ForceDirectories(pNZBInfo->GetDestDir(), szErrBuf, sizeof(szErrBuf))) { pNZBInfo->PrintMessage(Message::mkError, "Could not create directory %s: %s", pNZBInfo->GetDestDir(), szErrBuf); return false; } // move already downloaded files to new destination for (CompletedFiles::iterator it = pNZBInfo->GetCompletedFiles()->begin(); it != pNZBInfo->GetCompletedFiles()->end(); it++) { CompletedFile* pCompletedFile = *it; char szOldFileName[1024]; snprintf(szOldFileName, 1024, "%s%c%s", szOldDestDir, (int)PATH_SEPARATOR, pCompletedFile->GetFileName()); szOldFileName[1024-1] = '\0'; char szNewFileName[1024]; snprintf(szNewFileName, 1024, "%s%c%s", pNZBInfo->GetDestDir(), (int)PATH_SEPARATOR, pCompletedFile->GetFileName()); szNewFileName[1024-1] = '\0'; // check if file was not moved already if (strcmp(szOldFileName, szNewFileName)) { // prevent overwriting of existing files Util::MakeUniqueFilename(szNewFileName, 1024, pNZBInfo->GetDestDir(), pCompletedFile->GetFileName()); detail("Moving file %s to %s", szOldFileName, szNewFileName); if (!Util::MoveFile(szOldFileName, szNewFileName)) { char szErrBuf[256]; pNZBInfo->PrintMessage(Message::mkError, "Could not move file %s to %s: %s", szOldFileName, szNewFileName, Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf))); } } } // move brokenlog.txt if (g_pOptions->GetBrokenLog()) { char szOldBrokenLogName[1024]; snprintf(szOldBrokenLogName, 1024, "%s%c_brokenlog.txt", szOldDestDir, (int)PATH_SEPARATOR); szOldBrokenLogName[1024-1] = '\0'; if (Util::FileExists(szOldBrokenLogName)) { char szBrokenLogName[1024]; snprintf(szBrokenLogName, 1024, "%s%c_brokenlog.txt", pNZBInfo->GetDestDir(), (int)PATH_SEPARATOR); szBrokenLogName[1024-1] = '\0'; detail("Moving file %s to %s", szOldBrokenLogName, szBrokenLogName); if (Util::FileExists(szBrokenLogName)) { // copy content to existing new file, then delete old file FILE* outfile; outfile = fopen(szBrokenLogName, FOPEN_AB); if (outfile) { FILE* infile; infile = fopen(szOldBrokenLogName, FOPEN_RB); if (infile) { static const int BUFFER_SIZE = 1024 * 50; int cnt = BUFFER_SIZE; char* buffer = (char*)malloc(BUFFER_SIZE); while (cnt == BUFFER_SIZE) { cnt = (int)fread(buffer, 1, BUFFER_SIZE, infile); fwrite(buffer, 1, cnt, outfile); } fclose(infile); free(buffer); remove(szOldBrokenLogName); } else { pNZBInfo->PrintMessage(Message::mkError, "Could not open file %s", szOldBrokenLogName); } fclose(outfile); } else { pNZBInfo->PrintMessage(Message::mkError, "Could not open file %s", szBrokenLogName); } } else { // move to new destination if (!Util::MoveFile(szOldBrokenLogName, szBrokenLogName)) { char szErrBuf[256]; pNZBInfo->PrintMessage(Message::mkError, "Could not move file %s to %s: %s", szOldBrokenLogName, szBrokenLogName, Util::GetLastErrorMessage(szErrBuf, sizeof(szErrBuf))); } } } } // delete old directory (if empty) if (Util::DirEmpty(szOldDestDir)) { // check if there are pending writes into directory bool bPendingWrites = false; for (FileList::iterator it = pNZBInfo->GetFileList()->begin(); it != pNZBInfo->GetFileList()->end() && !bPendingWrites; it++) { FileInfo* pFileInfo = *it; if (pFileInfo->GetActiveDownloads() > 0) { pFileInfo->LockOutputFile(); bPendingWrites = pFileInfo->GetOutputInitialized() && !Util::EmptyStr(pFileInfo->GetOutputFilename()); pFileInfo->UnlockOutputFile(); } else { bPendingWrites = pFileInfo->GetOutputInitialized() && !Util::EmptyStr(pFileInfo->GetOutputFilename()); } } if (!bPendingWrites) { rmdir(szOldDestDir); } } return true; } ArticleCache::ArticleCache() { m_iAllocated = 0; m_bFlushing = false; m_pFileInfo = NULL; } void* ArticleCache::Alloc(int iSize) { m_mutexAlloc.Lock(); void* p = NULL; if (m_iAllocated + iSize <= (size_t)g_pOptions->GetArticleCache() * 1024 * 1024) { p = malloc(iSize); if (p) { if (!m_iAllocated && g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode() && g_pOptions->GetContinuePartial()) { g_pDiskState->WriteCacheFlag(); } m_iAllocated += iSize; } } m_mutexAlloc.Unlock(); return p; } void* ArticleCache::Realloc(void* buf, int iOldSize, int iNewSize) { m_mutexAlloc.Lock(); void* p = realloc(buf, iNewSize); if (p) { m_iAllocated += iNewSize - iOldSize; } else { p = buf; } m_mutexAlloc.Unlock(); return p; } void ArticleCache::Free(int iSize) { m_mutexAlloc.Lock(); m_iAllocated -= iSize; if (!m_iAllocated && g_pOptions->GetSaveQueue() && g_pOptions->GetServerMode() && g_pOptions->GetContinuePartial()) { g_pDiskState->DeleteCacheFlag(); } m_mutexAlloc.Unlock(); } void ArticleCache::LockFlush() { m_mutexFlush.Lock(); m_bFlushing = true; } void ArticleCache::UnlockFlush() { m_mutexFlush.Unlock(); m_bFlushing = false; } void ArticleCache::Run() { // automatically flush the cache if it is filled to 90% (only in DirectWrite mode) size_t iFillThreshold = (size_t)g_pOptions->GetArticleCache() * 1024 * 1024 / 100 * 90; int iResetCounter = 0; bool bJustFlushed = false; while (!IsStopped() || m_iAllocated > 0) { if ((bJustFlushed || iResetCounter >= 1000 || IsStopped() || (g_pOptions->GetDirectWrite() && m_iAllocated >= iFillThreshold)) && m_iAllocated > 0) { bJustFlushed = CheckFlush(m_iAllocated >= iFillThreshold); iResetCounter = 0; } else { usleep(5 * 1000); iResetCounter += 5; } } } bool ArticleCache::CheckFlush(bool bFlushEverything) { debug("Checking cache, Allocated: %i, FlushEverything: %i", m_iAllocated, (int)bFlushEverything); char szInfoName[1024]; DownloadQueue* pDownloadQueue = DownloadQueue::Lock(); for (NZBList::iterator it = pDownloadQueue->GetQueue()->begin(); it != pDownloadQueue->GetQueue()->end() && !m_pFileInfo; it++) { NZBInfo* pNZBInfo = *it; for (FileList::iterator it2 = pNZBInfo->GetFileList()->begin(); it2 != pNZBInfo->GetFileList()->end(); it2++) { FileInfo* pFileInfo = *it2; if (pFileInfo->GetCachedArticles() > 0 && (pFileInfo->GetActiveDownloads() == 0 || bFlushEverything)) { m_pFileInfo = pFileInfo; snprintf(szInfoName, 1024, "%s%c%s", m_pFileInfo->GetNZBInfo()->GetName(), (int)PATH_SEPARATOR, m_pFileInfo->GetFilename()); szInfoName[1024-1] = '\0'; break; } } } DownloadQueue::Unlock(); if (m_pFileInfo) { ArticleWriter* pArticleWriter = new ArticleWriter(); pArticleWriter->SetFileInfo(m_pFileInfo); pArticleWriter->SetInfoName(szInfoName); pArticleWriter->FlushCache(); delete pArticleWriter; m_pFileInfo = NULL; return true; } debug("Checking cache... nothing to flush"); return false; } nzbget-16.4/linux/0000755000175000017500000000000012630544544013717 5ustar andreasandreasnzbget-16.4/linux/build-unpack0000755000175000017500000001027112630544544016224 0ustar andreasandreas#!/bin/bash # # This file is part of nzbget # # Copyright (C) 2015 Andrey Prygunkov # # 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$ # # Setup strict bash error handling set -o nounset set -o errexit # Uncomment next line for debuging #set -x ALLTARGETS="i686 x86_64 armel armhf mipsel mipseb ppc6xx ppc500" ROOT=`pwd` OUTPUTDIR=$ROOT/setup BUILDDIR=temp echo "Usage:" echo " $(basename $0) [targets] [clean] [unpacker]" echo " unpacker - unrar, 7zip." echo # Parsing command line TARGETS="" CLEAN=no UNPACKERS="" for PARAM in "$@" do case $PARAM in clean|cleanup) CLEAN=yes ;; unrar|7zip) # using xargs to trim spaces UNPACKERS=`echo "$UNPACKERS $PARAM" | xargs` ;; *) if [ -d toolchain/$PARAM ]; then TARGETS=`echo "$TARGETS $PARAM" | xargs` else echo "Invalid parameter: $PARAM" exit 1 fi ;; esac done if [ "$TARGETS" == "" ]; then TARGETS="$ALLTARGETS" fi if [ "$UNPACKERS" == "" ]; then UNPACKERS="unrar 7zip" fi echo "Active configuration:" echo " targets : $TARGETS" echo " unpackers : $UNPACKERS" echo " cleanup : $CLEAN" echo # Building for UNPACKER in $UNPACKERS; do case $UNPACKER in unrar) EXENAME=unrar ;; 7zip) EXENAME=7za ;; esac if [ "$CLEAN" == "yes" ]; then rm -r -f $OUTPUTDIR/$EXENAME-* fi for TARGET in $TARGETS; do case $TARGET in mipsel|i?86|x86_64) ARCH=$TARGET ENDIAN=little ;; mipseb) ARCH=mips ENDIAN=big ;; arm*) ARCH=arm ENDIAN=little ;; ppc*) ARCH=powerpc ENDIAN=big ;; esac TOOLCHAIN_ROOT=$ROOT/toolchain/$TARGET rm -rf "$ROOT/$BUILDDIR/$UNPACKER" cd $ROOT/$BUILDDIR case $UNPACKER in unrar) tar xzf unrarsrc-*.tar.gz cd unrar sed 's:^CXX=g++:#CXX=g++:' -i makefile sed 's:^STRIP=strip:#STRIP=strip:' -i makefile sed 's:^LDFLAGS=:LDFLAGS=-static :' -i makefile sed 's:^CXXFLAGS=-O2:#CXXFLAGS=-O2:' -i makefile if test "$ENDIAN" = "big"; then sed 's:^DEFINES=:DEFINES=-DBIG_ENDIAN :' -i makefile fi EXEDIR= LICENSE=license.txt BUILDTARGET= ;; 7zip) tar xjf p7zip_*_src_all.tar.bz2 rm -rf 7zip mkdir 7zip mv p7zip_*/* 7zip find p7zip_* -maxdepth 0 -type d -exec rm -r {} \; cd 7zip rm makefile.machine cp makefile.linux_any_cpu_gcc_4.X makefile.machine sed 's:^CXX=g++:#CXX=g++:' -i makefile.machine sed 's:^CC=gcc:#CC=gcc:' -i makefile.machine EXEDIR=bin/ LICENSE=DOC/License.txt BUILDTARGET=$EXENAME ;; esac cd $ROOT/$BUILDDIR/$UNPACKER make clean CXX=$TOOLCHAIN_ROOT/output/host/usr/bin/$ARCH-linux-g++ \ CC=$TOOLCHAIN_ROOT/output/host/usr/bin/$ARCH-linux-gcc \ STRIP=$TOOLCHAIN_ROOT/output/host/usr/bin/$ARCH-linux-strip \ CXXFLAGS=-O2 \ LDFLAGS=-static \ make $BUILDTARGET cp $EXEDIR$EXENAME ../../setup/$EXENAME-$TARGET cp $LICENSE ../../setup/license-$UNPACKER.txt echo "Completed build for $TARGET ($UNPACKER)" done done nzbget-16.4/linux/build-nzbget0000755000175000017500000002655112630544544016244 0ustar andreasandreas#!/bin/bash # # This file is part of nzbget # # Copyright (C) 2015 Andrey Prygunkov # # 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$ # # Setup strict bash error handling set -o nounset set -o errexit # Uncomment next line for debuging #set -x ALLTARGETS="dist i686 x86_64 armel armhf mipsel mipseb ppc6xx ppc500" ROOT=`pwd` ROOTPATH=$PATH OUTPUTDIR=$ROOT/output echo "Usage:" echo " $(basename $0) [targets] [output] [revision] [reppath] [configs] [cleanup]" echo " targets : all (default) $ALLTARGETS" echo " output : bin installer" echo " revision : head (default) work " echo " branch : develop (default) master tags/XXX branches/XXX" echo " configs : release debug (default) release-nostrip debug-strip" echo " cleanup : cleanup output directory before building" echo # Parsing command line BUILD=no TARGETS="" OUTPUTS="" REVISION="" BRANCH="" CONFIGS="" CLEAN=no for PARAM in "$@" do case $PARAM in release|release-nostrip|debug|debug-strip) # using xargs to trim spaces CONFIGS=`echo "$CONFIGS $PARAM" | xargs` ;; master|develop|tags/*) BRANCH="$PARAM" ;; branches/*) BRANCH="${PARAM:9}" ;; head|work|[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]*) REVISION="$PARAM" ;; clean|cleanup) CLEAN=yes ;; bin|installer) # using xargs to trim spaces OUTPUTS=`echo "$OUTPUTS $PARAM" | xargs` ;; *) if [[ " $ALLTARGETS " == *" $PARAM "* ]]; then # using xargs to trim spaces TARGETS=`echo "$TARGETS $PARAM" | xargs` if [ "$PARAM" == "all" ]; then PARAM=$ALLTARGETS fi elif [ -d toolchain/$PARAM ]; then # non-standard target but the toolchain exist # using xargs to trim spaces TARGETS=`echo "$TARGETS $PARAM" | xargs` else echo "Invalid parameter: $PARAM" exit 1 fi ;; esac done if [ "$TARGETS" == "" ]; then TARGETS="$ALLTARGETS" fi if [ "$OUTPUTS" == "" ]; then OUTPUTS="bin installer" fi if [ "$REVISION" == "" ]; then REVISION="head" fi if [ "$BRANCH" == "" ]; then BRANCH="develop" fi if [ "$CONFIGS" == "" ]; then CONFIGS="release debug" fi echo "Active configuration:" echo " targets : $TARGETS" echo " outputs : $OUTPUTS" echo " revision : $REVISION" echo " branch : $BRANCH" echo " configs : $CONFIGS" echo " cleanup : $CLEAN" echo # Checkout and update from git if [ ! -d nzbget ]; then echo "Initial checkout" git clone https://github.com/nzbget/nzbget.git fi cd nzbget BUILDDIR=$ROOT/nzbget cd $BUILDDIR if [ "$REVISION" != "work" ]; then echo "Updating to $REVISION" git pull git checkout $BRANCH if [ "$REVISION" != "head" ]; then git checkout $REVISION fi touch Makefile.in configure config.h.in fi # File name format for output files VERSION=`grep "AC_INIT(nzbget, " configure.ac` VERSION=`expr "$VERSION" : '.*, \(.*\),.*)'` # Building revision name B=`git branch | sed -n -e 's/^\* \(.*\)/\1/p' | sed 's/ /-/g' | sed 's/(//g' | sed 's/)//g'` M=`git status --porcelain` if [ "$M" != "" ]; then M="M" fi if [ "$B" == "master" ]; then REVISION="$M" elif [ "$B" == "develop" ]; then REVISION=`git rev-list HEAD | wc -l | xargs` REVISION="${REVISION}$M" else REVISION=`git rev-list HEAD | wc -l | xargs` REVISION="${REVISION}$M-($B)" fi BASENAME="nzbget-$VERSION" if [ `expr "$VERSION" : ".*-testing"` != 0 ]; then BASENAME="$BASENAME-r$REVISION" fi # Building mkdir -p $OUTPUTDIR if [ "$CLEAN" == "yes" ]; then rm -r -f $OUTPUTDIR/* fi for CONFIG in $CONFIGS; do case $CONFIG in release) SUFFIX="" ;; debug) SUFFIX="-debug" ;; debug-strip) SUFFIX="-debug-strip" ;; release-nostrip) SUFFIX="-nostrip" ;; esac for OUTPUT in $OUTPUTS; do if [ "$OUTPUT" == "bin" ]; then for TARGET in $TARGETS; do cd $BUILDDIR echo "Building in `pwd` for $TARGET ($CONFIG)" case $TARGET in mipsel|i?86|x86_64) ARCH=$TARGET ;; mipseb) ARCH=mips ;; arm*) ARCH=arm ;; ppc*) ARCH=powerpc ;; esac if [ "$TARGET" == "dist" ]; then if [ ! -f $OUTPUTDIR/$BASENAME.tar.gz ]; then ./configure make dist cp nzbget-$VERSION.tar.gz $OUTPUTDIR/$BASENAME.tar.gz fi else PATH=:$ROOTPATH TOOLCHAIN_ROOT=$ROOT/toolchain/$TARGET PATH=$TOOLCHAIN_ROOT/output/host/usr/bin:$PATH STRIP="" if [ $CONFIG == "debug-strip" -o $CONFIG == "release" ]; then STRIP="-s" fi case $CONFIG in debug|debug-strip) LIBPREF="$TOOLCHAIN_ROOT/output/staging/usr" LDFLAGS="-static $STRIP" \ LIBS="-lcrypto -ldl -lz -lubacktrace" CXXFLAGS="-g -fasynchronous-unwind-tables" \ ./configure --host=$ARCH-linux --enable-debug ;; release|release-nostrip) LIBPREF="$TOOLCHAIN_ROOT/output/staging/usr" LDFLAGS="-static $STRIP" \ LIBS="-lcrypto -ldl -lz" ./configure --host=$ARCH-linux ;; esac make clean && make rm -r -f $OUTPUTDIR/install make DESTDIR=$OUTPUTDIR/install install cd $OUTPUTDIR rm -r -f nzbget mkdir -p nzbget mv install/usr/local/bin/nzbget nzbget mv install/usr/local/share/doc/nzbget/* nzbget rm nzbget/AUTHORS mv install/usr/local/share/nzbget/webui nzbget mv install/usr/local/share/nzbget/scripts nzbget CONFTEMPLATE=nzbget/webui/nzbget.conf.template mv install/usr/local/share/nzbget/nzbget.conf $CONFTEMPLATE rm -r -f $OUTPUTDIR/install # adjusting nzbget.conf sed 's:^MainDir=.*:MainDir=${AppDir}/downloads:' -i $CONFTEMPLATE sed 's:^DestDir=.*:DestDir=${MainDir}/completed:' -i $CONFTEMPLATE sed 's:^InterDir=.*:InterDir=${MainDir}/intermediate:' -i $CONFTEMPLATE sed 's:^WebDir=.*:WebDir=${AppDir}/webui:' -i $CONFTEMPLATE sed 's:^ScriptDir=.*:ScriptDir=${AppDir}/scripts:' -i $CONFTEMPLATE sed 's:^LogFile=.*:LogFile=${MainDir}/nzbget.log:' -i $CONFTEMPLATE sed 's:^ConfigTemplate=.*:ConfigTemplate=${AppDir}/webui/nzbget.conf.template:' -i $CONFTEMPLATE sed 's:^AuthorizedIP=.*:AuthorizedIP=127.0.0.1:' -i $CONFTEMPLATE tar -czf $BASENAME-bin-linux-$TARGET$SUFFIX.tar.gz nzbget rm -r -f nzbget echo "Completed build in `pwd` for $TARGET ($CONFIG)" fi done elif [ "$OUTPUT" == "installer" ]; then echo "Creating installer for $CONFIG..." cd $OUTPUTDIR # checking if all targets exists for TARGET in $TARGETS do ALLEXISTS="yes" if [ "$TARGET" != "dist" ]; then if [ ! -f $BASENAME-bin-linux-$TARGET$SUFFIX.tar.gz ]; then echo "Could not find $BASENAME-bin-linux-$TARGET$SUFFIX.tar.gz" ALLEXISTS="no" fi fi done if [ "$ALLEXISTS" == "no" ]; then exit 1; fi echo "Unpacking targets..." rm -r -f nzbget for TARGET in $TARGETS do ALLEXISTS="yes" if [ "$TARGET" != "dist" ]; then tar -xzf $BASENAME-bin-linux-$TARGET$SUFFIX.tar.gz mv nzbget/nzbget nzbget/nzbget-$TARGET cp ../setup/unrar-$TARGET nzbget cp ../setup/7za-$TARGET nzbget fi done # adjusting nzbget.conf sed 's:^UnrarCmd=unrar:UnrarCmd=${AppDir}/unrar:' -i nzbget/webui/nzbget.conf.template sed 's:^SevenZipCmd=7z:SevenZipCmd=${AppDir}/7za:' -i nzbget/webui/nzbget.conf.template INSTFILE=$BASENAME-bin-linux$SUFFIX.run echo "Building installer package..." cp $BUILDDIR/linux/installer.sh $INSTFILE cp $BUILDDIR/linux/package-info.json nzbget/webui cp $BUILDDIR/linux/install-update.sh nzbget cp $BUILDDIR/pubkey.pem nzbget cp ../setup/license-unrar.txt nzbget cp ../setup/license-7zip.txt nzbget # creating payload cd nzbget tar czf - * > ../$INSTFILE.data cd .. # creating installer script sed "s:^TITLE=$:TITLE=\"$BASENAME$SUFFIX\":" -i $INSTFILE DISTTARGETS="${TARGETS/dist/}" DISTTARGETS=`echo "$DISTTARGETS" | xargs` sed "s:^DISTARCHS=$:DISTARCHS=\"$DISTTARGETS\":" -i $INSTFILE MD5=`md5sum "$INSTFILE.data" | cut -b-32` sed "s:^MD5=$:MD5=\"$MD5\":" -i $INSTFILE PAYLOAD=`stat -c%s "$INSTFILE.data"` PAYLOADLEN=${#PAYLOAD} HEADER=`stat -c%s "$INSTFILE"` HEADERLEN=${#HEADER} HEADER=`expr $HEADER + $HEADERLEN + $PAYLOADLEN` TOTAL=`expr $HEADER + $PAYLOAD` TOTALLEN=${#TOTAL} HEADER=`expr $HEADER - $PAYLOADLEN + $TOTALLEN` TOTAL=`expr $TOTAL - $PAYLOADLEN + $TOTALLEN` sed "s:^HEADER=$:HEADER=$HEADER:" -i $INSTFILE sed "s:^TOTAL=$:TOTAL=$TOTAL:" -i $INSTFILE # attaching payload cat $INSTFILE.data >> $INSTFILE rm $INSTFILE.data chmod +x $INSTFILE rm -r nzbget fi done done nzbget-16.4/linux/package-info.json0000644000175000017500000000016412630544544017137 0ustar andreasandreas{ "update-info-link": "http://nzbget.net/info/nzbget-version-linux.json", "install-script": "install-update.sh" } nzbget-16.4/linux/build-info.txt0000644000175000017500000001253312630544544016514 0ustar andreasandreasAbout ----- "build-nzbget" is a bash script which is used to build universal installer for Linux. The script compiles NZBGet for each supported CPU-architecture and then packs all produced files into an installer package. Prerequisites ------------- To use the script you need a Linux (virtual) machine. Building -------- This script was written to work with toolchain from uClibc's Buildroot-project. 1. Create directory where you want to keep your files to compile NZBGet. Choose the path wisely because it cannot be changed later without rebuilding all toolchains again; 2. Put the build script 'build-nzbget' into that directory; 3. Create subdirectories: toolchain - for toolchains; nzbget - for source code of NZBGet; output - to store the results of build script; setup - for extra files required for installer; 4. Build toolchain for one CPU-architecture (see below); 4.1. Download Buildroot distribution archive from http://buildroot.uclibc.org/download.html (tested with version "buildroot-2015.02"); 4.2. Unpack the tarball into 'toolchain'-directory; 4.3. Rename the buildroot-directory according to the target CPU-architecture name; Be careful here, after the toolchain is built the directory cannot be renamed or moved, you will have to rebuild the toolchain if you want another name; 4.4. Run 'make nconfig'; 4.5. Configure toolchain: - Target architecture: - choose your target architecture; - Build options: - libraries (both static and shared); - Toolchain: - Kernel Headers (Manually specified Linux version); - (2.6.30) linux version; - Custom kernel headers series (2.6.x); - Enable large file (files > 2 GB) support; - Enable IPv6 support; - Enable toolchain locale/i18n support; - GCC compiler Version (gcc 4.9.x); - Enable C++ support; - Build cross gdb for the host; - Target packages: - Libraries: - Compression and decompression: zlib; - Crypto: openssl; - JSON/XML: libxml2; - Text and terminal handling: ncurses; - Save configuration and exit; 4.6. Do few extra manual adjustments: - in the file '.config' in the buildroot directory activate define to build 'ubacktrace'; - in 'packages/ncurses/ncurses.mk' add extra configure parameters to option 'NCURSES_CONF_OPTS‘ (without quotation marks): "--with-fallbacks=xterm xterm-color xterm-256color xterm-16color linux vt100 vt200"; - in 'packages/openssl/openssl.mk' replace 'zlib-dynamic' with 'zlib'; 4.7. Run 'make' to build the toolchain. It may take an hour or so depending on your hardware; 5. Now you should have a working toolchain for one CPU-architecture, let's test it. 5.1. Change to the ROOTBUILD-directory and run the build script: ./nzbget-build release bin 5.2. The script creates subdirectory 'nzbget/trunk' and checkouts the source code of NZBGet from subversion repository; 5.3. Then the source code is compiled for chosen CPU-architecture; 5.4. After the compiling a distribution binary archive for the chosen CPU-architecture is put into output-directory; 6. Build unrar and 7za for the CPU-architecture: 6.1. Download source of unrar; Compile for target, either manually or using script 'build-unpack'; 6.2. Put the compiled binaries of unrar and 7za into setup-directory, add suffix '-arch' to unrar and 7za names, for example 'unrar-armel'; 6.3. Copy license-files from unrar and 7-Zip projects using names 'license-unrar.txt' and 'license-7zip.txt'; 7. Now you can build installer for that one CPU-architecture: 7.1. If you build for CPU-architecture which is not supported by NZBGet's universal installer you have to edit the script 'build-nzbget' and add the architecture name into variable 'ALLTARGETS'; 7.2. Run the build script with: ./nzbget-build release installer 7.3. The created installer supports only one CPU-Architecture; 7.8. Run the installer on the target machine (with target CPU-Architecture); 8. Repeat step for each CPU-Architecture you intend to build the installer for; 9. To build installer for all CPU-Architectures listed in variable 'ALLTARGETS' of the script run the script without choosing CPU-Architecture: ./nzbget-build release installer 10. When the script is run without any parameters: 10.1. NZBGet is compiled twice for each CPU-Architecture listed in 'ALLTARGETS': once in release mode and once in debug mode; 10.2. Two installers are built: one for release and another for debug. Special functions ----------------- By default the script builds from trunk/HEAD. To specify another tag or branch pass it to the script, for example to build a tagged version 14.2: ./nzbget-build release bin tags/14.2 Installers can built only for version 15.0 (starting from revision r1279). Distribution archives can be built for older versions too. To build certain revision pass it to the script, for example ./nzbget-build release bin r1279 To cleanup the output directory before building pass parameter 'cleanup'. nzbget-16.4/linux/install-update.sh0000755000175000017500000001212312630544544017203 0ustar andreasandreas#!/bin/sh # # This file is part of nzbget # # Copyright (C) 2015 Andrey Prygunkov # # 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. # if test "$NZBUP_PROCESSID" = ""; then echo "This file is not supposed to be executed directly. To update NZBGet please choose Settings -> SYSTEM -> Check for update in the web-interface." exit 1 fi INSTALLERCFG=`cat "$NZBOP_APPDIR/installer.cfg" 2>/dev/null` if test "$INSTALLERCFG" = ""; then echo "[ERROR] File \"installer.cfg\" is missing in the installation directory. Please reinstall NZBGet." exit 1 fi echo "Downloading version information..." UPDATE_INFO_LINK=`cat "$NZBOP_APPDIR/webui/package-info.json" | sed -n 's/^.*update-info-link.*: "\(.*\)".*/\1/p'` "$NZBOP_APPBIN" -B webget "$NZBOP_TEMPDIR/NZBGET_UPDATE.txt" "$UPDATE_INFO_LINK" 2>/dev/null if test "$?" != "0"; then echo "[ERROR] Download failed, please try again later" exit 1 fi if test "$NZBUP_BRANCH" = "TESTING"; then VER_FIELD="testing-version" DNL_FIELD="testing-download" SIG_FIELD="testing-signature" elif test "$NZBUP_BRANCH" = "STABLE"; then VER_FIELD="stable-version" DNL_FIELD="stable-download" SIG_FIELD="stable-signature" else echo "[ERROR] Unsupported branch $NZBUP_BRANCH" exit 1 fi VER=`cat "$NZBOP_TEMPDIR/NZBGET_UPDATE.txt" | sed -n "s/^.*$VER_FIELD.*: \"\(.*\)\".*/\1/p"` DNL_LINK=`cat "$NZBOP_TEMPDIR/NZBGET_UPDATE.txt" | sed -n "s/^.*$DNL_FIELD.*: \"\(.*\)\".*/\1/p"` SIG_LINK=`cat "$NZBOP_TEMPDIR/NZBGET_UPDATE.txt" | sed -n "s/^.*$SIG_FIELD.*: \"\(.*\)\".*/\1/p"` rm -f "$NZBOP_TEMPDIR/NZBGET_UPDATE.txt" SIGNATURE="nzbget-$VER.sig.txt" echo "Downloading verification signature..." rm -f "$NZBOP_TEMPDIR/$SIGNATURE" "$NZBOP_APPBIN" -B webget "$NZBOP_TEMPDIR/$SIGNATURE" "$SIG_LINK" 2>/dev/null if test "$?" != "0"; then echo "[ERROR] Download failed, please try again later" exit 1 fi INSTALLER="nzbget-$VER-bin-linux.run" echo "Downloading $INSTALLER..." rm -f "$NZBOP_TEMPDIR/$INSTALLER" "$NZBOP_APPBIN" -B webget "$NZBOP_TEMPDIR/$INSTALLER" "$DNL_LINK" 2>/dev/null if test "$?" != "0"; then echo "[ERROR] Download failed, please try again later" exit 1 fi echo "Verifying package authenticity..." "$NZBOP_APPBIN" -B verify "$NZBOP_APPDIR/pubkey.pem" "$NZBOP_TEMPDIR/$SIGNATURE" "$NZBOP_TEMPDIR/$INSTALLER" 2>/dev/null if test "$?" != "93"; then echo "[ERROR] Package authenticity verification failed" rm -f "$NZBOP_TEMPDIR/$INSTALLER" rm -f "$NZBOP_TEMPDIR/$SIGNATURE" exit 1 fi rm -f "$NZBOP_TEMPDIR/$SIGNATURE" echo "Updating NZBGet..." echo "..." sh "$NZBOP_TEMPDIR/$INSTALLER" --update --destdir "$NZBOP_APPDIR" if test "$?" != "0"; then echo "[ERROR] Update failed, installer terminated with error status" exit 1 fi rm -f "$NZBOP_TEMPDIR/$INSTALLER" echo "..." echo "Update completed" # Recreating command line used to start NZBGet CMDLINE= N=1 while true do PARAMNAME="NZBUP_CMDLINE$N" eval PARAM="\$NZBUP_CMDLINE${N}" if test "$PARAM" = ""; then break fi if test "$CMDLINE" != ""; then CMDLINE="$CMDLINE " fi CMDLINE="$CMDLINE\"$PARAM\"" # Using "case" to implement expression "N=N+1" to overcome possibly disabled expression support on Busybox case $N in 1) N=2 ;; 2) N=3 ;; 3) N=4 ;; 4) N=5 ;; 5) N=6 ;; 6) N=7 ;; 7) N=8 ;; 8) N=9 ;; 9) N=10 ;; 10) N=11 ;; 11) N=12 ;; 12) N=13 ;; 13) N=14 ;; 14) N=15 ;; 15) N=16 ;; 16) N=17 ;; 17) N=18 ;; 18) N=19 ;; 19) N=20 ;; 21) N=22 ;; 22) N=23 ;; 23) N=24 ;; 24) N=25 ;; 25) N=26 ;; 26) N=27 ;; 27) N=28 ;; 28) N=29 ;; 29) N=30 ;; 31) N=32 ;; 32) N=33 ;; 33) N=34 ;; 34) N=35 ;; 35) N=36 ;; 36) N=37 ;; 37) N=38 ;; 38) N=39 ;; 39) N=40 ;; 41) N=42 ;; 42) N=43 ;; 43) N=44 ;; 44) N=45 ;; 45) N=46 ;; 46) N=47 ;; 47) N=48 ;; 48) N=49 ;; 49) N=50 ;; *) echo "..." echo "[ERROR] Could not restart NZBGet: cannot recreate command line" echo "[ERROR] Please restart NZBGet manually (reloading via web-interface isn't sufficient)" exit 1 esac done echo "Restarting NZBGet..." sleep 1 echo "[NZB] QUIT" echo "Waiting for NZBGet to terminate" PSOPT="-A" OPTWORKING=`ps $PSOPT` 2>/dev/null if test "$?" != "0"; then PSOPT="" fi while true do RUNNING=`ps $PSOPT | sed -n "s/^\s*$NZBUP_PROCESSID\s.*/&/p"` if test "$RUNNING" = ""; then break fi sleep 1 done echo "Starting NZBGet..." # Starting NZBGet eval "$NZBOP_APPBIN" $CMDLINE exit 0 nzbget-16.4/linux/installer.sh0000755000175000017500000003454012630544544016261 0ustar andreasandreas#!/bin/sh # # This file is part of nzbget # # Copyright (C) 2015 Andrey Prygunkov # # 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. # # Strict error handling for debugging set -o nounset set -o errexit # Installer title TITLE= # Size of installer script (package header) HEADER= # Size of installer package (header + payload) TOTAL= # Md5 sum of payload MD5= # List of included CPU architecture binaries DISTARCHS= SILENT=no ALLARCHS="$DISTARCHS all" ARCH="" SELECT=auto OUTDIR="nzbget" PRINTEDTITLE=no JUSTUNPACK=no UPDATE=no VERIFY=yes Info() { if test "$SILENT" = "no"; then echo "$1" fi } Error() { Info "ERROR: $1" exit 1 } ValidArch() { LIMARCH=`echo " $ALLARCHS " | sed "s/ $1 //"` if test " $ALLARCHS " = "$LIMARCH"; then return 1 fi return 0 } PrintArch() { if ValidArch $1; then Info "$2" fi } PrintHelp() { if test "$PRINTEDTITLE" = "no"; then Info "Installer for $TITLE" Info "" fi Info "This installer supports Linux kernel 2.6 or newer and the following CPU architectures:" PrintArch "i686" " i686 - x86, 32 or 64 Bit" PrintArch "x86_64" " x86_64 - x86, 64 Bit" PrintArch "armel" " armel - ARMv5/v6 (ARM9 and ARM11 families)" PrintArch "armhf" " armhf - ARMv7/v8 (Cortex family)" PrintArch "mipsel" " mipsel - MIPS (little endian)" PrintArch "mipseb" " mipseb - MIPS (big endian)" PrintArch "ppc6xx" " ppc6xx - PowerPC 6xx (603e series)" PrintArch "ppc500" " ppc500 - PowerPC e500 (core e500v1/e500v2)" Info "" # Check if command 'basename' is available and fallback to full path if it's not TEST=`eval TEST='$(basename /base/bin)' 2>/dev/null; echo $TEST` if test "$TEST" != ""; then EXENAME=$(basename $0) else EXENAME=$0 fi Info "Usage: sh $EXENAME [options]" Info " --help - print this help" Info " --arch - set CPU-architecture" Info " --destdir - set destination directory" Info " --list - list package content" Info " --unpack - just unpack, skip setup" Info " --silent - silent mode" Info " --nocheck - skip integrity check" Info " --tar - run custom tar command on archive" } Verify() { # Checking installer package file size REQSIZE=$TOTAL # Trying to get the size via 'stat' command, fallback to 'ls -l' if 'stat' isn't available ACTSIZE=`stat -c%s "$0" 2>/dev/null | cat` if test "$ACTSIZE" = ""; then NUM= for FIELD in `ls -l "$0" 2>/dev/null` do if test "$NUM" = "aaaa"; then ACTSIZE="$FIELD" break fi NUM="a$NUM" done fi if test "$REQSIZE" != "$ACTSIZE"; then Error "Corrupted installer package detected: file size mismatch." fi # Checking checksum (MD5) of package payload, only if command 'md5sum' is available ACTMD5=`dd "if=$0" bs=$HEADER skip=1 2>/dev/null | md5sum 2>/dev/null | cut -b-32 2>/dev/null | cat` LEN=${#ACTMD5} if test "$LEN" = "32" -a "$MD5" != "$ACTMD5"; then Error "Corrupted installer package detected: checksum mismatch." fi } DetectEndianness() { # Sixth byte of any executable indicates endianness ENDBYTE=`dd if=/bin/sh bs=1 count=6 2>/dev/null | sed -n 's/.ELF.\(.*\)/\1/p'` ENDIAN=unknown if test "$ENDBYTE" = $'\001'; then ENDIAN=little elif test "$ENDBYTE" = $'\002'; then ENDIAN=big fi } DetectArch() { OS=`uname -s` if test "$OS" != "Linux"; then PrintHelp Error "Operating system ($OS) isn't supported by this installer." fi if test "$UPDATE" = "yes"; then ARCH=`cat "$OUTDIR/installer.cfg" 2>/dev/null | sed -n 's/^arch=\(.*\)$/\1/p'` SELECT=`cat "$OUTDIR/installer.cfg" 2>/dev/null | sed -n 's/^select=\(.*\)$/\1/p'` fi if test "$ARCH" = ""; then CPU=`uname -m` case $CPU in i386|i686) ARCH=i686 ;; x86_64) ARCH=x86_64 ;; mips) ARCH=mipsx ;; armv5*|armv6*|armel) ARCH=armel ;; armv7*|armv8*|aarch64) ARCH=armhf ;; ppc) ARCH=ppcx ;; esac fi if test "$ARCH" = ""; then MIPS=`cat /proc/cpuinfo | sed -n 's/.*:.*\(mips\).*/&/p'` if test "$MIPS" != ""; then ARCH=mipsx fi fi if test "$ARCH" = "mipsx"; then DetectEndianness if test "$ENDIAN" = "big"; then ARCH=mipseb else ARCH=mipsel fi fi if test "$ARCH" = "ppcx"; then E500=`cat /proc/cpuinfo | sed -n 's/.*:.*\(e500\).*/&/p'` if test "$E500" != ""; then ARCH=ppc500 else ARCH=ppc6xx fi fi if test "$ARCH" = ""; then PrintHelp Error "CPU architecture ($CPU) isn't supported by this installer." fi if ! ValidArch $ARCH; then Error "CPU architecture ($ARCH) isn't supported by this installer." fi } Unpack() { mkdir -p "$OUTDIR" # Prepare list of files to ignore EXARCHS="" if test "$JUSTUNPACK" = "no" -a "$ARCH" != "all"; then rm -f "$OUTDIR/installer.tmp" for TARG in $ALLARCHS do if test "$TARG" != "$ARCH"; then echo "nzbget-$TARG" >> "$OUTDIR/installer.tmp" echo "unrar-$TARG" >> "$OUTDIR/installer.tmp" echo "7za-$TARG" >> "$OUTDIR/installer.tmp" EXARCHS="-X installer.tmp" fi done fi # Unpack (skip ignorable files) dd "if=$0" bs=$HEADER skip=1 2> /dev/null | gzip -c -d | ( cd "$OUTDIR"; tar x $EXARCHS 2>&1 ) || { Error "Unpacking failed."; rm -f "$OUTDIR/installer.tmp"; kill -15 $$; } if test "$EXARCHS" != ""; then rm -f "$OUTDIR/installer.tmp" fi # Rename unpacked binaries files and store arch selection if test "$JUSTUNPACK" = "no" -a "$ARCH" != "all"; then OLDDIR=`pwd` cd "$OUTDIR" rm -f nzbget rm -f unrar rm -f 7za mv nzbget-$ARCH nzbget mv unrar-$ARCH unrar mv 7za-$ARCH 7za echo "arch=$ARCH" > "installer.cfg" echo "select=$SELECT" >> "installer.cfg" cd "$OLDDIR" fi } TAR() { dd "if=$0" bs=$HEADER skip=1 2> /dev/null | gzip -c -d | tar "$ARG" $@ exit $? } # 'Expr' provides two versions of expression evaluations: either using shell # built-in syntax $((arith)) or using command 'expr'. # If both fail, an empty string is returned (caller must check for this). # Currently supporting '+' and '/' operations (can be extended for others if needed). Expr() { ARG1=$1 OP=$2 ARG2=$3 RET= TEST=`eval TEST='$((1+2))' 2>/dev/null || 1; echo $TEST` if test "$TEST" = "3"; then case "$OP" in "+") eval RET='$((ARG1 + ARG2))' ;; "/") eval RET='$((ARG1 / ARG2))' ;; esac else TEST=`expr 1 + 2 2>/dev/null | cat` if test "$TEST" = "3"; then case "$OP" in "+") RET=`expr $ARG1 + $ARG2` ;; "/") RET=`expr $ARG1 / $ARG2` ;; esac fi fi echo "$RET" } Configure() { cd "$OUTDIR" QUICKHELP=no if test ! -f nzbget.conf; then cp ./webui/nzbget.conf.template nzbget.conf # Adjusting config file to current system MEMFREE=`cat /proc/meminfo | sed -n 's/^MemFree: *\([0-9]*\).*/\1/p' 2>/dev/null | cat` MEMCACHED=`cat /proc/meminfo | sed -n 's/^Cached: *\([0-9]*\).*/\1/p' 2>/dev/null | cat` if test "$MEMFREE" != "" -a "$MEMCACHED" != ""; then TOTALFREE=$(Expr $MEMFREE + $MEMCACHED) # Expression evaluation with "Expr" may fail, check the result TOTALFREE=$(Expr "$TOTALFREE" / 1024) if test "$TOTALFREE" != ""; then Info " Free memory detected: $TOTALFREE MB" else Info " Free memory detected: $MEMFREE KB + $MEMCACHED KB" TOTALFREE=0 fi if test $TOTALFREE -gt 250000 -o $MEMFREE -gt 250000 -o $MEMCACHED -gt 250000; then Info " Activating article cache (ArticleCache=100)" sed 's:^ArticleCache=.*:ArticleCache=100:' -i nzbget.conf Info " Increasing write buffer (WriteBuffer=1024)" sed 's:^WriteBuffer=.*:WriteBuffer=1024:' -i nzbget.conf Info " Increasing par repair buffer (ParBuffer=100)" sed 's:^ParBuffer=.*:ParBuffer=100:' -i nzbget.conf elif test $TOTALFREE -gt 25000 -o $MEMFREE -gt 25000 -o $MEMCACHED -gt 25000; then Info " Increasing write buffer (WriteBuffer=256)" sed 's:^WriteBuffer=.*:WriteBuffer=256:' -i nzbget.conf fi fi BOGOLIST=`cat /proc/cpuinfo | sed -n 's/^bogomips\s*:\s\([0-9]*\).*/\1/pI' 2>/dev/null | cat` if test "$BOGOLIST" != ""; then BOGOMIPS=0 for CPU1 in $BOGOLIST do BOGOMIPS=$(Expr $BOGOMIPS + $CPU1) if test "$BOGOMIPS" = ""; then # Expression evaluation with "Expr" failed, using BogoMIPS for first core BOGOMIPS=$CPU1 fi done Info " CPU speed detected: $BOGOMIPS BogoMIPS" if test $BOGOMIPS -lt 4000; then Info " Disabling download during par check/repair (ParPauseQueue=yes)" sed 's:^ParPauseQueue=.*:ParPauseQueue=yes:' -i nzbget.conf Info " Disabling download during unpack (UnpackPauseQueue=yes)" sed 's:^UnpackPauseQueue=.*:UnpackPauseQueue=yes:' -i nzbget.conf Info " Disabling download during post-processing (ScriptPauseQueue=yes)" sed 's:^ScriptPauseQueue=.*:ScriptPauseQueue=yes:' -i nzbget.conf else Info " Simultaneous download and post-processing is on" fi fi QUICKHELP=yes fi } # ParseCommandLine while true do PARAM=${1:-} case $PARAM in -h|--help) PrintHelp exit 0 ;; --silent) SILENT=yes shift ;; --arch) ARCH=${2:-} SELECT=manual if ! ValidArch $ARCH; then PrintHelp Info "" Error "Bad argument ($ARCH) to option --arch." exit 1 fi shift 2 ;; --destdir) OUTDIR=${2:-} if test "$OUTDIR" = ""; then PrintHelp exit 1 fi shift 2 ;; --unpack) JUSTUNPACK=yes shift ;; --list) ARG=t TAR exit $? ;; --tar) ARG=${2:-} if test "$ARG" = ""; then PrintHelp exit 1 fi shift 2 TAR exit $? ;; --update) UPDATE=yes shift ;; --nocheck) VERIFY=no shift ;; "") break ;; *) PrintHelp exit 1 ;; esac done Info "Installer for $TITLE" if test "$SILENT" = "no"; then PRINTEDTITLE=yes fi if test "$VERIFY" = "yes"; then Info "Verifying package..." Verify fi if test "$JUSTUNPACK" = "no"; then Info "Checking system..." DetectArch if test "$SELECT" = "manual"; then Info "CPU-Architecture: $ARCH (manually set)" else Info "CPU-Architecture: $ARCH" fi fi Info "Unpacking..." Unpack ABSOUTDIR=`cd "$OUTDIR"; pwd` if test "$JUSTUNPACK" = "no"; then Info "Configuring..." Configure Info "Installation completed" if test "$QUICKHELP" = "yes" -a "$SILENT" = "no"; then Info "" Info "Quick help (from nzbget-directory):" Info " ./nzbget -s - start nzbget in console mode" Info " ./nzbget -D - start nzbget in daemon mode (in background)" Info " ./nzbget -C - connect to background process" Info " ./nzbget -Q - stop background process" Info " ./nzbget -h - help screen with all commands" Info "" Info "Successfully installed into $ABSOUTDIR" # Trying to get current IP-address IP="" INTERFACE="" { # First find default interface (ie 'wlan0') with the 'route' command INTERFACE=`route -n | sed -rn 's/^0.0.0.0.* ([^ ]+)$/\1/p'` } > /dev/null 2>&1 if test "$INTERFACE" != ""; then # OK, a default route 0.0.0.0 + corresponding interface was found # Now find the IPv4 address on that interface: { IP=`ifconfig "$INTERFACE" | sed -rn 's/.*r:([^ ]+) .*/\1/p'` } > /dev/null 2>&1 fi if test "$IP" = ""; then IP="localhost" fi Info "Web-interface is on http://$IP:6789 (login:nzbget, password:tegbzn6789)" else Info "Successfully installed into $ABSOUTDIR" fi Info "For support please visit http://nzbget.net/forum" else Info "Unpacked into $ABSOUTDIR" fi exit #END-OF-INSTALLER nzbget-16.4/scripts/0000755000175000017500000000000012630544544014247 5ustar andreasandreasnzbget-16.4/scripts/Logger.py0000755000175000017500000000650112630544544016045 0ustar andreasandreas#!/usr/bin/env python # # Logger post-processing script for NZBGet # # Copyright (C) 2013-2015 Andrey Prygunkov # # 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$ # ############################################################################## ### NZBGET POST-PROCESSING SCRIPT ### # Save nzb log into a file. # # This script saves the download and post-processing log of nzb-file # into file _nzblog.txt in the destination directory. # # NOTE: This script requires Python to be installed on your system. ### NZBGET POST-PROCESSING SCRIPT ### ############################################################################## import os import sys import datetime try: from xmlrpclib import ServerProxy # python 2 except ImportError: from xmlrpc.client import ServerProxy # python 3 # Exit codes used by NZBGet POSTPROCESS_SUCCESS=93 POSTPROCESS_NONE=95 POSTPROCESS_ERROR=94 # Check if the script is called from nzbget 15.0 or later if not 'NZBOP_NZBLOG' in os.environ: print('*** NZBGet post-processing script ***') print('This script is supposed to be called from nzbget (15.0 or later).') sys.exit(POSTPROCESS_ERROR) if not os.path.exists(os.environ['NZBPP_DIRECTORY']): print('Destination directory doesn\'t exist, exiting') sys.exit(POSTPROCESS_NONE) # To get the item log we connect to NZBGet via XML-RPC and call # method "loadlog", which returns the log for a given nzb item. # For more info visit http://nzbget.net/RPC_API_reference # First we need to know connection info: host, port and password of NZBGet server. # NZBGet passes all configuration options to post-processing script as # environment variables. host = os.environ['NZBOP_CONTROLIP']; port = os.environ['NZBOP_CONTROLPORT']; username = os.environ['NZBOP_CONTROLUSERNAME']; password = os.environ['NZBOP_CONTROLPASSWORD']; if host == '0.0.0.0': host = '127.0.0.1' # Build an URL for XML-RPC requests rpcUrl = 'http://%s:%s@%s:%s/xmlrpc' % (username, password, host, port); # Create remote server object server = ServerProxy(rpcUrl) # Call remote method 'loadlog' nzbid = int(os.environ['NZBPP_NZBID']) log = server.loadlog(nzbid, 0, 10000) # Now iterate through entries and save them to the output file if len(log) > 0: f = open('%s/_nzblog.txt' % os.environ['NZBPP_DIRECTORY'], 'wb') for entry in log: f.write((u'%s\t%s\t%s\n' % (entry['Kind'], datetime.datetime.fromtimestamp(int(entry['Time'])), entry['Text'])).encode('utf8')) f.close() # All OK, returning exit status 'POSTPROCESS_SUCCESS' (int <93>) to let NZBGet know # that our script has successfully completed. sys.exit(POSTPROCESS_SUCCESS) nzbget-16.4/scripts/EMail.py0000755000175000017500000002304712630544544015621 0ustar andreasandreas#!/usr/bin/env python # # E-Mail post-processing script for NZBGet # # Copyright (C) 2013-2015 Andrey Prygunkov # # 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$ # ############################################################################## ### NZBGET POST-PROCESSING SCRIPT ### # Send E-Mail notification. # # This script sends E-Mail notification when the job is done. # # NOTE: This script requires Python to be installed on your system. ############################################################################## ### OPTIONS ### # When to send the message (Always, OnFailure). #SendMail=Always # Email address you want this email to be sent from. #From="NZBGet" # Email address you want this email to be sent to. #To=myaccount@gmail.com # SMTP server host. #Server=smtp.gmail.com # SMTP server port (1-65535). #Port=25 # Secure communication using TLS/SSL (yes, no, force). # no - plain text communication (insecure); # yes - switch to secure session using StartTLS command; # force - start secure session on encrypted socket. #Encryption=yes # SMTP server user name, if required. #Username=myaccount # SMTP server password, if required. #Password=mypass # Append statistics to the message (yes, no). #Statistics=yes # Append list of files to the message (yes, no). # # Add the list of downloaded files (the content of destination directory). #FileList=yes # Append broken-log to the message (yes, no). # # Add the content of file _brokenlog.txt. This file contains the list of damaged # files and the result of par-check/repair. For successful downloads the broken-log # is usually deleted by cleanup-script and therefore is not sent. #BrokenLog=yes # Append nzb log to the message (Always, Never, OnFailure). # # Add the download and post-processing log of active job. #NzbLog=OnFailure ### NZBGET POST-PROCESSING SCRIPT ### ############################################################################## import os import sys import datetime import smtplib from email.mime.text import MIMEText try: from xmlrpclib import ServerProxy # python 2 except ImportError: from xmlrpc.client import ServerProxy # python 3 # Exit codes used by NZBGet POSTPROCESS_SUCCESS=93 POSTPROCESS_ERROR=94 POSTPROCESS_NONE=95 # Check if the script is called from nzbget 15.0 or later if not 'NZBOP_NZBLOG' in os.environ: print('*** NZBGet post-processing script ***') print('This script is supposed to be called from nzbget (15.0 or later).') sys.exit(POSTPROCESS_ERROR) print('[DETAIL] Script successfully started') sys.stdout.flush() required_options = ('NZBPO_FROM', 'NZBPO_TO', 'NZBPO_SERVER', 'NZBPO_PORT', 'NZBPO_ENCRYPTION', 'NZBPO_USERNAME', 'NZBPO_PASSWORD') for optname in required_options: if (not optname in os.environ): print('[ERROR] Option %s is missing in configuration file. Please check script settings' % optname[6:]) sys.exit(POSTPROCESS_ERROR) status = os.environ['NZBPP_STATUS'] total_status = os.environ['NZBPP_TOTALSTATUS'] # If any script fails the status of the item in the history is "WARNING/SCRIPT". # This status however is not passed to pp-scripts in the env var "NZBPP_STATUS" # because most scripts are independent of each other and should work even # if a previous script has failed. But not in the case of E-Mail script, # which should take the status of the previous scripts into account as well. if total_status == 'SUCCESS' and os.environ['NZBPP_SCRIPTSTATUS'] == 'FAILURE': total_status = 'WARNING' status = 'WARNING/SCRIPT' success = total_status == 'SUCCESS' if success and os.environ.get('NZBPO_SENDMAIL') == 'OnFailure': print('[INFO] Skipping sending of message for successful download') sys.exit(POSTPROCESS_NONE) if success: subject = 'Success for "%s"' % (os.environ['NZBPP_NZBNAME']) text = 'Download of "%s" has successfully completed.' % (os.environ['NZBPP_NZBNAME']) else: subject = 'Failure for "%s"' % (os.environ['NZBPP_NZBNAME']) text = 'Download of "%s" has failed.' % (os.environ['NZBPP_NZBNAME']) text += '\nStatus: %s' % status if os.environ.get('NZBPO_STATISTICS') == 'yes' or \ os.environ.get('NZBPO_NZBLOG') == 'Always' or \ (os.environ.get('NZBPO_NZBLOG') == 'OnFailure' and not success): # To get statistics or the post-processing log we connect to NZBGet via XML-RPC. # For more info visit http://nzbget.net/RPC_API_reference # First we need to know connection info: host, port and password of NZBGet server. # NZBGet passes all configuration options to post-processing script as # environment variables. host = os.environ['NZBOP_CONTROLIP']; port = os.environ['NZBOP_CONTROLPORT']; username = os.environ['NZBOP_CONTROLUSERNAME']; password = os.environ['NZBOP_CONTROLPASSWORD']; if host == '0.0.0.0': host = '127.0.0.1' # Build an URL for XML-RPC requests rpcUrl = 'http://%s:%s@%s:%s/xmlrpc' % (username, password, host, port); # Create remote server object server = ServerProxy(rpcUrl) if os.environ.get('NZBPO_STATISTICS') == 'yes': # Find correct nzb in method listgroups groups = server.listgroups(0) nzbID = int(os.environ['NZBPP_NZBID']) for nzbGroup in groups: if nzbGroup['NZBID'] == nzbID: break text += '\n\nStatistics:'; # add download size DownloadedSize = float(nzbGroup['DownloadedSizeMB']) unit = ' MB' if DownloadedSize > 1024: DownloadedSize = DownloadedSize / 1024 # GB unit = ' GB' text += '\nDownloaded size: %.2f' % (DownloadedSize) + unit # add average download speed DownloadedSizeMB = float(nzbGroup['DownloadedSizeMB']) DownloadTimeSec = float(nzbGroup['DownloadTimeSec']) if DownloadTimeSec > 0: # check x/0 errors avespeed = (DownloadedSizeMB/DownloadTimeSec) # MB/s unit = ' MB/s' if avespeed < 1: avespeed = avespeed * 1024 # KB/s unit = ' KB/s' text += '\nAverage download speed: %.2f' % (avespeed) + unit def format_time_sec(sec): Hour = sec/3600 Min = (sec - (sec/3600)*3600)/60 Sec = (sec - (sec/3600)*3600)%60 return '%d:%02d:%02d' % (Hour,Min,Sec) # add times text += '\nTotal time: ' + format_time_sec(int(nzbGroup['DownloadTimeSec']) + int(nzbGroup['PostTotalTimeSec'])) text += '\nDownload time: ' + format_time_sec(int(nzbGroup['DownloadTimeSec'])) text += '\nVerification time: ' + format_time_sec(int(nzbGroup['ParTimeSec']) - int(nzbGroup['RepairTimeSec'])) text += '\nRepair time: ' + format_time_sec(int(nzbGroup['RepairTimeSec'])) text += '\nUnpack time: ' + format_time_sec(int(nzbGroup['UnpackTimeSec'])) # add list of downloaded files files = False if os.environ.get('NZBPO_FILELIST') == 'yes': text += '\n\nFiles:' for dirname, dirnames, filenames in os.walk(os.environ['NZBPP_DIRECTORY']): for filename in filenames: text += '\n' + os.path.join(dirname, filename)[len(os.environ['NZBPP_DIRECTORY']) + 1:] files = True if not files: text += '\n' # add _brokenlog.txt (if exists) if os.environ.get('NZBPO_BROKENLOG') == 'yes': brokenlog = '%s/_brokenlog.txt' % os.environ['NZBPP_DIRECTORY'] if os.path.exists(brokenlog): text += '\n\nBrokenlog:\n' + open(brokenlog, 'r').read().strip() # add post-processing log if os.environ.get('NZBPO_NZBLOG') == 'Always' or \ (os.environ.get('NZBPO_NZBLOG') == 'OnFailure' and not success): # To get the item log we connect to NZBGet via XML-RPC and call # method "loadlog", which returns the log for a given nzb item. # For more info visit http://nzbget.net/RPC_API_reference # Call remote method 'loadlog' nzbid = int(os.environ['NZBPP_NZBID']) log = server.loadlog(nzbid, 0, 10000) # Now iterate through entries and save them to message text if len(log) > 0: text += '\n\nNzb-log:'; for entry in log: text += '\n%s\t%s\t%s' % (entry['Kind'], datetime.datetime.fromtimestamp(int(entry['Time'])), entry['Text']) # Create message msg = MIMEText(text) msg['Subject'] = subject msg['From'] = os.environ['NZBPO_FROM'] msg['To'] = os.environ['NZBPO_TO'] msg['Date'] = datetime.datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S +0000") msg['X-Application'] = 'NZBGet' # Send message print('[DETAIL] Sending E-Mail') sys.stdout.flush() try: if os.environ['NZBPO_ENCRYPTION'] == 'force': smtp = smtplib.SMTP_SSL(os.environ['NZBPO_SERVER'], os.environ['NZBPO_PORT']) else: smtp = smtplib.SMTP(os.environ['NZBPO_SERVER'], os.environ['NZBPO_PORT']) if os.environ['NZBPO_ENCRYPTION'] == 'yes': smtp.starttls() if os.environ['NZBPO_USERNAME'] != '' and os.environ['NZBPO_PASSWORD'] != '': smtp.login(os.environ['NZBPO_USERNAME'], os.environ['NZBPO_PASSWORD']) smtp.sendmail(os.environ['NZBPO_FROM'], os.environ['NZBPO_TO'], msg.as_string()) smtp.quit() except Exception as err: print('[ERROR] %s' % err) sys.exit(POSTPROCESS_ERROR) # All OK, returning exit status 'POSTPROCESS_SUCCESS' (int <93>) to let NZBGet know # that our script has successfully completed. sys.exit(POSTPROCESS_SUCCESS)