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 643186eba..473fb0fa8 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 62acbf899..e2e7df693 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 b49ca43fa..d7f59a918 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 db818650a..cbb06ad0c 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 63cdb73bb..792f15e1d 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 6c2fec146..34205c1f7 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 c85e7acd0..a692eeca2 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 d04ad35d4..5ef1c30de 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 53b189b8a..91948b68e 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 05cb678b2..6e26fc540 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 f8c77b8a0..c4b92642b 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 4566a45fe..caedc7aa0 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 5628f451c..1932efd28 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 3c111ab2b..61fb4a65a 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 2d674d816..398b994fc 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 1d99fde3f..79be60982 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