diff --git a/core/src/main/java/org/nzbhydra/systemcontrol/SystemControl.java b/core/src/main/java/org/nzbhydra/systemcontrol/SystemControl.java index 7d5879783..1807b4a8b 100644 --- a/core/src/main/java/org/nzbhydra/systemcontrol/SystemControl.java +++ b/core/src/main/java/org/nzbhydra/systemcontrol/SystemControl.java @@ -46,8 +46,8 @@ public class SystemControl { @Autowired private ApplicationEventPublisher applicationEventPublisher; - public void exitWithReturnCode(final int returnCode) { - if (Boolean.parseBoolean(environment.getProperty("hydradontshutdown", "false"))) { + public void exitWithReturnCode(final int returnCode, boolean forceShutdown) { + if (Boolean.parseBoolean(environment.getProperty("hydradontshutdown", "false")) && !forceShutdown) { logger.warn("Not shutting down because property hydradontshutdown is set"); return; } @@ -70,4 +70,8 @@ public void exitWithReturnCode(final int returnCode) { } }).start(); } + + public void exitWithReturnCode(final int returnCode) { + exitWithReturnCode(returnCode, false); + } } diff --git a/core/src/main/java/org/nzbhydra/systemcontrol/SystemControlWeb.java b/core/src/main/java/org/nzbhydra/systemcontrol/SystemControlWeb.java index 7bdf77df3..0a84cdcc3 100644 --- a/core/src/main/java/org/nzbhydra/systemcontrol/SystemControlWeb.java +++ b/core/src/main/java/org/nzbhydra/systemcontrol/SystemControlWeb.java @@ -26,6 +26,7 @@ import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @@ -40,18 +41,12 @@ public class SystemControlWeb { @Secured({"ROLE_ADMIN"}) @RequestMapping(value = "/internalapi/control/shutdown", method = RequestMethod.GET) - public GenericResponse shutdown() throws Exception { - return doShutdown(); - } - - @NotNull - private GenericResponse doShutdown() { + public GenericResponse shutdown(@RequestParam(required = false) Integer returnCode, @RequestParam(required = false) Boolean forceShutdown) throws Exception { logger.info("Shutting down due to external request"); - systemControl.exitWithReturnCode(SystemControl.SHUTDOWN_RETURN_CODE); + systemControl.exitWithReturnCode(returnCode == null ? SystemControl.SHUTDOWN_RETURN_CODE : returnCode, forceShutdown != null && forceShutdown); return GenericResponse.ok(); } - @Secured({"ROLE_ADMIN"}) @RequestMapping(value = "/internalapi/control/restart", method = RequestMethod.GET) public GenericResponse restart() throws Exception { diff --git a/other/gowrapper/base/base.go b/other/gowrapper/base/base.go index f170b12ef..9bbf816c7 100644 --- a/other/gowrapper/base/base.go +++ b/other/gowrapper/base/base.go @@ -509,7 +509,7 @@ func runMainProcess(executable string, arguments []string) int { if !*argsQuiet { println(line) } - handleProcessUriInLogLine(line) + checkLogLine(line) consoleLines = append(consoleLines, line) if len(consoleLines) > 250 { consoleLines = consoleLines[1:] @@ -536,7 +536,7 @@ func runMainProcess(executable string, arguments []string) int { return 0 } -func handleProcessUriInLogLine(line string) { +func checkLogLine(line string) { markerLine := "You can access NZBHydra 2 in your browser via " if strings.Contains(line, markerLine) { Uri = strings.TrimSpace(line[strings.Index(line, markerLine)+len(markerLine):]) @@ -547,6 +547,9 @@ func handleProcessUriInLogLine(line string) { urlToOpen := strings.TrimSpace(line[strings.Index(line, markerLine)+len(markerLine):]) OpenBrowser(urlToOpen) } + if strings.Contains(line, "Started NzbHydra in") { + Log(logrus.InfoLevel, "Main process has started successfully") + } } func OpenBrowser(urlToOpen string) { diff --git a/other/gowrapper/base/base_test.go b/other/gowrapper/base/base_test.go index a90886fdd..7768ccd4d 100644 --- a/other/gowrapper/base/base_test.go +++ b/other/gowrapper/base/base_test.go @@ -1,9 +1,12 @@ package base import ( + "bufio" + "fmt" "github.com/stretchr/testify/assert" "net/http" "os" + "strings" "sync" "testing" "time" @@ -11,32 +14,104 @@ import ( type Predicate func(error error, response *http.Response) bool +var reachedLineNumber int + //goland:noinspection GoUnhandledErrorResult func TestSimpleShutdown(t *testing.T) { var wg sync.WaitGroup - dir := "d:\\NZBHydra\\nzbhydra2\\gowrappertest\\automated\\mainfolder\\" - os.RemoveAll(dir) - os.Create(dir) - Unzip("d:\\NZBHydra\\nzbhydra2\\gowrappertest\\automated\\sources\\nzbhydra2-5.3.10-windows-withData.zip", dir) - os.Chdir(dir) - Uri = "http://127.0.0.1:5076/" - var exitCode int + prepareAndRun(&wg) + wg.Add(1) - go runCode(&wg, &exitCode) + go func() { + defer wg.Done() + started, _ := wrapperLogContainsString("Main process has started successfully") + assert.True(t, started, "Process has not started") + + shutdownWithCode(0) + + shutdown, _ := wrapperLogContainsString("NZBHydra main process has stopped for shutdown") + assert.True(t, shutdown, "Process has not shut down") + }() + wg.Wait() +} + +//goland:noinspection GoUnhandledErrorResult +func TestUpdate(t *testing.T) { + var wg sync.WaitGroup + prepareAndRun(&wg) wg.Add(1) go func() { defer wg.Done() - getWaiting(Uri, func(error error, response *http.Response) bool { return response != nil && response.StatusCode == 200 }) - getWaiting(Uri+"internalapi/control/shutdown?internalApiKey="+GetInternalApiKey(), func(error error, response *http.Response) bool { return error != nil }) - wasShutdown := getWaiting(Uri, func(error error, response *http.Response) bool { return error != nil }) - assert.True(t, wasShutdown, "Server was not shut down") + waitForServerUp() + + shutdownWithCode(11) + updated, _ := wrapperLogContainsString("Update successful") + assert.True(t, updated, "Process was not updated") + restarted, _ := wrapperLogContainsString("Main process has started successfully") + assert.True(t, restarted, "Process has not restarted after update") + + shutdownWithCode(0) + shutdown, _ := wrapperLogContainsString("NZBHydra main process has stopped for shutdown") + assert.True(t, shutdown, "Process has not shut down") + waitForServerDown(t) }() + wg.Wait() +} + +//goland:noinspection GoUnhandledErrorResult +func TestRestore(t *testing.T) { + var wg sync.WaitGroup + prepareAndRun(&wg) + + wg.Add(1) + go func() { + defer wg.Done() + waitForServerUp() + + shutdownWithCode(33) + restored, _ := wrapperLogContainsString("Moved all files from restore folder") + assert.True(t, restored, "Process has not restored") + restarted, _ := wrapperLogContainsString("Main process has started successfully") + assert.True(t, restarted, "Process has not restarted after restore") + + shutdownWithCode(0) + shutdown, _ := wrapperLogContainsString("NZBHydra main process has stopped for shutdown") + assert.True(t, shutdown, "Process has not shut down") + waitForServerDown(t) + }() wg.Wait() } +func shutdownWithCode(code int) bool { + url := fmt.Sprintf("%sinternalapi/control/shutdown?returnCode=%d&internalApiKey=%s", Uri, code, GetInternalApiKey()) + return getWaiting(url, func(error error, response *http.Response) bool { return error != nil }) +} + +func waitForServerDown(t *testing.T) { + wasShutdown := getWaiting(Uri, func(error error, response *http.Response) bool { return error != nil }) + assert.True(t, wasShutdown, "Server was not shut down") +} + +func waitForServerUp() bool { + return getWaiting(Uri, func(error error, response *http.Response) bool { return response != nil && response.StatusCode == 200 }) +} + +func prepareAndRun(wg *sync.WaitGroup) { + reachedLineNumber = 0 + dir := "d:\\NZBHydra\\nzbhydra2\\gowrappertest\\automated\\mainfolder\\" + os.RemoveAll(dir) + os.Create(dir) + Unzip("d:\\NZBHydra\\nzbhydra2\\gowrappertest\\automated\\sources\\nzbhydra2-5.3.10-windows-testSource.zip", dir) + os.Chdir(dir) + Uri = "http://127.0.0.1:5076/" + var exitCode int + wg.Add(1) + go runCode(wg, &exitCode) +} + func getWaiting(url string, predicate Predicate) bool { beganAt := time.Now() for { @@ -60,3 +135,38 @@ func runCode(wg *sync.WaitGroup, exitCode *int) { } Entrypoint(false, false) } + +func wrapperLogContainsString(searchString string) (bool, error) { + beganAt := time.Now() + for { + if beganAt.Add(time.Second * 10).Before(time.Now()) { + return false, nil + } + file, err := os.Open("d:\\NZBHydra\\nzbhydra2\\gowrappertest\\automated\\mainfolder\\data\\logs\\wrapper.log") + if err != nil { + //Assume file does not exist yet + continue + } + + scanner := bufio.NewScanner(file) + lineNumber := 0 + for scanner.Scan() { + lineNumber++ + //Do not read the same line again + if lineNumber < reachedLineNumber { + continue + } + if strings.Contains(scanner.Text(), searchString) { + _ = file.Close() + reachedLineNumber = lineNumber + return true, nil + } + } + _ = file.Close() + if err := scanner.Err(); err != nil { + return false, err + } + + time.Sleep(time.Millisecond * 500) + } +}