diff --git a/.github/ISSUE_TEMPLATE/01_bug_report.yml b/.github/ISSUE_TEMPLATE/01_bug_report.yml index 0ce20525..26dd81e6 100644 --- a/.github/ISSUE_TEMPLATE/01_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/01_bug_report.yml @@ -14,16 +14,13 @@ body: label: NZBGet Version description: Which version of NZBGet has this bug? options: + - v24.4-testing + - v24.3-stable - v24.2-stable - - v24.3-testing - v24.1-stable - - v24.1-testing - v24-stable - - v24-testing - v23-stable - - v23-testing - v22-stable (nzbgetcom takeover) - - v22-testing (nzbgetcom takeover) - v21 or earlier (orignal nzbget) - latest-stable (if not listed here) - latest-testing (if not listed here) diff --git a/.github/ISSUE_TEMPLATE/03_build.yml b/.github/ISSUE_TEMPLATE/03_build.yml index 1b184d3b..5a6b4800 100644 --- a/.github/ISSUE_TEMPLATE/03_build.yml +++ b/.github/ISSUE_TEMPLATE/03_build.yml @@ -7,16 +7,13 @@ body: label: NZBGet Version description: Version of NZBGet for the scope of this issue options: + - v24.4-testing + - v24.3-stable - v24.2-stable - - v24.3-testing - v24.1-stable - - v24.1-testing - v24-stable - - v24-testing - v23-stable - - v23-testing - v22-stable (nzbgetcom takeover) - - v22-testing (nzbgetcom takeover) - v21 or earlier (orignal nzbget) - latest-stable (if not listed here) - latest-testing (if not listed here) diff --git a/CMakeLists.txt b/CMakeLists.txt index b1b83380..d3f81848 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/ChangeLog.md b/ChangeLog.md index ac7e33fc..48d552ab 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3 +1,31 @@ +nzbget-24.3 + - Features: + - Disk performance tests for better analysis of user environment performance + [#375](https://github.com/nzbgetcom/nzbget/commit/220e842ad5181f6911a6b1796fdc01d0091d0c71) + - The `STATUS` page now displays `WriteBuffer` and `InterDir` disk information + - 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)` - Total 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. + - Bits/s units are now also displayed in server speed test results + + - Bug fixes: + - Fixed a critical bug related to corrupt downloaded files exceeding 2.6 GB on all x32 POSIX systems using `DirectWrite` + [#378](https://github.com/nzbgetcom/nzbget/commit/a59edac8bfbaf75d4f262909ef71026241b7bddc) + - Fixed a potential buffer overflow using `getrealpath` function + [#346](https://github.com/nzbgetcom/nzbget/commit/f89978f7479cbb0ff2f96c8632d9d2f31834e6c8) + - Added removal of unnecessary privileges if the nzbget daemon is run as root and an invalid `DaemonUsername` is specified + [#345](https://github.com/nzbgetcom/nzbget/commit/61585fac12e697baafa547012ed2970135de687f) + + - For developers: + - Fixed omission in `CMAKE_CXX_FLAGS_DEBUG` using CLang that caused a wrong debug build + [#338](https://github.com/nzbgetcom/nzbget/commit/8d2c00e8d69503858a1ee0414dc6825b30508a92) + - Fixed failed unit tests on POSIX systems built without ncurses + [#376](https://github.com/nzbgetcom/nzbget/commit/b5c3068803f037984eba4f493ba38c71852a3073) + nzbget-24.2 - Features: - System info tab and Server Speed Tests diff --git a/README.md b/README.md index f1630adf..6b5e0ee1 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ ![GitHub release (by tag)](https://img.shields.io/github/downloads/nzbgetcom/nzbget/v24.0/total?label=v24.0) ![GitHub release (by tag)](https://img.shields.io/github/downloads/nzbgetcom/nzbget/v24.1/total?label=v24.1) ![GitHub release (by tag)](https://img.shields.io/github/downloads/nzbgetcom/nzbget/v24.2/total?label=v24.2) +![GitHub release (by tag)](https://img.shields.io/github/downloads/nzbgetcom/nzbget/v24.3/total?label=v24.3) ![docker pulls](https://img.shields.io/docker/pulls/nzbgetcom/nzbget.svg) [![linux build](https://github.com/nzbgetcom/nzbget/actions/workflows/linux.yml/badge.svg?branch=main)](https://github.com/nzbgetcom/nzbget/actions/workflows/linux.yml) @@ -75,6 +76,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/daemon/extension/Extension.cpp b/daemon/extension/Extension.cpp index 62555d32..4e86ac41 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()); @@ -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()); } } @@ -440,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 0d04db57..ace0d84e 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/main/Options.cpp b/daemon/main/Options.cpp index ead51f06..eb8902c5 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(); @@ -429,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"); @@ -656,7 +662,7 @@ void Options::CheckDir(CString& dir, const char* optionName, } } -void Options::InitOptions() +void Options::CheckDirs() { const char* mainDir = GetOption(OPTION_MAINDIR); @@ -667,7 +673,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 0839928e..f9e37ff4 100644 --- a/daemon/main/Options.h +++ b/daemon/main/Options.h @@ -322,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 b24fbe8a..04ad097f 100644 --- a/daemon/main/nzbget.cpp +++ b/daemon/main/nzbget.cpp @@ -1010,18 +1010,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/daemon/main/nzbget.h b/daemon/main/nzbget.h index e5247f8b..2eaca085 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 @@ -355,6 +356,7 @@ typedef int pid_t; #define FOPEN_WB "wb" #define FOPEN_AB "ab" #define CHILD_WATCHDOG 1 +#define fseek fseeko #endif /* POSIX */ diff --git a/daemon/queue/DiskState.cpp b/daemon/queue/DiskState.cpp index b6bcc846..9c050afc 100644 --- a/daemon/queue/DiskState.cpp +++ b/daemon/queue/DiskState.cpp @@ -1233,8 +1233,12 @@ bool DiskState::SaveFileState(FileInfo* fileInfo, StateDiskFile& outfile, bool c outfile.PrintLine("%i", (int)fileInfo->GetArticles()->size()); for (ArticleInfo* articleInfo : fileInfo->GetArticles()) { - outfile.PrintLine("%i,%u,%i,%u", (int)articleInfo->GetStatus(), (uint32)articleInfo->GetSegmentOffset(), - articleInfo->GetSegmentSize(), (uint32)articleInfo->GetCrc()); + outfile.PrintLine("%i,%" PRIi64 ",%i,%u", + (int)articleInfo->GetStatus(), + articleInfo->GetSegmentOffset(), + articleInfo->GetSegmentSize(), + articleInfo->GetCrc() + ); } outfile.Close(); @@ -1313,9 +1317,10 @@ bool DiskState::LoadFileState(FileInfo* fileInfo, Servers* servers, StateDiskFil if (formatVersion >= 2) { - uint32 segmentOffset, crc; + int64 segmentOffset; + uint32 crc; int segmentSize; - if (infile.ScanLine("%i,%u,%i,%u", &statusInt, &segmentOffset, &segmentSize, &crc) != 4) goto error; + if (infile.ScanLine("%i,%" PRIi64 ",%i,%u", &statusInt, &segmentOffset, &segmentSize, &crc) != 4) goto error; pa->SetSegmentOffset(segmentOffset); pa->SetSegmentSize(segmentSize); pa->SetCrc(crc); diff --git a/daemon/remote/XmlRpc.cpp b/daemon/remote/XmlRpc.cpp index 9c300eec..9422f6e5 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(); @@ -1303,6 +1345,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 +1407,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 +1496,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())) + { + 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())) { - const auto& value = res.value(); - freeDiskSpace = value.available; - totalDiskSpace = value.total; + 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 +1572,12 @@ void StatusXmlCommand::Execute() totalDiskSpaceLo, totalDiskSpaceHi, totalDiskSpaceMB, + freeInterDiskSpaceLo, + freeInterDiskSpaceHi, + freeInterDiskSpaceMB, + totalInterDiskSpaceLo, + totalInterDiskSpaceHi, + totalInterDiskSpaceMB, serverTime, resumeTime, BoolToStr(feedActive), queuedScripts); int index = 0; @@ -3642,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 c5e15889..f799d86d 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/system/SystemInfo.cpp b/daemon/system/SystemInfo.cpp index 2673ef78..308d953e 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 diff --git a/daemon/util/Benchmark.cpp b/daemon/util/Benchmark.cpp new file mode 100644 index 00000000..a89d2506 --- /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 00000000..5d112b24 --- /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/daemon/util/FileSystem.cpp b/daemon/util/FileSystem.cpp index d42fa326..706d2e88 100644 --- a/daemon/util/FileSystem.cpp +++ b/daemon/util/FileSystem.cpp @@ -58,18 +58,19 @@ void FileSystem::NormalizePathSeparators(char* path) std::optional FileSystem::GetFileRealPath(const std::string& path) { - char buffer[256]; - #ifdef WIN32 - DWORD len = GetFullPathName(path.c_str(), 256, buffer, nullptr); + char buffer[MAX_PATH]; + DWORD len = GetFullPathName(path.c_str(), 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.c_str(), nullptr)) { - return std::optional{ buffer }; + std::string res = realPath; + free(realPath); + return std::optional(std::move(res)); } #endif diff --git a/daemon/util/Xml.cpp b/daemon/util/Xml.cpp index 213dfa53..758e6607 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 c41afff3..ba0ff099 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/docs/api/API.md b/docs/api/API.md new file mode 100644 index 00000000..0131d5c0 --- /dev/null +++ b/docs/api/API.md @@ -0,0 +1,99 @@ +## 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` (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) +- [downloadextension](DOWNLOADEXTENSION.md) +- [updateextension](UPDATEEXTENSION.md) +- [deleteextension](DELETEEXTENSION.md) + +## Tests + +- [testextension](TESTEXTENSION.md) +- [testserver](TESTSERVER.md) +- [testserverspeed](TESTSERVERSPEED.md) +- [testdiskspeed](TESTDISKSPEED.md) diff --git a/docs/api/APPEND.md b/docs/api/APPEND.md new file mode 100644 index 00000000..45d71713 --- /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](../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. + +### 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 00000000..0836fa80 --- /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 00000000..30fa0e8b --- /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 00000000..4c4701fa --- /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 00000000..7bd0c566 --- /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 00000000..ee57fcd5 --- /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](../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. + - **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](../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. +- **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 00000000..1807fc83 --- /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](../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; + - **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 00000000..8b10e77b --- /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 00000000..2f5c7b3e --- /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. [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. +- **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 00000000..9a2f4924 --- /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 00000000..0b29c352 --- /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 00000000..7fd14c05 --- /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 00000000..71d88282 --- /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 00000000..96212261 --- /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 00000000..17d0274a --- /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 00000000..30e2e71c --- /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 00000000..3d5d2074 --- /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 00000000..9ce3d73d --- /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 00000000..2ef6aded --- /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 00000000..66a6ced6 --- /dev/null +++ b/docs/api/RELOAD.md @@ -0,0 +1,12 @@ +## API-method `reload` + +### Signature +``` c++ +bool reload(); +``` + +### 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 00000000..f7a49243 --- /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 00000000..8ecdf514 --- /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 00000000..10f37cef --- /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 00000000..b64522ca --- /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 00000000..a6c13ebd --- /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 00000000..53710599 --- /dev/null +++ b/docs/api/SCAN.md @@ -0,0 +1,16 @@ +## API-method `scan` + +### 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 00000000..cceef777 --- /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 00000000..6fc67817 --- /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 00000000..0efad27e --- /dev/null +++ b/docs/api/SHUTDOWN.md @@ -0,0 +1,12 @@ +## API-method `shutdown` + +### Signature +``` c++ +bool shutdown(); +``` + +### 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 00000000..460cbf0f --- /dev/null +++ b/docs/api/STATUS.md @@ -0,0 +1,70 @@ +## 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. +- **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. + - **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 00000000..76617fa5 --- /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/TESTDISKSPEED.md b/docs/api/TESTDISKSPEED.md new file mode 100644 index 00000000..de894f1c --- /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/docs/api/TESTEXTENSION.md b/docs/api/TESTEXTENSION.md new file mode 100644 index 00000000..10bd7f9c --- /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 00000000..449d623e --- /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 00000000..d9f848bd --- /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 00000000..e8a0d68b --- /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 00000000..b776ab17 --- /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 00000000..22da67aa --- /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 6c077735..10c2ff7f 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): @@ -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 220f21fa..523a0aef 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 2752da15..84b353a8 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 diff --git a/docs/extensions/SCAN.md b/docs/extensions/SCAN.md index a4b8f2a2..d911482e 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 00000000..16dfc8c0 --- /dev/null +++ b/docs/usage/RSS.md @@ -0,0 +1,4 @@ +## RSS and duplicate check + + +### Coming soon diff --git a/osx/NZBGet-Info.plist b/osx/NZBGet-Info.plist index 3d868758..4bc8e12a 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 @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 24.2 + 24.3 CFBundleDocumentTypes diff --git a/tests/extension/ExtensionTest.cpp b/tests/extension/ExtensionTest.cpp index e7b33782..5288f161 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\ diff --git a/tests/system/SystemInfoTest.cpp b/tests/system/SystemInfoTest.cpp index b9e11d78..5e84d2d5 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(); } diff --git a/tests/util/Benchmark.cpp b/tests/util/Benchmark.cpp new file mode 100644 index 00000000..4534e805 --- /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 b0648a2c..fc6a66c4 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 6fbc5d3f..2c563807 100644 --- a/webui/index.html +++ b/webui/index.html @@ -655,19 +655,109 @@

+ +
progress_activity
@@ -729,16 +819,30 @@

System

Arch - - Free disk space - + + Free / Total disk space (DestDir) + + + + + + + Free / Total disk space (InterDir) + + + + - Total disk space - + Write buffer + - Article Cache + Article cache diff --git a/webui/index.js b/webui/index.js index 9527c0c5..ecba89b9 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 aa3c45a4..1dd6557e 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; @@ -72,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; @@ -87,6 +100,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 aefe52a2..226bcfcf 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"; @@ -29,9 +165,14 @@ 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_DestDirDiskTestBtn; + var $SysInfo_InterDirDiskTestBtn; + var $SysInfo_DestDiskSpaceContainer; + var $SysInfo_InterDiskSpaceContainer; var $SysInfo_ArticleCache; + var $SysInfo_WriteBuffer; var $SysInfo_ToolsTable; var $SysInfo_LibrariesTable; var $SysInfo_NewsServersTable; @@ -40,8 +181,15 @@ var SystemInfo = (new function($) var $SpeedTest_Stats; var $SpeedTest_StatsHeader; var $SpeedTest_StatsTable; + 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/'; @@ -55,6 +203,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 = []; @@ -64,7 +216,13 @@ var SystemInfo = (new function($) update: function(status) { $SysInfo_Uptime.text(Util.formatTimeHMS(status['UpTimeSec'])); - renderDiskSpace(+status['FreeDiskSpaceMB'], +status['TotalDiskSpaceMB']); + + var writeBufferKB = Options.option('WriteBuffer'); + var destDir = Options.option('DestDir'); + var interDir = Options.option('InterDir') || destDir; + + renderDestDiskSpace(writeBufferKB, +status['FreeDiskSpaceMB'], +status['TotalDiskSpaceMB'], destDir); + renderInterDiskSpace(writeBufferKB, +status['FreeInterDiskSpaceMB'], +status['TotalInterDiskSpaceMB'], interDir); } } @@ -95,17 +253,26 @@ 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_DestDirDiskTestBtn = $('#SysInfo_DestDirDiskTestBtn'); + $SysInfo_InterDirDiskTestBtn = $('#SysInfo_InterDirDiskTestBtn'); + $SysInfo_InterDiskSpaceContainer = $('#SysInfo_InterDiskSpaceContainer'); + $SysInfo_DestDiskSpaceContainer = $('#SysInfo_DestDiskSpaceContainer'); $SysInfo_ArticleCache = $('#SysInfo_ArticleCache'); + $SysInfo_WriteBuffer = $('#SysInfo_WriteBuffer'); $SysInfo_ToolsTable = $('#SysInfo_ToolsTable'); $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'); + $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'); @@ -208,6 +375,7 @@ var SystemInfo = (new function($) $SysInfo_ConfPath.text(Options.option('ConfigFile')); $SysInfo_ArticleCache.text(Util.formatSizeMB(+Options.option('ArticleCache'))); + renderWriteBuffer(+Options.option('WriteBuffer')); renderIP(sysInfo['Network']); renderAppVersion(Options.option('Version')); renderTools(sysInfo['Tools']); @@ -215,6 +383,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) { @@ -240,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') @@ -254,11 +429,47 @@ var SystemInfo = (new function($) }); } - function renderDiskSpace(free, total) + function renderDestDiskSpace(writeBufferKB, free, total, dirPath) + { + $SysInfo_DestDiskSpace.text(formatDiskInfo(free, total)); + $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(writeBufferKB, free, total, dirPath) + { + $SysInfo_InterDiskSpace.text(formatDiskInfo(free, total)); + $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) { - var percents = total !== 0 ? (free / total * 100).toFixed(1) + '%' : '0.0%'; - $SysInfo_FreeDiskSpace.text(Util.formatSizeMB(free) + ' / ' + percents); - $SysInfo_TotalDiskSpace.text(Util.formatSizeMB(total)); + if (free === 0 || total === 0) + { + return 'N/A'; + } + + var percents = (free / total * 100).toFixed(1) + '%'; + return Util.formatSizeMB(free) + ' (' + percents + ') / ' + Util.formatSizeMB(total); } function renderIP(network) @@ -380,7 +591,7 @@ var SystemInfo = (new function($) { var btn = $('