This commit is contained in:
Raykov-MS 2026-07-06 22:42:25 +03:00
parent 27a11ae3ee
commit 400a218fc7
3 changed files with 120 additions and 0 deletions

View File

@ -196,6 +196,42 @@ def check_network_dir_access(
return _check_samba_dir_access(str(path), samba_conn, "Сетевая папка")
def check_dir_write_access(
target_dir: str, samba_conn, source_name: str
) -> tuple[bool, str | None]:
probe_prefix = f".sfm_write_probe_{int(datetime.now().timestamp() * 1000)}"
probe_src = join_path(target_dir, f"{probe_prefix}.tmp")
probe_dst = join_path(target_dir, f"{probe_prefix}_renamed.tmp")
try:
with samba_conn:
if not samba_conn.path_exists(target_dir):
return False, f"{source_name} не найдена: {target_dir}"
_write_probe_file(probe_src, samba_conn)
if hasattr(samba_conn, "copy") and hasattr(samba_conn, "delete"):
samba_conn.copy(path_from=probe_src, path_to=probe_dst, replace=False)
samba_conn.delete(probe_src)
samba_conn.delete(probe_dst)
elif hasattr(samba_conn, "delete"):
samba_conn.delete(probe_src)
return True, None
except Exception as exc: # noqa: BLE001
# Best-effort cleanup for noisy probes.
with samba_conn:
for probe_path in (probe_src, probe_dst):
try:
if samba_conn.path_exists(probe_path) and hasattr(
samba_conn, "delete"
):
samba_conn.delete(probe_path)
except Exception: # noqa: BLE001
pass
return (
False,
f"Недостаточно прав на запись/переименование в папке: {target_dir}. {exc}",
)
def create_run_context(
input_dir: str, samba_conn, now: datetime | None = None
) -> RunContext:
@ -369,6 +405,27 @@ def _samba_move_file(path_from: str, path_to: str, samba_conn) -> None:
samba_conn.delete(path_from)
def _write_probe_file(remote_path: str, samba_conn) -> None:
if hasattr(samba_conn, "open"):
with samba_conn.open(remote_path, mode="wb") as remote_file:
remote_file.write(b"sfm-probe")
return
if hasattr(samba_conn, "upload"):
with tempfile.TemporaryDirectory() as temp_dir:
local_probe = Path(temp_dir) / "probe.tmp"
local_probe.write_bytes(b"sfm-probe")
try:
samba_conn.upload(
remote_file_path=remote_path,
local_file_path=str(local_probe),
replace=False,
)
except TypeError:
samba_conn.upload(path_from=str(local_probe), path_to=remote_path)
return
raise RuntimeError("Коннектор SAMBA не поддерживает проверку записи (open/upload).")
def _extract_server(smb_base_path: str) -> str:
server = urlparse(smb_base_path).netloc
if not server:

View File

@ -12,6 +12,7 @@ from .config import (
join_path,
)
from .file_manager import (
check_dir_write_access,
check_input_dir_access,
connect_samba,
create_run_context,
@ -80,6 +81,29 @@ def run_batch(
)
run_context = create_run_context(input_dir=input_dir, samba_conn=samba_conn)
checks = [
(run_context.run_dir, "Папка Out (текущий запуск)"),
(run_context.processed_dir, "Папка Processed"),
(run_context.error_dir, "Папка Err"),
]
for target_dir, label in checks:
is_ok, write_error = check_dir_write_access(
target_dir=target_dir,
samba_conn=samba_conn,
source_name=label,
)
if not is_ok:
return BatchResult(
status="error",
processed=0,
partial=0,
errors=1,
report_path="",
protocol_path="",
log_path="",
messages=[write_error or f"Нет прав на запись в: {target_dir}"],
)
protocol_rows: list[ProtocolRow] = []
log_lines: list[str] = []

View File

@ -4,6 +4,7 @@ import shutil
from pathlib import Path
from app.pipeline.file_manager import (
check_dir_write_access,
create_run_context,
move_file_to_error,
next_run_number,
@ -57,6 +58,11 @@ class FakeSamba:
if target.exists():
target.unlink()
def open(self, path: str, mode: str = "rb"):
target = self._to_local(path)
target.parent.mkdir(parents=True, exist_ok=True)
return target.open(mode)
def test_scan_input_files_splits_valid_and_invalid(tmp_path: Path) -> None:
samba = FakeSamba(tmp_path)
@ -112,3 +118,36 @@ def test_move_file_to_error_adds_suffix_on_collision(tmp_path: Path) -> None:
)
assert target.endswith("file_1.xml")
assert (error_dir / "file_1.xml").exists()
def test_check_dir_write_access_success(tmp_path: Path) -> None:
samba = FakeSamba(tmp_path)
target_dir = tmp_path / "Exchange" / "SMBDEMO" / "Out"
target_dir.mkdir(parents=True)
ok, error = check_dir_write_access(
target_dir="Exchange\\SMBDEMO\\Out",
samba_conn=samba,
source_name="Out",
)
assert ok is True
assert error is None
def test_check_dir_write_access_fails_without_open_or_upload(tmp_path: Path) -> None:
class NoWriteSamba(FakeSamba):
open = None # type: ignore[assignment]
samba = NoWriteSamba(tmp_path)
target_dir = tmp_path / "Exchange" / "SMBDEMO" / "Out"
target_dir.mkdir(parents=True)
ok, error = check_dir_write_access(
target_dir="Exchange\\SMBDEMO\\Out",
samba_conn=samba,
source_name="Out",
)
assert ok is False
assert error is not None