This commit is contained in:
Raykov-MS 2026-06-22 08:03:22 +03:00
parent 9cbfbd041f
commit ec5ab34686
5 changed files with 50 additions and 29 deletions

View File

@ -20,17 +20,18 @@ def validate_file_name(file_name: str) -> bool:
return any(pattern.match(file_name) for pattern in FILE_NAME_PATTERNS) return any(pattern.match(file_name) for pattern in FILE_NAME_PATTERNS)
def build_date_parts(now: datetime | None = None) -> tuple[str, str, str]: def build_date_parts(now: datetime | None = None) -> tuple[str, str, str, str]:
current = now or datetime.now() current = now or datetime.now()
year = current.strftime("%Y") year = str(current.year)
month_year = current.strftime("%m_%Y") month = str(current.month)
day = str(current.day)
date_key = current.strftime("%Y%m%d") date_key = current.strftime("%Y%m%d")
return year, month_year, date_key return year, month, day, date_key
def build_out_dir(base_dir: str, now: datetime | None = None) -> str: def build_out_dir(base_dir: str, now: datetime | None = None) -> str:
year, month_year, date_key = build_date_parts(now) year, month, day, _ = build_date_parts(now)
return join_path(base_dir, DEFAULT_OUTPUT_DIR_NAME, year, month_year, date_key) return join_path(base_dir, DEFAULT_OUTPUT_DIR_NAME, year, month, day)
def build_run_dir(base_dir: str, run_number: int, now: datetime | None = None) -> str: def build_run_dir(base_dir: str, run_number: int, now: datetime | None = None) -> str:
@ -59,8 +60,22 @@ def build_process_log_file_name(date_key: str, run_number: int) -> str:
def join_path(*parts: str) -> str: def join_path(*parts: str) -> str:
cleaned = [part.strip("/\\") for part in parts if part.strip("/\\")] raw_parts = [part for part in parts if part and part.strip("/\\")]
return "\\".join(cleaned) if not raw_parts:
return ""
first = raw_parts[0]
is_unc = (
first.startswith("//")
or first.startswith("\\\\")
or first.startswith("/")
or first.startswith("\\")
)
cleaned = [part.replace("/", "\\").strip("\\") for part in raw_parts]
joined = "\\".join(cleaned)
if is_unc:
return "\\\\" + joined.lstrip("\\")
return joined
def get_parent_dir(path: str) -> str: def get_parent_dir(path: str) -> str:

View File

