From ec0756d5f63137d879f56a6bc60cab6b81238eaf Mon Sep 17 00:00:00 2001 From: Denis <146707790+dnzbk@users.noreply.github.com> Date: Fri, 15 Dec 2023 12:29:36 +0300 Subject: [PATCH] Feature/automatic search for a suitable interpreter on POSIX (#74) * added: automatic search for a suitable interpreter to run extensions on POSIX systems. * refactored: renamed Script.cpp to a more appropriate ScriptController.cpp name --- Makefile.am | 4 +- daemon/extension/NzbScript.h | 2 +- daemon/main/Maintenance.h | 2 +- daemon/postprocess/Cleanup.h | 2 +- daemon/postprocess/DirectUnpack.h | 2 +- daemon/postprocess/DupeMatcher.cpp | 2 +- daemon/postprocess/Rename.h | 2 +- daemon/postprocess/Repair.h | 2 +- daemon/postprocess/Unpack.h | 2 +- .../util/{Script.cpp => ScriptController.cpp} | 90 ++++++++++++++++--- daemon/util/{Script.h => ScriptController.h} | 10 ++- nzbget.vcxproj | 4 +- scripts/EMail.py | 2 +- scripts/Logger.py | 2 +- tests/postprocess/CMakeLists.txt | 2 +- windows/build-nzbget-vs22.bat | 9 -- 16 files changed, 97 insertions(+), 42 deletions(-) rename daemon/util/{Script.cpp => ScriptController.cpp} (90%) rename daemon/util/{Script.h => ScriptController.h} (94%) diff --git a/Makefile.am b/Makefile.am index 643186eb..473fb0fa 100644 --- a/Makefile.am +++ b/Makefile.am @@ -150,8 +150,8 @@ nzbget_SOURCES = \ daemon/util/Container.h \ daemon/util/Observer.cpp \ daemon/util/Observer.h \ - daemon/util/Script.cpp \ - daemon/util/Script.h \ + daemon/util/ScriptController.cpp \ + daemon/util/ScriptController.h \ daemon/util/Thread.cpp \ daemon/util/Thread.h \ daemon/util/Service.cpp \ diff --git a/daemon/extension/NzbScript.h b/daemon/extension/NzbScript.h index 62acbf89..e2e7df69 100644 --- a/daemon/extension/NzbScript.h +++ b/daemon/extension/NzbScript.h @@ -21,7 +21,7 @@ #ifndef NZBSCRIPT_H #define NZBSCRIPT_H -#include "Script.h" +#include "ScriptController.h" #include "DownloadInfo.h" #include "ScriptConfig.h" diff --git a/daemon/main/Maintenance.h b/daemon/main/Maintenance.h index b49ca43f..d7f59a91 100644 --- a/daemon/main/Maintenance.h +++ b/daemon/main/Maintenance.h @@ -22,7 +22,7 @@ #include "NString.h" #include "Thread.h" -#include "Script.h" +#include "ScriptController.h" #include "Log.h" #include "Util.h" diff --git a/daemon/postprocess/Cleanup.h b/daemon/postprocess/Cleanup.h index db818650..cbb06ad0 100644 --- a/daemon/postprocess/Cleanup.h +++ b/daemon/postprocess/Cleanup.h @@ -25,7 +25,7 @@ #include "Log.h" #include "Thread.h" #include "DownloadInfo.h" -#include "Script.h" +#include "ScriptController.h" class MoveController : public Thread, public ScriptController { diff --git a/daemon/postprocess/DirectUnpack.h b/daemon/postprocess/DirectUnpack.h index 63cdb73b..792f15e1 100644 --- a/daemon/postprocess/DirectUnpack.h +++ b/daemon/postprocess/DirectUnpack.h @@ -24,7 +24,7 @@ #include "Log.h" #include "Thread.h" #include "DownloadInfo.h" -#include "Script.h" +#include "ScriptController.h" class DirectUnpack : public Thread, public ScriptController { diff --git a/daemon/postprocess/DupeMatcher.cpp b/daemon/postprocess/DupeMatcher.cpp index 6c2fec14..34205c1f 100644 --- a/daemon/postprocess/DupeMatcher.cpp +++ b/daemon/postprocess/DupeMatcher.cpp @@ -24,7 +24,7 @@ #include "Util.h" #include "FileSystem.h" #include "Options.h" -#include "Script.h" +#include "ScriptController.h" #include "Thread.h" class RarLister : public Thread, public ScriptController diff --git a/daemon/postprocess/Rename.h b/daemon/postprocess/Rename.h index c85e7acd..a692eeca 100644 --- a/daemon/postprocess/Rename.h +++ b/daemon/postprocess/Rename.h @@ -23,7 +23,7 @@ #include "Thread.h" #include "DownloadInfo.h" -#include "Script.h" +#include "ScriptController.h" #include "RarRenamer.h" #ifndef DISABLE_PARCHECK diff --git a/daemon/postprocess/Repair.h b/daemon/postprocess/Repair.h index d04ad35d..5ef1c30d 100644 --- a/daemon/postprocess/Repair.h +++ b/daemon/postprocess/Repair.h @@ -23,7 +23,7 @@ #include "DownloadInfo.h" #include "Thread.h" -#include "Script.h" +#include "ScriptController.h" #ifndef DISABLE_PARCHECK #include "ParChecker.h" diff --git a/daemon/postprocess/Unpack.h b/daemon/postprocess/Unpack.h index 53b189b8..91948b68 100644 --- a/daemon/postprocess/Unpack.h +++ b/daemon/postprocess/Unpack.h @@ -24,7 +24,7 @@ #include "Log.h" #include "Thread.h" #include "DownloadInfo.h" -#include "Script.h" +#include "ScriptController.h" class UnpackController : public Thread, public ScriptController { diff --git a/daemon/util/Script.cpp b/daemon/util/ScriptController.cpp similarity index 90% rename from daemon/util/Script.cpp rename to daemon/util/ScriptController.cpp index 05cb678b..6e26fc54 100644 --- a/daemon/util/Script.cpp +++ b/daemon/util/ScriptController.cpp @@ -14,12 +14,12 @@ * 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 . */ #include "nzbget.h" -#include "Script.h" +#include "ScriptController.h" #include "Log.h" #include "Util.h" #include "FileSystem.h" @@ -236,10 +236,10 @@ void ScriptController::SetEnvVarSpecial(const char* prefix, const char* name, co void ScriptController::PrepareArgs() { + const char* extension = strrchr(m_args[0], '.'); + if (m_args.size() == 1 && !Util::EmptyStr(g_Options->GetShellOverride())) { - const char* extension = strrchr(m_args[0], '.'); - Tokenizer tok(g_Options->GetShellOverride(), ",;"); while (CString shellover = tok.Next()) { @@ -259,14 +259,17 @@ void ScriptController::PrepareArgs() } } + PrepareCmdLine(extension); +} + #ifdef WIN32 +void ScriptController::PrepareCmdLine(const char* extension) +{ *m_cmdLine = '\0'; - if (m_args.size() == 1) { // Special support for script languages: // automatically find the app registered for this extension and run it - const char* extension = strrchr(m_args[0], '.'); if (extension && strcasecmp(extension, ".exe") && strcasecmp(extension, ".bat") && strcasecmp(extension, ".cmd")) { debug("Looking for associated program for %s", extension); @@ -283,9 +286,9 @@ void ScriptController::PrepareArgs() { command[bufLen] = '\0'; CString scommand(command); - scommand.Replace("%L", "%1");// Python 3.x has %L in command instead of %1 + scommand.Replace("%L", "%1"); // Python 3.x has %L in command instead of %1 debug("Command: %s", scommand.Str()); - DWORD_PTR args[] = {(DWORD_PTR)*m_args[0], (DWORD_PTR)0}; + DWORD_PTR args[] = { (DWORD_PTR)*m_args[0], (DWORD_PTR)0 }; if (FormatMessage(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY, scommand, 0, 0, m_cmdLine, sizeof(m_cmdLine), (va_list*)args)) { @@ -299,8 +302,68 @@ void ScriptController::PrepareArgs() extension, FileSystem::BaseFileName(m_args[0])); } } +} #endif + +#ifndef WIN32 +void ScriptController::PrepareCmdLine(const char* extension) +{ + *m_cmdLine = '\0'; + m_cmdArgs.clear(); + if (m_args.size() == 1) + { + if (strcmp(extension, ".py") == 0) + { + if (std::system("python3 --version > /dev/null 2>&1") == 0) + { + strncpy(m_cmdLine, "python3", sizeof(m_cmdLine)); + m_cmdArgs.emplace_back("python3"); + debug("CmdLine: %s", m_cmdLine); + return; + } + if (std::system("python --version > /dev/null 2>&1") == 0) + { + strncpy(m_cmdLine, "python", sizeof(m_cmdLine)); + m_cmdArgs.emplace_back("python"); + debug("CmdLine: %s", m_cmdLine); + return; + } + if (std::system("py --version > /dev/null 2>&1") == 0) + { + strncpy(m_cmdLine, "py", sizeof(m_cmdLine)); + m_cmdArgs.emplace_back("py"); + debug("CmdLine: %s", m_cmdLine); + return; + } + } + if (strcmp(extension, ".sh") == 0) + { + if (std::system("bash --version > /dev/null 2>&1") == 0) + { + strncpy(m_cmdLine, "bash", sizeof(m_cmdLine)); + m_cmdArgs.emplace_back("bash"); + debug("CmdLine: %s", m_cmdLine); + return; + } + if (std::system("sh --version > /dev/null 2>&1") == 0) + { + strncpy(m_cmdLine, "sh", sizeof(m_cmdLine)); + m_cmdArgs.emplace_back("sh"); + debug("CmdLine: %s", m_cmdLine); + return; + } + } + warn("Could not find associated program for %s. Trying to execute %s directly", + extension, FileSystem::BaseFileName(m_args[0])); + strncpy(m_cmdLine, m_args[0], sizeof(m_cmdLine)); + return; + } + else + { + strncpy(m_cmdLine, m_args[0], sizeof(m_cmdLine)); + } } +#endif int ScriptController::Execute() { @@ -580,10 +643,9 @@ void ScriptController::StartProcess(int* pipein, int* pipeout) std::vector environmentStrings = m_environmentStrings.GetStrings(); char** envdata = environmentStrings.data(); - ArgList args; - std::copy(m_args.begin(), m_args.end(), std::back_inserter(args)); - args.emplace_back(nullptr); - char* const* argdata = (char* const*)args.data(); + std::copy(std::begin(m_args), std::end(m_args), std::back_inserter(m_cmdArgs)); + m_cmdArgs.emplace_back(nullptr); + char* const* argdata = (char* const*)m_cmdArgs.data(); #ifdef DEBUG debug("Starting process: %s", script); @@ -641,7 +703,7 @@ void ScriptController::StartProcess(int* pipein, int* pipeout) chdir(workingDir); environ = envdata; - execvp(script, argdata); + execvp(m_cmdLine, argdata); if (errno == EACCES) { @@ -650,7 +712,7 @@ void ScriptController::StartProcess(int* pipein, int* pipeout) write(1, "\n", 1); fsync(1); FileSystem::FixExecPermission(script); - execvp(script, argdata); + execvp(m_cmdLine, argdata); } // NOTE: the text "[ERROR] Could not start " is checked later, diff --git a/daemon/util/Script.h b/daemon/util/ScriptController.h similarity index 94% rename from daemon/util/Script.h rename to daemon/util/ScriptController.h index f8c77b8a..c4b92642 100644 --- a/daemon/util/Script.h +++ b/daemon/util/ScriptController.h @@ -14,12 +14,12 @@ * 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 . */ -#ifndef SCRIPT_H -#define SCRIPT_H +#ifndef SCRIPTCONTROLLER_H +#define SCRIPTCONTROLLER_H #include "NString.h" #include "Container.h" @@ -78,6 +78,7 @@ class ScriptController void ResetEnv(); void PrepareEnvOptions(const char* stripPrefix); void PrepareArgs(); + void PrepareCmdLine(const char* extension); virtual const char* GetOptValue(const char* name, const char* value) { return value; } void StartProcess(int* pipein, int* pipeout); int WaitProcess(); @@ -90,6 +91,7 @@ class ScriptController private: ArgList m_args; + ArgList m_cmdArgs; const char* m_workingDir = nullptr; CString m_infoName; const char* m_logPrefix = nullptr; @@ -100,10 +102,10 @@ class ScriptController bool m_needWrite = false; FILE* m_readpipe = 0; FILE* m_writepipe = 0; + char m_cmdLine[2048]; #ifdef WIN32 HANDLE m_processId = 0; DWORD m_dwProcessId = 0; - char m_cmdLine[2048]; #else pid_t m_processId = 0; #endif diff --git a/nzbget.vcxproj b/nzbget.vcxproj index 4566a45f..caedc7aa 100755 --- a/nzbget.vcxproj +++ b/nzbget.vcxproj @@ -273,7 +273,7 @@ - + @@ -392,7 +392,7 @@ - + diff --git a/scripts/EMail.py b/scripts/EMail.py index 5628f451..1932efd2 100755 --- a/scripts/EMail.py +++ b/scripts/EMail.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python # # E-Mail post-processing script for NZBGet # diff --git a/scripts/Logger.py b/scripts/Logger.py index 3c111ab2..61fb4a65 100755 --- a/scripts/Logger.py +++ b/scripts/Logger.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python # # Logger post-processing script for NZBGet # diff --git a/tests/postprocess/CMakeLists.txt b/tests/postprocess/CMakeLists.txt index 2d674d81..398b994f 100644 --- a/tests/postprocess/CMakeLists.txt +++ b/tests/postprocess/CMakeLists.txt @@ -20,7 +20,7 @@ file(GLOB PostprocessTestsSrc ${CMAKE_SOURCE_DIR}/daemon/util/Util.cpp ${CMAKE_SOURCE_DIR}/daemon/util/Thread.cpp ${CMAKE_SOURCE_DIR}/daemon/util/Log.cpp - ${CMAKE_SOURCE_DIR}/daemon/util/Script.cpp + ${CMAKE_SOURCE_DIR}/daemon/util/ScriptController.cpp ${CMAKE_SOURCE_DIR}/daemon/util/FileSystem.cpp ${CMAKE_SOURCE_DIR}/daemon/queue/DownloadInfo.cpp ${CMAKE_SOURCE_DIR}/daemon/queue/DiskState.cpp diff --git a/windows/build-nzbget-vs22.bat b/windows/build-nzbget-vs22.bat index 1d99fde3..79be6098 100644 --- a/windows/build-nzbget-vs22.bat +++ b/windows/build-nzbget-vs22.bat @@ -225,15 +225,6 @@ mkdir ..\distrib\NZBGet\scripts xcopy /E scripts ..\distrib\NZBGet\scripts if errorlevel 1 goto BUILD_FAILED -rem The default PATH to python changed in most POSIX systems after python2 was deprecated. -rem On Windows, it remained the same. -rem Now we need to add Windows specific shebang (#!/usr/bin/env python) to the scripts. -set "SCRIPTS=..\distrib\NZBGet\scripts\EMail.py ..\distrib\NZBGet\scripts\Logger.py" -for %%F in (%SCRIPTS%) do ( - %SED% -e "s|#!/usr/bin/env python3|#!/usr/bin/env python|" -i %%F -) -if errorlevel 1 goto BUILD_FAILED - copy ..\..\image\* ..\distrib\NZBGet copy ..\..\image\32\* ..\distrib\NZBGet\32 copy ..\..\image\64\* ..\distrib\NZBGet\64