From e48ee555242d26a80ae3f22f8c0ebe9f2b232921 Mon Sep 17 00:00:00 2001 From: Denis <146707790+dnzbk@users.noreply.github.com> Date: Tue, 6 Aug 2024 00:18:47 -0700 Subject: [PATCH 01/19] Promote the new v24.3 testing version (#342) --- CMakeLists.txt | 2 +- osx/NZBGet-Info.plist | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b1b83380b..d3f818488 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ set_property(GLOBAL PROPERTY PACKAGE) set_property(GLOBAL PROPERTY LIBS) set_property(GLOBAL PROPERTY INCLUDES) -set(VERSION "24.2") +set(VERSION "24.3") set(PACKAGE "nzbget") set(PACKAGE_BUGREPORT "https://github.com/nzbgetcom/nzbget/issues") set(CMAKE_CXX_STANDARD 17) diff --git a/osx/NZBGet-Info.plist b/osx/NZBGet-Info.plist index 3d868758c..2d17696a7 100644 --- a/osx/NZBGet-Info.plist +++ b/osx/NZBGet-Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 24.2 + 24.3 CFBundleDocumentTypes From f89978f7479cbb0ff2f96c8632d9d2f31834e6c8 Mon Sep 17 00:00:00 2001 From: Denis <146707790+dnzbk@users.noreply.github.com> Date: Wed, 7 Aug 2024 11:54:33 -0700 Subject: [PATCH 02/19] Fixed: buffer overflow using getrealpath function (#346) - use a safer approach of using `getrealpath` according to the [doc](https://man7.org/linux/man-pages/man3/realpath.3.html) - using `std::string_view` instead of `std::string&` for better performance - improved `SystemInfoTest` to make it more flexible --- daemon/util/FileSystem.cpp | 15 +++++---- daemon/util/FileSystem.h | 2 +- tests/system/SystemInfoTest.cpp | 58 +++++++++++++++++++++++++-------- 3 files changed, 53 insertions(+), 22 deletions(-) diff --git a/daemon/util/FileSystem.cpp b/daemon/util/FileSystem.cpp index d42fa326e..2b3d6b076 100644 --- a/daemon/util/FileSystem.cpp +++ b/daemon/util/FileSystem.cpp @@ -56,20 +56,21 @@ void FileSystem::NormalizePathSeparators(char* path) } } -std::optional FileSystem::GetFileRealPath(const std::string& path) +std::optional FileSystem::GetFileRealPath(std::string_view path) { - char buffer[256]; - #ifdef WIN32 - DWORD len = GetFullPathName(path.c_str(), 256, buffer, nullptr); + char buffer[MAX_PATH]; + DWORD len = GetFullPathName(path.data(), MAX_PATH, buffer, nullptr); if (len != 0) { - return std::optional{ buffer }; + return std::optional{ buffer }; } #else - if (realpath(path.c_str(), buffer) != nullptr) + if (char* realPath = realpath(path.data(), nullptr)) { - return std::optional{ buffer }; + std::string res = realPath; + free(realPath); + return std::optional(std::move(res)); } #endif diff --git a/daemon/util/FileSystem.h b/daemon/util/FileSystem.h index 1aabb697a..2d9cc200c 100644 --- a/daemon/util/FileSystem.h +++ b/daemon/util/FileSystem.h @@ -40,7 +40,7 @@ class FileSystem static char* BaseFileName(const char* filename); static bool SameFilename(const char* filename1, const char* filename2); static void NormalizePathSeparators(char* path); - static std::optional GetFileRealPath(const std::string& path); + static std::optional GetFileRealPath(std::string_view path); static bool LoadFileIntoBuffer(const char* filename, CharBuffer& buffer, bool addTrailingNull); static bool SaveBufferIntoFile(const char* filename, const char* buffer, int bufLen); static bool AllocateFile(const char* filename, int64 size, bool sparse, CString& errmsg); diff --git a/tests/system/SystemInfoTest.cpp b/tests/system/SystemInfoTest.cpp index b9e11d783..5e84d2d50 100644 --- a/tests/system/SystemInfoTest.cpp +++ b/tests/system/SystemInfoTest.cpp @@ -28,22 +28,22 @@ #include "Log.h" #include "DiskState.h" -Log* g_Log = new Log(); +Log* g_Log; Options* g_Options; DiskState* g_DiskState; -std::string GetToolsJsonStr(const std::vector tools) +std::string GetToolsJsonStr(const std::vector& tools) { std::string json = "\"Tools\":["; for (size_t i = 0; i < tools.size(); ++i) { std::string path = tools[i].path; - for (size_t i = 0; i < path.length(); ++i) { - if (path[i] == '\\') + for (size_t j = 0; j < path.length(); ++j) { + if (path[j] == '\\') { - path.insert(i, "\\"); - ++i; + path.insert(j, "\\"); + ++j; } } @@ -62,7 +62,7 @@ std::string GetToolsJsonStr(const std::vector tools) return json; } -std::string GetLibrariesJsonStr(const std::vector libs) +std::string GetLibrariesJsonStr(const std::vector& libs) { std::string json = "\"Libraries\":["; @@ -82,7 +82,7 @@ std::string GetLibrariesJsonStr(const std::vector libs) return json; } -std::string GetToolsXmlStr(const std::vector tools) +std::string GetToolsXmlStr(const std::vector& tools) { std::string xml = ""; @@ -110,7 +110,7 @@ std::string GetToolsXmlStr(const std::vector tools) return xml; } -std::string GetLibrariesXmlStr(const std::vector libs) +std::string GetLibrariesXmlStr(const std::vector& libs) { std::string xml = ""; @@ -126,13 +126,32 @@ std::string GetLibrariesXmlStr(const std::vector libs) return xml; } +std::string GetNetworkXmlStr(const System::Network& network) +{ + std::string res = ""; + res += network.publicIP.empty() + ? "PublicIP" + : "PublicIP" + network.publicIP + ""; + + res += network.privateIP.empty() + ? "PrivateIP" + : "PrivateIP" + network.privateIP + ""; + + res += ""; + return res; +} + BOOST_AUTO_TEST_CASE(SystemInfoTest) { - BOOST_CHECK(0 == 0); + Log log; + DiskState ds; Options::CmdOptList cmdOpts; cmdOpts.push_back("SevenZipCmd=7z"); cmdOpts.push_back("UnrarCmd=unrar"); Options options(&cmdOpts, nullptr); + + g_Log = &log; + g_DiskState = &ds; g_Options = &options; auto sysInfo = std::make_unique(); @@ -157,14 +176,25 @@ BOOST_AUTO_TEST_CASE(SystemInfoTest) "" + "Arch" + sysInfo->GetCPUInfo().GetArch() + "" + - "PublicIP" + sysInfo->GetNetworkInfo().publicIP + - "" - "PrivateIP" + sysInfo->GetNetworkInfo().privateIP + - "" + + GetNetworkXmlStr(sysInfo->GetNetworkInfo()) + GetToolsXmlStr(sysInfo->GetTools()) + GetLibrariesXmlStr(sysInfo->GetLibraries()) + ""; + BOOST_TEST_MESSAGE("EXPECTED JSON STR: "); + BOOST_TEST_MESSAGE(jsonStrExpected); + + BOOST_TEST_MESSAGE("RESULT JSON STR: "); + BOOST_TEST_MESSAGE(jsonStrResult); + + BOOST_TEST_MESSAGE("EXPECTED XML STR: "); + BOOST_TEST_MESSAGE(xmlStrExpected); + + BOOST_TEST_MESSAGE("RESULT XML STR: "); + BOOST_TEST_MESSAGE(xmlStrResult); + BOOST_CHECK(jsonStrResult == jsonStrExpected); BOOST_CHECK(xmlStrResult == xmlStrExpected); + + xmlCleanupParser(); } From 61585fac12e697baafa547012ed2970135de687f Mon Sep 17 00:00:00 2001 From: bket Date: Fri, 9 Aug 2024 10:20:22 +0200 Subject: [PATCH 03/19] Check if privddrop was successful (#345) Currently, if nzbget is daemonized as root, and an invalid `DaemonUsername` is set, privileges are not dropped. Consequence is that nzbget runs with elevated privileges, which is undesirable for any software with network-facing interfaces. --- daemon/main/Options.cpp | 5 +++- daemon/main/Options.h | 1 + daemon/main/nzbget.cpp | 30 +++++++++++++++--------- tests/extension/ExtensionManagerTest.cpp | 4 ++++ tests/main/OptionsTest.cpp | 2 ++ 5 files changed, 30 insertions(+), 12 deletions(-) diff --git a/daemon/main/Options.cpp b/daemon/main/Options.cpp index ead51f061..5626a7fe8 100644 --- a/daemon/main/Options.cpp +++ b/daemon/main/Options.cpp @@ -656,7 +656,7 @@ void Options::CheckDir(CString& dir, const char* optionName, } } -void Options::InitOptions() +void Options::CheckDirs() { const char* mainDir = GetOption(OPTION_MAINDIR); @@ -667,7 +667,10 @@ void Options::InitOptions() CheckDir(m_webDir, OPTION_WEBDIR, nullptr, true, false); CheckDir(m_scriptDir, OPTION_SCRIPTDIR, mainDir, true, false); CheckDir(m_nzbDir, OPTION_NZBDIR, mainDir, false, true); +} +void Options::InitOptions() +{ m_requiredDir = GetOption(OPTION_REQUIREDDIR); m_configTemplate = GetOption(OPTION_CONFIGTEMPLATE); diff --git a/daemon/main/Options.h b/daemon/main/Options.h index 0839928ed..1d4d38dc7 100644 --- a/daemon/main/Options.h +++ b/daemon/main/Options.h @@ -196,6 +196,7 @@ class Options GuardedOptEntries GuardOptEntries() { return GuardedOptEntries(&m_optEntries, &m_optEntriesMutex); } void CreateSchedulerTask(int id, const char* time, const char* weekDays, ESchedulerCommand command, const char* param); + void CheckDirs(); // Options const char* GetConfigFilename() const { return m_configFilename; } diff --git a/daemon/main/nzbget.cpp b/daemon/main/nzbget.cpp index b24fbe8ac..fe6e237d6 100644 --- a/daemon/main/nzbget.cpp +++ b/daemon/main/nzbget.cpp @@ -327,6 +327,8 @@ void NZBGet::Init() info("nzbget %s remote-mode", Util::VersionRevision()); } + m_options->CheckDirs(); + info("using %s", m_options->GetConfigFilename()); info("nzbget runs on %s:%i", m_options->GetControlIp(), m_options->GetControlPort()); @@ -1010,18 +1012,24 @@ void NZBGet::Daemonize() if (getuid() == 0 || geteuid() == 0) { struct passwd *pw = getpwnam(m_options->GetDaemonUsername()); - if (pw) + if (pw == nullptr) { - // 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(m_options->GetDaemonUsername(), pw->pw_gid); - // Finally, set uid. - setuid(pw->pw_uid); + error("Starting daemon failed: invalid DaemonUsername"); + exit(1); + } + + // Change owner of lock- and logfile + chown(m_options->GetLockFile(), pw->pw_uid, pw->pw_gid); + chown(m_options->GetLogFile(), pw->pw_uid, pw->pw_gid); + + // Set aux groups to null, configure primary and aux groups, and then assign uid + if (setgroups(0, (const gid_t*)0) || + setgid(pw->pw_gid) || + initgroups(m_options->GetDaemonUsername(), pw->pw_gid) || + setuid(pw->pw_uid)) + { + error("Starting daemon failed: could not drop privileges"); + exit(1); } } diff --git a/tests/extension/ExtensionManagerTest.cpp b/tests/extension/ExtensionManagerTest.cpp index a03d38d3f..d22e3d92e 100644 --- a/tests/extension/ExtensionManagerTest.cpp +++ b/tests/extension/ExtensionManagerTest.cpp @@ -52,6 +52,8 @@ BOOST_AUTO_TEST_CASE(LoadExtesionsTest) Options options(&cmdOpts, nullptr); g_Options = &options; + options.CheckDirs(); + std::vector correctOrder = { "Extension2", "Extension1", "email" }; ExtensionManager::Manager manager; @@ -78,6 +80,8 @@ BOOST_AUTO_TEST_CASE(ShouldNotDeleteExtensionIfExtensionIsBusyTest) g_Options = &options; ExtensionManager::Manager manager; + options.CheckDirs(); + BOOST_REQUIRE(manager.LoadExtensions() == std::nullopt); const auto busyExt = manager.GetExtensions()[0]; diff --git a/tests/main/OptionsTest.cpp b/tests/main/OptionsTest.cpp index fd58bb998..0aee3f161 100644 --- a/tests/main/OptionsTest.cpp +++ b/tests/main/OptionsTest.cpp @@ -58,6 +58,8 @@ BOOST_AUTO_TEST_CASE(OptionsInitWithoutConfigurationFileTest) { Options options(nullptr, nullptr); + options.CheckDirs(); + BOOST_CHECK(options.GetConfigFilename() == nullptr); #ifdef WIN32 BOOST_CHECK(strcmp(options.GetTempDir(), "nzbget/tmp") == 0); From 550fd8997d229a13aa5321f1ac894d0a8a7a691a Mon Sep 17 00:00:00 2001 From: Denis <146707790+dnzbk@users.noreply.github.com> Date: Fri, 9 Aug 2024 02:21:48 -0700 Subject: [PATCH 04/19] Fix: macOS bundle identifier (#334) - use com.nzbgetinstead of net.sourceforge.nzbget as a bundle identifier --- osx/NZBGet-Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osx/NZBGet-Info.plist b/osx/NZBGet-Info.plist index 2d17696a7..4bc8e12a6 100644 --- a/osx/NZBGet-Info.plist +++ b/osx/NZBGet-Info.plist @@ -9,7 +9,7 @@ CFBundleIconFile mainicon.icns CFBundleIdentifier - net.sourceforge.nzbget + com.nzbget CFBundleInfoDictionaryVersion 6.0 CFBundleName From 07a8ab4b791e75f075a0f8ebfa55654496e9f8a0 Mon Sep 17 00:00:00 2001 From: Denis <146707790+dnzbk@users.noreply.github.com> Date: Sat, 10 Aug 2024 22:39:48 -0700 Subject: [PATCH 05/19] Fix: build with old OpenSSL (#348) - fixed build with old versions of OpenSSL due to missing OPENSSL_FULL_VERSION_STR --- daemon/system/SystemInfo.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/daemon/system/SystemInfo.cpp b/daemon/system/SystemInfo.cpp index 2673ef781..308d953e7 100644 --- a/daemon/system/SystemInfo.cpp +++ b/daemon/system/SystemInfo.cpp @@ -29,8 +29,6 @@ #include "Json.h" #include "Xml.h" -namespace System -{ #ifdef HAVE_NCURSES_H #include #endif @@ -38,6 +36,8 @@ namespace System #include #endif +namespace System +{ static const size_t BUFFER_SIZE = 512; SystemInfo::SystemInfo() @@ -52,7 +52,7 @@ namespace System void SystemInfo::InitLibsInfo() { - m_libraries.reserve(4); + m_libraries.reserve(5); m_libraries.push_back({ "LibXML2", LIBXML_DOTTED_VERSION }); #if defined(HAVE_NCURSES_H) || defined(HAVE_NCURSES_NCURSES_H) @@ -66,10 +66,12 @@ namespace System #ifdef HAVE_OPENSSL #ifdef LIBRESSL_VERSION_TEXT std::string str = LIBRESSL_VERSION_TEXT; - m_libraries.push_back({ "LibreSSL", - str.substr(str.find(" ") + 1) }); -#else + m_libraries.push_back({ "LibreSSL", str.substr(str.find(" ") + 1) }); +#elif defined(OPENSSL_FULL_VERSION_STR) m_libraries.push_back({ "OpenSSL", OPENSSL_FULL_VERSION_STR }); +#else + std::string str = OPENSSL_VERSION_TEXT; + m_libraries.push_back({ "OpenSSL", str.substr(str.find(" ") + 1) }); #endif #endif @@ -80,6 +82,8 @@ namespace System #ifdef BOOST_LIB_VERSION m_libraries.push_back({ "Boost", BOOST_LIB_VERSION }); #endif + + m_libraries.shrink_to_fit(); } const CPU& SystemInfo::GetCPUInfo() const From 9657e6ee22a02e7c7ba1c922a5eec140a579ff12 Mon Sep 17 00:00:00 2001 From: Denis <146707790+dnzbk@users.noreply.github.com> Date: Wed, 14 Aug 2024 03:08:57 -0700 Subject: [PATCH 06/19] Updated API doc and fixed Python examples (#350) - updated and moved NZBGet API documentation to the repository - fixed Python examples --- README.md | 1 + docs/api/API.md | 98 ++++++++++++++++++ docs/api/APPEND.md | 64 ++++++++++++ docs/api/CONFIG.md | 19 ++++ docs/api/CONFIGTEMPLATES.md | 34 ++++++ docs/api/DELETEEXTENSION.md | 18 ++++ docs/api/DOWNLOADEXTENSION.md | 19 ++++ docs/api/EDITQUEUE.md | 79 ++++++++++++++ docs/api/HISTORY.md | 161 +++++++++++++++++++++++++++++ docs/api/LISTFILES.md | 39 +++++++ docs/api/LISTGROUPS.md | 83 +++++++++++++++ docs/api/LOADCONFIG.md | 18 ++++ docs/api/LOADEXTENSIONS.md | 55 ++++++++++ docs/api/LOADLOG.md | 17 +++ docs/api/LOG.md | 27 +++++ docs/api/LOGSCRIPT.md | 11 ++ docs/api/LOGUPDATE.md | 11 ++ docs/api/PAUSEDOWNLOAD.md | 12 +++ docs/api/PAUSEPOST.md | 12 +++ docs/api/PAUSESCAN.md | 12 +++ docs/api/RATE.md | 15 +++ docs/api/RELOAD.md | 12 +++ docs/api/RESETSERVERVOLUME.md | 16 +++ docs/api/RESUMEDOWNLOAD.md | 12 +++ docs/api/RESUMEPOST.md | 12 +++ docs/api/RESUMESCAN.md | 12 +++ docs/api/SAVECONFIG.md | 17 +++ docs/api/SCAN.md | 16 +++ docs/api/SCHEDULERESUME.md | 20 ++++ docs/api/SERVERVOLUMES.md | 71 +++++++++++++ docs/api/SHUTDOWN.md | 12 +++ docs/api/STATUS.md | 64 ++++++++++++ docs/api/SYSINFO.md | 33 ++++++ docs/api/TESTEXTENSION.md | 18 ++++ docs/api/TESTSERVER.md | 29 ++++++ docs/api/TESTSERVERSPEED.md | 19 ++++ docs/api/UPDATEEXTENSION.md | 19 ++++ docs/api/VERSION.md | 12 +++ docs/api/WRITELOG.md | 16 +++ docs/extensions/EXTENSIONS.md | 37 ++++--- docs/extensions/POST-PROCESSING.md | 89 ++++++++-------- docs/extensions/QUEUE.md | 30 +++--- 42 files changed, 1294 insertions(+), 77 deletions(-) create mode 100644 docs/api/API.md create mode 100644 docs/api/APPEND.md create mode 100644 docs/api/CONFIG.md create mode 100644 docs/api/CONFIGTEMPLATES.md create mode 100644 docs/api/DELETEEXTENSION.md create mode 100644 docs/api/DOWNLOADEXTENSION.md create mode 100644 docs/api/EDITQUEUE.md create mode 100644 docs/api/HISTORY.md create mode 100644 docs/api/LISTFILES.md create mode 100644 docs/api/LISTGROUPS.md create mode 100644 docs/api/LOADCONFIG.md create mode 100644 docs/api/LOADEXTENSIONS.md create mode 100644 docs/api/LOADLOG.md create mode 100644 docs/api/LOG.md create mode 100644 docs/api/LOGSCRIPT.md create mode 100644 docs/api/LOGUPDATE.md create mode 100644 docs/api/PAUSEDOWNLOAD.md create mode 100644 docs/api/PAUSEPOST.md create mode 100644 docs/api/PAUSESCAN.md create mode 100644 docs/api/RATE.md create mode 100644 docs/api/RELOAD.md create mode 100644 docs/api/RESETSERVERVOLUME.md create mode 100644 docs/api/RESUMEDOWNLOAD.md create mode 100644 docs/api/RESUMEPOST.md create mode 100644 docs/api/RESUMESCAN.md create mode 100644 docs/api/SAVECONFIG.md create mode 100644 docs/api/SCAN.md create mode 100644 docs/api/SCHEDULERESUME.md create mode 100644 docs/api/SERVERVOLUMES.md create mode 100644 docs/api/SHUTDOWN.md create mode 100644 docs/api/STATUS.md create mode 100644 docs/api/SYSINFO.md create mode 100644 docs/api/TESTEXTENSION.md create mode 100644 docs/api/TESTSERVER.md create mode 100644 docs/api/TESTSERVERSPEED.md create mode 100644 docs/api/UPDATEEXTENSION.md create mode 100644 docs/api/VERSION.md create mode 100644 docs/api/WRITELOG.md diff --git a/README.md b/README.md index f1630adfd..ae9dd784d 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ Android packages are available for Android 5.0+. [Android readme](docs/ANDROID.m ## Brief introduction on how to use NZBGet - [How to use](docs/HOW_TO_USE.md) - [Performance tips](docs/PERFORMANCE.md) + - [API reference](docs/api/API.md) ## Contribution diff --git a/docs/api/API.md b/docs/api/API.md new file mode 100644 index 000000000..545b3540e --- /dev/null +++ b/docs/api/API.md @@ -0,0 +1,98 @@ +## Status of this document + +The document describes methods available in NZBGet `version 13.0 and later`. The document is updated after a new stable version is out. Current testing version may have methods or fields not described here; these methods or fields may change before getting stable. + +## Protocols + +NZBGet supports `XML-RPC`, `JSON-RPC` and `JSON-P-RPC`. RPC-protocols allow to control the program from other applications. Many programming languages have libraries, which make the usage of `XML-RPC`, `JSON-RPC` and `JSON-P-RPC` very simple. The implementations of all three protocols in NZBGet are equal: all methods are identical. You may choose the protocol, which is better supported by your programming language. + +## Authentication + +NZBGet has three pairs of username/password: + +- options `ControlUsername` and `ControlPassword` - Full access, usually used when connecting to web-interface; +- options `RestrictedUsername` and `RestrictedPassword` - `v15.0` 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. In terms of RPC-API the user: + - cannot use method `saveconfig`; + - methods `config` and `loadconfig` return string `***` for options those content is protected from the user. +- options `AddUsername` and `AddPassword` - `v15.0` This user has only two permissions: + - add new downloads using RPC-method `append`; + - check program version using RPC-method `version`. + + +The RPC server uses HTTP basic authentication. The URL would be something like: + +- for **XML-RPC**: `http://username:password@localhost:6789/xmlrpc` +- for **JSON-RPC**: `http://username:password@localhost:6789/jsonrpc` +- for **JSON-P-RPC**: `http://username:password@localhost:6789/jsonprp` + +If HTTP basic authentication is somewhat problematic the username/password can also be passed in the URL as the first part of the path: + +- `http://localhost:6789/username:password/xmlrpc` + +`Security warning`: HTTP authentication is not secure. Although the password is encoded using Base64 it is not encrypted. For secure communication use HTTPS (needs to be explicitly enabled in NZBGet settings by user). + +## Features and limitations + +- Multicalls are supported for `XML-RPC` and not supported for `JSON-RPC`. +- Introspection is not supported. +- Only positional parameters are supported in `JSON-RPC`. Named parameters are not supported. Therefore parameter names are ignored but the order of parameters is important. All parameters are mandatory. +- Each call to `JSON-P-RPC` has one additional parameter - the name of callback-function. This parameter must be named `callback` and must be passed first (before any other parameters). +- 64 bit integers are returned in two separate fields `Hi` and `Lo` (for example `FileSizeHi` and `FileSizeLo`). These fields are unsigned 32 bit integers. Although dynamic languages such as PHP or Python have no problems with these fields the `XML-RPC` specification does not allow unsigned integers. This may cause troubles in statically typed languages such as Java or C++ if `XML-RPC`-parser expects only signed integers in 32 bit range. As a solution use `JSON-RPC` instead (which does allow unsigned integers) instead of `XML-RPC`. + +## Program control + +- [version](VERSION.md) +- [shutdown](SHUTDOWN.md) +- [reload](RELOAD.md) + +## Queue and history + +- [listgroups](LISTGROUPS.md) +- [listfiles](LISTFILES.md) +- [history](HISTORY.md) +- [append](APPEND.md) +- [editqueue](EDITQUEUE.md) +- [scan](SCAN.md) + +## Status, logging and statistics + +- [status](STATUS.md) +- [sysinfo](SYSINFO.md) +- [log](LOG.md) +- [writelog](WRITELOG.md) +- [loadlog](LOADLOG.md) +- [logscript](LOGSCRIPT.md) +- [logupdate](LOGUPDATE.md) +- [servervolumes](SERVERVOLUMES.md) +- [resetservervolume](RESETSERVERVOLUME.md) + +## Pause and speed limit + +- [rate](RATE.md) +- [pausedownload](PAUSEDOWNLOAD.md) +- [resumedownload](RESUMEDOWNLOAD.md) +- [pausepost](PAUSEPOST.md) +- [resumepost](RESUMEPOST.md) +- [pausescan](PAUSESCAN.md) +- [resumescan](RESUMESCAN.md) +- [scheduleresume](SCHEDULERESUME.md) + +## Configuration + +- [config](CONFIG.md) +- [loadconfig](LOADCONFIG.md) +- [saveconfig](SAVECONFIG.md) +- [configtemplates](CONFIGTEMPLATES.md) + +## Extensions + +- [loadextensions](LOADEXTENSIONS.md) +- [donwloadextension](DOWNLOADEXTENSION.md) +- [updateextension](UPDATEEXTENSION.md) +- [deleteextension](DELETEEXTENSION.md) + +## Tests + +- [testextension](TESTEXTENSION.md) +- [testserver](TESTSERVER.md) +- [testserverspeed](TESTSERVERSPEED.md) diff --git a/docs/api/APPEND.md b/docs/api/APPEND.md new file mode 100644 index 000000000..43060a238 --- /dev/null +++ b/docs/api/APPEND.md @@ -0,0 +1,64 @@ +## API-method `append` + +### Signature +``` c++ +int append( + string NZBFilename, + string NZBContent, + string Category, + int Priority, + bool AddToTop, + bool AddPaused, + string DupeKey, + int DupeScore, + string DupeMode, + struct[] PPParameters +); +``` + +_Add nzb-file or URL to download queue_ + +### Arguments +- **NZBFilename** `(string)` - name of nzb-file (with extension). This parameter can be an empty string if parameter Content contains an URL; in that case the file name is read from http headers. If `NZBFilename` is provided it must have correct extension (usually `.nzb`) according to file content. Files without `.nzb`-extension are not added to queue directly; all files however are sent to scan-scripts. +- **Content** `(string)` - content of nzb-file encoded with Base64 or URL to fetch nzb-file from. +- **Category** `(string)` - category for nzb-file. Can be empty string. +- **Priority** `(int)` - priority for nzb-file. 0 for `normal priority`, positive values for high priority and negative values for low 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). Default priorities are: -100 (very low), -50 (low), 0 (normal), 50 (high), 100 (very high), 900 (force). +- **AddToTop** `(bool)` - `true` if the file should be added to the top of the download queue or `false` if to the end. +- **AddPaused** `(bool)` - `true` if the file should be added in paused state. +DupeKey (string) - duplicate key for nzb-file. See RSS _(comming soon)_. +- **DupeScore** `(int)` - duplicate score for nzb-file. See RSS _(comming soon)_. +- **DupeMode** `(string)` - duplicate mode for nzb-file. See RSS _(comming soon)_. +- **PPParameters** `(array)` - `v16.0` post-processing parameters. The array consists of structures with following fields: + - **Name** `(string)` - name of post-processing parameter. + - **Value** `(string)` - value of post-processing parameter. + +### Return value +Positive number representing `NZBID` of the queue item. `0` and negative numbers represent error codes. Current version uses only error code `0`, newer versions may use other error codes for detailed information about error. + +### Example + +```python +from xmlrpc.client import ServerProxy +from base64 import standard_b64encode + +server = ServerProxy("http://nzbget:tegbzn6789@localhost:6789/xmlrpc") +filename = "/tmp/test.nzb" +with open(filename, "rb") as f: + nzb_content = f.read() +base64_nzb_content = standard_b64encode(nzb_content).decode() +server.append( + filename, + base64_nzb_content, + "software", + 0, + False, + False, + "", + 0, + "SCORE", + [ + ("*unpack:", "yes"), + ("EMail.py:", "yes") + ], +) +``` diff --git a/docs/api/CONFIG.md b/docs/api/CONFIG.md new file mode 100644 index 000000000..0836fa809 --- /dev/null +++ b/docs/api/CONFIG.md @@ -0,0 +1,19 @@ +## API-method `config` + +### Signature +``` c++ +struct[] config(); +``` + +### Description +Returns current configuration loaded into program. Please note that the configuration file on disk may differ from the loaded configuration. This may occur if the configuration file on disk was changed after the program was launched or the program may get some options passed via command line. + +### Return value +This method returns array of structures with following fields: + +- **Name** `(string)` - Option name. +- **Value** `(string)` - Option value. + +### Remarks +- For options with predefined possible values (yes/no, etc.) the values are returned always in lower case. +- If the option has variable references (e. g. `${MainDir}/dst`) the returned value has all variables substituted (e. g. `/home/user/downloads/dst`). diff --git a/docs/api/CONFIGTEMPLATES.md b/docs/api/CONFIGTEMPLATES.md new file mode 100644 index 000000000..30fa0e8be --- /dev/null +++ b/docs/api/CONFIGTEMPLATES.md @@ -0,0 +1,34 @@ +## API-method `configtemplates` + +### Signature +``` c++ +struct[] configtemplates(bool LoadFromDisk) +``` + +### Description +Returns NZBGet configuration file template and also extracts configuration sections from all post-processing files. This information is for example used by web-interface to build settings page or page `Postprocess` in download details dialog. + +### Arguments +- **LoadFromDisk** `(bool)` - `v15.0` `true` - load templates from disk, `false` - give templates loaded on program start. + +## Since +`v23.0` + +### Return value +This method returns array of structures with a field: + +- **Template** `(string)` - Content of the configuration template (multiple lines). + +## Till +`v23.0` + +### Return value +This method returns array of structures with following fields: + +- **Name** `(string)` - Post-processing script name. For example `videosort/VideoSort.py`. This field is empty in the first record, which holds the config template of the program itself. +- **DisplayName** `(string)` - Nice script name ready for displaying. For example `VideoSort`. +- **PostScript** `(bool)` - `true` for post-processing scripts. +- **ScanScript** `(bool)` - `true` for scan scripts. +- **QueueScript** `(bool)` - `true` for queue scripts. +- **SchedulerScript** `(bool)` - `true` for scheduler scripts. +- **Template** `(string)` - Content of the configuration template (multiple lines). diff --git a/docs/api/DELETEEXTENSION.md b/docs/api/DELETEEXTENSION.md new file mode 100644 index 000000000..4c4701fae --- /dev/null +++ b/docs/api/DELETEEXTENSION.md @@ -0,0 +1,18 @@ +## API-method `deleteextension` + +## Since +`v23.0` + +### Signature +``` c++ +bool deleteextension(string ExtName); +``` + +### Description +Deletes an extension. + +### Arguments +- **ExtName** - Extension name. + +### Return value +`true` on success or `false` on failure. diff --git a/docs/api/DOWNLOADEXTENSION.md b/docs/api/DOWNLOADEXTENSION.md new file mode 100644 index 000000000..7bd0c5666 --- /dev/null +++ b/docs/api/DOWNLOADEXTENSION.md @@ -0,0 +1,19 @@ +## API-method `downloadextension` + +## Since +`v23.0` + +### Signature +``` c++ +bool downloadextension(string URL, string ExtName); +``` + +### Description +Downloads and installs the extension. + +### Arguments +- **URL** - URL to download an extension. +- **ExtName** - Extension name. + +### Return value +`true` on success or `false` on failure. diff --git a/docs/api/EDITQUEUE.md b/docs/api/EDITQUEUE.md new file mode 100644 index 000000000..1a7b1aa98 --- /dev/null +++ b/docs/api/EDITQUEUE.md @@ -0,0 +1,79 @@ +## API-method `editqueue` + +### Signature + +### Since +`v18.0` +``` c++ +bool editqueue(string Command, string Param, int[] IDs); +``` + +### Till +`v18.0` +```c++ +bool editqueue(string Command, int Offset, string Param, int[] IDs); +``` + +### Description +Edit items in download queue or in history. + +### Arguments +- **Command** `(string)` - one of the following commands: + - **FileMoveOffset** - Move files relative to the current position in queue. `v18.0` Param contains offset. ~~`v18.0`~~ Offset is passed in `Offset`. + - **FileMoveTop** - Move files to top of queue. + - **FileMoveBottom** - Move files to bottom of queue. + - **FilePause** - Pause files. + - **FileResume** - Resume (unpause) files. + - **FileDelete** - Delete files. + - **FilePauseAllPars** - Pause only pars (does not affect other files). + - **FilePauseExtraPars** - Pause only pars, except main par-file (does not affect other files). + - **FileSetPriority** - ~~`v13.0`~~ Deprecated, use GroupSetPriority instead. + - **FileReorder** - Reorder files in the group. The list of IDs may include files only from one group. + - **FileSplit** - Split nzb-file. The list of IDs contains the files to move into new download item. + - **GroupMoveOffset** - Move groups relative to the current position in queue. `v18.0` Param contains offset. ~~`v18.0`~~ Offset is passed in Offset. + - **GroupMoveTop** - Move groups to top of queue. + - **GroupMoveBottom** - Move groups to bottom of queue. + - **GroupPause** - Pause groups. + - **GroupResume** - Resume (unpause) groups. + - **GroupDelete** - Delete groups and put to history. + - **GroupDupeDelete** - Delete groups, put to history and mark as duplicate. + - **GroupFinalDelete** - Delete groups without adding to history. + - **GroupPauseAllPars** - Pause only pars (does not affect other files). + - **GroupPauseExtraPars** - Pause only pars, except main par-file (does not affect other files). + - **GroupSetPriority** - Set priority for all files in group. Param contains priority value. + - **GroupSetCategory** - Set category for group. Param contains category name. + - **GroupApplyCategory** - Set or change category for groups and reassign pp-params according to category settings. `Param` contains category name. + - **GroupMerge** - Merge groups. + - **GroupSetParameter** - Set post-processing parameter for group. `Param` contains string in form of `Paramname=Paramvalue`. + - **GroupSetName** - Rename group. Param contains new name. + - **GroupSetDupeKey** - Set duplicate key. Param contains duplicate key. See RSS _(comming soon)_. + - **GroupSetDupeScore** - Set duplicate score. Param contains duplicate score. See RSS _(comming soon)_. + - **GroupSetDupeMode** - Set duplicate mode. Param contains one of `SCORE`, `ALL`, `FORCE`. See RSS _(comming soon)_. + - **GroupSort** - `v15.0` Sort selected or all groups. Parameter `Param` must be one of: `name`, `priority`, `category`, `size`, `left`; add character `+` or `-` to sort 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. `Parameter IDs` contains the list of groups to sort; pass empty array to sort all groups. + - **PostMoveOffset** - ~~`v13.0`~~ Deprecated, use `GroupMoveOffset` instead. + - **PostMoveTop** - ~~`v13.0`~~ Deprecated, use `GroupMoveTop` instead. + - **PostMoveBottom** - ~~`v13.0`~~ Deprecated, use `GroupMoveBottom instead. + - **PostDelete** - Delete post-jobs. + - **HistoryDelete** - Hide history items (mark as hidden). + - **HistoryFinalDelete** - Delete history items. + - **HistoryReturn** - Return history items back to download queue. + - **HistoryProcess** - Post-process history items again. + - **HistoryRedownload** - Move history items back to download queue for redownload. + - **HistorySetName** - `v15.0` Rename history item. `Param` contains new name. + - **HistorySetCategory** - `v15.0` Set category for history item. Param contains category name. + - **HistorySetParameter** - Set post-processing parameter for history items. `Param` contains string in form of `Paramname=Paramvalue`. + - **HistorySetDupeKey** - Set duplicate key. `Param` contains duplicate key. See RSS _(comming soon)_. + - **HistorySetDupeScore** - Set duplicate score. `Param` contains duplicate score. See RSS _(comming soon)_. + - **HistorySetDupeMode** - Set duplicate mode. `Param` contains one of `SCORE`, `ALL`, `FORCE`. See RSS _(comming soon)_. + - **HistorySetDupeBackup** - Set `use as duplicate backup-flag` for history items. `Param` contains `0` or `1`. See RSS _(comming soon)_. + - **HistoryMarkBad** - Mark history item as bad (and download other duplicate). See RSS _(comming soon)_. + - **HistoryMarkGood** - Mark history item as good. See RSS _(comming soon)_. + - **HistoryMarkSuccess** - `v15.0` Mark history item as success. See RSS _(comming soon)_. +- **Offset (int)** - ~~`v18.0`~~ offset for commands `FileMoveOffset` and `GroupMoveOffset`. For all other commands must be `0`. `v18.0` Offset is passed in `Param` and parameter `Offset` should not be passed at all. +- **Param `(string)` - additional parameter if mentioned in the command description, otherwise an empty string. +- **IDs** `(struct[])` - array of IDs (as integers). + - File-commands (FileXXX) need ID of file. + - All other commands need `NZBID`. + +### Return value +`true` on success or `false` on failure. diff --git a/docs/api/HISTORY.md b/docs/api/HISTORY.md new file mode 100644 index 000000000..662a83c3a --- /dev/null +++ b/docs/api/HISTORY.md @@ -0,0 +1,161 @@ +## API-method `history` + +### Signature +``` c++ +struct[] history(bool Hidden); +``` + +### Description +Request for list of items in history-list. + +### Arguments +- **Hidden** `(bool)` - Also return hidden history records. Use this only if you need to see the old (hidden) history records (Kind=DUP). Normal (unhidden) records are always returned. + +### Return value +This method returns an array of structures with following fields: + +- **NZBID** `(int)` - ID of NZB-file. +- **ID (int)** - ~~`v13.0`~~ Deprecated, use `NZBID` instead. +- **Kind** `(string)` - Kind of history item. One of the predefined text constants: + - **NZB** for nzb-files; + - **URL** for failed URL downloads. **NOTE**: successful URL-downloads result in adding files to download queue, a success-history-item is not created in this case; + - **DUP** for hidden history items. +- **NZBFilename** `(string)` - Name of nzb-file, this file was added to queue from. The filename could include fullpath (if client sent it by adding the file to queue). +- **Name** `(string)` - The name of nzb-file or info-name of URL, without path and extension. Ready for user-friendly output. +- **NZBName** `(string)` - nzb-file name. +- **NZBNicename** `(string)` - ~~`v12.0`~~ Deprecated, use `NZBName` instead. +- **URL** `(string)` - URL. +- **RetryData** `(bool)` - Has completed files. +- **HistoryTime** `(int)` - Date/time when the file was added to history (Time is in C/Unix format). +- **DestDir** `(string)` - Destination directory for output files. +- **FinalDir** `(string)` - Final destination if set by one of post-processing scripts. +- **Category** `(string)` - Category for group or empty string if none category is assigned. +- **FileSizeLo** `(int)` - Initial size of all files in group in bytes, Low 32-bits of 64-bit value. +- **FileSizeHi** `(int)` - Initial size of all files in group in bytes, High 32-bits of 64-bit value. +- **FileSizeMB** `(int)` - Initial size of all files in group in MiB. +- **FileCount** `(int)` - Initial number of files in group. +- **RemainingFileCount** `(int)` - Number of parked files in group. If this number is greater than `0`, the history item can be returned to download queue using command `HistoryReturn` of method [editqueue](EDITQUEUE.md). +- **MinPostTime** `(int)` - Date/time when the oldest file in the item was posted to newsgroup (Time is in C/Unix format). +- **MaxPostTime** `(int)` - Date/time when the newest file in the item was posted to newsgroup (Time is in C/Unix format). +- **TotalArticles** `(int)` - Total number of articles in all files of the group. +- **SuccessArticles** `(int)` - Number of successfully downloaded articles. +- **FailedArticles** `(int)` - Number of failed article downloads. +- **Health `(int)` - Final health of the group, in permille. 1000 means 100.0%. Higher values are better. +- **CriticalHealth** `(int)` - Calculated critical health of the group, in permille. 1000 means 100.0%. The critical health is calculated based on the number and size of par-files. Lower values are better. +- **Deleted** `(bool)` - ~~`v12.0`~~ Deprecated, use DeleteStatus instead. +- **DownloadedSizeLo** `(int)` - `v14.0` Amount of downloaded data for group in bytes, Low 32-bits of 64-bit value. +- **DownloadedSizeHi `(int)` - v14.0 Amount of downloaded data for group in bytes, High 32-bits of 64-bit value. +- **DownloadedSizeMB** `(int)` - `v14.0` Amount of downloaded data for group in MiB. +- **DownloadTimeSec** `(int)` - `v14.0` Download time in seconds. +- **PostTotalTimeSec** `(int)` - `v14.0` Total post-processing time in seconds. +- **ParTimeSec** `(int)` - `v14.0` Par-check time in seconds (incl. verification and repair). +- **RepairTimeSec** `(int)` - `v14.0` Par-repair time in seconds. +- **UnpackTimeSec** `(int)` - `v14.0` Unpack time in seconds. +- **MessageCount** `(int)` - `v15.0` Number of messages stored in the item log. Messages can be retrieved with method loadlog. +- **DupeKey** `(string)` - Duplicate key. See RSS __(comming soon)__. +- **DupeScore** `(int)` - Duplicate score. See RSS __(comming soon)__. +- **DupeMode** `(string)` - Duplicate mode. One of **SCORE**, **ALL**, **FORCE**. See RSS __(comming soon)__. +- **Status** `(string)` - Total status of the download. One of the predefined text constants such as `SUCCESS/ALL` or `FAILURE/UNPACK`, etc. For the complete list see below. +- **ParStatus** `(string)` - Result of par-check/repair: + - **NONE** - par-check wasn’t performed; + - **FAILURE** - par-check has failed; + - **REPAIR_POSSIBLE** - download is damaged, additional par-files were downloaded but the download was not repaired. Either the option ParRepair is disabled or the par-repair was cancelled by option ParTimeLimit; + - **SUCCESS** - par-check was successful; + - **MANUAL** - download is damaged but was not checked/repaired because option `ParCheck` is set to `Manual`. +- **ExParStatus** `(string)` - `v16.0` Indicates if the download was repaired using duplicate par-scan mode (option `ParScan=dupe`): + - **RECIPIENT** - repaired using blocks from other duplicates; + - **DONOR** - has donated blocks to repair another duplicate; +- **UnpackStatus** `(string)` - Result of unpack: + - **NONE** - unpack wasn’t performed, either no archive files were found or the unpack is disabled for that download or globally; + - **FAILURE** - unpack has failed; + - **SPACE** - unpack has failed due to not enough disk space; + - **PASSWORD** - unpack has failed because the password was not provided or was wrong. Only for rar5-archives; + - **SUCCESS** - unpack was successful. +- **UrlStatus** `(string)` - Result of URL-download: + - **NONE** - that nzb-file were not fetched from an URL; + - **SUCCESS** - that nzb-file was fetched from an URL; + - **FAILURE** - the fetching of the URL has failed. + - **SCAN_SKIPPED** - The URL was fetched successfully but downloaded file was not nzb-file and was skipped by the scanner; + - **SCAN_FAILURE** - The URL was fetched successfully but an error occurred during scanning of the downloaded file. The downloaded file isn’t a proper nzb-file. This status usually means the web-server has returned an error page (HTML page) instead of the nzb-file. +- **ScriptStatus `(string)` - Accumulated result of all post-processing scripts. One of the predefined text constants: `NONE`, `FAILURE`, `SUCCESS`. Also see field `ScriptStatuses`. +- **ScriptStatuses** `(struct[])` - Status info of each post-processing script. See below. +- **MoveStatus** `(string)` - Result of moving files from intermediate directory into final directory: + - **NONE** - the moving wasn’t made because either the option `InterDir` is not in use or the par-check or unpack have failed; + - **SUCCESS** - files were moved successfully; + - **FAILURE** - the moving has failed. +- **DeleteStatus** `(string)` - Indicates if the download was deleted: + - **NONE** - not deleted; + - **MANUAL** - the download was manually deleted by user; + - **HEALTH** - the download was deleted by health check; + - **DUPE** - the download was deleted by duplicate check; + - **BAD** - `v14.0` the download was marked as `BAD` by a queue-script during download; + - **SCAN** - `v16.0` the download was deleted because the nzb-file could not be parsed (malformed nzb-file); + - **COPY** - `v16.0` the download was deleted by duplicate check because an nzb-file with exactly same content exists in download queue or in history. +- **MarkStatus** `(string)` - Indicates if the download was marked by user: + - **NONE** - not marked; + - **GOOD** - the download was marked as good by user using command `Mark as good` in history dialog; + - **BAD** - the download was marked as bad by user using command `Mark as bad` in history dialog; +- **ExtraParBlocks** `(int)` - `v16.0` amount of extra par-blocks received from other duplicates or donated to other duplicates, when duplicate par-scan mode was used (option `ParScan`=`dupe`): + - **> 0** - has received extra blocks; + - **< 0** - has donated extra blocks; +- **Parameters** `(struct[])` - Post-processing parameters for group. For description of struct see method [listgroups](LISTGROUPS.md). +- **ServerStats** `(struct[])` - Per-server article completion statistics. +- **Log** `(struct[])` - ~~`v13.0`~~ Deprecated, was never really used. + +## Field Status +Field `Status` can be set to one of the following values: + +**For history items with `Kind=NZB`** + +- **SUCCESS/ALL** - Downloaded and par-checked or unpacked successfully. All post-processing scripts were successful. The download is completely OK. +- **SUCCESS/UNPACK** - Similar to `SUCCESS/ALL` but no post-processing scripts were executed. Downloaded and unpacked successfully. Par-check was successful or was not necessary. +- **SUCCESS/PAR** - Similar to `SUCCESS/ALL` but no post-processing scripts were executed. Downloaded and par-checked successfully. No unpack was made (there are no archive files or unpack was disabled for that download or globally). At least one of the post-processing scripts has failed. +- **SUCCESS/HEALTH** - Download was successful, download health is 100.0%. No par-check was made (there are no par-files). No unpack was made (there are no archive files or unpack was disabled for that download or globally). +- **SUCCESS/GOOD** - The download was marked as good by user using command Mark as good in history dialog. +- **SUCCESS/MARK** - `v15.0` The download was marked as success by user using command `Mark as success` in history dialog. +- **WARNING/SCRIPT** - Downloaded successfully. Par-check and unpack were either successful or were not performed. At least one post-processing script has failed. +- **WARNING/SPACE** - Unpack has failed due to not enough space on the drive. +- **WARNING/PASSWORD** - Unpack has failed because the password was not provided or was wrong. +- **WARNING/DAMAGED** - Par-check is required but is disabled in settings (option `ParCheck=Manual`). +- **WARNING/REPAIRABLE** - Par-check has detected a damage and has downloaded additional par-files but the repair is disabled in settings (option `ParRepair=no`). +- **WARNING/HEALTH** - Download health is below 100.0%. No par-check was made (there are no par-files). No unpack was made (there are no archive files or unpack was disabled for that download or globally). +- **DELETED/MANUAL** - The download was manually deleted by user. +- **DELETED/DUPE** - The download was deleted by duplicate check. +- **DELETED/COPY** - `v16.0` The download was deleted by duplicate check because this nzb-file already exists in download queue or in history. +- **DELETED/GOOD** - `v16.0` The download was deleted by duplicate check because there is a duplicate history item with status `GOOD` or a duplicate hidden history item with status`SUCCESS` which do not have any visible duplicates. +- **FAILURE/PAR** - The par-check has failed. +- **FAILURE/UNPACK** - The unpack has failed and there are no par-files. +- **FAILURE/MOVE** - An error has occurred when moving files from intermediate directory into the final destination directory. +- **FAILURE/SCAN** - `v16.0` nzb-file could not be parsed (malformed nzb-file). +- **FAILURE/BAD** - The download was marked as bad by user using command `Mark as bad` in history dialog. +- **FAILURE/HEALTH** - Download health is below critical health. No par-check was made (there are no par-files or the download was aborted by health check). No unpack was made (there are no archive files or unpack was disabled for that download or globally or the download was aborted by health check). + +**For history items with `Kind=URL`** + +- **DELETED/MANUAL** - The download was manually deleted by user. +- **DELETED/DUPE - The download was deleted by duplicate check. +- **WARNING/SKIPPED** - The URL was fetched successfully but downloaded file was not nzb-file and was skipped by the scanner. +- **FAILURE/FETCH** - Fetching of the URL has failed. +- **FAILURE/SCAN** - The URL was fetched successfully but an error occurred during scanning of the downloaded file. The downloaded file isn’t a proper nzb-file. This status usually means the web-server has returned an error page (HTML page) instead of the nzb-file. + +**For history items with `Kind=DUP`** + +- **SUCCESS/HIDDEN** - The hidden history item has status `SUCCESS`. +- **SUCCESS/GOOD** - The download was marked as good by user using command Mark as good in history dialog. +- **FAILURE/HIDDEN** - The hidden history item has status `FAILURE`. +- **DELETED/MANUAL** - The download was manually deleted by user. +- **DELETED/DUPE** - The download was deleted by duplicate check. +- **FAILURE/BAD** - The download was marked as bad by user using command `Mark as bad` in history dialog. + +### Field ScriptStatuses +Contains an array of structs with following fields: + +- **Name** `(string)` - Script name. +- **Status** `(string)` - Result of post-processing script exection. One of the predefined text constants: `NONE`, `FAILURE`, `SUCCESS`. + +### Field ServerStats +Contains an array of structs with following fields: + +- **ServerID** `(int)` - Server number as defined in section `news servers` of the configuration file. +- **SuccessArticles** `(int)` - Number of successfully downloaded articles. +- **FailedArticles** `(int)` - Number of failed articles. diff --git a/docs/api/LISTFILES.md b/docs/api/LISTFILES.md new file mode 100644 index 000000000..8b10e77bc --- /dev/null +++ b/docs/api/LISTFILES.md @@ -0,0 +1,39 @@ +## API-method `listfiles` + +### Signature +``` c++ +struct[] listfiles(int IDFrom, int IDTo, int NZBID); +``` + +### Description +Request for file's list of a group (nzb-file). + +### Arguments +- **IDFrom** `(int)` - ~~`v12.0`~~ Deprecated, must be 0. +- **IDTo** `(int)` - ~~`v12.0`~~ Deprecated, must be 0. +- **NZBID** `(int)` - NZBID of the group to be returned. + +To get the list of all files from all groups set all parameters to 0. + +### Return value +This method returns an array of structures with following fields: + +- **ID** `(int)` - ID of file. +- **NZBID** `(int)` - ID of NZB-file. +- **NZBFilename** `(string)` - Name of nzb-file. The filename could include fullpath (if client sent it by adding the file to queue). +- **NZBName** `(string)` - The name of nzb-file without path and extension. Ready for user-friendly output. +- **NZBNicename** `(string)` - ~~`v12.0`~~ Deprecated, use `NZBName` instead. +- **Subject** `(string)` - Subject of article (read from nzb-file). +- **Filename** `(string)` - Filename parsed from subject. It could be incorrect since the subject not always correct formated. After the first article for file is read, the correct filename is read from article body. +- **FilenameConfirmed** `(bool)` - `true` if filename was already read from article’s body. `false` if the name was parsed from subject. For confirmed filenames the destination file on disk will be exactly as specified in field `filename`. For unconfirmed filenames the name could change later. +- **DestDir** `(string)` - Destination directory for output file. +- **FileSizeLo** `(int)` - Filesize in bytes, Low 32-bits of 64-bit value. +- **FileSizeHi** `(int)` - Filesize in bytes, High 32-bits of 64-bit value. +- **RemainingSizeLo** `(int)` - Remaining size in bytes, Low 32-bits of 64-bit value. +- **RemainingSizeHi** `(int)` - Remaining size in bytes, High 32-bits of 64-bit value. +- **Paused** `(bool)` - `true` if file is paused. +- **PostTime** `(int)` - Date/time when the file was posted to newsgroup (Time is in C/Unix format). +- **Category** `(string)` - Category for group or empty string if none category is assigned. +- **Priority** `(int)` - ~~`v13.0`~~ Deprecated, use MaxPriority of the group (method [listgroups](LISTGROUPS.md)) instead. +- **ActiveDownloads** `(int)` - Number of active downloads for the file. With this filed can be determined what file(s) is (are) being currently downloaded. +- **Progress** `(int) `- `v15.0` Download progress, a number in the range 0..1000. Divide it to 10 to get percent-value. diff --git a/docs/api/LISTGROUPS.md b/docs/api/LISTGROUPS.md new file mode 100644 index 000000000..9e8d0ecec --- /dev/null +++ b/docs/api/LISTGROUPS.md @@ -0,0 +1,83 @@ +## API-method `listgroups` + +### Signature +``` c++ +struct[] listgroups(int NumberOfLogEntries); +``` + +### Description +Request for list of downloads (nzb-files). This method returns summary information for each group (nzb-file). + +### Arguments +- **NumberOfLogEntries** `(int)` - ~~`v15.0`~~ Number of post-processing log-entries (field `Log`), which should be returned for the top (currently processing) item in post-processing state. Deprecated, must be 0. + +### Return value +This method returns array of structures with following fields: + +- **NZBID** `(int)` - ID of NZB-file. +- **FirstID** `(int)` - ~~`v13.0`~~ Deprecated, use `NZBID` instead. +- **LastID** `(int)` - ~~`v13.0`~~ Deprecated, use `NZBID` instead. +- **NZBFilename** `(string)` - Name of nzb-file, this file was added to queue from. The filename could include fullpath (if client sent it by adding the file to queue). +- **NZBName** `(string)` - The name of nzb-file without path and extension. Ready for user-friendly output. +- **NZBNicename** `(string)` - ~~`v15.0`~~ Deprecated, use `NZBName` instead. +- **Kind** `(string)` - Kind of queue entry: NZB or URL. +- **URL** `(string)` - URL where the NZB-file was fetched `(Kind=NZB)` or should be fetched `(Kind=URL)`. +- **DestDir** `(string)` - Destination directory for output file. +- **FinalDir** `(string)` - Final destination if set by one of post-processing scripts. Can be set only for items in post-processing state. +- **Category** `(string)` - Category for group or empty string if none category is assigned. +- **FileSizeLo** `(int)` - Initial size of all files in group in bytes, Low 32-bits of 64-bit value. +- **FileSizeHi** `(int)` - Initial size of all files in group in bytes, High 32-bits of 64-bit value. +- **FileSizeMB** `(int)` - Initial size of all files in group in MiB. +- **RemainingSizeLo** `(int)` - Remaining size of all (remaining) files in group in bytes, Low 32-bits of 64-bit value. +- **RemainingSizeHi** `(int)` - Remaining size of all (remaining) files in group in bytes, High 32-bits of 64-bit value. +- **RemainingSizeMB** `(int)` - Remaining size of all (remaining) files in group in MiB. +- **PausedSizeLo** `(int)` - Size of all paused files in group in bytes, Low 32-bits of 64-bit value. +- **PausedSizeHi** `(int)` - Size of all paused files in group in bytes, High 32-bits of 64-bit value. +- **PausedSizeMB** `(int)` - Size of all paused files in group in MiB. +- **FileCount** `(int)` - Initial number of files in group. +- **RemainingFileCount** `(int)` - Remaining (current) number of files in group. +- **RemainingParCount** `(int)` - Remaining (current) number of par-files in group. +- **MinPostTime** `(int)` - Date/time when the oldest file in the group was posted to newsgroup (Time is in C/Unix format). +- **MaxPostTime** `(int)` - Date/time when the newest file in the group was posted to newsgroup (Time is in C/Unix format). +- **MinPriority** `(int)` - ~~`v13.0`~~ Deprecated, use `MaxPriority` instead. +- **MaxPriority** `(int)` - Priority of the group. “Max” in the field name has historical reasons. +- **ActiveDownloads** `(int)` - Number of active downloads in the group. With this filed can be determined what group(s) is (are) being currently downloaded. In most cases only one group is downloaded at a time however more that one group can be downloaded simultaneously when the first group is almost completely downloaded. +- **Status** `(string)`- Status of the group: + - **QUEUED** - queued for download; + - **PAUSED** - paused; + - **DOWNLOADING** - item is being downloaded; + - **FETCHING** - nzb-file is being fetched from URL `(Kind=URL)`; + - **PP_QUEUED** - queued for post-processing (completely downloaded); + - **LOADING_PARS** - stage of par-check; + - **VERIFYING_SOURCES** - stage of par-check; + - **REPAIRING** - stage of par-check; + - **VERIFYING_REPAIRED** - stage of par-check; + - **RENAMING** - processed by par-renamer; + - **UNPACKING** - being unpacked; + - **MOVING** - moving files from intermediate directory into destination directory; + - **EXECUTING_SCRIPT** - executing post-processing script; + - **PP_FINISHED** - post-processing is finished, the item is about to be moved to history. +- **TotalArticles** `(int)` - Total number of articles in all files of the group. +- **SuccessArticles** `(int)` - Number of successfully downloaded articles. +- **FailedArticles** `(int)` - Number of failed article downloads. +- **Health** `(int)` - Current health of the group, in permille. 1000 means 100.0%. The health can go down below this valued during download if more article fails. It can never increase (unless merging of groups). Higher values are better. +- **CriticalHealth** `(int)` - Calculated critical health of the group, in permille. 1000 means 100.0%. The critical health is calculated based on the number and size of par-files. Lower values are better. +- **DownloadedSizeLo** `(int)` - `v14.0` Amount of downloaded data for group in bytes, Low 32-bits of 64-bit value. +- **DownloadedSizeHi** `(int)` - `v14.0` Amount of downloaded data for group in bytes, High 32-bits of 64-bit value. +- **DownloadedSizeMB** `(int)` - `v14.0` Amount of downloaded data for group in MiB. +- **DownloadTimeSec** `(int)` - `v14.0` Download time in seconds. +- **MessageCount** `(int)` - `v15.0` Number of messages stored in the item log. Messages can be retrieved with method [loadlog](LOADLOG.md). +- **DupeKey** `(string)` - Duplicate key. See RSS _(comming soon)_. +- **DupeScore** `(int)` - Duplicate score. See RSS _(comming soon)_. +- **DupeMode** `(string)` - Duplicate mode. One of SCORE, ALL, FORCE. See RSS _(comming soon)_. +- **Parameters** `(struct[])` - Post-processing parameters for group. An array of structures with following fields: + - **Name** `(string)`- Name of post-processing parameter. + - **Value** `(string)` - Value of post-processing parameter. +- **Deleted** `(bool)` - ~~`v12.0`~~ Deprecated, use DeleteStatus instead. +- **ServerStats** `(struct[])` - Per news-server download statistics. For description see method [history](HISTORY.md). +- **ParStatus**, **UnpackStatus**, **ExParStatus**, **MoveStatus**, **ScriptStatus**, **DeleteStatus**, **UrlStatus**, **MarkStatus**, **ScriptStatuses**, **PostTotalTimeSec**, **ParTimeSec**, **RepairTimeSec**, **UnpackTimeSec** - These fields have meaning only for a group which is being currently post-processed. For description see method [history](HISTORY.md). +- **PostInfoText** `(string)` - Text with short description of current action in post processor. For example: `Verifying file myfile.rar`. Only for a group which is being currently post-processed. +- **PostStageProgress** `(int)` - Completing of current stage, in permille. 1000 means 100.0%. Only for a group which is being currently post-processed. +- **PostTotalTimeSec** `(int)` - Number of seconds this post-job is being processed (after it first changed the state from PP-QUEUED). Only for a group which is being currently post-processed. +- **PostStageTimeSec** `(int)` - Number of seconds the current stage is being processed. Only for a group which is being currently post-processed. +- **Log** `(struct[])` - ~~`v15.0`~~ Array of structs with log-messages. For description of struct see method [log](LOG.md). Only for a group which is being currently post-processed. The number of returned entries is limited by parameter `NumberOfLogEntries`. Deprecated, use method [loadlog](LOADLOG.md) instead. diff --git a/docs/api/LOADCONFIG.md b/docs/api/LOADCONFIG.md new file mode 100644 index 000000000..9a2f4924a --- /dev/null +++ b/docs/api/LOADCONFIG.md @@ -0,0 +1,18 @@ +## API-method `loadconfig` + +### Signature +``` c++ +struct[] loadconfig(); +``` + +### Description +Reads configuration file from the disk. + +### Return value +This method returns array of structures with following fields: + +- **Name** `(string)` - Option name. +- **Value** `(string)` - Option value. + +### Remarks +The option value is returned exactly as it is stored in the configuration file. For example it may contain variable references (e. g. `${MainDir}/dst`). diff --git a/docs/api/LOADEXTENSIONS.md b/docs/api/LOADEXTENSIONS.md new file mode 100644 index 000000000..0b29c352a --- /dev/null +++ b/docs/api/LOADEXTENSIONS.md @@ -0,0 +1,55 @@ +## API-method `loadextensions` + +## Since +`v23.0` + +### Signature +``` c++ +struct[] loadextensions(bool LoadFromDisk); +``` + +### Description +Reads extensions from the disk. + +### Arguments +- **LoadFromDisk** `(bool)` - `true` - load extensions from disk, `false` - give extensions loaded on program start. + +### Return value +This method returns array of structures. + +- **Entry** `(string)` +- **Location** `(string)` +- **RootDir** `(string)` +- **Name** `(string)` +- **DisplayName** `(string)` +- **About** `(string)` +- **Author** `(string)` +- **Homepage** `(string)` +- **License** `(string)` +- **Version** `(string)` +- **NZBGetMinVersion** `(string)` +- **PostScript** `(bool)` +- **ScanScript** `(bool)` +- **QueueScript** `(bool)` +- **SchedulerScript** `(bool)` +- **FeedScript** `(bool)` +- **QueueEvents** `(string)` +- **TaskTime** `(string)` +- **Description** `(string[])` +- **Requirements** `(string[])` +- **Options** `(struct[])` + - **Name** `(string)` + - **DisplayName** `(string)` + - **Description** `(string[])` + - **Select** `(string[])` +- **Commands** `(struct[])` + - **Name** `(string)` + - **Action** `(string)` + - **DisplayName** `(string)` + - **Description** `(struct[])` +- **Sections** `(struct[])` + - **Name** `(string)` + - **Prefix** `(string)` + - **Multi** `(bool)` + +See [Extensions](../extensions/EXTENSIONS.md). diff --git a/docs/api/LOADLOG.md b/docs/api/LOADLOG.md new file mode 100644 index 000000000..7fd14c050 --- /dev/null +++ b/docs/api/LOADLOG.md @@ -0,0 +1,17 @@ +## API-method `loadlog` + +## Since +`v15.0` + +### Signature +``` c++ +struct[] loadlog(int NZBID, int IDFrom, int NumberOfEntries); +``` + +### Description +Loads from disk and returns nzb-log for a specific nzb-file. + +### Arguments +- **NZBID** `(int)` - id of nzb-file. + +**NOTE** For other arguments and return values see method [log](LOG.md) diff --git a/docs/api/LOG.md b/docs/api/LOG.md new file mode 100644 index 000000000..71d88282a --- /dev/null +++ b/docs/api/LOG.md @@ -0,0 +1,27 @@ +## API-method `log` + +### Signature +``` c++ +struct[] log(int IDFrom, int NumberOfEntries); +``` + +### Description +This method returns entries from screen’s log-buffer. The size of this buffer is limited and can be set via option LogBufferSize. Which messages should be saved to screen-log and which should be saved to log-file can be set via options `DetailTarget`, `InfoTarget`, `WarningTarget`, `ErrorTarget` and `DebugTarget`. + +### Arguments +- **IDFrom** `(int)` - The first ID to be returned. +- **NumberOfEntries** `(int)` - Number of entries, which should be returned. + +**NOTE**: only one parameter - either `IDFrom` or `NumberOfEntries` - can be specified. The other parameter must be `0`. + +**TIP**: If your application stores log-entries between sub-sequential calls to method log(), the usage of parameter IDFrom is recommended, since it reduces the amount of transferred data. + +### Return value +This method returns array of structures with following fields: + +- **ID** `(int)` - ID of log-entry. +- **Kind** `(string)` - Class of log-entry, one of the following strings: `INFO`, `WARNING`, `ERROR`, `DEBUG`. +- **Time** `(int)` - Time in C/Unix format (number of seconds since 00:00:00 UTC, January 1, 1970). +- **Text** `(string)` - Log-message. + +**NOTE**: if there are no entries for requested criteria, an empty array is returned. diff --git a/docs/api/LOGSCRIPT.md b/docs/api/LOGSCRIPT.md new file mode 100644 index 000000000..962122611 --- /dev/null +++ b/docs/api/LOGSCRIPT.md @@ -0,0 +1,11 @@ +## API-method `logscript` + +### Signature +``` c++ +struct[] logscript(int idfrom, int entries); +``` + +### Description +Loads from disk and returns the log of a specific extension script. + +**NOTE** For the arguments and return values see method [log](LOG.md) diff --git a/docs/api/LOGUPDATE.md b/docs/api/LOGUPDATE.md new file mode 100644 index 000000000..17d0274a5 --- /dev/null +++ b/docs/api/LOGUPDATE.md @@ -0,0 +1,11 @@ +## API-method `logupdate` + +### Signature +``` c++ +struct[] logupdate(int idfrom, int entries); +``` + +### Description +Loads from disk and returns an update log. + +**NOTE** For the arguments and return values see method [log](LOG.md) diff --git a/docs/api/PAUSEDOWNLOAD.md b/docs/api/PAUSEDOWNLOAD.md new file mode 100644 index 000000000..30e2e71cb --- /dev/null +++ b/docs/api/PAUSEDOWNLOAD.md @@ -0,0 +1,12 @@ +## API-method `pausedownload` + +### Signature +``` c++ +bool pausedownload(); +``` + +### Description +Pause download queue. + +### Return value +Always `true`. diff --git a/docs/api/PAUSEPOST.md b/docs/api/PAUSEPOST.md new file mode 100644 index 000000000..3d5d2074c --- /dev/null +++ b/docs/api/PAUSEPOST.md @@ -0,0 +1,12 @@ +## API-method `pausepost` + +### Signature +``` c++ +bool pausepost(); +``` + +### Description +Pause post-processing. + +### Return value +Always `true`. diff --git a/docs/api/PAUSESCAN.md b/docs/api/PAUSESCAN.md new file mode 100644 index 000000000..9ce3d73d8 --- /dev/null +++ b/docs/api/PAUSESCAN.md @@ -0,0 +1,12 @@ +## API-method `pausescan` + +### Signature +``` c++ +bool pausescan(); +``` + +### Description +Pause scanning of directory with incoming nzb-files (option `NzbDir`). + +### Return value +Always `true`. diff --git a/docs/api/RATE.md b/docs/api/RATE.md new file mode 100644 index 000000000..2ef6aded8 --- /dev/null +++ b/docs/api/RATE.md @@ -0,0 +1,15 @@ +## API-method `rate` + +### Signature +``` c++ +bool rate(int Limit); +``` + +### Description +Set download speed limit. + +### Arguments +- **Limit** `(int)` - new download speed limit in KBytes/second. Value `0` disables speed throttling. + +### Return value +Usually `true` but may return `false` if parameter Limit was out of range. diff --git a/docs/api/RELOAD.md b/docs/api/RELOAD.md new file mode 100644 index 000000000..066da306e --- /dev/null +++ b/docs/api/RELOAD.md @@ -0,0 +1,12 @@ +## API-method `reload` + +### Signature +``` c++ +bool shutdown(); +``` + +### Description +Shutdown the program. + +### Return value +Always `true` diff --git a/docs/api/RESETSERVERVOLUME.md b/docs/api/RESETSERVERVOLUME.md new file mode 100644 index 000000000..f7a492436 --- /dev/null +++ b/docs/api/RESETSERVERVOLUME.md @@ -0,0 +1,16 @@ +## API-method `resetservervolume` + +### Signature +``` c++ +bool resetservervolume(int ServerId, string Sounter); +``` + +### Description +Reset download volume statistics for a specified news-server. + +### Arguments +- **ServerId** `(int)` - Server ID to reset. +- **Counter** `(string)` - The custom counter. + +### Return value +`true` on success or `false` on failure. diff --git a/docs/api/RESUMEDOWNLOAD.md b/docs/api/RESUMEDOWNLOAD.md new file mode 100644 index 000000000..8ecdf514e --- /dev/null +++ b/docs/api/RESUMEDOWNLOAD.md @@ -0,0 +1,12 @@ +## API-method `resumedownload` + +### Signature +``` c++ +bool resumedownload(); +``` + +### Description +Resume (previously paused) download queue. + +### Return value +Always `true`. diff --git a/docs/api/RESUMEPOST.md b/docs/api/RESUMEPOST.md new file mode 100644 index 000000000..10f37cef3 --- /dev/null +++ b/docs/api/RESUMEPOST.md @@ -0,0 +1,12 @@ +## API-method `resumepost` + +### Signature +``` c++ +bool resumepost(); +``` + +### Description +Resume (previously paused) post-processing. + +### Return value +Always `true`. diff --git a/docs/api/RESUMESCAN.md b/docs/api/RESUMESCAN.md new file mode 100644 index 000000000..b64522ca4 --- /dev/null +++ b/docs/api/RESUMESCAN.md @@ -0,0 +1,12 @@ +## API-method `resumescan` + +### Signature +``` c++ +bool resumescan(); +``` + +### Description +Resume (previously paused) scanning of directory with incoming nzb-files (option `NzbDir`). + +### Return value +Always `true`. diff --git a/docs/api/SAVECONFIG.md b/docs/api/SAVECONFIG.md new file mode 100644 index 000000000..a6c13ebd3 --- /dev/null +++ b/docs/api/SAVECONFIG.md @@ -0,0 +1,17 @@ +## API-method `saveconfig` + +### Signature +``` c++ +bool saveconfig(struct[] Options); +``` + +### Description +Saves configuration file to the disk. + +### Arguments +- **Options** `(struct[])` + - **Name** `(string)` - Option name. + - **Value** `(string)` - Option value. + +### Return value +`true` on success or `false` on failure. diff --git a/docs/api/SCAN.md b/docs/api/SCAN.md new file mode 100644 index 000000000..2cf46dc60 --- /dev/null +++ b/docs/api/SCAN.md @@ -0,0 +1,16 @@ +## API-method `shutdown` + +### Signature +``` c++ +bool scan(bool SyncMode); +``` + +### Description +Request rescanning of incoming directory for nzb-files (option `NzbDir`). + +### Arguments +- **SyncMode** `(bool)` - `Optional`. waits for completing of scan + before reporting the status. + +### Return value +Always `true` diff --git a/docs/api/SCHEDULERESUME.md b/docs/api/SCHEDULERESUME.md new file mode 100644 index 000000000..cceef7776 --- /dev/null +++ b/docs/api/SCHEDULERESUME.md @@ -0,0 +1,20 @@ +## API-method `scheduleresume` + +### Signature +``` c++ +bool scheduleresume(int Seconds); +``` + +### Description +Schedule resuming of all activities after expiring of wait interval. + +### Arguments +- **Seconds** `(int)` - Amount of seconds to wait before resuming. + +### Return value +`true` on success or `failure` result on error. + +### Remarks +Method `scheduleresume` activates a wait timer. When the timer fires then all pausable activities resume: download, post-processing and scan of incoming directory. This method doesn’t pause anything, activities must be paused before calling this method. + +Calling any of methods `pausedownload`, `resumedownload`, `pausepost`, `resumepost`, `pausescan`, `resumescan` deactivates the wait timer. diff --git a/docs/api/SERVERVOLUMES.md b/docs/api/SERVERVOLUMES.md new file mode 100644 index 000000000..a917a8d26 --- /dev/null +++ b/docs/api/SERVERVOLUMES.md @@ -0,0 +1,71 @@ +## API-method `servervolumes` + +### Signature +``` c++ +struct[] servervolumes(); +``` + +### Description +Returns download volume statistics per news-server. + +### Return value +This method returns an array of structures with following fields: + +- **ServerID** `(int)` - ID of news server. +- **DataTime** `(int)` - Date/time when the data was last updated (time is in C/Unix format). +- **TotalSizeLo** `(int)` - Total amount of downloaded data since program installation, low 32-bits of 64-bit value. +- **TotalSizeHi** `(int)` - Total amount of downloaded data since program installation, high 32-bits of 64-bit value. +- **TotalSizeMB** `(int)` - Total amount of downloaded data since program installation, in MiB. +- **CustomSizeLo** `(int)` - Amount of downloaded data since last reset of custom counter, low 32-bits of 64-bit value. +- **CustomSizeHi** `(int)` - Amount of downloaded data since last reset of custom counter, high 32-bits of 64-bit value. +- **CustomSizeMB** `(int)` - Amount of downloaded data since last reset of custom counter, in MiB. +- **CustomTime `(int)` - Date/time of the last reset of custom counter (time is in C/Unix format). +- **BytesPerSeconds** `(struct[])` - Per-second amount of data downloaded in last 60 seconds. See below. +- **BytesPerMinutes** `(struct[])` - Per-minute amount of data downloaded in last 60 minutes. See below. +- **BytesPerHours** `(struct[])` - Per-hour amount of data downloaded in last 24 hours. See below. +- **BytesPerDays** `(struct[])` - Per-day amount of data downloaded since program installation. See below. +- **SecSlot** `(int)` - The current second slot of field `BytesPerSeconds` the program writes into. +- **MinSlot** `(int)` - The current minute slot of field `BytesPerMinutes` the program writes into. +- **HourSlot** `(int)` - The current hour slot of field `BytesPerHours` the program writes into. +- **DaySlot** `(int)` - The current day slot of field `BytesPerDays` the program writes into. +- **FirstDay** `(int)` - Indicates which calendar day the very first slot of `BytesPerDays` corresponds to. Details see below. + +**NOTE**: The first record (serverid=0) are totals for all servers. + +### Field BytesPerSeconds, BytesPerMinutes, BytesPerHours, BytesPerDays +Contains an array of structs with following fields: + +- **SizeLo** `(int)` - Amount of downloaded data, low 32-bits of 64-bit value. +- **SizeHi** `(int)` - Amount of downloaded data, high 32-bits of 64-bit value. +- **SizeMB** `(int)` - Amount of downloaded data, in MiB. + +### Seconds, minutes and hours slots +These slots are arrays of fixed sizes (60, 60 and 24) which contain data for the last 60 seconds, 60 minutes and 24 hours. For example if current time “16:00:21” when the current slots would be: + +- `SecSlot = 21`; +- `MinSlot = 0`; +- `HourSlot = 16`. +Element 21 of `BytesPerSeconds` contains the amount of data downloaded in current second. Element 20 - in the previous second and element 22 - data downloaded 59 seconds ago. + +Similarly for minutes `(BytesPerMinutes)` and hours `(BytesPerHours)` arrays. + +### Daily slots +Daily slots are counted from the installation date, more precisely - from the date the program was used the first time after the installation. Or the first time it was used after deleting of statistics data, which is stored in directory pointed by option `QueueDir`. + +Therefore the first element of `BytesPerDays` array contains the amount of data downloaded at the first day of program usage. The second element - on the second day. The sub-sequential slots are created for each day, regardless of the fact if the program was running on this day or not. + +Field `DaySlot` shows into which day slot the data is currently being written. + +To find out the day slot for a specific day: + +- get the timestamp for any time (hour/minute/second) of this day, in C/Unix format. The C/Unix time format is defined as the number of seconds that have elapsed since 00:00:00, Thursday, 1 January 1970. +- divide this number by the number of seconds in one day `(24*60*60=86400)`, take the integral part. +- subtract the number contained in field `FirstDay`. + +For example, if the program was just installed and today is `17 March 2017` the field `FirstDay` will have value `17242` (timestamp 1489788000 divided by 86400). To find out the slot for `17 March 2017` we take the timestamp for any second of this day (for example [1489788000](http://www.unixtimestamp.com/)) and divide it by 86400 and subtract value of `FirstDay` (17242). We get number `0`, indicating slot 0. For `18 March 2017` the formula gives slot number `1` (which BTW doesn’t exist on 17 March yet). + +### Monthly and yearly data +You need to calculate the slot numbers for the first and the last days of a certain month, year or any other period and then sum the data from all slots belonging to the period. As an example see the source code of web-interface in file `status.js`. + +### Summary +As you see this method returns the raw data from the statistics meter and you have to perform calculations to represent the data in a user friendly format such as: downloaded today, yesterday, this or previous week or month etc. diff --git a/docs/api/SHUTDOWN.md b/docs/api/SHUTDOWN.md new file mode 100644 index 000000000..7c95d3302 --- /dev/null +++ b/docs/api/SHUTDOWN.md @@ -0,0 +1,12 @@ +## API-method `shutdown` + +### Signature +``` c++ +bool reload(); +``` + +### Description +Stop all activities and reinitialize the program. This method must be called after changing of program options for them to have effect. + +### Return value +Always `true` diff --git a/docs/api/STATUS.md b/docs/api/STATUS.md new file mode 100644 index 000000000..b5d726d6a --- /dev/null +++ b/docs/api/STATUS.md @@ -0,0 +1,64 @@ +## API-method `status` + +### Signature +``` c++ +struct status(); +``` + +### Description +Request for current status (summary) information. + +### Return value +This method returns structure with following fields: + +- **RemainingSizeLo** `(int)` - Remaining size of all entries in download queue, in bytes. This field contains the low 32-bits of 64-bit value +- **RemainingSizeHi** `(int)` - Remaining size of all entries in download queue, in bytes. This field contains the high 32-bits of 64-bit value +- **RemainingSizeMB** `(int)` - Remaining size of all entries in download queue, in MiB. +- **ForcedSizeLo** `(int)` - Remaining size of entries with FORCE priority, in bytes. This field contains the low 32-bits of 64-bit value +- **ForcedSizeHi** `(int)` - Remaining size of entries with FORCE priority, in bytes. This field contains the high 32-bits of 64-bit value +- **ForcedSizeMB** `(int)` - Remaining size of entries with FORCE priority, in MiB. +- **DownloadedSizeLo** `(int)` - Amount of data downloaded since server start, in bytes. This field contains the low 32-bits of 64-bit value +- **DownloadedSizeHi** `(int)` - Amount of data downloaded since server start, in bytes. This field contains the high 32-bits of 64-bit value +- **DownloadedSizeMB** `(int)` - Amount of data downloaded since server start, in MiB. +- **MonthSizeLo** `(int)` - Amount of data downloaded this month, in bytes. This field contains the low 32-bits of 64-bit value +- **MonthSizeHi** `(int)` - Amount of data downloaded this month, in bytes. This field contains the high 32-bits of 64-bit value +- **MonthSizeMB** `(int)` - Amount of data downloaded this month, in MiB. +- **DaySizeLo** `(int)` - Amount of data downloaded today, in bytes. This field contains the low 32-bits of 64-bit value. +- **DaySizeHi** `(int)` - Amount of data downloaded today, in bytes. This field contains the high 32-bits of 64-bit value. +- **DaySizeMB** `(int)` - Amount of data downloaded today, in MiB. +- **QuotaReached** `(bool)` - Indicates whether the quota has been reached. +- **ArticleCacheLo** `(int)` - `v14.0` Current usage of article cache, in bytes. This field contains the low 32-bits of 64-bit value. +- **ArticleCacheHi** `(int)` - `v14.0` Current usage of article cache, in bytes. This field contains the high 32-bits of 64-bit value. +- **ArticleCacheMB** `(int)` - `v14.0` Current usage of article cache, in MiB. +- **DownloadRate** `(int)` - ~~`24.2`~~ Deprecated. Current download speed, in Bytes per Second. +- **DownloadRateLo** `(int)` - `v24.2` Current download speed, in Bytes per Second. This field contains the low 32-bits of 64-bit value. +- **DownloadRateHi** `(int)` - `v24.2` Current download speed, in Bytes per Second. This field contains the high 32-bits of 64-bit value. +- **AverageDownloadRate** `(int)` - ~~`v24.2`~~ Deprecated. Average download speed since server start, in Bytes per Second. +- **AverageDownloadRateLo** `(int)` - `v24.2` Average download speed since server start, in Bytes per Second. This field contains the low 32-bits of 64-bit value. +- **AverageDownloadRateHi** `(int)` - `v24.2` Average download speed since server start, in Bytes per Second. This field contains the high 32-bits of 64-bit value. +- **DownloadLimit** `(int)` - Current download limit, in Bytes per Second. The limit can be changed via method “rate”. Be aware of different scales used by the method rate (Kilobytes) and this field (Bytes). +- **ThreadCount** `(int)` - Number of threads running. It includes all threads, created by the program, not only download-threads. +- **PostJobCount** `(int)` - Number of Par-Jobs or Post-processing script jobs in the post-processing queue (including current file). +- **ParJobCount** `(int)` - ~~`v12.0`~~ Deprecated, use PostJobCount instead. +- **UrlCount** `(int)` - Number of URLs in the URL-queue (including current file). +- **UpTimeSec** `(int)` - Server uptime in seconds. +- **DownloadTimeSec** `(int)` - Server download time in seconds. +- **ServerStandBy** `(bool)` - `false` - there are currently downloads running, `true` - no downloads in progress (server paused or all jobs completed). +- **DownloadPaused** `(bool)` - `true` if download queue is paused via first pause register (soft-pause). +- **Download2Paused** `(bool)` - ~~`v13.0`~~ Deprecated, use DownloadPaused instead. +- **ServerPaused** `(bool)` - ~~`v12.0`~~ Deprecated, use DownloadPaused instead. +- **PostPaused** `(bool)` - `true` if post-processor queue is currently in paused-state. +- **ScanPaused** `(bool)` - `true` if the scanning of incoming nzb-directory is currently in paused-state. +- **ServerTime** `(int)` - Current time on computer running NZBGet. Time is in C/Unix format (number of seconds since 00:00:00 UTC, January 1, 1970). +- **ResumeTime** `(int)` - Time to resume if set with method `scheduleresume`. Time is in C/Unix format. +- **FeedActive** `(bool)` - `true` if any RSS feed is being fetched right now. +- **FreeDiskSpaceLo** `(int)` - Free disk space on `DestDir`, in bytes. This field contains the low 32-bits of 64-bit value +- **FreeDiskSpaceHi** `(int)` - Free disk space on `DestDir`, in bytes. This field contains the high 32-bits of 64-bit value +- **FreeDiskSpaceMB** `(int)` - Free disk space on `DestDir`, in MiB. +- **TotalDiskSpaceLo** `(int)` - `v24.2` Total disk space on `DestDir`, in bytes. This field contains the low 32-bits of 64-bit value +- **TotalDiskSpaceHi** `(int)` - `v24.2` Total disk space on `DestDir`, in bytes. This field contains the high 32-bits of 64-bit value +- **TotalDiskSpaceMB** `(int)` - `v24.2` Total disk space on `DestDir`, in MiB. +- **QueueScriptCount** `(int)` - Indicates number of queue-scripts queued for execution including the currently running. +- **NewsServers** `(struct[])` - Status of news-servers, array of structures with following fields + - **ID** `(int)` - Server number in the configuration file. For example `1` for server defined by options `Server1.Host`, `Server1.Port`, etc. + - **Active** `(bool)` - `true` if server is in active state (enabled). `Active` doesn’t mean that the data is being downloaded from the server right now. This only means the server can be used for download (if there are any download jobs). diff --git a/docs/api/SYSINFO.md b/docs/api/SYSINFO.md new file mode 100644 index 000000000..76617fa5f --- /dev/null +++ b/docs/api/SYSINFO.md @@ -0,0 +1,33 @@ +## API-method `sysinfo` + +## Since +`v24.2` + +### Signature +``` c++ +struct sysinfo(); +``` + +### Description +returns information about the user's environment and hardware. + + +### Return value +This method returns an array of structures with following fields: + +- **OS** `(struct)` + - **Name** `(string)` - OS name. + - **Version** `(string)` - OS version. +- **CPU** `(struct)` + - **Model** `(string)` - CPU model. + - **Arch** `(string)` - CPU arch. +- **Network** `(struct)` + - **PublicIP** `(string)` - User's public IP. + - **PrivateIP** `(string)` - User's private IP. +- **Tools** `(struct[])` + - **Name** `(string)` - Tool name, e.g. `unrar`. + - **Version** `(string)` - Tool version, e.g. `7.00`. + - **Path** `(string)` - Tool path, e.g. `C:\ProgramData\NZBGet\unrar`. +- **Libraries** `(struct[])` - Libraries + - **Name** `(string)` - Library name, e.g. `OpenSSL`. + - **Version** `(string)` - Library version, e.g. `3.3.1`. diff --git a/docs/api/TESTEXTENSION.md b/docs/api/TESTEXTENSION.md new file mode 100644 index 000000000..10bd7f9ce --- /dev/null +++ b/docs/api/TESTEXTENSION.md @@ -0,0 +1,18 @@ +## API-method `testextension` + +## Since +`v23.0` + +### Signature +``` c++ +bool testextension(string ExtEntryName); +``` + +### Description +Tries to find an executor program, e.g. Pytohn for the extension. + +### Arguments +- `ExtEntryName` - Extension entry filename. + +### Return value +`true` on success or `false` on failure. diff --git a/docs/api/TESTSERVER.md b/docs/api/TESTSERVER.md new file mode 100644 index 000000000..449d623e8 --- /dev/null +++ b/docs/api/TESTSERVER.md @@ -0,0 +1,29 @@ +## API-method `testserver` + +### Signature +``` c++ +string testserver( + string host, + int port, + string username, + string password, + bool encryption, + string cipher, + int timeout +); +``` + +### Description +Tries to connect to a server. + +### Arguments +- **host** `(string)` - Server host. +- **port** `(int)` - Port. +- **username** `(string)` - User name. +- **password** `(string)` - User password. +- **encryption** `(bool)` - The inscription should be used. +- **cipher** `(string)` - Cipher for use. +- **timeout** `(int)` - Connection timeout. + +### Return value +`string` result. diff --git a/docs/api/TESTSERVERSPEED.md b/docs/api/TESTSERVERSPEED.md new file mode 100644 index 000000000..d9f848bdf --- /dev/null +++ b/docs/api/TESTSERVERSPEED.md @@ -0,0 +1,19 @@ +## API-method `testserverspeed` + +## Since +`v24.2` + +### Signature +``` c++ +bool testserverspeed(string nzbFileUrl, int serverId); +``` + +### Description +Adds a test NZB file with the highest priority to be downloaded upon receiving download speed results. + +### Arguments +- **nzbFileUrl** `(string)` - NZB file url to download. +- **serverId** `(int)` - Server ID. + +### Return value +`true` on success or `failure` result on error. diff --git a/docs/api/UPDATEEXTENSION.md b/docs/api/UPDATEEXTENSION.md new file mode 100644 index 000000000..e8a0d68bf --- /dev/null +++ b/docs/api/UPDATEEXTENSION.md @@ -0,0 +1,19 @@ +## API-method `updateextension` + +## Since +`v23.0` + +### Signature +``` c++ +bool updateextension(string URL, string ExtName); +``` + +### Description +Updates an extension. + +### Arguments +- **URL** `(string)` - URL to download an extension. +- **ExtName** `(string)` - Extension name. + +### Return value +`true` on success or `failure` result on error. diff --git a/docs/api/VERSION.md b/docs/api/VERSION.md new file mode 100644 index 000000000..b776ab170 --- /dev/null +++ b/docs/api/VERSION.md @@ -0,0 +1,12 @@ +## API-method `version` + +### Signature +``` c++ +string version(); +``` + +### Description +Request the version-string of the program. + +### Return value +Version `string` diff --git a/docs/api/WRITELOG.md b/docs/api/WRITELOG.md new file mode 100644 index 000000000..22da67aa6 --- /dev/null +++ b/docs/api/WRITELOG.md @@ -0,0 +1,16 @@ +## API-method `writelog` + +### Signature +``` c++ +bool writelog(string Kind, string Text); +``` + +### Description +Append log-entry into server’s log-file and on-screen log-buffer. + +### Arguments +- **Kind** `(string)` - Kind of log-message. Must be one of the following strings: `INFO`, `WARNING`, `ERROR`, `DETAIL`, `DEBUG`. Debug-messages are available, only if the program was compiled in debug-mode. +- **Text** `(string)` - Text to be added into log. + +### Return value +`true` on success or `failure` result on error. diff --git a/docs/extensions/EXTENSIONS.md b/docs/extensions/EXTENSIONS.md index 6c0777356..0f9702b3f 100644 --- a/docs/extensions/EXTENSIONS.md +++ b/docs/extensions/EXTENSIONS.md @@ -382,7 +382,7 @@ Example: obtaining post-processing log of current nzb-file (this is a short vers import os import sys import datetime -from xmlrpclib import ServerProxy +from xmlrpc.client import ServerProxy ## Exit codes used by NZBGet POSTPROCESS_SUCCESS = 93 @@ -397,31 +397,38 @@ POSTPROCESS_ERROR = 94 # First we need to know connection info: host, port, username 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'] +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' +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) +rpcUrl = f"http://{username}:{password}@{host}:{port}/xmlrpc" # Create remote server object server = ServerProxy(rpcUrl) -# Call remote method 'postqueue'. The only parameter tells how many log-entries to return as maximum. +# # Call remote method 'postqueue'. The only parameter tells how many log-entries to return as maximum. postqueue = server.postqueue(10000) -# Get field 'Log' from the first post-processing job -log = postqueue[0]['Log'] +# # Get field 'Log' from the first post-processing job +log = postqueue[0]["Log"] -# Now iterate through entries and save them to the output file +# post proccessing log file +pplog_file = f"{os.environ["NZBPP_DIRECTORY"]}/_postprocesslog.txt" + +# # Now iterate through entries and save them to the output file if len(log) > 0: -f = open('%s/_postprocesslog.txt' % os.environ['NZBPP_DIRECTORY'], 'w') -for entry in log: - f.write('%s\t%s\t%s\n' % (entry['Kind'], datetime.datetime.fromtimestamp(int(entry['Time'])), entry['Text'])) - f.close() + with open(pplog_file, "w") as f: + for entry in log: + timestamp = datetime.datetime.fromtimestamp(int(entry["Time"])) + output_file = f"{entry["Kind"]}\t{timestamp}\t{entry["Text"]}\n" + f.write(output_file) + + f.close() sys.exit(POSTPROCESS_SUCCESS) ``` diff --git a/docs/extensions/POST-PROCESSING.md b/docs/extensions/POST-PROCESSING.md index 220f21fa7..523a0aefa 100644 --- a/docs/extensions/POST-PROCESSING.md +++ b/docs/extensions/POST-PROCESSING.md @@ -21,54 +21,50 @@ in the Extension Manager. ### The information about nzb-file which is currently processed: - - NZBPP_DIRECTORY - Path to destination dir for downloaded files. - - NZBPP_FINALDIR - Final directory, if set by one of previous scripts (see below). - - NZBPP_NZBNAME - User-friendly name of processed nzb-file as it is displayed by the program. +- **NZBPP_DIRECTORY** - Path to destination dir for downloaded files. +- **NZBPP_FINALDIR** - Final directory, if set by one of previous scripts (see below). +- **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. If the file was added from incoming nzb-directory, +- **NZBPP_NZBFILENAME** - Name of processed nzb-file. If the file was added from incoming nzb-directory, this is a full file name, including path and extension. If the file was added from web-interface, it’s only the file name with extension. If the file was added via RPC-API (method append), this can be any string but the use of actual file name is recommended for developers. - - NZBPP_CATEGORY - Category assigned to nzb-file (can be empty string). - - NZBPP_TOTALSTATUS - v13.0 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; +- **NZBPP_CATEGORY** - Category assigned to nzb-file (can be empty string). +- **NZBPP_TOTALSTATUS** - `v13.0` 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. +- **NZBPP_STATUS** - Complete status info for nzb-file: it consists of total status and status detail separated with slash. There are many combinations. Just few examples: - - FAILURE/HEALTH; - - FAILURE/PAR; - - FAILURE/UNPACK; - - WARNING/REPAIRABLE; - - WARNING/SPACE; - - WARNING/PASSWORD; - - SUCCESS/ALL; - - SUCCESS/UNPACK. -For the complete list see description of [API-Method "history"](https://nzbget.com/documentation/api/history/). - - `NOTE:` one difference to the status returned by method history is that `NZBPP_STATUS` assumes all scripts are ended successfully. -Even if one of the scripts executed before the current one has failed the status will not be set to `WARNING/SCRIPT` -as method history will do. For example for a successful download the status would be `SUCCESS/ALL` instead. -Because most scripts don’t depend on other scripts they shouldn’t assume the download has failed if any of the previous scripts -(such as a notification script) has failed. The scripts interested in that info still can use parameter `NZBPP_SCRIPTSTATUS`. - - NZBPP_SCRIPTSTATUS - v13.0 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_PARSTATUS - v13.0 Result of par-check: - - 0 = not checked: par-check is disabled or nzb-file does not contain any par-files; - - 1 = checked and failed to repair; - - 2 = checked and successfully repaired; - - 3 = checked and can be repaired but repair is disabled; - - 4 = par-check needed but skipped (option ParCheck=manual). + - **FAILURE/HEALTH**; + - **FAILURE/PAR**; + - **FAILURE/UNPACK**; + - **WARNING/REPAIRABLE**; + - **WARNING/SPACE**; + - **WARNING/PASSWORD**; + - **SUCCESS/ALL**; + - **SUCCESS/UNPACK**. +For the complete list see description of [API-Method "history"](../api/HISTORY.md). + - **NOTE** one difference to the status returned by method history is that `NZBPP_STATUS` assumes all scripts are ended successfully. Even if one of the scripts executed before the current one has failed the status will not be set to `WARNING/SCRIPT` as method history will do. For example for a successful download the status would be `SUCCESS/ALL` instead. Because most scripts don’t depend on other scripts they shouldn’t assume the download has failed if any of the previous scripts (such as a notification script) has failed. The scripts interested in that info still can use parameter `NZBPP_SCRIPTSTATUS`. +- **NZBPP_SCRIPTSTATUS** - `v13.0` 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_PARSTATUS** - `v13.0` Result of par-check: + - **0** = not checked: par-check is disabled or nzb-file does not contain any par-files; + - **1** = checked and failed to repair; + - **2** = checked and successfully repaired; + - **3** = checked and can be repaired but repair is disabled; + - **4** = par-check needed but skipped (option ParCheck=manual). - `Deprecated, use NZBPP_STATUS and NZBPP_TOTALSTATUS instead.` - - NZBPP_UNPACKSTATUS - v13.0 Result of unpack: - - 0 = unpack is disabled or was skipped due to nzb-file properties or due to errors during par-check; - - 1 = unpack failed; - - 2 = unpack successful; - - 3 = write error (usually not enough disk space); - - 4 = wrong password (only for rar5 archives). +- **NZBPP_UNPACKSTATUS** - `v13.0` Result of unpack: + - **0** = unpack is disabled or was skipped due to nzb-file properties or due to errors during par-check; + - **1** = unpack failed; + - **2** = unpack successful; + - **3** = write error (usually not enough disk space); + - **4** = wrong password (only for rar5 archives). - `Deprecated, use NZBPP_STATUS and NZBPP_TOTALSTATUS instead.` ### Example: test if download failed @@ -82,11 +78,10 @@ fi ## Exit codes For post-processing extensions NZBGet checks the exit code of the script and sets the status in history item accordingly. Extensions can use following exit codes: - - 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 immediately without doing any - job and when this is not a failure termination. - - 92 - Request NZBGet to do par-check/repair for current nzb-file. This code can be used by pp-scripts doing unpack on their own. +- **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 immediately without doing any job and when this is not a failure termination. +- **92** - Request NZBGet to do par-check/repair for current nzb-file. This code can be used by pp-scripts doing unpack on their own. Extensions MUST return one of the listed status codes. If any other code (including 0) is returned the history item is marked with status `FAILURE`. @@ -102,7 +97,7 @@ exit $POSTPROCESS_SUCCESS pp-extensions which move downloaded files can inform NZBGet about that by printing special messages into standard output (which is processed by NZBGet). -This allows the extensions called thereafter to process the files in the new location. Use command DIRECTORY: +This allows the extensions called thereafter to process the files in the new location. Use command `DIRECTORY`: ```sh echo "[NZB] DIRECTORY=/path/to/new/location" ``` diff --git a/docs/extensions/QUEUE.md b/docs/extensions/QUEUE.md index 2752da15a..84b353a82 100644 --- a/docs/extensions/QUEUE.md +++ b/docs/extensions/QUEUE.md @@ -9,26 +9,26 @@ In the future they can be called on other events too. ## Queue event information - - NZBNA_DIRECTORY - Destination directory for downloaded files. - - NZBNA_FILENAME - Filename of the nzb-file. If the file was added from nzb-directory this is the fullname with path. + - **NZBNA_DIRECTORY** - Destination directory for downloaded files. + - **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_NZBNAME - Nzb-name as displayed in web-interface. - - NZBNA_URL - URL if the nzb-file was fetched from an URL. - - NZBNA_CATEGORY - Category of nzb-file. - - NZBNA_PRIORITY - Priority of nzb-file. - - NZBNA_NZBID - ID of queue entry, can be used in RPC-calls. - - NZBNA_EVENT - Describes why the extension was called. See below. - - NZBNA_URLSTATUS - Details for event type URL_COMPLETED. One of `FAILURE`, `SCAN_SKIPPED`, `SCAN_FAILURE`. - - NZBNA_DELETESTATUS - Details for event type NZB_DELETED. One of `MANUAL`, `DUPE`, `BAD`, `GOOD`, `COPY`, `SCAN`. + - **NZBNA_NZBNAME** - Nzb-name as displayed in web-interface. + - **NZBNA_URL** - URL if the nzb-file was fetched from an URL. + - **NZBNA_CATEGORY** - Category of nzb-file. + - **NZBNA_PRIORITY** - Priority of nzb-file. + - **NZBNA_NZBID** - ID of queue entry, can be used in RPC-calls. + - **NZBNA_EVENT** - Describes why the extension was called. See below. + - **NZBNA_URLSTATUS** - Details for event type URL_COMPLETED. One of `FAILURE`, `SCAN_SKIPPED`, `SCAN_FAILURE`. + - **NZBNA_DELETESTATUS** - Details for event type NZB_DELETED. One of `MANUAL`, `DUPE`, `BAD`, `GOOD`, `COPY`, `SCAN`. ## Queue event type The event type is passed with env. var NZBNA_EVENT and can have following values: - - 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 - after nzb-file is deleted from queue, by duplicate check or manually by user; - - URL_COMPLETED - after an nzb-file queued with URL is fetched but could no be added for download. + - **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** - after nzb-file is deleted from queue, by duplicate check or manually by user; + - **URL_COMPLETED** - after an nzb-file queued with URL is fetched but could no be added for download. In the future the list of supported events may be extended. To avoid conflicts with future NZBGet versions the extension must exit if the parameter has a value unknown to the extension: ```python From 1a3c3f62f786e10a9912221f9c9a29bf1b657291 Mon Sep 17 00:00:00 2001 From: Denis <146707790+dnzbk@users.noreply.github.com> Date: Fri, 16 Aug 2024 02:08:05 -0700 Subject: [PATCH 07/19] Fix: API link, typo, missing closing ** (#354) --- docs/api/API.md | 2 +- docs/api/APPEND.md | 6 +++--- docs/api/EDITQUEUE.md | 22 +++++++++++----------- docs/api/HISTORY.md | 8 ++++---- docs/api/LISTGROUPS.md | 6 +++--- docs/api/SERVERVOLUMES.md | 2 +- docs/extensions/EXTENSIONS.md | 2 +- docs/extensions/SCAN.md | 6 +++--- docs/usage/RSS.md | 4 ++++ 9 files changed, 31 insertions(+), 27 deletions(-) create mode 100644 docs/usage/RSS.md diff --git a/docs/api/API.md b/docs/api/API.md index 545b3540e..9037f239d 100644 --- a/docs/api/API.md +++ b/docs/api/API.md @@ -87,7 +87,7 @@ If HTTP basic authentication is somewhat problematic the username/password can a ## Extensions - [loadextensions](LOADEXTENSIONS.md) -- [donwloadextension](DOWNLOADEXTENSION.md) +- [downloadextension](DOWNLOADEXTENSION.md) - [updateextension](UPDATEEXTENSION.md) - [deleteextension](DELETEEXTENSION.md) diff --git a/docs/api/APPEND.md b/docs/api/APPEND.md index 43060a238..45d717136 100644 --- a/docs/api/APPEND.md +++ b/docs/api/APPEND.md @@ -25,9 +25,9 @@ _Add nzb-file or URL to download queue_ - **Priority** `(int)` - priority for nzb-file. 0 for `normal priority`, positive values for high priority and negative values for low 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). Default priorities are: -100 (very low), -50 (low), 0 (normal), 50 (high), 100 (very high), 900 (force). - **AddToTop** `(bool)` - `true` if the file should be added to the top of the download queue or `false` if to the end. - **AddPaused** `(bool)` - `true` if the file should be added in paused state. -DupeKey (string) - duplicate key for nzb-file. See RSS _(comming soon)_. -- **DupeScore** `(int)` - duplicate score for nzb-file. See RSS _(comming soon)_. -- **DupeMode** `(string)` - duplicate mode for nzb-file. See RSS _(comming soon)_. +DupeKey (string) - duplicate key for nzb-file. See [RSS](../usage/RSS.md). +- **DupeScore** `(int)` - duplicate score for nzb-file. See [RSS](../usage/RSS.md). +- **DupeMode** `(string)` - duplicate mode for nzb-file. See [RSS](../usage/RSS.md). - **PPParameters** `(array)` - `v16.0` post-processing parameters. The array consists of structures with following fields: - **Name** `(string)` - name of post-processing parameter. - **Value** `(string)` - value of post-processing parameter. diff --git a/docs/api/EDITQUEUE.md b/docs/api/EDITQUEUE.md index 1a7b1aa98..ee57fcd51 100644 --- a/docs/api/EDITQUEUE.md +++ b/docs/api/EDITQUEUE.md @@ -46,9 +46,9 @@ Edit items in download queue or in history. - **GroupMerge** - Merge groups. - **GroupSetParameter** - Set post-processing parameter for group. `Param` contains string in form of `Paramname=Paramvalue`. - **GroupSetName** - Rename group. Param contains new name. - - **GroupSetDupeKey** - Set duplicate key. Param contains duplicate key. See RSS _(comming soon)_. - - **GroupSetDupeScore** - Set duplicate score. Param contains duplicate score. See RSS _(comming soon)_. - - **GroupSetDupeMode** - Set duplicate mode. Param contains one of `SCORE`, `ALL`, `FORCE`. See RSS _(comming soon)_. + - **GroupSetDupeKey** - Set duplicate key. Param contains duplicate key. See [RSS](../usage/RSS.md). + - **GroupSetDupeScore** - Set duplicate score. Param contains duplicate score. See [RSS](../usage/RSS.md). + - **GroupSetDupeMode** - Set duplicate mode. Param contains one of `SCORE`, `ALL`, `FORCE`. See [RSS](../usage/RSS.md). - **GroupSort** - `v15.0` Sort selected or all groups. Parameter `Param` must be one of: `name`, `priority`, `category`, `size`, `left`; add character `+` or `-` to sort 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. `Parameter IDs` contains the list of groups to sort; pass empty array to sort all groups. - **PostMoveOffset** - ~~`v13.0`~~ Deprecated, use `GroupMoveOffset` instead. - **PostMoveTop** - ~~`v13.0`~~ Deprecated, use `GroupMoveTop` instead. @@ -62,15 +62,15 @@ Edit items in download queue or in history. - **HistorySetName** - `v15.0` Rename history item. `Param` contains new name. - **HistorySetCategory** - `v15.0` Set category for history item. Param contains category name. - **HistorySetParameter** - Set post-processing parameter for history items. `Param` contains string in form of `Paramname=Paramvalue`. - - **HistorySetDupeKey** - Set duplicate key. `Param` contains duplicate key. See RSS _(comming soon)_. - - **HistorySetDupeScore** - Set duplicate score. `Param` contains duplicate score. See RSS _(comming soon)_. - - **HistorySetDupeMode** - Set duplicate mode. `Param` contains one of `SCORE`, `ALL`, `FORCE`. See RSS _(comming soon)_. - - **HistorySetDupeBackup** - Set `use as duplicate backup-flag` for history items. `Param` contains `0` or `1`. See RSS _(comming soon)_. - - **HistoryMarkBad** - Mark history item as bad (and download other duplicate). See RSS _(comming soon)_. - - **HistoryMarkGood** - Mark history item as good. See RSS _(comming soon)_. - - **HistoryMarkSuccess** - `v15.0` Mark history item as success. See RSS _(comming soon)_. + - **HistorySetDupeKey** - Set duplicate key. `Param` contains duplicate key. See [RSS](../usage/RSS.md). + - **HistorySetDupeScore** - Set duplicate score. `Param` contains duplicate score. See [RSS](../usage/RSS.md). + - **HistorySetDupeMode** - Set duplicate mode. `Param` contains one of `SCORE`, `ALL`, `FORCE`. See [RSS](../usage/RSS.md). + - **HistorySetDupeBackup** - Set `use as duplicate backup-flag` for history items. `Param` contains `0` or `1`. See [RSS](../usage/RSS.md). + - **HistoryMarkBad** - Mark history item as bad (and download other duplicate). See [RSS](../usage/RSS.md). + - **HistoryMarkGood** - Mark history item as good. See [RSS](../usage/RSS.md). + - **HistoryMarkSuccess** - `v15.0` Mark history item as success. See [RSS](../usage/RSS.md). - **Offset (int)** - ~~`v18.0`~~ offset for commands `FileMoveOffset` and `GroupMoveOffset`. For all other commands must be `0`. `v18.0` Offset is passed in `Param` and parameter `Offset` should not be passed at all. -- **Param `(string)` - additional parameter if mentioned in the command description, otherwise an empty string. +- **Param** `(string)` - additional parameter if mentioned in the command description, otherwise an empty string. - **IDs** `(struct[])` - array of IDs (as integers). - File-commands (FileXXX) need ID of file. - All other commands need `NZBID`. diff --git a/docs/api/HISTORY.md b/docs/api/HISTORY.md index 662a83c3a..1807fc83b 100644 --- a/docs/api/HISTORY.md +++ b/docs/api/HISTORY.md @@ -52,9 +52,9 @@ This method returns an array of structures with following fields: - **RepairTimeSec** `(int)` - `v14.0` Par-repair time in seconds. - **UnpackTimeSec** `(int)` - `v14.0` Unpack time in seconds. - **MessageCount** `(int)` - `v15.0` Number of messages stored in the item log. Messages can be retrieved with method loadlog. -- **DupeKey** `(string)` - Duplicate key. See RSS __(comming soon)__. -- **DupeScore** `(int)` - Duplicate score. See RSS __(comming soon)__. -- **DupeMode** `(string)` - Duplicate mode. One of **SCORE**, **ALL**, **FORCE**. See RSS __(comming soon)__. +- **DupeKey** `(string)` - Duplicate key. See [RSS](../usage/RSS.md). +- **DupeScore** `(int)` - Duplicate score. See [RSS](../usage/RSS.md). +- **DupeMode** `(string)` - Duplicate mode. One of **SCORE**, **ALL**, **FORCE**. See [RSS](../usage/RSS.md). - **Status** `(string)` - Total status of the download. One of the predefined text constants such as `SUCCESS/ALL` or `FAILURE/UNPACK`, etc. For the complete list see below. - **ParStatus** `(string)` - Result of par-check/repair: - **NONE** - par-check wasn’t performed; @@ -133,7 +133,7 @@ Field `Status` can be set to one of the following values: **For history items with `Kind=URL`** - **DELETED/MANUAL** - The download was manually deleted by user. -- **DELETED/DUPE - The download was deleted by duplicate check. +- **DELETED/DUPE** - The download was deleted by duplicate check. - **WARNING/SKIPPED** - The URL was fetched successfully but downloaded file was not nzb-file and was skipped by the scanner. - **FAILURE/FETCH** - Fetching of the URL has failed. - **FAILURE/SCAN** - The URL was fetched successfully but an error occurred during scanning of the downloaded file. The downloaded file isn’t a proper nzb-file. This status usually means the web-server has returned an error page (HTML page) instead of the nzb-file. diff --git a/docs/api/LISTGROUPS.md b/docs/api/LISTGROUPS.md index 9e8d0ecec..2f5c7b3e8 100644 --- a/docs/api/LISTGROUPS.md +++ b/docs/api/LISTGROUPS.md @@ -67,9 +67,9 @@ This method returns array of structures with following fields: - **DownloadedSizeMB** `(int)` - `v14.0` Amount of downloaded data for group in MiB. - **DownloadTimeSec** `(int)` - `v14.0` Download time in seconds. - **MessageCount** `(int)` - `v15.0` Number of messages stored in the item log. Messages can be retrieved with method [loadlog](LOADLOG.md). -- **DupeKey** `(string)` - Duplicate key. See RSS _(comming soon)_. -- **DupeScore** `(int)` - Duplicate score. See RSS _(comming soon)_. -- **DupeMode** `(string)` - Duplicate mode. One of SCORE, ALL, FORCE. See RSS _(comming soon)_. +- **DupeKey** `(string)` - Duplicate key. [RSS](../usage/RSS.md). +- **DupeScore** `(int)` - Duplicate score. [RSS](../usage/RSS.md). +- **DupeMode** `(string)` - Duplicate mode. One of SCORE, ALL, FORCE. [RSS](../usage/RSS.md). - **Parameters** `(struct[])` - Post-processing parameters for group. An array of structures with following fields: - **Name** `(string)`- Name of post-processing parameter. - **Value** `(string)` - Value of post-processing parameter. diff --git a/docs/api/SERVERVOLUMES.md b/docs/api/SERVERVOLUMES.md index a917a8d26..6fc678174 100644 --- a/docs/api/SERVERVOLUMES.md +++ b/docs/api/SERVERVOLUMES.md @@ -19,7 +19,7 @@ This method returns an array of structures with following fields: - **CustomSizeLo** `(int)` - Amount of downloaded data since last reset of custom counter, low 32-bits of 64-bit value. - **CustomSizeHi** `(int)` - Amount of downloaded data since last reset of custom counter, high 32-bits of 64-bit value. - **CustomSizeMB** `(int)` - Amount of downloaded data since last reset of custom counter, in MiB. -- **CustomTime `(int)` - Date/time of the last reset of custom counter (time is in C/Unix format). +- **CustomTime** `(int)` - Date/time of the last reset of custom counter (time is in C/Unix format). - **BytesPerSeconds** `(struct[])` - Per-second amount of data downloaded in last 60 seconds. See below. - **BytesPerMinutes** `(struct[])` - Per-minute amount of data downloaded in last 60 minutes. See below. - **BytesPerHours** `(struct[])` - Per-hour amount of data downloaded in last 24 hours. See below. diff --git a/docs/extensions/EXTENSIONS.md b/docs/extensions/EXTENSIONS.md index 0f9702b3f..10c2ff7f6 100644 --- a/docs/extensions/EXTENSIONS.md +++ b/docs/extensions/EXTENSIONS.md @@ -374,7 +374,7 @@ if NZBGetVersion[0:5] < '11.1': ## Communication with NZBGet via RPC-API With RPC-API more things can be done than using command line. For documentation on available -RPC-methods see [API](https://nzbget.com/documentation/api). +RPC-methods see [API](../api/API.md). Example: obtaining post-processing log of current nzb-file (this is a short version of script Logger.py supplied with NZBGet): diff --git a/docs/extensions/SCAN.md b/docs/extensions/SCAN.md index a4b8f2a2f..d911482e5 100644 --- a/docs/extensions/SCAN.md +++ b/docs/extensions/SCAN.md @@ -17,9 +17,9 @@ This allows for example for scan extensions which unpack zip-files containing nz - NZBNP_PRIORITY - Priority of nzb-file. The extension can change this setting (see later). - NZBNP_TOP - Flag indicating that the file will be added to the top of queue: 0 or 1. The extension can change this setting (see later). - NZBNP_PAUSED - Flag indicating that the file will be added as paused: 0 or 1. The extension can change this setting (see later). - - NZBNP_DUPEKEY - Duplicate key (see RSS). The extension can change this setting (see later). - - NZBNP_DUPESCORE - Duplicate score (see RSS). The extension can change this setting (see later). - - NZBNP_DUPEMODE - Duplicate mode, one of SCORE, ALL, FORCE (see RSS). The extension can change this setting (see later). + - NZBNP_DUPEKEY - Duplicate key (see [RSS](../usage/RSS.md)). The extension can change this setting (see later). + - NZBNP_DUPESCORE - Duplicate score (see [RSS](../usage/RSS.md)). The extension can change this setting (see later). + - NZBNP_DUPEMODE - Duplicate mode, one of SCORE, ALL, FORCE (see [RSS](../usage/RSS.md)). The extension can change this setting (see later). ## Control commands diff --git a/docs/usage/RSS.md b/docs/usage/RSS.md new file mode 100644 index 000000000..16dfc8c0d --- /dev/null +++ b/docs/usage/RSS.md @@ -0,0 +1,4 @@ +## RSS and duplicate check + + +### Coming soon From 895f1890d717ca682deea736e5a9bd4028cf9693 Mon Sep 17 00:00:00 2001 From: Denis <146707790+dnzbk@users.noreply.github.com> Date: Fri, 16 Aug 2024 04:26:12 -0700 Subject: [PATCH 08/19] Fix: typos (#356) --- docs/api/API.md | 2 +- docs/api/RELOAD.md | 2 +- docs/api/SCAN.md | 2 +- docs/api/SHUTDOWN.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/api/API.md b/docs/api/API.md index 9037f239d..eb6191218 100644 --- a/docs/api/API.md +++ b/docs/api/API.md @@ -37,7 +37,7 @@ If HTTP basic authentication is somewhat problematic the username/password can a - Introspection is not supported. - Only positional parameters are supported in `JSON-RPC`. Named parameters are not supported. Therefore parameter names are ignored but the order of parameters is important. All parameters are mandatory. - Each call to `JSON-P-RPC` has one additional parameter - the name of callback-function. This parameter must be named `callback` and must be passed first (before any other parameters). -- 64 bit integers are returned in two separate fields `Hi` and `Lo` (for example `FileSizeHi` and `FileSizeLo`). These fields are unsigned 32 bit integers. Although dynamic languages such as PHP or Python have no problems with these fields the `XML-RPC` specification does not allow unsigned integers. This may cause troubles in statically typed languages such as Java or C++ if `XML-RPC`-parser expects only signed integers in 32 bit range. As a solution use `JSON-RPC` instead (which does allow unsigned integers) instead of `XML-RPC`. +- 64 bit integers are returned in two separate fields `Hi` and `Lo` (for example `FileSizeHi` and `FileSizeLo`). These fields are unsigned 32 bit integers. Although dynamic languages such as PHP or Python have no problems with these fields the `XML-RPC` specification does not allow unsigned integers. This may cause troubles in statically typed languages such as Java or C++ if `XML-RPC`-parser expects only signed integers in 32 bit range. As a solution use `JSON-RPC` (which does allow unsigned integers) instead of `XML-RPC`. ## Program control diff --git a/docs/api/RELOAD.md b/docs/api/RELOAD.md index 066da306e..66a6ced60 100644 --- a/docs/api/RELOAD.md +++ b/docs/api/RELOAD.md @@ -2,7 +2,7 @@ ### Signature ``` c++ -bool shutdown(); +bool reload(); ``` ### Description diff --git a/docs/api/SCAN.md b/docs/api/SCAN.md index 2cf46dc60..53710599e 100644 --- a/docs/api/SCAN.md +++ b/docs/api/SCAN.md @@ -1,4 +1,4 @@ -## API-method `shutdown` +## API-method `scan` ### Signature ``` c++ diff --git a/docs/api/SHUTDOWN.md b/docs/api/SHUTDOWN.md index 7c95d3302..0efad27e1 100644 --- a/docs/api/SHUTDOWN.md +++ b/docs/api/SHUTDOWN.md @@ -2,7 +2,7 @@ ### Signature ``` c++ -bool reload(); +bool shutdown(); ``` ### Description From f3db79afcc27b6fe0dcf1634137b2aaa0aa2f71f Mon Sep 17 00:00:00 2001 From: Denis <146707790+dnzbk@users.noreply.github.com> Date: Fri, 16 Aug 2024 05:20:39 -0700 Subject: [PATCH 09/19] Fix: checking dirs before running the daemon (#355) --- daemon/main/Options.cpp | 2 ++ daemon/main/Options.h | 3 ++- daemon/main/nzbget.cpp | 2 -- tests/extension/ExtensionManagerTest.cpp | 4 ---- tests/main/OptionsTest.cpp | 2 -- 5 files changed, 4 insertions(+), 9 deletions(-) diff --git a/daemon/main/Options.cpp b/daemon/main/Options.cpp index 5626a7fe8..df411ea5c 100644 --- a/daemon/main/Options.cpp +++ b/daemon/main/Options.cpp @@ -349,6 +349,8 @@ void Options::Init(const char* exeName, const char* configFilename, bool noConfi } ConvertOldOptions(&m_optEntries); + + CheckDirs(); InitOptions(); CheckOptions(); diff --git a/daemon/main/Options.h b/daemon/main/Options.h index 1d4d38dc7..f9e37ff4f 100644 --- a/daemon/main/Options.h +++ b/daemon/main/Options.h @@ -196,7 +196,6 @@ class Options GuardedOptEntries GuardOptEntries() { return GuardedOptEntries(&m_optEntries, &m_optEntriesMutex); } void CreateSchedulerTask(int id, const char* time, const char* weekDays, ESchedulerCommand command, const char* param); - void CheckDirs(); // Options const char* GetConfigFilename() const { return m_configFilename; } @@ -323,6 +322,8 @@ class Options bool GetRemoteClientMode() { return m_remoteClientMode; } private: + void CheckDirs(); + OptEntries m_optEntries; Mutex m_optEntriesMutex; Categories m_categories; diff --git a/daemon/main/nzbget.cpp b/daemon/main/nzbget.cpp index fe6e237d6..04ad097f3 100644 --- a/daemon/main/nzbget.cpp +++ b/daemon/main/nzbget.cpp @@ -327,8 +327,6 @@ void NZBGet::Init() info("nzbget %s remote-mode", Util::VersionRevision()); } - m_options->CheckDirs(); - info("using %s", m_options->GetConfigFilename()); info("nzbget runs on %s:%i", m_options->GetControlIp(), m_options->GetControlPort()); diff --git a/tests/extension/ExtensionManagerTest.cpp b/tests/extension/ExtensionManagerTest.cpp index d22e3d92e..a03d38d3f 100644 --- a/tests/extension/ExtensionManagerTest.cpp +++ b/tests/extension/ExtensionManagerTest.cpp @@ -52,8 +52,6 @@ BOOST_AUTO_TEST_CASE(LoadExtesionsTest) Options options(&cmdOpts, nullptr); g_Options = &options; - options.CheckDirs(); - std::vector correctOrder = { "Extension2", "Extension1", "email" }; ExtensionManager::Manager manager; @@ -80,8 +78,6 @@ BOOST_AUTO_TEST_CASE(ShouldNotDeleteExtensionIfExtensionIsBusyTest) g_Options = &options; ExtensionManager::Manager manager; - options.CheckDirs(); - BOOST_REQUIRE(manager.LoadExtensions() == std::nullopt); const auto busyExt = manager.GetExtensions()[0]; diff --git a/tests/main/OptionsTest.cpp b/tests/main/OptionsTest.cpp index 0aee3f161..fd58bb998 100644 --- a/tests/main/OptionsTest.cpp +++ b/tests/main/OptionsTest.cpp @@ -58,8 +58,6 @@ BOOST_AUTO_TEST_CASE(OptionsInitWithoutConfigurationFileTest) { Options options(nullptr, nullptr); - options.CheckDirs(); - BOOST_CHECK(options.GetConfigFilename() == nullptr); #ifdef WIN32 BOOST_CHECK(strcmp(options.GetTempDir(), "nzbget/tmp") == 0); From 97ed96891877a14eff3bcc9d55530822e7baaa62 Mon Sep 17 00:00:00 2001 From: Denis <146707790+dnzbk@users.noreply.github.com> Date: Wed, 21 Aug 2024 02:37:36 -0700 Subject: [PATCH 10/19] Add: Write buffer raw to the System table (#357) --- webui/index.html | 6 +++++- webui/system-info.js | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/webui/index.html b/webui/index.html index 6fbc5d3f0..d090e28a6 100644 --- a/webui/index.html +++ b/webui/index.html @@ -738,7 +738,11 @@

System

- Article Cache + Write buffer + + + + Article cache diff --git a/webui/system-info.js b/webui/system-info.js index aefe52a28..15c369035 100644 --- a/webui/system-info.js +++ b/webui/system-info.js @@ -32,6 +32,7 @@ var SystemInfo = (new function($) var $SysInfo_FreeDiskSpace; var $SysInfo_TotalDiskSpace; var $SysInfo_ArticleCache; + var $SysInfo_WriteBuffer; var $SysInfo_ToolsTable; var $SysInfo_LibrariesTable; var $SysInfo_NewsServersTable; @@ -98,6 +99,7 @@ var SystemInfo = (new function($) $SysInfo_FreeDiskSpace = $('#SysInfo_FreeDiskSpace'); $SysInfo_TotalDiskSpace = $('#SysInfo_TotalDiskSpace'); $SysInfo_ArticleCache = $('#SysInfo_ArticleCache'); + $SysInfo_WriteBuffer = $('#SysInfo_WriteBuffer'); $SysInfo_ToolsTable = $('#SysInfo_ToolsTable'); $SysInfo_LibrariesTable = $('#SysInfo_LibrariesTable'); $SysInfo_NewsServersTable = $('#SysInfo_NewsServersTable'); @@ -207,6 +209,7 @@ var SystemInfo = (new function($) $SysInfo_Arch.text(sysInfo['CPU'].Arch || 'Unknown'); $SysInfo_ConfPath.text(Options.option('ConfigFile')); $SysInfo_ArticleCache.text(Util.formatSizeMB(+Options.option('ArticleCache'))); + $SysInfo_WriteBuffer.text(Util.formatSizeMB(+Options.option('WriteBuffer'))); renderIP(sysInfo['Network']); renderAppVersion(Options.option('Version')); From c7f971ed5c132454727cb122615affb4182911a1 Mon Sep 17 00:00:00 2001 From: Denis <146707790+dnzbk@users.noreply.github.com> Date: Mon, 26 Aug 2024 23:14:18 -0700 Subject: [PATCH 11/19] fix: potential dangling pointers and buffer overflow (#366) - resolved potential problems with dangling pointers and buffer overflow --- daemon/extension/Extension.cpp | 6 ++++-- daemon/util/FileSystem.cpp | 6 +++--- daemon/util/FileSystem.h | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/daemon/extension/Extension.cpp b/daemon/extension/Extension.cpp index 62555d32b..26531e5a4 100644 --- a/daemon/extension/Extension.cpp +++ b/daemon/extension/Extension.cpp @@ -402,7 +402,8 @@ namespace Extension } else if (const double* val = std::get_if(&option.value)) { - Xml::AddNewNode(optionsNode, "Value", "double", std::to_string(*val).c_str()); + std::string valStr = std::to_string(*val); + Xml::AddNewNode(optionsNode, "Value", "double", valStr.c_str()); } xmlNodePtr selectNode = xmlNewNode(nullptr, BAD_CAST "Select"); @@ -414,7 +415,8 @@ namespace Extension } else if (const double* val = std::get_if(&selectOption)) { - Xml::AddNewNode(selectNode, "Value", "double", std::to_string(*val).c_str()); + std::string valStr = std::to_string(*val); + Xml::AddNewNode(selectNode, "Value", "double", valStr.c_str()); } } diff --git a/daemon/util/FileSystem.cpp b/daemon/util/FileSystem.cpp index 2b3d6b076..706d2e88e 100644 --- a/daemon/util/FileSystem.cpp +++ b/daemon/util/FileSystem.cpp @@ -56,17 +56,17 @@ void FileSystem::NormalizePathSeparators(char* path) } } -std::optional FileSystem::GetFileRealPath(std::string_view path) +std::optional FileSystem::GetFileRealPath(const std::string& path) { #ifdef WIN32 char buffer[MAX_PATH]; - DWORD len = GetFullPathName(path.data(), MAX_PATH, buffer, nullptr); + DWORD len = GetFullPathName(path.c_str(), MAX_PATH, buffer, nullptr); if (len != 0) { return std::optional{ buffer }; } #else - if (char* realPath = realpath(path.data(), nullptr)) + if (char* realPath = realpath(path.c_str(), nullptr)) { std::string res = realPath; free(realPath); diff --git a/daemon/util/FileSystem.h b/daemon/util/FileSystem.h index 2d9cc200c..1aabb697a 100644 --- a/daemon/util/FileSystem.h +++ b/daemon/util/FileSystem.h @@ -40,7 +40,7 @@ class FileSystem static char* BaseFileName(const char* filename); static bool SameFilename(const char* filename1, const char* filename2); static void NormalizePathSeparators(char* path); - static std::optional GetFileRealPath(std::string_view path); + static std::optional GetFileRealPath(const std::string& path); static bool LoadFileIntoBuffer(const char* filename, CharBuffer& buffer, bool addTrailingNull); static bool SaveBufferIntoFile(const char* filename, const char* buffer, int bufLen); static bool AllocateFile(const char* filename, int64 size, bool sparse, CString& errmsg); From 0d9a7ee33bc26769b640c0f03ab8ffdccd111489 Mon Sep 17 00:00:00 2001 From: Denis <146707790+dnzbk@users.noreply.github.com> Date: Thu, 29 Aug 2024 23:52:31 -0700 Subject: [PATCH 12/19] Fix: xml-rpc bool type (#371) - fixed invalid bool type in the loadextensions API method in XML format --- daemon/extension/Extension.cpp | 19 +++++++------------ daemon/extension/Extension.h | 2 -- daemon/util/Xml.cpp | 5 +++++ daemon/util/Xml.h | 1 + tests/extension/ExtensionTest.cpp | 14 +++++++------- 5 files changed, 20 insertions(+), 21 deletions(-) diff --git a/daemon/extension/Extension.cpp b/daemon/extension/Extension.cpp index 26531e5a4..4e86ac413 100644 --- a/daemon/extension/Extension.cpp +++ b/daemon/extension/Extension.cpp @@ -348,11 +348,11 @@ namespace Extension Xml::AddNewNode(structNode, "Version", "string", script.GetVersion()); Xml::AddNewNode(structNode, "NZBGetMinVersion", "string", script.GetNzbgetMinVersion()); - Xml::AddNewNode(structNode, "PostScript", "boolean", BoolToStr(script.GetPostScript())); - Xml::AddNewNode(structNode, "ScanScript", "boolean", BoolToStr(script.GetScanScript())); - Xml::AddNewNode(structNode, "QueueScript", "boolean", BoolToStr(script.GetQueueScript())); - Xml::AddNewNode(structNode, "SchedulerScript", "boolean", BoolToStr(script.GetSchedulerScript())); - Xml::AddNewNode(structNode, "FeedScript", "boolean", BoolToStr(script.GetFeedScript())); + Xml::AddNewNode(structNode, "PostScript", "boolean", Xml::BoolToStr(script.GetPostScript())); + Xml::AddNewNode(structNode, "ScanScript", "boolean", Xml::BoolToStr(script.GetScanScript())); + Xml::AddNewNode(structNode, "QueueScript", "boolean", Xml::BoolToStr(script.GetQueueScript())); + Xml::AddNewNode(structNode, "SchedulerScript", "boolean", Xml::BoolToStr(script.GetSchedulerScript())); + Xml::AddNewNode(structNode, "FeedScript", "boolean", Xml::BoolToStr(script.GetFeedScript())); Xml::AddNewNode(structNode, "QueueEvents", "string", script.GetQueueEvents()); Xml::AddNewNode(structNode, "TaskTime", "string", script.GetTaskTime()); @@ -375,7 +375,7 @@ namespace Extension Xml::AddNewNode(commandsNode, "Name", "string", command.name.c_str()); Xml::AddNewNode(commandsNode, "DisplayName", "string", command.displayName.c_str()); Xml::AddNewNode(commandsNode, "Action", "string", command.action.c_str()); - Xml::AddNewNode(commandsNode, "Multi", "boolean", BoolToStr(command.section.multi)); + Xml::AddNewNode(commandsNode, "Multi", "boolean", Xml::BoolToStr(command.section.multi)); Xml::AddNewNode(commandsNode, "Section", "string", command.section.name.c_str()); Xml::AddNewNode(commandsNode, "Prefix", "string", command.section.prefix.c_str()); @@ -392,7 +392,7 @@ namespace Extension { Xml::AddNewNode(optionsNode, "Name", "string", option.name.c_str()); Xml::AddNewNode(optionsNode, "DisplayName", "string", option.displayName.c_str()); - Xml::AddNewNode(optionsNode, "Multi", "boolean", BoolToStr(option.section.multi)); + Xml::AddNewNode(optionsNode, "Multi", "boolean", Xml::BoolToStr(option.section.multi)); Xml::AddNewNode(optionsNode, "Section", "string", option.section.name.c_str()); Xml::AddNewNode(optionsNode, "Prefix", "string", option.section.prefix.c_str()); @@ -442,9 +442,4 @@ namespace Extension return result; } - - const char* BoolToStr(bool value) - { - return value ? "true" : "false"; - } } diff --git a/daemon/extension/Extension.h b/daemon/extension/Extension.h index 0d04db577..ace0d84e2 100644 --- a/daemon/extension/Extension.h +++ b/daemon/extension/Extension.h @@ -109,8 +109,6 @@ namespace Extension std::string ToJsonStr(const Script& script); std::string ToXmlStr(const Script& script); - - const char* BoolToStr(bool value); } #endif diff --git a/daemon/util/Xml.cpp b/daemon/util/Xml.cpp index 213dfa530..758e6607f 100644 --- a/daemon/util/Xml.cpp +++ b/daemon/util/Xml.cpp @@ -49,4 +49,9 @@ namespace Xml { xmlAddChild(memberNode, valueNode); xmlAddChild(rootNode, memberNode); } + + const char* BoolToStr(bool value) noexcept + { + return value ? "1" : "0"; + } } diff --git a/daemon/util/Xml.h b/daemon/util/Xml.h index c41afff30..ba0ff0999 100644 --- a/daemon/util/Xml.h +++ b/daemon/util/Xml.h @@ -27,6 +27,7 @@ namespace Xml { std::string Serialize(const xmlNodePtr rootNode); void AddNewNode(xmlNodePtr rootNode, const char* name, const char* type, const char* value); + const char* BoolToStr(bool value) noexcept; } #endif diff --git a/tests/extension/ExtensionTest.cpp b/tests/extension/ExtensionTest.cpp index e7b33782b..5288f1615 100644 --- a/tests/extension/ExtensionTest.cpp +++ b/tests/extension/ExtensionTest.cpp @@ -121,11 +121,11 @@ BOOST_AUTO_TEST_CASE(ToXmlStrTest) LicenseLicense\ VersionVersion\ NZBGetMinVersion23.1\ -PostScripttrue\ -ScanScriptfalse\ -QueueScriptfalse\ -SchedulerScriptfalse\ -FeedScriptfalse\ +PostScript1\ +ScanScript0\ +QueueScript0\ +SchedulerScript0\ +FeedScript0\ QueueEventsQueueEvents\ TaskTimeTaskTime\ \ @@ -138,7 +138,7 @@ BOOST_AUTO_TEST_CASE(ToXmlStrTest) Namename\ DisplayNamedisplayName\ Actionaction\ -Multitrue\ +Multi1\ SectionSection\ PrefixPrefix\ \ @@ -148,7 +148,7 @@ BOOST_AUTO_TEST_CASE(ToXmlStrTest) \ Namename\ DisplayNamedisplayName\ -Multitrue\ +Multi1\ SectionSection\ PrefixPrefix\ Value5.000000\ From cc8ff5800d3b2acc162e4b8dcc9dc2a71fab8411 Mon Sep 17 00:00:00 2001 From: Denis <146707790+dnzbk@users.noreply.github.com> Date: Fri, 30 Aug 2024 21:34:05 -0700 Subject: [PATCH 13/19] Show InterDir disk info on the STATUS page (#369) - InterDir disk info is now also displayed on the Status page - the API method `status` now returns 6 additional properties: - FreeInterDiskSpaceLo `(int)` - Free disk space on `InterDir`, in bytes. This field contains the low 32-bits of 64-bit value - FreeInterDiskSpaceHi`(int)` - Free disk space on `InterDir`, in bytes. This field contains the high 32-bits of 64-bit value - FreeInterDiskSpaceMB `(int)` - Free disk space on `InterDir`, in MiB. - TotalInterDiskSpaceLo `(int)` - Total disk space on `InterDir`, in bytes. This field contains the low 32-bits of 64-bit value - TotalInterDiskSpaceHi `(int)` - otal disk space on `InterDir`, in bytes. This field contains the high 32-bits of 64-bit value - TotalInterDiskSpaceMB `(int)` - Total disk space on `InterDir`, in MiB. - updated doc --- daemon/remote/XmlRpc.cpp | 64 ++++++++++++++++++++++++++++++++++++---- docs/api/STATUS.md | 6 ++++ webui/index.html | 12 ++++---- webui/system-info.js | 54 ++++++++++++++++++++++++++++----- 4 files changed, 117 insertions(+), 19 deletions(-) diff --git a/daemon/remote/XmlRpc.cpp b/daemon/remote/XmlRpc.cpp index 9c300eec7..530dd8eba 100644 --- a/daemon/remote/XmlRpc.cpp +++ b/daemon/remote/XmlRpc.cpp @@ -1303,6 +1303,12 @@ void StatusXmlCommand::Execute() "TotalDiskSpaceLo%u\n" "TotalDiskSpaceHi%u\n" "TotalDiskSpaceMB%i\n" + "FreeInterDiskSpaceLo%u\n" + "FreeInterDiskSpaceHi%u\n" + "FreeInterDiskSpaceMB%i\n" + "TotalInterDiskSpaceLo%u\n" + "TotalInterDiskSpaceHi%u\n" + "TotalInterDiskSpaceMB%i\n" "ServerTime%i\n" "ResumeTime%i\n" "FeedActive%s\n" @@ -1359,6 +1365,12 @@ void StatusXmlCommand::Execute() "\"TotalDiskSpaceLo\" : %u,\n" "\"TotalDiskSpaceHi\" : %u,\n" "\"TotalDiskSpaceMB\" : %i,\n" + "\"FreeInterDiskSpaceLo\" : %u,\n" + "\"FreeInterDiskSpaceHi\" : %u,\n" + "\"FreeInterDiskSpaceMB\" : %i,\n" + "\"TotalInterDiskSpaceLo\" : %u,\n" + "\"TotalInterDiskSpaceHi\" : %u,\n" + "\"TotalInterDiskSpaceMB\" : %i,\n" "\"ServerTime\" : %i,\n" "\"ResumeTime\" : %i,\n" "\"FeedActive\" : %s,\n" @@ -1442,19 +1454,55 @@ void StatusXmlCommand::Execute() uint32 freeDiskSpaceHi, freeDiskSpaceLo; uint32 totalDiskSpaceHi, totalDiskSpaceLo; + uint32 freeInterDiskSpaceHi, freeInterDiskSpaceLo; + uint32 totalInterDiskSpaceHi, totalInterDiskSpaceLo; + int64 freeDiskSpace = 0; int64 totalDiskSpace = 0; - auto res = FileSystem::GetDiskState(g_Options->GetDestDir()); - if (res.has_value()) + int64 freeInterDiskSpace = 0; + int64 totalInterDiskSpace = 0; + + if (Util::EmptyStr(g_Options->GetDestDir())) { - const auto& value = res.value(); - freeDiskSpace = value.available; - totalDiskSpace = value.total; + freeDiskSpace = 0; + totalDiskSpace = 0; } + else + { + auto res = FileSystem::GetDiskState(g_Options->GetDestDir()); + if (res.has_value()) + { + const auto& value = res.value(); + freeDiskSpace = value.available; + totalDiskSpace = value.total; + } + } + + if (Util::EmptyStr(g_Options->GetInterDir())) + { + freeInterDiskSpace = freeDiskSpace; + totalInterDiskSpace = totalDiskSpace; + } + else + { + auto res = FileSystem::GetDiskState(g_Options->GetInterDir()); + if (res.has_value()) + { + const auto& value = res.value(); + freeInterDiskSpace = value.available; + totalInterDiskSpace = value.total; + } + } + Util::SplitInt64(freeDiskSpace, &freeDiskSpaceHi, &freeDiskSpaceLo); Util::SplitInt64(totalDiskSpace, &totalDiskSpaceHi, &totalDiskSpaceLo); + Util::SplitInt64(freeInterDiskSpace, &freeInterDiskSpaceHi, &freeInterDiskSpaceLo); + Util::SplitInt64(totalInterDiskSpace, &totalInterDiskSpaceHi, &totalInterDiskSpaceLo); + int freeDiskSpaceMB = static_cast(freeDiskSpace / 1024 / 1024); int totalDiskSpaceMB = static_cast(totalDiskSpace / 1024 / 1024); + int freeInterDiskSpaceMB = static_cast(freeInterDiskSpace / 1024 / 1024); + int totalInterDiskSpaceMB = static_cast(totalInterDiskSpace / 1024 / 1024); int serverTime = (int)Util::CurrentTime(); int resumeTime = (int)g_WorkState->GetResumeTime(); @@ -1482,6 +1530,12 @@ void StatusXmlCommand::Execute() totalDiskSpaceLo, totalDiskSpaceHi, totalDiskSpaceMB, + freeInterDiskSpaceLo, + freeInterDiskSpaceHi, + freeInterDiskSpaceMB, + totalInterDiskSpaceLo, + totalInterDiskSpaceHi, + totalInterDiskSpaceMB, serverTime, resumeTime, BoolToStr(feedActive), queuedScripts); int index = 0; diff --git a/docs/api/STATUS.md b/docs/api/STATUS.md index b5d726d6a..460cbf0f3 100644 --- a/docs/api/STATUS.md +++ b/docs/api/STATUS.md @@ -58,6 +58,12 @@ This method returns structure with following fields: - **TotalDiskSpaceLo** `(int)` - `v24.2` Total disk space on `DestDir`, in bytes. This field contains the low 32-bits of 64-bit value - **TotalDiskSpaceHi** `(int)` - `v24.2` Total disk space on `DestDir`, in bytes. This field contains the high 32-bits of 64-bit value - **TotalDiskSpaceMB** `(int)` - `v24.2` Total disk space on `DestDir`, in MiB. +- **FreeInterDiskSpaceLo** `(int)` - `v24.3` Free disk space on `InterDir`, in bytes. This field contains the low 32-bits of 64-bit value +- **FreeInterDiskSpaceHi** `(int)` - `v24.3` Free disk space on `InterDir`, in bytes. This field contains the high 32-bits of 64-bit value +- **FreeInterDiskSpaceMB** `(int)` - `v24.3` Free disk space on `InterDir`, in MiB. +- **TotalInterDiskSpaceLo** `(int)` - `v24.3` Total disk space on `InterDir`, in bytes. This field contains the low 32-bits of 64-bit value +- **TotalInterDiskSpaceHi** `(int)` - `v24.3` Total disk space on `InterDir`, in bytes. This field contains the high 32-bits of 64-bit value +- **TotalInterDiskSpaceMB** `(int)` - `v24.3` Total disk space on `InterDir`, in MiB. - **QueueScriptCount** `(int)` - Indicates number of queue-scripts queued for execution including the currently running. - **NewsServers** `(struct[])` - Status of news-servers, array of structures with following fields - **ID** `(int)` - Server number in the configuration file. For example `1` for server defined by options `Server1.Host`, `Server1.Port`, etc. diff --git a/webui/index.html b/webui/index.html index d090e28a6..2a61fa22b 100644 --- a/webui/index.html +++ b/webui/index.html @@ -729,13 +729,13 @@

System

Arch - - Free disk space - + + Free / Total disk space (DestDir) + - - Total disk space - + + Free / Total disk space (InterDir) + Write buffer diff --git a/webui/system-info.js b/webui/system-info.js index 15c369035..7a6d87d8b 100644 --- a/webui/system-info.js +++ b/webui/system-info.js @@ -29,8 +29,10 @@ var SystemInfo = (new function($) var $SysInfo_CPUModel; var $SysInfo_Arch; var $SysInfo_IP; - var $SysInfo_FreeDiskSpace; - var $SysInfo_TotalDiskSpace; + var $SysInfo_DestDiskSpace; + var $SysInfo_InterDiskSpace; + var $SysInfo_DestDiskSpaceContainer; + var $SysInfo_InterDiskSpaceContainer; var $SysInfo_ArticleCache; var $SysInfo_WriteBuffer; var $SysInfo_ToolsTable; @@ -65,7 +67,19 @@ var SystemInfo = (new function($) update: function(status) { $SysInfo_Uptime.text(Util.formatTimeHMS(status['UpTimeSec'])); - renderDiskSpace(+status['FreeDiskSpaceMB'], +status['TotalDiskSpaceMB']); + + var destDirOpt = Options.findOption(Options.options, 'DestDir'); + var interDirOpt = Options.findOption(Options.options, 'InterDir'); + + + + if (destDirOpt && interDirOpt) + { + var destDirPath = destDirOpt.Value; + var interDistPath = interDirOpt.Value ? interDirOpt.Value : destDirPath; + renderDiskSpace(+status['FreeDiskSpaceMB'], +status['TotalDiskSpaceMB'], destDirPath); + renderInterDiskSpace(+status['FreeInterDiskSpaceMB'], +status['TotalInterDiskSpaceMB'], interDistPath); + } } } @@ -96,8 +110,10 @@ var SystemInfo = (new function($) $SysInfo_CPUModel = $('#SysInfo_CPUModel'); $SysInfo_Arch = $('#SysInfo_Arch'); $SysInfo_IP = $('#SysInfo_IP'); - $SysInfo_FreeDiskSpace = $('#SysInfo_FreeDiskSpace'); - $SysInfo_TotalDiskSpace = $('#SysInfo_TotalDiskSpace'); + $SysInfo_DestDiskSpace = $('#SysInfo_DestDiskSpace'); + $SysInfo_InterDiskSpace = $('#SysInfo_InterDiskSpace'); + $SysInfo_InterDiskSpaceContainer = $('#SysInfo_InterDiskSpaceContainer'); + $SysInfo_DestDiskSpaceContainer = $('#SysInfo_DestDiskSpaceContainer'); $SysInfo_ArticleCache = $('#SysInfo_ArticleCache'); $SysInfo_WriteBuffer = $('#SysInfo_WriteBuffer'); $SysInfo_ToolsTable = $('#SysInfo_ToolsTable'); @@ -136,6 +152,17 @@ var SystemInfo = (new function($) ); } + function pathsOnSameDisk(path1, path2) + { + path1 = path1.replace(/\\/g, '/'); + path2 = path2.replace(/\\/g, '/'); + + var drive1 = path1.match(/^[a-zA-Z]:\//i) ? path1.match(/^[a-zA-Z]:\//i)[0] : '/'; + var drive2 = path2.match(/^[a-zA-Z]:\//i) ? path2.match(/^[a-zA-Z]:\//i)[0] : '/'; + + return drive1 === drive2; + } + function hideSpinner() { $SystemInfo_Spinner.hide(); @@ -257,11 +284,22 @@ var SystemInfo = (new function($) }); } - function renderDiskSpace(free, total) + function renderDiskSpace(free, total, path) + { + $SysInfo_DestDiskSpace.text(formatDiskInfo(free, total)); + $SysInfo_DestDiskSpaceContainer.attr('title', path); + } + + function renderInterDiskSpace(free, total, path) + { + $SysInfo_InterDiskSpace.text(formatDiskInfo(free, total)); + $SysInfo_InterDiskSpaceContainer.attr('title', path); + } + + function formatDiskInfo(free, total) { var percents = total !== 0 ? (free / total * 100).toFixed(1) + '%' : '0.0%'; - $SysInfo_FreeDiskSpace.text(Util.formatSizeMB(free) + ' / ' + percents); - $SysInfo_TotalDiskSpace.text(Util.formatSizeMB(total)); + return Util.formatSizeMB(free) + ' (' + percents + ') / ' + Util.formatSizeMB(total); } function renderIP(network) From 220e842ad5181f6911a6b1796fdc01d0091d0c71 Mon Sep 17 00:00:00 2001 From: Denis <146707790+dnzbk@users.noreply.github.com> Date: Tue, 3 Sep 2024 05:40:27 -0700 Subject: [PATCH 14/19] Disk performance tests (#375) - added disk performance tests - added the new `testdiskspeed` API method: #### Arguments - **dirPath** `(string)` - The path to the directory where the test file will be created. - **writeBuffer** `(int)` - The size of the buffer used for writing data to the file (in KiB). - **maxFileSize** `(int)` - The maximum size of the file to be created (in GiB). - **timeout** `(int)` - Test timeout (in seconds). #### Return value - **SizeMB** `(int)` - Written data size (in MiB)`. - **DurationMS** `(int)` - Test duration (in milliseconds). --- daemon/main/nzbget.h | 3 +- daemon/remote/XmlRpc.cpp | 118 +++++++++++++++++++++ daemon/sources.cmake | 1 + daemon/util/Benchmark.cpp | 170 +++++++++++++++++++++++++++++ daemon/util/Benchmark.h | 70 ++++++++++++ docs/api/API.md | 1 + docs/api/TESTDISKSPEED.md | 27 +++++ tests/util/Benchmark.cpp | 48 +++++++++ tests/util/CMakeLists.txt | 2 + webui/index.html | 89 +++++++++++++++- webui/index.js | 1 + webui/style.css | 26 ++++- webui/system-info.js | 217 +++++++++++++++++++++++++++++++++----- 13 files changed, 740 insertions(+), 33 deletions(-) create mode 100644 daemon/util/Benchmark.cpp create mode 100644 daemon/util/Benchmark.h create mode 100644 docs/api/TESTDISKSPEED.md create mode 100644 tests/util/Benchmark.cpp diff --git a/daemon/main/nzbget.h b/daemon/main/nzbget.h index e5247f8bc..8dbc4d1de 100644 --- a/daemon/main/nzbget.h +++ b/daemon/main/nzbget.h @@ -15,7 +15,7 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * along with this program. If not, see . */ @@ -224,6 +224,7 @@ compiled */ #include #include #include +#include #include #include diff --git a/daemon/remote/XmlRpc.cpp b/daemon/remote/XmlRpc.cpp index 530dd8eba..9422f6e50 100644 --- a/daemon/remote/XmlRpc.cpp +++ b/daemon/remote/XmlRpc.cpp @@ -39,6 +39,8 @@ #include "UrlCoordinator.h" #include "ExtensionManager.h" #include "SystemInfo.h" +#include "Benchmark.h" +#include "Xml.h" extern void ExitProc(); extern void Reload(); @@ -361,6 +363,42 @@ class TestServerSpeedXmlCommand: public SafeXmlCommand virtual void Execute(); }; +class TestDiskSpeedXmlCommand final : public SafeXmlCommand +{ +public: + void Execute() override; + + std::string ToJsonStr(uint64_t size, double time) + { + Json::JsonObject json; + + json["SizeMB"] = size / 1024ull / 1024ull; + json["DurationMS"] = time; + + return Json::Serialize(json); + } + + std::string ToXmlStr(uint64_t size, double time) + { + xmlNodePtr rootNode = xmlNewNode(nullptr, BAD_CAST "value"); + xmlNodePtr structNode = xmlNewNode(nullptr, BAD_CAST "struct"); + + std::string sizeMB = std::to_string(size / 1024ull / 1024ull); + std::string durationMS = std::to_string(time); + + Xml::AddNewNode(structNode, "SizeMB", "i4", sizeMB.c_str()); + Xml::AddNewNode(structNode, "DurationMS", "double", durationMS.c_str()); + + xmlAddChild(rootNode, structNode); + + std::string result = Xml::Serialize(rootNode); + + xmlFreeNode(rootNode); + + return result; + } +}; + class StartScriptXmlCommand : public XmlCommand { public: @@ -817,6 +855,10 @@ std::unique_ptr XmlRpcProcessor::CreateCommand(const char* methodNam { command = std::make_unique(); } + else if (!strcasecmp(methodName, "testdiskspeed")) + { + command = std::make_unique(); + } else if (!strcasecmp(methodName, "startscript")) { command = std::make_unique(); @@ -3696,6 +3738,82 @@ void TestServerSpeedXmlCommand::Execute() BuildBoolResponse(true); } + + +void TestDiskSpeedXmlCommand::Execute() +{ + char* dirPath; + int writeBufferKiB; + int maxFileSizeGiB; + int timeoutSec; + + if (!NextParamAsStr(&dirPath)) + { + BuildErrorResponse(2, "Invalid argument (Path)"); + return; + } + + if (!NextParamAsInt(&writeBufferKiB)) + { + BuildErrorResponse(2, "Invalid argument (Write Buffer)"); + return; + } + + if (writeBufferKiB < 0) + { + BuildErrorResponse(2, "Write Buffer cannot be negative"); + return; + } + + if (!NextParamAsInt(&maxFileSizeGiB)) + { + BuildErrorResponse(2, "Invalid argument (Max file size)"); + return; + } + + if (maxFileSizeGiB < 1) + { + BuildErrorResponse(2, "Max file size must be positive"); + return; + } + + if (!NextParamAsInt(&timeoutSec)) + { + BuildErrorResponse(2, "Invalid argument (Timeout)"); + return; + } + + if (timeoutSec < 1) + { + BuildErrorResponse(2, "Timeout must be positive"); + return; + } + + try + { + size_t bufferSizeBytes = writeBufferKiB * 1024; + uint64_t maxFileSizeBytes = maxFileSizeGiB * 1024ull * 1024ull * 1024ull; + + Benchmark::DiskBenchmark db; + auto [size, time] = db.Run( + dirPath, + bufferSizeBytes, + maxFileSizeBytes, + std::chrono::seconds(timeoutSec) + ); + + std::string jsonStr = IsJson() ? + ToJsonStr(size, time) : + ToXmlStr(size, time); + + AppendResponse(jsonStr.c_str()); + } + catch (const std::exception& e) + { + BuildErrorResponse(2, e.what()); + } +} + // bool startscript(string script, string command, string context, struct[] options); void StartScriptXmlCommand::Execute() { diff --git a/daemon/sources.cmake b/daemon/sources.cmake index c5e158895..f799d86d0 100644 --- a/daemon/sources.cmake +++ b/daemon/sources.cmake @@ -93,6 +93,7 @@ set(SRC ${CMAKE_SOURCE_DIR}/daemon/util/Util.cpp ${CMAKE_SOURCE_DIR}/daemon/util/Json.cpp ${CMAKE_SOURCE_DIR}/daemon/util/Xml.cpp + ${CMAKE_SOURCE_DIR}/daemon/util/Benchmark.cpp ${CMAKE_SOURCE_DIR}/daemon/system/SystemInfo.cpp ${CMAKE_SOURCE_DIR}/daemon/system/OS.cpp diff --git a/daemon/util/Benchmark.cpp b/daemon/util/Benchmark.cpp new file mode 100644 index 000000000..a89d25069 --- /dev/null +++ b/daemon/util/Benchmark.cpp @@ -0,0 +1,170 @@ +/* + * This file is part of nzbget. See . + * + * Copyright (C) 2024 Denis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "nzbget.h" + +#include +#include +#include "FileSystem.h" +#include "Benchmark.h" + +namespace Benchmark +{ + using namespace std::chrono; + + std::pair DiskBenchmark::Run( + const std::string& dir, + size_t bufferSizeBytes, + uint64_t maxFileSizeBytes, + seconds timeout) const noexcept(false) + { + ValidateBufferSize(bufferSizeBytes); + + const std::string filename = dir + PATH_SEPARATOR + GetUniqueFilename(); + std::ofstream file = OpenFile(filename); + + std::vector buffer; + UseBuffer(file, buffer, bufferSizeBytes); + + size_t dataSize = bufferSizeBytes > 0 ? bufferSizeBytes : 1024; + std::vector data = GenerateRandomCharsVec(dataSize); + + nanoseconds timeoutNS = duration_cast(timeout); + + return RunBench(file, filename, data, maxFileSizeBytes, timeoutNS); + } + + std::pair DiskBenchmark::RunBench( + std::ofstream& file, + const std::string& filename, + const std::vector& data, + uint64_t maxFileSizeBytes, + std::chrono::nanoseconds timeoutNS) const noexcept(false) + { + uint64_t totalWritten = 0; + + auto start = steady_clock::now(); + try + { + while (totalWritten < maxFileSizeBytes && (steady_clock::now() - start) < timeoutNS) + { + file.write(data.data(), data.size()); + totalWritten += data.size(); + } + } + catch (const std::exception& e) + { + CleanUp(file, filename); + + std::string errMsg = "Failed to write data to file " + filename + ". " + e.what(); + throw std::runtime_error(errMsg); + } + auto finish = steady_clock::now(); + double elapsed = duration(finish - start).count(); + + CleanUp(file, filename); + + return { totalWritten, elapsed }; + } + + void DiskBenchmark::DeleteFile(const std::string& filename) const noexcept(false) + { + if (!FileSystem::DeleteFile(filename.c_str())) + { + std::string errMsg = "Failed to delete " + filename + " test file"; + throw std::runtime_error(errMsg); + } + } + + void DiskBenchmark::CleanUp( + std::ofstream& file, + const std::string& filename) const noexcept(false) + { + file.close(); + DeleteFile(filename); + } + + std::string DiskBenchmark::GetUniqueFilename() const noexcept(false) + { + return std::to_string( + high_resolution_clock::now().time_since_epoch().count() + ) + ".bin"; + } + + std::ofstream DiskBenchmark::OpenFile(const std::string& filename) const noexcept(false) + { + std::ofstream file(filename, std::ios::binary); + + if (!file.is_open()) + { + std::string errMsg = std::string("Failed to create test file: ") + strerror(errno); + throw std::runtime_error(errMsg); + } + + file.exceptions(std::ofstream::badbit | std::ofstream::failbit); + file.sync_with_stdio(false); + + return file; + } + + void DiskBenchmark::ValidateBufferSize(size_t bufferSz) const noexcept(false) + { + if (bufferSz > m_maxBufferSize) + { + throw std::invalid_argument("The buffer size is too big"); + } + } + + void DiskBenchmark::UseBuffer( + std::ofstream& file, + std::vector& buffer, + size_t buffSize + ) const noexcept(false) + { + if (buffSize > 0) + { + buffer.resize(buffSize); + file.rdbuf()->pubsetbuf(buffer.data(), buffSize); + } + else + { + file.rdbuf()->pubsetbuf(nullptr, 0); + } + } + + std::vector DiskBenchmark::GenerateRandomCharsVec(size_t size) const noexcept(false) + { + if (size == 0) + { + return {}; + } + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> distrib(0, 127); + + std::vector v(size); + std::generate(begin(v), end(v), [&distrib, &gen]() + { + return static_cast(distrib(gen)); + }); + + return v; + } +} diff --git a/daemon/util/Benchmark.h b/daemon/util/Benchmark.h new file mode 100644 index 000000000..5d112b248 --- /dev/null +++ b/daemon/util/Benchmark.h @@ -0,0 +1,70 @@ +/* + * This file is part of nzbget. See . + * + * Copyright (C) 2024 Denis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BENCHMARK_H +#define BENCHMARK_H + +#include +#include +#include + +namespace Benchmark +{ + class DiskBenchmark final + { + public: + std::pair Run( + const std::string& dir, + size_t bufferSizeBytes, + uint64_t maxFileSizeBytes, + std::chrono::seconds timeout) const noexcept(false); + + private: + std::pair RunBench( + std::ofstream& file, + const std::string& filename, + const std::vector& data, + uint64_t maxFileSizeBytes, + std::chrono::nanoseconds timeoutNS) const noexcept(false); + + void DeleteFile(const std::string& filename) const noexcept(false); + + std::string GetUniqueFilename() const noexcept(false); + + void CleanUp( + std::ofstream& file, + const std::string& filename) const noexcept(false); + + std::ofstream OpenFile(const std::string& filename) const noexcept(false); + + void ValidateBufferSize(size_t bufferSz) const noexcept(false); + + void UseBuffer( + std::ofstream& file, + std::vector& buffer, + size_t buffSize + ) const noexcept(false); + + std::vector GenerateRandomCharsVec(size_t size) const noexcept(false); + + const uint32_t m_maxBufferSize = 1024 * 1024 * 512; + }; +} + +#endif diff --git a/docs/api/API.md b/docs/api/API.md index eb6191218..0131d5c02 100644 --- a/docs/api/API.md +++ b/docs/api/API.md @@ -96,3 +96,4 @@ If HTTP basic authentication is somewhat problematic the username/password can a - [testextension](TESTEXTENSION.md) - [testserver](TESTSERVER.md) - [testserverspeed](TESTSERVERSPEED.md) +- [testdiskspeed](TESTDISKSPEED.md) diff --git a/docs/api/TESTDISKSPEED.md b/docs/api/TESTDISKSPEED.md new file mode 100644 index 000000000..de894f1c2 --- /dev/null +++ b/docs/api/TESTDISKSPEED.md @@ -0,0 +1,27 @@ +## API-method `testdiskspeed` + +## Since +`v24.3` + +### Signature +``` c++ +bool testdiskspeed( + string dirPath, + int writeBufferSize, + int maxFileSize, + int timeout, +); +``` + +### Description +The function writes data to a file until either the maximum file size is reached or the timeout period expires. + +### Arguments +- **dirPath** `(string)` - The path to the directory where the test file will be created. +- **writeBuffer** `(int)` - The size of the buffer used for writing data to the file (in KiB). +- **maxFileSize** `(int)` - The maximum size of the file to be created (in GiB). +- **timeout** `(int)` - Test timeout (in seconds). + +### Return value +- **SizeMB** `(int)` - Written data size (in MiB). +- **DurationMS** `(int)` - Test duration (in milliseconds). diff --git a/tests/util/Benchmark.cpp b/tests/util/Benchmark.cpp new file mode 100644 index 000000000..4534e8056 --- /dev/null +++ b/tests/util/Benchmark.cpp @@ -0,0 +1,48 @@ +/* + * This file is part of nzbget. See . + * + * Copyright (C) 2024 Denis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "nzbget.h" + +#include + +#include +#include "Benchmark.h" + +BOOST_AUTO_TEST_CASE(BenchmarkTest) +{ + Benchmark::DiskBenchmark db; + { + size_t tooBigBuffer = 1024ul * 1024ul * 1024ul; + BOOST_CHECK_THROW( + db.Run("./", tooBigBuffer, 1024, std::chrono::seconds(1)), std::invalid_argument + ); + } + + { + BOOST_CHECK_THROW( + db.Run("InvalidPath", 1024, 1024, std::chrono::seconds(1)), std::runtime_error + ); + } + + { + uint64_t maxFileSize = 1024 * 1024; + auto [size, duration] = db.Run("./", 1024, maxFileSize, std::chrono::seconds(1)); + BOOST_CHECK(size >= maxFileSize || duration < 1.3); + } +} diff --git a/tests/util/CMakeLists.txt b/tests/util/CMakeLists.txt index b0648a2c4..fc6a66c41 100644 --- a/tests/util/CMakeLists.txt +++ b/tests/util/CMakeLists.txt @@ -4,12 +4,14 @@ file(GLOB UtilTestSrc UtilTest.cpp NStringTest.cpp JsonTest.cpp + BenchmarkTest.cpp ${CMAKE_SOURCE_DIR}/daemon/util/FileSystem.cpp ${CMAKE_SOURCE_DIR}/daemon/util/NString.cpp ${CMAKE_SOURCE_DIR}/daemon/util/Util.cpp ${CMAKE_SOURCE_DIR}/daemon/util/Json.cpp ${CMAKE_SOURCE_DIR}/daemon/util/Xml.cpp ${CMAKE_SOURCE_DIR}/daemon/util/Log.cpp + ${CMAKE_SOURCE_DIR}/daemon/util/Benchmark.cpp ) add_executable(UtilTests ${UtilTestSrc}) diff --git a/webui/index.html b/webui/index.html index 2a61fa22b..b6b82e032 100644 --- a/webui/index.html +++ b/webui/index.html @@ -661,13 +661,86 @@

+ +
progress_activity
@@ -731,11 +804,21 @@

System

Free / Total disk space (DestDir) - + + + + Free / Total disk space (InterDir) - + + + + Write buffer diff --git a/webui/index.js b/webui/index.js index 9527c0c56..ecba89b95 100644 --- a/webui/index.js +++ b/webui/index.js @@ -853,6 +853,7 @@ var Refresher = (new function($) 'readurl', 'servervolumes', 'testserverspeed', + 'testdiskspeed', ]; $('#RefreshMenu li a').click(refreshIntervalClick); diff --git a/webui/style.css b/webui/style.css index aa3c45a41..984da831b 100644 --- a/webui/style.css +++ b/webui/style.css @@ -18,7 +18,7 @@ * along with this program. If not, see . */ -* { + * { scrollbar-width: thin; } @@ -51,6 +51,13 @@ body { padding-right: 0; } +.flex-center { + display: flex; + align-items: center; + width: inherit; + gap: 5px; +} + #AddDialog_URLProp { vertical-align: sub; color: inherit; @@ -87,6 +94,23 @@ body { font-size: 128px; } +.dist-speedtest__controls { + display: flex; + gap: 10px; +} + +#ConfigContent select.dist-speedtest__select--width { + width: 80px; +} + +.help-text-error { + color: #b94a48; +} + +#SysInfo_DiskSpeedTestBtn.btn--disabled { + opacity: 0.6 !important; +} + .spinner { color: #0088cc; -webkit-animation:spin 1s linear infinite; diff --git a/webui/system-info.js b/webui/system-info.js index 7a6d87d8b..f5214950c 100644 --- a/webui/system-info.js +++ b/webui/system-info.js @@ -18,6 +18,142 @@ */ +function DiskSpeedTestsForm() +{ + var _512MiB = 1024 * 1024 * 512; + var $timeout; + var $maxSize; + var $writeBufferInput; + var $diskSpeedTestInputLabel; + var $diskSpeedTestInput; + var $diskSpeedTestBtn; + var $diskSpeedTestErrorTxt + + var SPINNER = 'progress_activity'; + var TEST_BTN_DEFAULT_TEXT = 'Run test'; + + this.init = function(writeBuffer, dirPath, label, lsKey) + { + $timeout = $('#SysInfo_DiskSpeedTestTimeout'); + $maxSize = $('#SysInfo_DiskSpeedTestMaxSize'); + $writeBufferInput = $('#SysInfo_DiskSpeedTestWriteBufferInput'); + $diskSpeedTestInputLabel = $('#SysInfo_DiskSpeedTestInputLabel'); + $diskSpeedTestInput = $('#SysInfo_DiskSpeedTestInput'); + $diskSpeedTestBtn = $('#SysInfo_DiskSpeedTestBtn'); + $diskSpeedTestErrorTxt = $('#SysInfo_DiskSpeedTestErrorTxt'); + + disableBtnToggle(false); + + $diskSpeedTestBtn.text(getSpeedResFromLS(lsKey) || TEST_BTN_DEFAULT_TEXT); + + $writeBufferInput.val(writeBuffer); + $diskSpeedTestInputLabel.text(label); + $diskSpeedTestInput.val(dirPath); + + $diskSpeedTestBtn + .off('click') + .on('click', function() + { + var writeBufferVal = + $writeBufferInput.val(); + var path = $diskSpeedTestInput.val(); + var maxSize = + $maxSize.val(); + var timeout = + $timeout.val(); + runDiskSpeedTest(path, writeBufferVal, maxSize, timeout, lsKey); + }); + + $writeBufferInput + .off('change paste keyup') + .on('change paste keyup', function(event) + { + if (isValidWriteBuffer(event.target.value)) + disableBtnToggle(false); + else + disableBtnToggle(true); + }); + + $diskSpeedTestInput + .off('change paste keyup') + .on('change paste keyup', function(event) + { + if (isValidPath(event.target.value)) + disableBtnToggle(false); + else + disableBtnToggle(true); + }); + } + + function runDiskSpeedTest(path, writeBufferSize, maxFileSize, timeout, lsKey) + { + $diskSpeedTestErrorTxt.empty(); + $diskSpeedTestBtn.html(SPINNER); + disableBtnToggle(true); + + RPC.call('testdiskspeed', [path, writeBufferSize, maxFileSize, timeout], + function(rawRes) + { + var res = makeResults(rawRes); + saveSpeedResToLS(lsKey, res); + $diskSpeedTestBtn.html(makeResults(rawRes)); + disableBtnToggle(false); + }, + function(res) + { + $diskSpeedTestBtn.html(getSpeedResFromLS(lsKey) || TEST_BTN_DEFAULT_TEXT); + disableBtnToggle(false); + + var errTxt = res.split('
')[0]; + $diskSpeedTestErrorTxt.html(errTxt); + }, + ); + } + + function makeResults(res) + { + var r = res.SizeMB / (res.DurationMS / 1000); + return Util.formatSizeMB(r) + '/s'; + } + + function disableBtnToggle(disable) + { + if (disable) + { + $diskSpeedTestBtn.addClass('btn--disabled'); + } + else + { + $diskSpeedTestBtn.removeClass('btn--disabled'); + } + } + + function isValidWriteBuffer(input) + { + var val = input.trim(); + if (!val) + return false; + + var num = Number(val); + + return !isNaN(num) && num >= 0 && num < _512MiB; + } + + function isValidPath(input) + { + var path = input.trim(); + + return path !== ''; + } + + function saveSpeedResToLS(key, res) + { + localStorage.setItem(key, res); + } + + function getSpeedResFromLS(key) + { + return localStorage.getItem(key) || ''; + } +} + var SystemInfo = (new function($) { this.id = "Config-SystemInfo"; @@ -31,6 +167,8 @@ var SystemInfo = (new function($) var $SysInfo_IP; var $SysInfo_DestDiskSpace; var $SysInfo_InterDiskSpace; + var $SysInfo_DestDirDiskTestBtn; + var $SysInfo_InterDirDiskTestBtn; var $SysInfo_DestDiskSpaceContainer; var $SysInfo_InterDiskSpaceContainer; var $SysInfo_ArticleCache; @@ -43,6 +181,7 @@ var SystemInfo = (new function($) var $SpeedTest_Stats; var $SpeedTest_StatsHeader; var $SpeedTest_StatsTable; + var $DiskSpeedTest_Modal; var $SystemInfo_Spinner; var $SystemInfo_MainContent; @@ -58,6 +197,10 @@ var SystemInfo = (new function($) var testNZBListId = 'dropdown_test_nzb_list_'; var lastTestStatsId = 'last_test_stats_'; var serverTestSpinnerId = 'server_test_spinner_'; + + var DEST_DIR_LS_KEY = 'DestDirSpeedResults'; + var INTER_DIR_LS_KEY = 'InterDirSpeedResults'; + var lastTestStatsBtns = {}; var spinners = {}; var allStats = []; @@ -68,18 +211,12 @@ var SystemInfo = (new function($) { $SysInfo_Uptime.text(Util.formatTimeHMS(status['UpTimeSec'])); - var destDirOpt = Options.findOption(Options.options, 'DestDir'); - var interDirOpt = Options.findOption(Options.options, 'InterDir'); + var writeBufferKB = Options.option('WriteBuffer'); + var destDir = Options.option('DestDir'); + var interDir = Options.option('InterDir') || destDir; - - - if (destDirOpt && interDirOpt) - { - var destDirPath = destDirOpt.Value; - var interDistPath = interDirOpt.Value ? interDirOpt.Value : destDirPath; - renderDiskSpace(+status['FreeDiskSpaceMB'], +status['TotalDiskSpaceMB'], destDirPath); - renderInterDiskSpace(+status['FreeInterDiskSpaceMB'], +status['TotalInterDiskSpaceMB'], interDistPath); - } + renderDestDiskSpace(writeBufferKB, +status['FreeDiskSpaceMB'], +status['TotalDiskSpaceMB'], destDir); + renderInterDiskSpace(writeBufferKB, +status['FreeInterDiskSpaceMB'], +status['TotalInterDiskSpaceMB'], interDir); } } @@ -112,6 +249,8 @@ var SystemInfo = (new function($) $SysInfo_IP = $('#SysInfo_IP'); $SysInfo_DestDiskSpace = $('#SysInfo_DestDiskSpace'); $SysInfo_InterDiskSpace = $('#SysInfo_InterDiskSpace'); + $SysInfo_DestDirDiskTestBtn = $('#SysInfo_DestDirDiskTestBtn'); + $SysInfo_InterDirDiskTestBtn = $('#SysInfo_InterDirDiskTestBtn'); $SysInfo_InterDiskSpaceContainer = $('#SysInfo_InterDiskSpaceContainer'); $SysInfo_DestDiskSpaceContainer = $('#SysInfo_DestDiskSpaceContainer'); $SysInfo_ArticleCache = $('#SysInfo_ArticleCache'); @@ -120,10 +259,11 @@ var SystemInfo = (new function($) $SysInfo_LibrariesTable = $('#SysInfo_LibrariesTable'); $SysInfo_NewsServersTable = $('#SysInfo_NewsServersTable'); $SysInfo_ErrorAlert = $('#SystemInfoErrorAlert'); - $SysInfo_ErrorAlertText = $('#SystemInfoAlert-text'); + $SysInfo_ErrorAlertText = $('#SystemInfo_alertText'); $SpeedTest_Stats = $('#SpeedTest_Stats'); $SpeedTest_StatsHeader = $('#SpeedTest_StatsHeader'); $SpeedTest_StatsTable = $('#SpeedTest_StatsTable tbody'); + $DiskSpeedTest_Modal = $('#DiskSpeedTest_Modal'); $SystemInfo_Spinner = $('#SystemInfo_Spinner'); $SystemInfo_MainContent = $('#SystemInfo_MainContent'); @@ -152,17 +292,6 @@ var SystemInfo = (new function($) ); } - function pathsOnSameDisk(path1, path2) - { - path1 = path1.replace(/\\/g, '/'); - path2 = path2.replace(/\\/g, '/'); - - var drive1 = path1.match(/^[a-zA-Z]:\//i) ? path1.match(/^[a-zA-Z]:\//i)[0] : '/'; - var drive2 = path2.match(/^[a-zA-Z]:\//i) ? path2.match(/^[a-zA-Z]:\//i)[0] : '/'; - - return drive1 === drive2; - } - function hideSpinner() { $SystemInfo_Spinner.hide(); @@ -236,8 +365,8 @@ var SystemInfo = (new function($) $SysInfo_Arch.text(sysInfo['CPU'].Arch || 'Unknown'); $SysInfo_ConfPath.text(Options.option('ConfigFile')); $SysInfo_ArticleCache.text(Util.formatSizeMB(+Options.option('ArticleCache'))); - $SysInfo_WriteBuffer.text(Util.formatSizeMB(+Options.option('WriteBuffer'))); + renderWriteBuffer(+Options.option('WriteBuffer')); renderIP(sysInfo['Network']); renderAppVersion(Options.option('Version')); renderTools(sysInfo['Tools']); @@ -245,6 +374,12 @@ var SystemInfo = (new function($) renderNewsServers(Status.getStatus()['NewsServers']); } + function renderWriteBuffer(writeBufferKB) + { + var writeBufferMB = writeBufferKB / 1024; + $SysInfo_WriteBuffer.text(Util.formatSizeMB(writeBufferMB)); + } + function renderSpinners(downloads) { for (var key in spinners) { @@ -284,16 +419,36 @@ var SystemInfo = (new function($) }); } - function renderDiskSpace(free, total, path) + function renderDestDiskSpace(writeBufferKB, free, total, dirPath) { $SysInfo_DestDiskSpace.text(formatDiskInfo(free, total)); - $SysInfo_DestDiskSpaceContainer.attr('title', path); + $SysInfo_DestDiskSpaceContainer.attr('title', dirPath); + $SysInfo_DestDirDiskTestBtn.off('click').on('click', function() + { + showDiskSpeedModal(writeBufferKB, dirPath, 'DestDir', DEST_DIR_LS_KEY); + }); + + var savedResults = localStorage.getItem(DEST_DIR_LS_KEY); + if (savedResults) + { + $SysInfo_DestDirDiskTestBtn.text(savedResults); + } } - function renderInterDiskSpace(free, total, path) + function renderInterDiskSpace(writeBufferKB, free, total, dirPath) { $SysInfo_InterDiskSpace.text(formatDiskInfo(free, total)); - $SysInfo_InterDiskSpaceContainer.attr('title', path); + $SysInfo_InterDiskSpaceContainer.attr('title', dirPath); + $SysInfo_InterDirDiskTestBtn.off('click').on('click', function() + { + showDiskSpeedModal(writeBufferKB, dirPath, 'InterDir', INTER_DIR_LS_KEY); + }); + + var savedResults = localStorage.getItem(INTER_DIR_LS_KEY); + if (savedResults) + { + $SysInfo_InterDirDiskTestBtn.text(savedResults); + } } function formatDiskInfo(free, total) @@ -538,6 +693,12 @@ var SystemInfo = (new function($) ); } + function showDiskSpeedModal(writeBuffer, dirPath, label, lsKey) + { + $DiskSpeedTest_Modal.show(); + (new DiskSpeedTestsForm()).init(writeBuffer, dirPath, label, lsKey); + } + function showStatsTable(stats) { $($SpeedTest_Stats).show(); From b5c3068803f037984eba4f493ba38c71852a3073 Mon Sep 17 00:00:00 2001 From: Fabricio Silva Date: Thu, 5 Sep 2024 06:34:29 +0100 Subject: [PATCH 15/19] Fix: default value for OutputMode when disable curses (#376) --- daemon/main/Options.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/daemon/main/Options.cpp b/daemon/main/Options.cpp index df411ea5c..eb8902c5f 100644 --- a/daemon/main/Options.cpp +++ b/daemon/main/Options.cpp @@ -431,7 +431,11 @@ void Options::InitDefaults() SetOption(OPTION_WRITELOG, "append"); SetOption(OPTION_ROTATELOG, "3"); SetOption(OPTION_APPENDCATEGORYDIR, "yes"); +#ifdef DISABLE_CURSES + SetOption(OPTION_OUTPUTMODE, "color"); +#else SetOption(OPTION_OUTPUTMODE, "curses"); +#endif SetOption(OPTION_DUPECHECK, "yes"); SetOption(OPTION_DOWNLOADRATE, "0"); SetOption(OPTION_CONTROLIP, "0.0.0.0"); From e7ffa5f917a47dc7cf31e324167d661e9b80e941 Mon Sep 17 00:00:00 2001 From: Denis <146707790+dnzbk@users.noreply.github.com> Date: Mon, 9 Sep 2024 00:36:01 -0700 Subject: [PATCH 16/19] Bits/s in the speed test results (#381) - added Bits/s units to the speed test results --- webui/index.html | 19 +++++++++++- webui/style.css | 8 ++++- webui/system-info.js | 70 +++++++++++++++++++++++++++++++------------- webui/util.js | 6 ++++ 4 files changed, 80 insertions(+), 23 deletions(-) diff --git a/webui/index.html b/webui/index.html index b6b82e032..2c5638070 100644 --- a/webui/index.html +++ b/webui/index.html @@ -655,7 +655,24 @@

diff --git a/webui/style.css b/webui/style.css index 984da831b..1dd6557e6 100644 --- a/webui/style.css +++ b/webui/style.css @@ -79,10 +79,16 @@ body { .test-server-dropdwon-menu { - width: 150px; + width: 195px; min-width: auto; } +.approx-speed-txt { + padding-left: 3px; + color: darkgray; + font-style: italic; +} + .system-info__spinner-container { display: flex; align-items: center; diff --git a/webui/system-info.js b/webui/system-info.js index f5214950c..219605d53 100644 --- a/webui/system-info.js +++ b/webui/system-info.js @@ -184,6 +184,12 @@ var SystemInfo = (new function($) var $DiskSpeedTest_Modal; var $SystemInfo_Spinner; var $SystemInfo_MainContent; + var $SpeedTest_Stats; + var $SpeedTest_StatsHeader; + var $SpeedTest_StatsSpeed; + var $SpeedTest_StatsSize; + var $SpeedTest_StatsTime; + var $SpeedTest_StatsDate; var nzbFileTestPrefix = 'NZBGet Speed Test '; var testNZBUrl = 'https://nzbget.com/nzb/'; @@ -262,7 +268,10 @@ var SystemInfo = (new function($) $SysInfo_ErrorAlertText = $('#SystemInfo_alertText'); $SpeedTest_Stats = $('#SpeedTest_Stats'); $SpeedTest_StatsHeader = $('#SpeedTest_StatsHeader'); - $SpeedTest_StatsTable = $('#SpeedTest_StatsTable tbody'); + $SpeedTest_StatsSpeed = $('#SpeedTest_StatsSpeed'); + $SpeedTest_StatsSize = $('#SpeedTest_StatsSize'); + $SpeedTest_StatsTime = $('#SpeedTest_StatsTime'); + $SpeedTest_StatsDate = $('#SpeedTest_StatsDate'); $DiskSpeedTest_Modal = $('#DiskSpeedTest_Modal'); $SystemInfo_Spinner = $('#SystemInfo_Spinner'); $SystemInfo_MainContent = $('#SystemInfo_MainContent'); @@ -405,8 +414,9 @@ var SystemInfo = (new function($) var id = lastTestStatsId + stats['ServerStats'][0]['ServerID']; if (lastTestStatsBtns[id] && !alreadyRendered[id]) { + lastTestStatsBtns[id].empty(); alreadyRendered[id] = true; - lastTestStatsBtns[id].text(getSpeed(stats)); + lastTestStatsBtns[id].append(makeSpeed(stats)); lastTestStatsBtns[id].show(); lastTestStatsBtns[id] .off('click') @@ -576,7 +586,7 @@ var SystemInfo = (new function($) { var btn = $('