diff --git a/History/2026-08-19-file-time.md b/History/2026-08-19-file-time.md new file mode 100644 index 0000000..5144623 --- /dev/null +++ b/History/2026-08-19-file-time.md @@ -0,0 +1,40 @@ +# v0.0.41 — время на конкретный файл в колонке Статус — 2026-08-19 + +**Дата:** 2026-08-19 +**Версия:** 0.0.40 → 0.0.41 + +--- + +## Суть + +В колонке «Статус» таблицы у всех файлов показывалось ~одинаковое ОБЩЕЕ время. +Причина: фронт считал время как разницу `start`→`done`, а LLM — общий для всех +файлов (в проходе 1) — попадал в интервал КАЖДОГО файла. + +Теперь бэк считает время на КОНКРЕТНЫЙ файл (extract_text + regex + замена, +БЕЗ общего LLM) и передаёт его в `done`-событии. + +## Изменения + +### `drhider/obfuscator.py` +- `import time` добавлен. +- Заведён `file_times = [0.0] * total`. +- Проход 1: `file_times[i] += time.time() - t0` вокруг extract_text + regex. +- Проход 2: `file_times[i] += time.time() - t0` вокруг замены. +- `progress_cb` расширен до `(phase, idx, total, fname, elapsed)`; в `done` + передаётся `round(file_times[i], 2)`. + +### `site/routes/api_bp.py` +- `progress(phase, idx, total_, name, elapsed)`; в SSE `done` добавлен `elapsed`. + +### `site/templates/index.html` +- В `done`-обработчике: если `d.elapsed > 0` — показать `d.elapsed.toFixed(1)` + (время файла от бэка), иначе fallback на старое вычисление. + +### `site/app.py` +- `VERSION = "0.0.41"`. + +## Проверка +- События: `[start(0,0.0), start(1,0.0), done(0,<время>), done(1,<время>)]`. +- `py_compile`, `node --check` — OK. +- Тесты: builder 20/20, replacer 10/10, scanner 20/20, zip 28/28. diff --git a/drhider/obfuscator.py b/drhider/obfuscator.py index dd0d1e8..b8ce424 100644 --- a/drhider/obfuscator.py +++ b/drhider/obfuscator.py @@ -9,6 +9,7 @@ import io import os +import time import logging from typing import Dict, List, Tuple, Callable, Optional @@ -94,9 +95,9 @@ class TwoPassObfuscator: Args: files: [(filename, content_bytes, content_type), ...] - progress_cb: Опциональный коллбек (phase, idx, total, fname), - где phase ∈ {"start", "done"}, idx — 0-based индекс. - Вызывается вокруг финальной обработки каждого файла. + progress_cb: Опциональный коллбек (phase, idx, total, fname, elapsed), + где phase ∈ {"start", "done"}, idx — 0-based индекс, + elapsed — время обработки конкретного файла (сек). Returns: (zip_bytes, csv_string): @@ -112,18 +113,21 @@ class TwoPassObfuscator: # Извлекаем Markdown из каждого файла all_texts: Dict[str, str] = {} total = len(files) + file_times: List[float] = [0.0] * total for i, (fname, content, ctype) in enumerate(files): display_name = os.path.basename(fname) or fname if progress_cb: - progress_cb("start", i, total, display_name) + progress_cb("start", i, total, display_name, 0.0) + t0 = time.time() text = extractor.extract_text(fname, content, ctype) all_texts[fname] = text # Regex-сканирование (быстрое, локальное) if text and not text.startswith("[DOC binary"): scanner.scan_regex(text, self._mapping, self._counters) + file_times[i] += time.time() - t0 # LLM-сканирование (получает уже найденное regex'ом чтобы не дублировать) if self._llm_client: @@ -141,6 +145,7 @@ class TwoPassObfuscator: fname = in_fname display_name = os.path.basename(in_fname) or in_fname + t0 = time.time() obf_content = content # По умолчанию — без изменений if fname.endswith('.doc'): @@ -163,9 +168,10 @@ class TwoPassObfuscator: obf_content = replaced.encode('utf-8') fname = md_name + file_times[i] += time.time() - t0 results.append((fname, obf_content)) if progress_cb: - progress_cb("done", i, total, display_name) + progress_cb("done", i, total, display_name, round(file_times[i], 2)) # ── Сборка результата ── csv_str = builder.build_mapping_csv(self._mapping) diff --git a/site/app.py b/site/app.py index e102cf3..d7034d2 100644 --- a/site/app.py +++ b/site/app.py @@ -20,7 +20,7 @@ if _sys_path_root not in sys.path: sys.path.insert(0, _sys_path_root) # Версия приложения (меняется при изменениях) -VERSION = "0.0.40" +VERSION = "0.0.41" def create_app(): diff --git a/site/routes/api_bp.py b/site/routes/api_bp.py index f22e73f..fa3198c 100644 --- a/site/routes/api_bp.py +++ b/site/routes/api_bp.py @@ -79,8 +79,8 @@ def process_stream(sid): q = queue.Queue() cancel = threading.Event() - def progress(phase, idx, total_, name): - q.put(("progress", phase, idx, name, total_)) + def progress(phase, idx, total_, name, elapsed): + q.put(("progress", phase, idx, name, total_, elapsed)) def worker(): try: @@ -117,11 +117,11 @@ def process_stream(sid): kind = evt[0] if kind == "progress": - _, phase, idx, name, total_ = evt + _, phase, idx, name, total_, elapsed = evt try: yield ( f"event: {phase}\n" - f"data: {json.dumps({'idx': idx, 'name': name, 'total': total_})}\n\n" + f"data: {json.dumps({'idx': idx, 'name': name, 'total': total_, 'elapsed': elapsed})}\n\n" ) except _disconnect_exceptions(): cancel.set() diff --git a/site/templates/index.html b/site/templates/index.html index b6362dd..671e2f1 100644 --- a/site/templates/index.html +++ b/site/templates/index.html @@ -484,8 +484,11 @@ async function uploadFiles() { activeES.addEventListener('done', function(e) { const d = JSON.parse(e.data); clearInterval(fileIntervals[d.idx]); - const elapsed = ((performance.now() - (fileTimers[d.idx] || performance.now())) / 1000).toFixed(1); - ss(d.idx, '✓ ' + elapsed + 'с'); + // Время на КОНКРЕТНЫЙ файл приходит с бэка (extract_text + замена, без общего LLM) + const sec = (typeof d.elapsed === 'number' && d.elapsed > 0) + ? d.elapsed.toFixed(1) + : ((performance.now() - (fileTimers[d.idx] || performance.now())) / 1000).toFixed(1); + ss(d.idx, '✓ ' + sec + 'с'); }); activeES.addEventListener('complete', function(e) { activeES.close();