Fix Windows scheduled startup failures
This commit is contained in:
@@ -6,6 +6,9 @@ cd /d "%~dp0"
|
||||
set "LOG_DIR=%~dp0logs"
|
||||
set "LOG_FILE=%LOG_DIR%\autostart.log"
|
||||
set "DOCUTRANSLATE_NONINTERACTIVE=1"
|
||||
set "DOCUTRANSLATE_STRICT_PORT=1"
|
||||
set "PYTHONUTF8=1"
|
||||
set "PYTHONIOENCODING=utf-8"
|
||||
|
||||
if not exist "%LOG_DIR%" mkdir "%LOG_DIR%"
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import os
|
||||
import re
|
||||
import shutil
|
||||
import socket
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import uuid
|
||||
@@ -2654,6 +2655,11 @@ def run_app(host=None, port: int | None = None, enable_CORS=False,
|
||||
try:
|
||||
port_to_use = find_free_port(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"正在启动 DocuTranslate WebUI 版本号:{__version__}")
|
||||
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)
|
||||
except Exception as e:
|
||||
print(f"启动失败: {e}")
|
||||
print(f"启动失败: {e}", file=sys.stderr)
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -4,7 +4,19 @@ import argparse
|
||||
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():
|
||||
_configure_stdio()
|
||||
parser = argparse.ArgumentParser(
|
||||
description="DocuTranslate: 一个文档翻译工具。",
|
||||
# 更新示例,展示如何使用 host 参数
|
||||
|
||||
@@ -4,6 +4,11 @@ setlocal
|
||||
REM 切换到脚本所在目录
|
||||
cd /d "%~dp0"
|
||||
|
||||
REM 后台计划任务默认可能使用 cp1252,强制使用 UTF-8 输出日志
|
||||
chcp 65001 >nul
|
||||
set "PYTHONUTF8=1"
|
||||
set "PYTHONIOENCODING=utf-8"
|
||||
|
||||
REM 检查 .venv 是否存在
|
||||
if not exist ".venv\Scripts\activate.bat" (
|
||||
echo [ERROR] 未找到 .venv\Scripts\activate.bat
|
||||
@@ -16,9 +21,9 @@ call ".venv\Scripts\activate.bat"
|
||||
|
||||
REM 如果传了参数,就用参数;否则默认用当前目录
|
||||
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 (
|
||||
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%
|
||||
|
||||
@@ -28,6 +28,7 @@ try {
|
||||
-StartWhenAvailable `
|
||||
-MultipleInstances IgnoreNew `
|
||||
-ExecutionTimeLimit ([TimeSpan]::Zero) `
|
||||
-AllowStartIfOnBatteries `
|
||||
-DontStopIfGoingOnBatteries `
|
||||
-RestartCount 3 `
|
||||
-RestartInterval (New-TimeSpan -Minutes 1)
|
||||
@@ -36,6 +37,7 @@ try {
|
||||
-StartWhenAvailable `
|
||||
-MultipleInstances IgnoreNew `
|
||||
-ExecutionTimeLimit ([TimeSpan]::Zero) `
|
||||
-AllowStartIfOnBatteries `
|
||||
-DontStopIfGoingOnBatteries
|
||||
}
|
||||
|
||||
@@ -57,6 +59,7 @@ $WatchdogSettings = New-ScheduledTaskSettingsSet `
|
||||
-StartWhenAvailable `
|
||||
-MultipleInstances IgnoreNew `
|
||||
-ExecutionTimeLimit (New-TimeSpan -Minutes 2) `
|
||||
-AllowStartIfOnBatteries `
|
||||
-DontStopIfGoingOnBatteries
|
||||
|
||||
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