Fix Windows scheduled startup failures
This commit is contained in:
@@ -6,6 +6,9 @@ cd /d "%~dp0"
|
|||||||
set "LOG_DIR=%~dp0logs"
|
set "LOG_DIR=%~dp0logs"
|
||||||
set "LOG_FILE=%LOG_DIR%\autostart.log"
|
set "LOG_FILE=%LOG_DIR%\autostart.log"
|
||||||
set "DOCUTRANSLATE_NONINTERACTIVE=1"
|
set "DOCUTRANSLATE_NONINTERACTIVE=1"
|
||||||
|
set "DOCUTRANSLATE_STRICT_PORT=1"
|
||||||
|
set "PYTHONUTF8=1"
|
||||||
|
set "PYTHONIOENCODING=utf-8"
|
||||||
|
|
||||||
if not exist "%LOG_DIR%" mkdir "%LOG_DIR%"
|
if not exist "%LOG_DIR%" mkdir "%LOG_DIR%"
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import os
|
|||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import socket
|
import socket
|
||||||
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
@@ -2654,6 +2655,11 @@ def run_app(host=None, port: int | None = None, enable_CORS=False,
|
|||||||
try:
|
try:
|
||||||
port_to_use = find_free_port(initial_port)
|
port_to_use = find_free_port(initial_port)
|
||||||
if port_to_use != initial_port:
|
if port_to_use != initial_port:
|
||||||
|
strict_port = os.environ.get(
|
||||||
|
"DOCUTRANSLATE_STRICT_PORT", ""
|
||||||
|
).strip().lower() in {"1", "true", "yes", "on"}
|
||||||
|
if strict_port:
|
||||||
|
raise RuntimeError(f"端口 {initial_port} 已被占用")
|
||||||
print(f"端口 {initial_port} 被占用,将使用端口 {port_to_use} 代替")
|
print(f"端口 {initial_port} 被占用,将使用端口 {port_to_use} 代替")
|
||||||
print(f"正在启动 DocuTranslate WebUI 版本号:{__version__}")
|
print(f"正在启动 DocuTranslate WebUI 版本号:{__version__}")
|
||||||
app.state.port_to_use = port_to_use
|
app.state.port_to_use = port_to_use
|
||||||
@@ -2668,7 +2674,8 @@ def run_app(host=None, port: int | None = None, enable_CORS=False,
|
|||||||
)
|
)
|
||||||
uvicorn.run(app, host=host, port=port_to_use, workers=1)
|
uvicorn.run(app, host=host, port=port_to_use, workers=1)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"启动失败: {e}")
|
print(f"启动失败: {e}", file=sys.stderr)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
+13
-1
@@ -4,7 +4,19 @@ import argparse
|
|||||||
import sys # 用于检查命令行参数数量
|
import sys # 用于检查命令行参数数量
|
||||||
|
|
||||||
|
|
||||||
|
def _configure_stdio() -> None:
|
||||||
|
"""Keep redirected Windows service logs Unicode-safe."""
|
||||||
|
for stream in (sys.stdout, sys.stderr):
|
||||||
|
reconfigure = getattr(stream, "reconfigure", None)
|
||||||
|
if callable(reconfigure):
|
||||||
|
try:
|
||||||
|
reconfigure(encoding="utf-8", errors="backslashreplace")
|
||||||
|
except (OSError, ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
_configure_stdio()
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="DocuTranslate: 一个文档翻译工具。",
|
description="DocuTranslate: 一个文档翻译工具。",
|
||||||
# 更新示例,展示如何使用 host 参数
|
# 更新示例,展示如何使用 host 参数
|
||||||
@@ -88,4 +100,4 @@ def main():
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ setlocal
|
|||||||
REM 切换到脚本所在目录
|
REM 切换到脚本所在目录
|
||||||
cd /d "%~dp0"
|
cd /d "%~dp0"
|
||||||
|
|
||||||
|
REM 后台计划任务默认可能使用 cp1252,强制使用 UTF-8 输出日志
|
||||||
|
chcp 65001 >nul
|
||||||
|
set "PYTHONUTF8=1"
|
||||||
|
set "PYTHONIOENCODING=utf-8"
|
||||||
|
|
||||||
REM 检查 .venv 是否存在
|
REM 检查 .venv 是否存在
|
||||||
if not exist ".venv\Scripts\activate.bat" (
|
if not exist ".venv\Scripts\activate.bat" (
|
||||||
echo [ERROR] 未找到 .venv\Scripts\activate.bat
|
echo [ERROR] 未找到 .venv\Scripts\activate.bat
|
||||||
@@ -16,9 +21,9 @@ call ".venv\Scripts\activate.bat"
|
|||||||
|
|
||||||
REM 如果传了参数,就用参数;否则默认用当前目录
|
REM 如果传了参数,就用参数;否则默认用当前目录
|
||||||
if "%~1"=="" (
|
if "%~1"=="" (
|
||||||
python docutranslate/cli.py -i --host 0.0.0.0
|
".venv\Scripts\python.exe" -u docutranslate/cli.py -i --host 0.0.0.0
|
||||||
) else (
|
) else (
|
||||||
python docutranslate/cli.py -i --host 0.0.0.0 %*
|
".venv\Scripts\python.exe" -u docutranslate/cli.py -i --host 0.0.0.0 %*
|
||||||
)
|
)
|
||||||
|
|
||||||
set EXIT_CODE=%ERRORLEVEL%
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ try {
|
|||||||
-StartWhenAvailable `
|
-StartWhenAvailable `
|
||||||
-MultipleInstances IgnoreNew `
|
-MultipleInstances IgnoreNew `
|
||||||
-ExecutionTimeLimit ([TimeSpan]::Zero) `
|
-ExecutionTimeLimit ([TimeSpan]::Zero) `
|
||||||
|
-AllowStartIfOnBatteries `
|
||||||
-DontStopIfGoingOnBatteries `
|
-DontStopIfGoingOnBatteries `
|
||||||
-RestartCount 3 `
|
-RestartCount 3 `
|
||||||
-RestartInterval (New-TimeSpan -Minutes 1)
|
-RestartInterval (New-TimeSpan -Minutes 1)
|
||||||
@@ -36,6 +37,7 @@ try {
|
|||||||
-StartWhenAvailable `
|
-StartWhenAvailable `
|
||||||
-MultipleInstances IgnoreNew `
|
-MultipleInstances IgnoreNew `
|
||||||
-ExecutionTimeLimit ([TimeSpan]::Zero) `
|
-ExecutionTimeLimit ([TimeSpan]::Zero) `
|
||||||
|
-AllowStartIfOnBatteries `
|
||||||
-DontStopIfGoingOnBatteries
|
-DontStopIfGoingOnBatteries
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,6 +59,7 @@ $WatchdogSettings = New-ScheduledTaskSettingsSet `
|
|||||||
-StartWhenAvailable `
|
-StartWhenAvailable `
|
||||||
-MultipleInstances IgnoreNew `
|
-MultipleInstances IgnoreNew `
|
||||||
-ExecutionTimeLimit (New-TimeSpan -Minutes 2) `
|
-ExecutionTimeLimit (New-TimeSpan -Minutes 2) `
|
||||||
|
-AllowStartIfOnBatteries `
|
||||||
-DontStopIfGoingOnBatteries
|
-DontStopIfGoingOnBatteries
|
||||||
|
|
||||||
Register-ScheduledTask `
|
Register-ScheduledTask `
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from docutranslate import app
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceStartupTest(unittest.TestCase):
|
||||||
|
@patch.dict(os.environ, {"DOCUTRANSLATE_STRICT_PORT": "1"}, clear=False)
|
||||||
|
@patch.object(app, "find_free_port", return_value=8011)
|
||||||
|
def test_strict_port_rejects_occupied_configured_port(self, _find_free_port):
|
||||||
|
with self.assertRaisesRegex(RuntimeError, "端口 8010 已被占用"):
|
||||||
|
app.run_app(host="127.0.0.1", port=8010)
|
||||||
|
|
||||||
|
@patch.dict(os.environ, {"DOCUTRANSLATE_STRICT_PORT": ""}, clear=False)
|
||||||
|
@patch.object(app.uvicorn, "run")
|
||||||
|
@patch.object(app, "find_free_port", return_value=8011)
|
||||||
|
def test_interactive_mode_can_still_fall_back_to_free_port(
|
||||||
|
self, _find_free_port, uvicorn_run
|
||||||
|
):
|
||||||
|
app.run_app(host="127.0.0.1", port=8010)
|
||||||
|
uvicorn_run.assert_called_once_with(
|
||||||
|
app.app, host="127.0.0.1", port=8011, workers=1
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user