@ -67,7 +67,9 @@ class SMBClientConnection:
def listdir(self, path: str) -> list[str]: def listdir(self, path: str) -> list[str]:
smbclient = _import_smbclient() smbclient = _import_smbclient()
full_path = self._resolve_path(path) full_path = self._resolve_path(path)
with smbclient.scandir(full_path) as entries: # In some smbclient/smbprotocol versions scandir() returns a generator,
# not a context manager, so we iterate directly.
entries = smbclient.scandir(full_path)
return [entry.name for entry in entries] return [entry.name for entry in entries]
def is_file(self, path: str) -> bool: def is_file(self, path: str) -> bool:
@ -168,6 +170,8 @@ class SMBClientConnection:
def _resolve_path(self, path: str) -> str: def _resolve_path(self, path: str) -> str:
if path.startswith("//") or path.startswith("\\\\"): if path.startswith("//") or path.startswith("\\\\"):
return _normalize_unc_path(path) return _normalize_unc_path(path)
if _looks_like_unc_without_prefix(path, self.smb_base_path):
return _normalize_unc_path("//" + path.lstrip("/\\"))
return _normalize_unc_path(join_path(self.smb_base_path, path)) return _normalize_unc_path(join_path(self.smb_base_path, path))
@ -207,7 +211,7 @@ def create_run_context(
run_number = next_run_number(out_day_dir, samba_conn) run_number = next_run_number(out_day_dir, samba_conn)
run_dir = join_path(out_day_dir, str(run_number)) run_dir = join_path(out_day_dir, str(run_number))
ensure_dir_exists(run_dir, samba_conn) ensure_dir_exists(run_dir, samba_conn)
_, _, date_key = build_date_parts(now) _, _, _, date_key = build_date_parts(now)
return RunContext( return RunContext(
run_dir=run_dir, run_dir=run_dir,
out_day_dir=out_day_dir, out_day_dir=out_day_dir,
@ -384,3 +388,11 @@ def _import_smbclient():
except ImportError as exc: # noqa: BLE001 except ImportError as exc: # noqa: BLE001
raise RuntimeError("Библиотека smbprotocol/smbclient не установлена.") from exc raise RuntimeError("Библиотека smbprotocol/smbclient не установлена.") from exc
return smbclient return smbclient
def _looks_like_unc_without_prefix(path: str, smb_base_path: str) -> bool:
normalized_path = path.replace("/", "\\").lstrip("\\")
if not normalized_path:
return False
server = _extract_server(smb_base_path).lower()
return normalized_path.lower().startswith(f"{server}\\")

View File

@ -25,6 +25,8 @@ from .parser import FileParseResult, parse_xml_content
from .protocol import ProtocolRow, write_protocol from .protocol import ProtocolRow, write_protocol
from .report import ReportRow, write_report from .report import ReportRow, write_report
EMPTY_BATCH_STATUS = "info"
@dataclass @dataclass
class BatchResult: class BatchResult:
@ -84,7 +86,7 @@ def run_batch(
scan_result = scan_input_files(input_dir=input_dir, samba_conn=samba_conn) scan_result = scan_input_files(input_dir=input_dir, samba_conn=samba_conn)
log_lines.append( log_lines.append(
f"{_now()} scan files: valid={len(scan_result.valid_files)} invalid={len(scan_result.invalid_files)}" f"{_now()} | scan files: valid={len(scan_result.valid_files)} invalid={len(scan_result.invalid_files)}"
) )
for remote_file in scan_result.invalid_files: for remote_file in scan_result.invalid_files:
@ -106,7 +108,7 @@ def run_batch(
processed_at=datetime.now(), processed_at=datetime.now(),
) )
) )
log_lines.append(f"{_now()} invalid -> err: {remote_file.path} => {moved_to}") log_lines.append(f"{_now()} | invalid -> err: {remote_file.path} => {moved_to}")
for remote_file in scan_result.valid_files: for remote_file in scan_result.valid_files:
parse_result = _parse_remote_xml(remote_file.name, remote_file.path, samba_conn) parse_result = _parse_remote_xml(remote_file.name, remote_file.path, samba_conn)
@ -131,7 +133,7 @@ def run_batch(
samba_conn=samba_conn, samba_conn=samba_conn,
) )
log_lines.append( log_lines.append(
f"{_now()} success -> processed: {remote_file.path} => {moved_to}" f"{_now()} | success -> processed: {remote_file.path} => {moved_to}"
) )
else: else:
moved_to = move_file_to_error( moved_to = move_file_to_error(
@ -141,14 +143,14 @@ def run_batch(
samba_conn=samba_conn, samba_conn=samba_conn,
) )
log_lines.append( log_lines.append(
f"{_now()} {status} -> err: {remote_file.path} => {moved_to}" f"{_now()} | {status} -> err: {remote_file.path} => {moved_to}"
) )
if not protocol_rows: if not protocol_rows:
protocol_rows.append( protocol_rows.append(
ProtocolRow( ProtocolRow(
file_name="-", file_name="-",
status="success", status=EMPTY_BATCH_STATUS,
message="Новых файлов для обработки нет.", message="Новых файлов для обработки нет.",
operations_total=0, operations_total=0,
rows_written=0, rows_written=0,
@ -156,7 +158,7 @@ def run_batch(
processed_at=datetime.now(), processed_at=datetime.now(),
) )
) )
log_lines.append(f"{_now()} empty batch") log_lines.append(f"{_now()} | empty batch")
report_file_name = build_report_file_name( report_file_name = build_report_file_name(
run_context.date_key, run_context.run_number run_context.date_key, run_context.run_number

12
main.py
View File

@ -43,16 +43,7 @@ def main() -> None:
help="Пароль учетной записи для доступа к SMB-шаре.", help="Пароль учетной записи для доступа к SMB-шаре.",
) )
if st.button("Изменить путь", use_container_width=True): if st.button("Изменить путь", use_container_width=True):
ok, message, _ = check_access_and_list_files(
smb_base_path=SFM_SMB_BASE_PATH,
input_dir=resolved_input_dir,
samba_user=samba_user,
samba_password=samba_password,
)
if ok:
open_path_editor(current_input_dir) open_path_editor(current_input_dir)
else:
st.error(f"{message} Обратитесь к {ACCESS_SUPPORT_CONTACT}.")
edited_path = render_path_editor() edited_path = render_path_editor()
if edited_path is not None: if edited_path is not None:
@ -78,12 +69,13 @@ def main() -> None:
use_container_width=True, use_container_width=True,
): ):
with st.spinner("Выполняется обработка..."): with st.spinner("Выполняется обработка..."):
launcher_name = samba_user.strip() or user_id
batch_result = run_batch( batch_result = run_batch(
smb_base_path=SFM_SMB_BASE_PATH, smb_base_path=SFM_SMB_BASE_PATH,
input_dir=resolved_input_dir, input_dir=resolved_input_dir,
samba_user=samba_user, samba_user=samba_user,
samba_password=samba_password, samba_password=samba_password,
launcher=user_id, launcher=launcher_name,
) )
if batch_result.status == "success": if batch_result.status == "success":
st.success("Обработка завершена успешно.") st.success("Обработка завершена успешно.")

View File

@ -112,4 +112,4 @@ def test_run_batch_smoke_and_idempotency(tmp_path: Path, monkeypatch) -> None:
launcher="tester", launcher="tester",
) )
assert second.status == "success" assert second.status == "success"
assert second.processed >= 1 assert second.processed == 0