From e9356ebe796438b9d99e86ca2caff891e32df94e Mon Sep 17 00:00:00 2001 From: Andrey Prygunkov Date: Mon, 2 Mar 2015 20:49:05 +0000 Subject: [PATCH] added built-in update feature to windows package; accessible via web-interface -> settings -> system -> check for updates --- Makefile.am | 5 +- Makefile.in | 7 +- daemon/connect/Connection.cpp | 6 + daemon/main/Maintenance.cpp | 21 +- daemon/main/Options.cpp | 16 +- daemon/main/Options.h | 4 + daemon/main/nzbget.cpp | 29 +++ daemon/util/Script.cpp | 23 ++- windows/README-WINDOWS.txt | 43 ++++ windows/install-update.bat | 189 ++++++++++++++++++ .../nzbget-command-shell.bat | 0 windows/package-info.json | 4 + 12 files changed, 336 insertions(+), 11 deletions(-) create mode 100644 windows/README-WINDOWS.txt create mode 100644 windows/install-update.bat rename nzbget-shell.bat => windows/nzbget-command-shell.bat (100%) create mode 100644 windows/package-info.json diff --git a/Makefile.am b/Makefile.am index 5bbae855..f21bc89d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -195,7 +195,10 @@ windows_FILES = \ daemon/windows/WinConsole.h \ nzbget.sln \ nzbget.vcproj \ - nzbget-shell.bat \ + windows/nzbget-command-shell.bat \ + windows/install-update.bat \ + windows/README-WINDOWS.txt \ + windows/package-info.json \ windows/resources/mainicon.ico \ windows/resources/nzbget.rc \ windows/resources/resource.h \ diff --git a/Makefile.in b/Makefile.in index 63f558c9..eb5c1f9e 100644 --- a/Makefile.in +++ b/Makefile.in @@ -465,7 +465,10 @@ windows_FILES = \ daemon/windows/WinConsole.h \ nzbget.sln \ nzbget.vcproj \ - nzbget-shell.bat \ + windows/nzbget-command-shell.bat \ + windows/install-update.bat \ + windows/README-WINDOWS.txt \ + windows/package-info.json \ windows/resources/mainicon.ico \ windows/resources/nzbget.rc \ windows/resources/resource.h \ @@ -1857,7 +1860,7 @@ distclean-tags: distdir: $(DISTFILES) $(am__remove_distdir) mkdir $(distdir) - $(mkdir_p) $(distdir)/daemon/windows $(distdir)/lib/par2 $(distdir)/osx $(distdir)/osx/NZBGet.xcodeproj $(distdir)/osx/Resources $(distdir)/osx/Resources/Images $(distdir)/osx/Resources/licenses $(distdir)/scripts $(distdir)/webui $(distdir)/webui/img $(distdir)/webui/lib $(distdir)/windows/resources $(distdir)/windows/setup + $(mkdir_p) $(distdir)/daemon/windows $(distdir)/lib/par2 $(distdir)/osx $(distdir)/osx/NZBGet.xcodeproj $(distdir)/osx/Resources $(distdir)/osx/Resources/Images $(distdir)/osx/Resources/licenses $(distdir)/scripts $(distdir)/webui $(distdir)/webui/img $(distdir)/webui/lib $(distdir)/windows $(distdir)/windows/resources $(distdir)/windows/setup @srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's|.|.|g'`; \ list='$(DISTFILES)'; for file in $$list; do \ diff --git a/daemon/connect/Connection.cpp b/daemon/connect/Connection.cpp index 518010be..723443c1 100644 --- a/daemon/connect/Connection.cpp +++ b/daemon/connect/Connection.cpp @@ -263,6 +263,9 @@ bool Connection::Bind() for (addr = addr_list; addr != NULL; addr = addr->ai_next) { m_iSocket = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); +#ifdef WIN32 + SetHandleInformation((HANDLE)m_iSocket, HANDLE_FLAG_INHERIT, 0); +#endif if (m_iSocket != INVALID_SOCKET) { int opt = 1; @@ -553,6 +556,9 @@ bool Connection::DoConnect() { bool bLastAddr = !addr->ai_next; m_iSocket = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); +#ifdef WIN32 + SetHandleInformation((HANDLE)m_iSocket, HANDLE_FLAG_INHERIT, 0); +#endif if (m_iSocket != INVALID_SOCKET) { if (ConnectWithTimeout(addr->ai_addr, addr->ai_addrlen)) diff --git a/daemon/main/Maintenance.cpp b/daemon/main/Maintenance.cpp index 98fe5c27..30a804c4 100644 --- a/daemon/main/Maintenance.cpp +++ b/daemon/main/Maintenance.cpp @@ -48,6 +48,7 @@ extern Options* g_pOptions; extern Maintenance* g_pMaintenance; +extern void ExitProc(); Maintenance::Maintenance() { @@ -260,8 +261,24 @@ void UpdateScriptController::AddMessage(Message::EKind eKind, const char* szText { szText = szText + m_iPrefixLen; - g_pMaintenance->AddMessage(eKind, time(NULL), szText); - ScriptController::AddMessage(eKind, szText); + if (!strncmp(szText, "[NZB] ", 6)) + { + debug("Command %s detected", szText + 6); + if (!strcmp(szText + 6, "QUIT")) + { + Detach(); + ExitProc(); + } + else + { + error("Invalid command \"%s\" received", szText); + } + } + else + { + g_pMaintenance->AddMessage(eKind, time(NULL), szText); + ScriptController::AddMessage(eKind, szText); + } } void UpdateInfoScriptController::ExecuteScript(const char* szScript, char** pUpdateInfo) diff --git a/daemon/main/Options.cpp b/daemon/main/Options.cpp index 79d74003..84f7f3c6 100644 --- a/daemon/main/Options.cpp +++ b/daemon/main/Options.cpp @@ -83,7 +83,7 @@ static struct option long_options[] = {"pause", no_argument, 0, 'P'}, {"unpause", no_argument, 0, 'U'}, {"rate", required_argument, 0, 'R'}, - {"debug", no_argument, 0, 'B'}, + {"system", no_argument, 0, 'B'}, {"log", required_argument, 0, 'G'}, {"top", no_argument, 0, 'T'}, {"edit", required_argument, 0, 'E'}, @@ -590,6 +590,8 @@ Options::Options(int argc, char* argv[]) m_bParCleanupQueue = false; m_iDiskSpace = 0; m_bTestBacktrace = false; + m_bWebGet = false; + m_szWebGetFilename = NULL; m_bTLS = false; m_bDumpCore = false; m_bParPauseQueue = false; @@ -736,6 +738,7 @@ Options::~Options() free(m_szUnpackPassFile); free(m_szExtCleanupDisk); free(m_szParIgnoreExt); + free(m_szWebGetFilename); for (NameList::iterator it = m_EditQueueNameList.begin(); it != m_EditQueueNameList.end(); it++) { @@ -1512,6 +1515,17 @@ void Options::InitCommandLine(int argc, char* argv[]) { m_bTestBacktrace = true; } + else if (!strcasecmp(optarg, "webget")) + { + m_bWebGet = true; + optind++; + if (optind > argc) + { + abort("FATAL ERROR: Could not parse value of option 'E'\n"); + } + optarg = argv[optind-1]; + m_szWebGetFilename = strdup(optarg); + } else { abort("FATAL ERROR: Could not parse value of option 'B'\n"); diff --git a/daemon/main/Options.h b/daemon/main/Options.h index 413ee899..f1b69c44 100644 --- a/daemon/main/Options.h +++ b/daemon/main/Options.h @@ -372,6 +372,8 @@ private: int m_iLogLines; int m_iWriteLogKind; bool m_bTestBacktrace; + bool m_bWebGet; + char* m_szWebGetFilename; // Current state bool m_bPauseDownload; @@ -560,6 +562,8 @@ public: int GetLogLines() { return m_iLogLines; } int GetWriteLogKind() { return m_iWriteLogKind; } bool GetTestBacktrace() { return m_bTestBacktrace; } + bool GetWebGet() { return m_bWebGet; } + const char* GetWebGetFilename() { return m_szWebGetFilename; } // Current state void SetPauseDownload(bool bPauseDownload) { m_bPauseDownload = bPauseDownload; } diff --git a/daemon/main/nzbget.cpp b/daemon/main/nzbget.cpp index 1b34cdb2..2faf8dc1 100644 --- a/daemon/main/nzbget.cpp +++ b/daemon/main/nzbget.cpp @@ -83,6 +83,7 @@ #ifdef WIN32 #include "NTService.h" #include "WinConsole.h" +#include "WebDownloader.h" #endif // Prototypes @@ -91,6 +92,7 @@ void Run(bool bReload); void Reload(); void Cleanup(); void ProcessClientRequest(); +void ProcessWebGet(); #ifndef WIN32 void Daemonize(); #endif @@ -279,6 +281,12 @@ void Run(bool bReload) } #endif + if (g_pOptions->GetWebGet()) + { + ProcessWebGet(); + return; + } + // client request if (g_pOptions->GetClientOperation() != Options::opClientNoOperation) { @@ -572,6 +580,27 @@ void ProcessClientRequest() delete Client; } +void ProcessWebGet() +{ + WebDownloader downloader; + downloader.SetURL(g_pOptions->GetLastArg()); + downloader.SetForce(true); + downloader.SetRetry(false); + downloader.SetOutputFilename(g_pOptions->GetWebGetFilename()); + downloader.SetInfoName("WebGet"); + + int iRedirects = 0; + WebDownloader::EStatus eStatus = WebDownloader::adRedirect; + while (eStatus == WebDownloader::adRedirect && iRedirects < 5) + { + iRedirects++; + eStatus = downloader.Download(); + } + bool bOK = eStatus == WebDownloader::adFinished; + + exit(bOK ? 0 : 1); +} + void ExitProc() { if (!g_bReloading) diff --git a/daemon/util/Script.cpp b/daemon/util/Script.cpp index dec42bd9..2cb32df7 100644 --- a/daemon/util/Script.cpp +++ b/daemon/util/Script.cpp @@ -132,7 +132,14 @@ void EnvironmentStrings::InitFromCurrentProcess() for (int i = 0; (*g_szEnvironmentVariables)[i]; i++) { char* szVar = (*g_szEnvironmentVariables)[i]; - Append(strdup(szVar)); + // Ignore all env vars set by NZBGet. + // This is to avoid the passing of env vars after program update (when NZBGet is + // started from a script which was started by a previous instance of NZBGet). + // Format: NZBXX_YYYY (XX are any two characters, YYYY are any number of any characters). + if (!(!strncmp(szVar, "NZB", 3) && strlen(szVar) > 5 && szVar[5] == '_')) + { + Append(strdup(szVar)); + } } } @@ -202,7 +209,7 @@ ScriptController::ScriptController() m_bTerminated = false; m_bDetached = false; m_hProcess = 0; - m_environmentStrings.InitFromCurrentProcess(); + ResetEnv(); m_mutexRunning.Lock(); m_RunningScripts.push_back(this); @@ -225,9 +232,13 @@ ScriptController::~ScriptController() void ScriptController::UnregisterRunningScript() { - m_mutexRunning.Lock(); - m_RunningScripts.erase(std::find(m_RunningScripts.begin(), m_RunningScripts.end(), this)); - m_mutexRunning.Unlock(); + m_mutexRunning.Lock(); + RunningScripts::iterator it = std::find(m_RunningScripts.begin(), m_RunningScripts.end(), this); + if (it != m_RunningScripts.end()) + { + m_RunningScripts.erase(it); + } + m_mutexRunning.Unlock(); } void ScriptController::ResetEnv() @@ -401,6 +412,8 @@ int ScriptController::Execute() CreatePipe(&hReadPipe, &hWritePipe, &SecurityAttributes, 0); + SetHandleInformation(hReadPipe, HANDLE_FLAG_INHERIT, 0); + STARTUPINFO StartupInfo; memset(&StartupInfo, 0, sizeof(StartupInfo)); StartupInfo.cb = sizeof(StartupInfo); diff --git a/windows/README-WINDOWS.txt b/windows/README-WINDOWS.txt new file mode 100644 index 00000000..4eefbe76 --- /dev/null +++ b/windows/README-WINDOWS.txt @@ -0,0 +1,43 @@ +===================================== + NZBGet ReadMe for Windows +===================================== + +This is a short documentation. For more info visit +http://nzbget.net/Installation_on_Windows + +===================================== + +NZBGet can be used in application mode (tray icon and optional +DOS window) or as a service. When you use the program for the +first time you should start it at least once in application mode +to create the necessary configuration file from template. + + +===================== +Provided batch-file: +===================== + +nzbget-command-shell.bat +Starts console window (DOS box) where you can execute remote +commands to communicate with running NZBGet. + + +===================== +Service mode +===================== + +First you need to install the service. +From NZBGet shell (batch file nzbget-shell.bat) use command: + nzbget -install + +To remove the service use command: + nzbget -remove + +To start service: + net start NZBGet + +To stop service: + net stop NZBGet + +=================================================================== +For description of the program and more information see file README. diff --git a/windows/install-update.bat b/windows/install-update.bat new file mode 100644 index 00000000..2795b7dc --- /dev/null +++ b/windows/install-update.bat @@ -0,0 +1,189 @@ +@echo off + +rem +rem Batch file to update nzbget from web-interface +rem +rem Copyright (C) 2015 Andrey Prygunkov +rem +rem This program is free software; you can redistribute it and/or modify +rem it under the terms of the GNU General Public License as published by +rem the Free Software Foundation; either version 2 of the License, or +rem (at your option) any later version. +rem +rem This program is distributed in the hope that it will be useful, +rem but WITHOUT ANY WARRANTY; without even the implied warranty of +rem MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +rem GNU General Public License for more details. +rem +rem You should have received a copy of the GNU General Public License +rem along with this program; if not, write to the Free Software +rem Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +rem + +title Updating NZBGet + +set BASE_URL=http://sourceforge.net/projects/nzbget/files + +if x%NZBUP_BRANCH%==x ( + echo This script is executed by NZBGet during update and is not supposed to be started manually by user. + echo. + echo.To update NZBGet go to Web-interface - Settings - System - Check for updates. + echo. + pause + exit +) + +@setlocal enabledelayedexpansion + +rem extracting link to update-info-URL from "webui\package-info.json" +set UPDATE_INFO_LINK= +for /f "delims=" %%a in (%NZBOP_WEBDIR%\package-info.json) do ( + set line=%%a + set line=!line:update-info-link=! + if not %%a==!line! ( + set UPDATE_INFO_LINK=!line! + rem deleting tabs, spaces, quotation marks and commas + set UPDATE_INFO_LINK=!UPDATE_INFO_LINK: =! + set UPDATE_INFO_LINK=!UPDATE_INFO_LINK: =! + set UPDATE_INFO_LINK=!UPDATE_INFO_LINK:"=! + set UPDATE_INFO_LINK=!UPDATE_INFO_LINK:,=! + rem deleteing the leading colon + set UPDATE_INFO_LINK=!UPDATE_INFO_LINK:~1%! + ) +) + +rem "%~dp0" means the location of the current batch file +set NZBGET_DIR=%~dp0 +cd %NZBGET_DIR% + +if "%1"=="/step2" goto STEP2 + +echo Downloading version information... +rem using special command "-B webget" NZBGet works like a simple wget +rem and fetches files from web-servers +nzbget.exe -B webget "%TEMP%\NZBGET_UPDATE.txt" "%UPDATE_INFO_LINK%" +if errorlevel 1 goto DOWNLOAD_FAILURE + +if %NZBUP_BRANCH%==TESTING set VER_FIELD=testing-version +if %NZBUP_BRANCH%==STABLE set VER_FIELD=stable-version +set VER=0 +for /f "delims=" %%a in (%TEMP%\NZBGET_UPDATE.txt) do ( + set line=%%a + set line=!line:%VER_FIELD%=! + if not %%a==!line! ( + set VER=!line! + rem deleting tabs, spaces, quotation marks, colons and commas + set VER=!VER: =! + set VER=!VER: =! + set VER=!VER:"=! + set VER=!VER::=! + set VER=!VER:,=! + ) +) + +SET SETUP_EXE=nzbget-%VER%-bin-win32-setup.exe + +echo Downloading %SETUP_EXE%... +nzbget.exe -B webget "%TEMP%\%SETUP_EXE%" "%BASE_URL%/%SETUP_EXE%" +if errorlevel 1 goto DOWNLOAD_FAILURE +echo Downloaded successfully +rem using ping as wait-command, the third parameter (2) causes ping to wait 1 (one) second +ping 127.0.0.1 -n 2 -w 1000 > nul + +echo Stopping NZBGet and installing update... +ping 127.0.0.1 -n 2 -w 1000 > nul + +rem After NZBGet is stopped the script cannot pring any messages to web-interface +rem In order for user to see any error messages we start another instance of the +rem script with its own a console window. +rem We need to do that because of another reeson too. When the update is installed +rem it is possible that the script "install-update.bat" will be updated too. +rem In that case the command interpreter will go grazy because it doesn't like the +rem batch files being replaced during execution. +copy install-update.bat "%TEMP%\nzbget-update.bat" > nul +if errorlevel 1 goto COPYSCRIPT_FAILURE +start "Updating NZBGet" /I /MIN CALL "%TEMP%\nzbget-update.bat" /step2 "%NZBGET_DIR%" %SETUP_EXE% + +echo [NZB] QUIT + +exit + + +:STEP2 +rem init from command line params +set NZBGET_DIR=%2 +cd %NZBGET_DIR% +set SETUP_EXE=%3 + +rem check if nzbget.exe is running +echo Stopping NZBGet... +echo. + +tasklist 2> nul > nul +if errorlevel 1 goto WINXPHOME + +set WAIT_SECONDS=30 +:CHECK_RUNNING +if "%WAIT_SECONDS%"=="0" goto QUIT_FAILURE +tasklist /FI "IMAGENAME eq nzbget.exe" 2> nul | find /I /N "nzbget.exe" > nul +if "%ERRORLEVEL%"=="0" ( + ping 127.0.0.1 -n 2 -w 1000 > nul + set /a "WAIT_SECONDS=%WAIT_SECONDS%-1" + goto CHECK_RUNNING +) + +goto INSTALL + +:WINXPHOME +rem Alternative solution when command "tasklist" isn't available: +rem just wait 30 seconds +ping 127.0.0.1 -n 31 -w 1000 > nul + +:INSTALL + +echo Installing new version... +echo. +%TEMP%\%SETUP_EXE% /S + +del %TEMP%\%SETUP_EXE% + +echo Starting NZBGet... +start /MIN nzbget.exe -app -auto -s +if errorlevel 1 goto START_FAILURE +ping 127.0.0.1 -n 2 -w 1000 > nul +exit + + +:DOWNLOAD_FAILURE +rem This is in the first instance, the error is printed to web-interface +echo. +echo [ERROR] *********************************************** +echo [ERROR] Download failed, please try again later +echo [ERROR] *********************************************** +echo. +exit + + +:COPYSCRIPT_FAILURE +rem This is in the first instance, the error is printed to web-interface +echo. +echo [ERROR] *********************************************** +echo [ERROR] Failed to copy the update script +echo [ERROR] *********************************************** +echo. +exit + + +:QUIT_FAILURE +rem This is in the second instance, the error is printed to console window +start "Error during update" CMD /c "echo ERROR: Failed to stop NZBGet && pause" +ping 127.0.0.1 -n 11 -w 1000 > nul +exit + + +:START_FAILURE +rem This is in the second instance, the error is printed to console window +start "Error during update" CMD /c "echo ERROR: Failed to start NZBGet && pause" +ping 127.0.0.1 -n 11 -w 1000 > nul +exit + diff --git a/nzbget-shell.bat b/windows/nzbget-command-shell.bat similarity index 100% rename from nzbget-shell.bat rename to windows/nzbget-command-shell.bat diff --git a/windows/package-info.json b/windows/package-info.json new file mode 100644 index 00000000..041a88b9 --- /dev/null +++ b/windows/package-info.json @@ -0,0 +1,4 @@ +{ + "update-info-link": "http://nzbget.net/info/nzbget-version-win32.php", + "install-script": "install-update.bat" +}