mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-11-03 22:04:57 +08:00
fix: bound downloader connection retries for v3.0.8
This commit is contained in:
7
.github/workflows/build-v3.yml
vendored
7
.github/workflows/build-v3.yml
vendored
@@ -67,8 +67,11 @@ jobs:
|
||||
run: |
|
||||
plugins_revision=$(git ls-remote https://github.com/jxxghp/MoviePilot-Plugins.git refs/heads/main | awk '{print $1}')
|
||||
resources_revision=$(git ls-remote https://github.com/jxxghp/MoviePilot-Resources.git refs/heads/main | awk '{print $1}')
|
||||
frontend_digest=$(gh api "repos/jxxghp/MoviePilot-Frontend/releases/tags/${FRONTEND_VERSION}" \
|
||||
--jq '.assets[] | select(.name == "dist.zip") | .digest')
|
||||
frontend_release_id=$(gh api "repos/jxxghp/MoviePilot-Frontend/releases/tags/${FRONTEND_VERSION}" \
|
||||
--jq '.id')
|
||||
[[ "$frontend_release_id" =~ ^[0-9]+$ ]]
|
||||
frontend_digest=$(gh api "repos/jxxghp/MoviePilot-Frontend/releases/${frontend_release_id}/assets" \
|
||||
--jq '.[] | select(.name == "dist.zip") | .digest')
|
||||
|
||||
[[ "$plugins_revision" =~ ^[0-9a-f]{40}$ ]]
|
||||
[[ "$resources_revision" =~ ^[0-9a-f]{40}$ ]]
|
||||
|
||||
@@ -17,6 +17,8 @@ from app.foundation import url as url_tools
|
||||
|
||||
_TORRENT_TAG_CLEANUP_RETRY_TIMES = 10
|
||||
_TORRENT_TAG_CLEANUP_RETRY_INTERVAL = 1
|
||||
_DOWNLOADER_CONNECT_TIMEOUT_SECONDS = 3
|
||||
_DOWNLOADER_READ_TIMEOUT_SECONDS = 60
|
||||
|
||||
|
||||
class Qbittorrent:
|
||||
@@ -192,7 +194,7 @@ class Qbittorrent:
|
||||
|
||||
def __login_qbittorrent(self) -> Optional[Client]:
|
||||
"""
|
||||
连接qbittorrent
|
||||
连接 qbittorrent;缩短建连等待,关闭 HTTP 重试,并采用地址中显式指定的协议。
|
||||
:return: qbittorrent对象
|
||||
"""
|
||||
if not self._host or not self._port:
|
||||
@@ -207,7 +209,14 @@ class Qbittorrent:
|
||||
EXTRA_HEADERS={"Authorization": f"Bearer {self._apikey}"}
|
||||
if self.__use_api_key_auth() else None,
|
||||
VERIFY_WEBUI_CERTIFICATE=False,
|
||||
REQUESTS_ARGS={'timeout': (15, 60)})
|
||||
REQUESTS_ARGS={
|
||||
"timeout": (
|
||||
_DOWNLOADER_CONNECT_TIMEOUT_SECONDS,
|
||||
_DOWNLOADER_READ_TIMEOUT_SECONDS,
|
||||
)
|
||||
},
|
||||
HTTPADAPTER_ARGS={"max_retries": 0},
|
||||
FORCE_SCHEME_FROM_HOST=True)
|
||||
try:
|
||||
if self.__use_api_key_auth():
|
||||
qbt.app_version()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Optional, Union, Tuple, List
|
||||
from typing import Any, List, Optional, Tuple, Union, cast
|
||||
|
||||
import transmission_rpc
|
||||
from transmission_rpc import Client, Torrent, File
|
||||
@@ -65,7 +65,7 @@ class Transmission:
|
||||
|
||||
def __login_transmission(self) -> Optional[Client]:
|
||||
"""
|
||||
连接transmission
|
||||
连接 Transmission;缩短 TCP 建连等待,同时保留原有 HTTP 响应读取时限。
|
||||
:return: transmission对象
|
||||
"""
|
||||
if not self._host or not self._port:
|
||||
@@ -78,7 +78,8 @@ class Transmission:
|
||||
port=self._port,
|
||||
username=self._username,
|
||||
password=self._password,
|
||||
timeout=60)
|
||||
# transmission-rpc supports Requests timeout tuples at runtime.
|
||||
timeout=cast(Any, (3, 60)))
|
||||
self.__sync_incomplete_file_suffix(trt, enabled=bool(self._rename_partial_files))
|
||||
return trt
|
||||
except Exception as err:
|
||||
|
||||
@@ -761,7 +761,7 @@ flowchart LR
|
||||
| 指标 | 当前值 |
|
||||
|---|---:|
|
||||
| Python 模块 | 1044 |
|
||||
| 内部导入边 | 8,881 |
|
||||
| 内部导入边 | 8,882 |
|
||||
| 非平凡 SCC | 1(精确 containment 的 TMDB 移植包环) |
|
||||
| Application / Chain 具体 Adapter 直连 | 0 / 0 |
|
||||
| Direct egress | 53(债务已清零,53 条精确 containment) |
|
||||
|
||||
@@ -94,7 +94,7 @@ ARCH-201 至 ARCH-204 均达到实现、验证、提交、推送和远端门禁
|
||||
|
||||
| 指标 | 当前值 | 解释 |
|
||||
|---|---:|---|
|
||||
| 宿主 Python 模块 / 内部依赖边 | 1045 / 8,881 | `dependency-baseline.json` 当前快照;系统设置合同按 `app.application.settings` 包组织,系统设置 API 端点独立按 `app.api.endpoints.settings` 归档,分类、下载资源归类、订阅搜索、整理恢复、Agent 计划、工具视觉、终端生命周期与终端作用域模块的受控依赖 |
|
||||
| 宿主 Python 模块 / 内部依赖边 | 1045 / 8,882 | `dependency-baseline.json` 当前快照;系统设置合同按 `app.application.settings` 包组织,系统设置 API 端点独立按 `app.api.endpoints.settings` 归档,分类、下载资源归类、订阅搜索、整理恢复、Agent 计划、工具视觉、终端生命周期与终端作用域模块的受控依赖 |
|
||||
| 非平凡 SCC | 1 | 仅保留精确 containment 的 29 模块 TMDB 移植包环 |
|
||||
| 跨层 DB 边界债务 | 0 | Application、Chain、API、Agent、Runtime、Workflow 到 DB 的受控债务均为零 |
|
||||
| Model/Oper 事务债务 | 0 | 自建 Session、自动事务装饰器、直接 commit/rollback 等基线均为零 |
|
||||
|
||||
@@ -1084,8 +1084,8 @@
|
||||
"runtime_only": true
|
||||
}
|
||||
},
|
||||
"edge_count": 8881,
|
||||
"edge_sha256": "700858f0eb08a886c206c9be9685964b01f0e3412bed7b1c1fa84fa343231a14",
|
||||
"edge_count": 8882,
|
||||
"edge_sha256": "fd866881390126c9f4a68c671c9641e27223e23a1b6c242c15042cb3c7d1a087",
|
||||
"edges": [
|
||||
"app -> app.foundation",
|
||||
"app -> app.foundation.environment",
|
||||
@@ -3040,6 +3040,7 @@
|
||||
"app.api.endpoints.system -> app.application.configuration",
|
||||
"app.api.endpoints.system -> app.application.database",
|
||||
"app.api.endpoints.system -> app.application.image",
|
||||
"app.api.endpoints.system -> app.application.mediaserver",
|
||||
"app.api.endpoints.system -> app.application.messaging",
|
||||
"app.api.endpoints.system -> app.application.messaging.message",
|
||||
"app.api.endpoints.system -> app.application.network",
|
||||
|
||||
@@ -319,13 +319,18 @@ def test_login_uses_api_key_header_without_auth_login():
|
||||
fake_client = MagicMock()
|
||||
fake_client.app_version.return_value = "v5.2.0"
|
||||
|
||||
with patch.object(qbittorrent_module.qbittorrentapi, "Client", return_value=fake_client) as client_cls:
|
||||
with patch.object(
|
||||
qbittorrent_module.qbittorrentapi, "Client", return_value=fake_client
|
||||
) as client_cls:
|
||||
downloader = Qbittorrent(host="http://127.0.0.1", port=8080, apikey="secret-token")
|
||||
|
||||
assert downloader.qbc is fake_client
|
||||
fake_client.auth_log_in.assert_not_called()
|
||||
fake_client.app_version.assert_called_once_with()
|
||||
assert client_cls.call_args.kwargs["EXTRA_HEADERS"] == {"Authorization": "Bearer secret-token"}
|
||||
assert client_cls.call_args.kwargs["REQUESTS_ARGS"] == {"timeout": (3, 60)}
|
||||
assert client_cls.call_args.kwargs["HTTPADAPTER_ARGS"] == {"max_retries": 0}
|
||||
assert client_cls.call_args.kwargs["FORCE_SCHEME_FROM_HOST"] is True
|
||||
|
||||
|
||||
def test_login_enables_incomplete_file_suffix_by_default():
|
||||
|
||||
@@ -59,7 +59,8 @@ _STUB_MODULES = dict([
|
||||
_stub("app.api.dependencies.auth", get_current_active_superuser=_Dummy,
|
||||
get_current_active_superuser_async=_Dummy, get_current_active_user_async=_Dummy),
|
||||
_stub("app.agent.llm", LLMHelper=_Dummy, LLMTestError=_DummyError, LLMTestTimeout=_DummyError),
|
||||
_stub("app.application.mediaserver", MediaServerHelper=_Dummy),
|
||||
_stub("app.application.mediaserver", MediaServerHelper=_Dummy,
|
||||
get_mediaserver_configs=lambda: []),
|
||||
_stub("app.application.messaging.message", MessageHelper=_Dummy),
|
||||
_stub("app.runtime.progress", ProgressHelper=_Dummy, AsyncProgressHelper=_Dummy),
|
||||
_stub("app.application.rules", RuleHelper=_Dummy),
|
||||
|
||||
@@ -100,11 +100,16 @@ def test_login_enables_incomplete_file_suffix_by_default():
|
||||
fake_client = MagicMock()
|
||||
fake_client.get_session.return_value = {"rename-partial-files": False}
|
||||
|
||||
with patch.object(transmission_module.transmission_rpc, "Client", return_value=fake_client):
|
||||
with patch.object(
|
||||
transmission_module.transmission_rpc,
|
||||
"Client",
|
||||
return_value=fake_client,
|
||||
) as client_cls:
|
||||
downloader = Transmission(host="127.0.0.1", port=9091)
|
||||
|
||||
assert downloader.trc is fake_client
|
||||
fake_client.set_session.assert_called_once_with(rename_partial_files=True)
|
||||
assert client_cls.call_args.kwargs["timeout"] == (3, 60)
|
||||
|
||||
|
||||
def test_login_disables_incomplete_file_suffix_when_configured():
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
APP_VERSION = 'v3.0.7'
|
||||
FRONTEND_VERSION = 'v3.0.7'
|
||||
APP_VERSION = 'v3.0.8'
|
||||
FRONTEND_VERSION = 'v3.0.8'
|
||||
|
||||
Reference in New Issue
Block a user