fix scan
This commit is contained in:
+66
-44
@@ -1,11 +1,12 @@
|
||||
import os
|
||||
import threading
|
||||
from pathlib import Path
|
||||
|
||||
from flask import Flask, Response, jsonify, request, send_from_directory
|
||||
from flask_cors import CORS
|
||||
|
||||
from db_py import count_tests, del_config, get_all_tests, get_config, set_config
|
||||
from scanner import full_scan, scan_results_only
|
||||
from scanner import full_scan, is_scan_in_progress, resolve_runtime_path, scan_results_only
|
||||
from sse_py import broadcast, stream_events
|
||||
from watcher import start_watching
|
||||
|
||||
@@ -43,6 +44,39 @@ def _apply_smb_env_from_config():
|
||||
os.environ[env_key] = str(value)
|
||||
|
||||
|
||||
def _start_full_scan_background(target_dir, results_dir, results_dir_ref, source_label):
|
||||
def _job():
|
||||
try:
|
||||
full_scan(target_dir, results_dir, results_dir_ref)
|
||||
start_watching(target_dir, results_dir)
|
||||
tests = get_all_tests()
|
||||
completed = len([t for t in tests if t.get("completed")])
|
||||
print(f"[{source_label}] Scan complete -> {len(tests)} tests, {completed} completed")
|
||||
except Exception as exc:
|
||||
print(f"[{source_label}] background fullScan error: {exc}")
|
||||
finally:
|
||||
broadcast({"type": "update"})
|
||||
|
||||
worker = threading.Thread(target=_job, daemon=True)
|
||||
worker.start()
|
||||
|
||||
|
||||
def _start_results_scan_background(results_dir, results_dir_ref, source_label):
|
||||
def _job():
|
||||
try:
|
||||
scan_results_only(results_dir, results_dir_ref)
|
||||
tests = get_all_tests()
|
||||
completed = len([t for t in tests if t.get("completed")])
|
||||
print(f"[{source_label}] Results scan complete -> {len(tests)} tests, {completed} completed")
|
||||
except Exception as exc:
|
||||
print(f"[{source_label}] background rescan-results error: {exc}")
|
||||
finally:
|
||||
broadcast({"type": "update"})
|
||||
|
||||
worker = threading.Thread(target=_job, daemon=True)
|
||||
worker.start()
|
||||
|
||||
|
||||
@app.get("/api/tests")
|
||||
def get_tests_route():
|
||||
completed = request.args.get("completed")
|
||||
@@ -197,6 +231,11 @@ def get_stats_route():
|
||||
)
|
||||
|
||||
|
||||
@app.get("/api/scan-status")
|
||||
def get_scan_status_route():
|
||||
return jsonify({"scanning": is_scan_in_progress()})
|
||||
|
||||
|
||||
@app.get("/api/config")
|
||||
def get_config_route():
|
||||
config = {}
|
||||
@@ -227,21 +266,17 @@ def set_config_route():
|
||||
|
||||
if dirs_changed:
|
||||
_apply_smb_env_from_config()
|
||||
target_dir = get_config("target_dir")
|
||||
results_dir = get_config("results_dir")
|
||||
results_dir_ref = get_config("results_dir_ref")
|
||||
try:
|
||||
full_scan(target_dir, results_dir, results_dir_ref)
|
||||
start_watching(target_dir, results_dir)
|
||||
except Exception as exc:
|
||||
print(f"[config] fullScan error: {exc}")
|
||||
return jsonify({"error": f"Scan failed: {exc}"}), 500
|
||||
target_dir = resolve_runtime_path(get_config("target_dir"))
|
||||
results_dir = resolve_runtime_path(get_config("results_dir"))
|
||||
results_dir_ref = resolve_runtime_path(get_config("results_dir_ref"))
|
||||
if not target_dir or not results_dir:
|
||||
return jsonify({"error": "Directories not configured"}), 400
|
||||
|
||||
tests = get_all_tests()
|
||||
completed = len([t for t in tests if t.get("completed")])
|
||||
print(f"[config] Scan complete -> {len(tests)} tests found, {completed} completed")
|
||||
broadcast({"type": "update"})
|
||||
return jsonify({"ok": True, "testCount": len(tests), "completedCount": completed})
|
||||
if is_scan_in_progress():
|
||||
return jsonify({"ok": True, "scanning": True, "testCount": None, "completedCount": None})
|
||||
|
||||
_start_full_scan_background(target_dir, results_dir, results_dir_ref, "config")
|
||||
return jsonify({"ok": True, "scanning": True, "testCount": None, "completedCount": None})
|
||||
|
||||
broadcast({"type": "update"})
|
||||
return jsonify({"ok": True, "testCount": None, "completedCount": None})
|
||||
@@ -250,45 +285,32 @@ def set_config_route():
|
||||
@app.post("/api/config/rescan")
|
||||
def rescan_route():
|
||||
_apply_smb_env_from_config()
|
||||
target_dir = get_config("target_dir")
|
||||
results_dir = get_config("results_dir")
|
||||
results_dir_ref = get_config("results_dir_ref")
|
||||
target_dir = resolve_runtime_path(get_config("target_dir"))
|
||||
results_dir = resolve_runtime_path(get_config("results_dir"))
|
||||
results_dir_ref = resolve_runtime_path(get_config("results_dir_ref"))
|
||||
if not target_dir or not results_dir:
|
||||
return jsonify({"error": "Directories not configured"}), 400
|
||||
|
||||
try:
|
||||
full_scan(target_dir, results_dir, results_dir_ref)
|
||||
start_watching(target_dir, results_dir)
|
||||
except Exception as exc:
|
||||
print(f"[config] rescan error: {exc}")
|
||||
return jsonify({"error": f"Scan failed: {exc}"}), 500
|
||||
if is_scan_in_progress():
|
||||
return jsonify({"ok": True, "scanning": True, "testCount": None, "completedCount": None})
|
||||
|
||||
tests = get_all_tests()
|
||||
completed = len([t for t in tests if t.get("completed")])
|
||||
print(f"[config] Rescan complete -> {len(tests)} tests, {completed} completed")
|
||||
broadcast({"type": "update"})
|
||||
return jsonify({"ok": True, "testCount": len(tests), "completedCount": completed})
|
||||
_start_full_scan_background(target_dir, results_dir, results_dir_ref, "config")
|
||||
return jsonify({"ok": True, "scanning": True, "testCount": None, "completedCount": None})
|
||||
|
||||
|
||||
@app.post("/api/config/rescan-results")
|
||||
def rescan_results_route():
|
||||
_apply_smb_env_from_config()
|
||||
results_dir = get_config("results_dir")
|
||||
results_dir_ref = get_config("results_dir_ref")
|
||||
results_dir = resolve_runtime_path(get_config("results_dir"))
|
||||
results_dir_ref = resolve_runtime_path(get_config("results_dir_ref"))
|
||||
if not results_dir:
|
||||
return jsonify({"error": "Results directory not configured"}), 400
|
||||
|
||||
try:
|
||||
scan_results_only(results_dir, results_dir_ref)
|
||||
except Exception as exc:
|
||||
print(f"[config] rescan-results error: {exc}")
|
||||
return jsonify({"error": f"Scan failed: {exc}"}), 500
|
||||
if is_scan_in_progress():
|
||||
return jsonify({"ok": True, "scanning": True, "testCount": None, "completedCount": None})
|
||||
|
||||
tests = get_all_tests()
|
||||
completed = len([t for t in tests if t.get("completed")])
|
||||
print(f"[config] Results rescan complete -> {len(tests)} tests, {completed} completed")
|
||||
broadcast({"type": "update"})
|
||||
return jsonify({"ok": True, "testCount": len(tests), "completedCount": completed})
|
||||
_start_results_scan_background(results_dir, results_dir_ref, "config")
|
||||
return jsonify({"ok": True, "scanning": True, "testCount": None, "completedCount": None})
|
||||
|
||||
|
||||
@app.get("/api/events")
|
||||
@@ -314,9 +336,9 @@ def static_or_spa(path=""):
|
||||
|
||||
def bootstrap():
|
||||
_apply_smb_env_from_config()
|
||||
target_dir = get_config("target_dir")
|
||||
results_dir = get_config("results_dir")
|
||||
results_dir_ref = get_config("results_dir_ref")
|
||||
target_dir = resolve_runtime_path(get_config("target_dir"))
|
||||
results_dir = resolve_runtime_path(get_config("results_dir"))
|
||||
results_dir_ref = resolve_runtime_path(get_config("results_dir_ref"))
|
||||
|
||||
if target_dir and results_dir:
|
||||
existing = count_tests()
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -166,6 +166,63 @@ def set_p3p_pair(test_row_id, p3p_pair):
|
||||
)
|
||||
|
||||
|
||||
def update_all_p3p_pairs_sql():
|
||||
with _tx():
|
||||
cursor = _conn.execute(
|
||||
"""
|
||||
UPDATE tests AS t
|
||||
SET p3p_pair = (
|
||||
SELECT CASE
|
||||
WHEN COUNT(*) = 0 THEN '[]'
|
||||
ELSE '[' || GROUP_CONCAT('"' || m.device || '_' || m.test_id || '"') || ']'
|
||||
END
|
||||
FROM tests AS m
|
||||
WHERE m.interference = 'P3P'
|
||||
AND UPPER(m.device) = UPPER(t.device)
|
||||
AND UPPER(m.test_id) = CASE
|
||||
WHEN INSTR(UPPER(t.test_id), 'TH') > 0 THEN REPLACE(UPPER(t.test_id), 'TH', 'UT')
|
||||
WHEN INSTR(UPPER(t.test_id), 'UT') > 0 THEN REPLACE(UPPER(t.test_id), 'UT', 'TH')
|
||||
ELSE '__NO_MATCH__'
|
||||
END
|
||||
)
|
||||
WHERE t.interference = 'P3P'
|
||||
"""
|
||||
)
|
||||
return cursor.rowcount
|
||||
|
||||
|
||||
def update_all_p2p_coe_pairs_sql():
|
||||
with _tx():
|
||||
cursor = _conn.execute(
|
||||
"""
|
||||
UPDATE tests AS t
|
||||
SET coe_pair = (
|
||||
SELECT CASE
|
||||
WHEN COUNT(*) = 0 THEN '[]'
|
||||
ELSE '[' || GROUP_CONCAT('"' || pair_value || '"') || ']'
|
||||
END
|
||||
FROM (
|
||||
SELECT DISTINCT m.device || '_' || m.test_id AS pair_value
|
||||
FROM tests AS m
|
||||
WHERE m.interference = 'COE'
|
||||
AND IFNULL(m.device, '') = IFNULL(t.device, '')
|
||||
AND IFNULL(m.rotation, '') = IFNULL(t.rotation, '')
|
||||
AND IFNULL(m.test_point, '') = IFNULL(t.test_point, '')
|
||||
AND IFNULL(m.rssi, '') = IFNULL(t.rssi, '')
|
||||
AND IFNULL(m.station, '') = IFNULL(t.station, '')
|
||||
AND IFNULL(m.band, '') = IFNULL(t.band, '')
|
||||
AND IFNULL(m.channel, '') = IFNULL(t.channel, '')
|
||||
AND IFNULL(m.bandwidth, '') = IFNULL(t.bandwidth, '')
|
||||
AND IFNULL(m.direction, '') = IFNULL(t.direction, '')
|
||||
ORDER BY pair_value
|
||||
)
|
||||
)
|
||||
WHERE t.interference = 'P2P'
|
||||
"""
|
||||
)
|
||||
return cursor.rowcount
|
||||
|
||||
|
||||
def get_station_for_test(test_id, device):
|
||||
with _lock:
|
||||
row = _conn.execute(
|
||||
|
||||
+90
-118
@@ -1,13 +1,13 @@
|
||||
import os
|
||||
import re
|
||||
import threading
|
||||
import smbclient
|
||||
|
||||
from db_py import (
|
||||
clear_tests,
|
||||
get_all_tests,
|
||||
mark_completed,
|
||||
set_coe_pair,
|
||||
set_p3p_pair,
|
||||
update_all_p2p_coe_pairs_sql,
|
||||
update_all_p3p_pairs_sql,
|
||||
upsert_test,
|
||||
)
|
||||
from parser import (
|
||||
@@ -20,13 +20,67 @@ from parser import (
|
||||
|
||||
|
||||
_SMB_SESSIONS = set()
|
||||
_WIN_DRIVE_PATH_RE = re.compile(r"^[A-Za-z]:[\\/]")
|
||||
_SCAN_STATE_LOCK = threading.Lock()
|
||||
_ACTIVE_SCAN_COUNT = 0
|
||||
|
||||
|
||||
def _scan_started():
|
||||
global _ACTIVE_SCAN_COUNT
|
||||
with _SCAN_STATE_LOCK:
|
||||
_ACTIVE_SCAN_COUNT += 1
|
||||
|
||||
|
||||
def _scan_finished():
|
||||
global _ACTIVE_SCAN_COUNT
|
||||
with _SCAN_STATE_LOCK:
|
||||
_ACTIVE_SCAN_COUNT = max(0, _ACTIVE_SCAN_COUNT - 1)
|
||||
|
||||
|
||||
def is_scan_in_progress():
|
||||
with _SCAN_STATE_LOCK:
|
||||
return _ACTIVE_SCAN_COUNT > 0
|
||||
|
||||
|
||||
def resolve_runtime_path(path_value):
|
||||
if not path_value:
|
||||
return path_value
|
||||
|
||||
raw_path = str(path_value).strip()
|
||||
if not raw_path:
|
||||
return raw_path
|
||||
|
||||
# If the path is already valid in the current runtime, keep it.
|
||||
if os.path.exists(raw_path):
|
||||
return raw_path
|
||||
|
||||
# UNC/network paths are handled separately via smbclient.
|
||||
if raw_path.startswith("\\\\") or raw_path.startswith("//"):
|
||||
return raw_path
|
||||
|
||||
# Map Windows host paths when backend runs in Linux container.
|
||||
if os.name != "nt" and _WIN_DRIVE_PATH_RE.match(raw_path):
|
||||
mount_root = os.getenv("HOST_MOUNT_ROOT", "/host").strip() or "/host"
|
||||
host_root = os.getenv("HOST_BROWSE_ROOT", "").strip()
|
||||
|
||||
raw_norm = raw_path.replace("\\", "/")
|
||||
if host_root:
|
||||
host_norm = host_root.replace("\\", "/").rstrip("/")
|
||||
if raw_norm.lower() == host_norm.lower() or raw_norm.lower().startswith(host_norm.lower() + "/"):
|
||||
relative = raw_norm[len(host_norm):].lstrip("/")
|
||||
if relative:
|
||||
return os.path.join(mount_root, *relative.split("/"))
|
||||
return mount_root
|
||||
|
||||
return raw_path
|
||||
|
||||
|
||||
def _normalize_input_path(path_value):
|
||||
if not path_value:
|
||||
return path_value
|
||||
|
||||
path = str(path_value).strip()
|
||||
path = resolve_runtime_path(path_value)
|
||||
path = str(path).strip()
|
||||
|
||||
# Accept //server/share style and normalize to UNC for smbclient.
|
||||
if path.startswith("//"):
|
||||
@@ -102,138 +156,54 @@ def _read_text_lines(path):
|
||||
yield line
|
||||
|
||||
|
||||
def scan_results_only(results_dir, results_dir_ref=None):
|
||||
def scan_results_only(results_dir, results_dir_ref):
|
||||
"""Re-scan only the results directories without clearing or re-scanning targets."""
|
||||
results_dir = _normalize_input_path(results_dir)
|
||||
results_dir_ref = _normalize_input_path(results_dir_ref)
|
||||
|
||||
if not results_dir:
|
||||
if not results_dir or not results_dir_ref:
|
||||
return
|
||||
|
||||
print(f"[scanner] results-only scan dir : {results_dir}")
|
||||
if results_dir_ref:
|
||||
_scan_started()
|
||||
try:
|
||||
print(f"[scanner] results-only scan dir : {results_dir}")
|
||||
print(f"[scanner] results-only scan dir ref : {results_dir_ref}")
|
||||
|
||||
scan_results(results_dir)
|
||||
if results_dir_ref:
|
||||
scan_results(results_dir)
|
||||
scan_results(results_dir_ref)
|
||||
update_p2p_coe_pairs()
|
||||
update_p3p_throttle_pairs()
|
||||
update_p2p_coe_pairs()
|
||||
update_p3p_throttle_pairs()
|
||||
finally:
|
||||
_scan_finished()
|
||||
|
||||
|
||||
def full_scan(target_dir, results_dir, results_dir_ref=None):
|
||||
def full_scan(target_dir, results_dir, results_dir_ref):
|
||||
target_dir = _normalize_input_path(target_dir)
|
||||
results_dir = _normalize_input_path(results_dir)
|
||||
results_dir_ref = _normalize_input_path(results_dir_ref)
|
||||
|
||||
clear_tests()
|
||||
if not target_dir or not results_dir:
|
||||
return
|
||||
_scan_started()
|
||||
try:
|
||||
clear_tests()
|
||||
if not target_dir or not results_dir or not results_dir_ref:
|
||||
return
|
||||
|
||||
print(f"[scanner] target dir : {target_dir}")
|
||||
print(f"[scanner] results dir : {results_dir}")
|
||||
if results_dir_ref:
|
||||
print(f"[scanner] target dir : {target_dir}")
|
||||
print(f"[scanner] results dir : {results_dir}")
|
||||
print(f"[scanner] results dir ref : {results_dir_ref}")
|
||||
|
||||
scan_targets(target_dir)
|
||||
scan_results(results_dir)
|
||||
if results_dir_ref:
|
||||
if not scan_targets(target_dir):
|
||||
print("[scanner] Skipping results scan because target scan failed")
|
||||
return
|
||||
|
||||
scan_results(results_dir)
|
||||
scan_results(results_dir_ref)
|
||||
update_p2p_coe_pairs()
|
||||
update_p3p_throttle_pairs()
|
||||
|
||||
|
||||
def update_p2p_coe_pairs():
|
||||
pair_fields = [
|
||||
"device",
|
||||
"rotation",
|
||||
"test_point",
|
||||
"rssi",
|
||||
"station",
|
||||
"band",
|
||||
"channel",
|
||||
"bandwidth",
|
||||
"direction",
|
||||
]
|
||||
|
||||
tests = get_all_tests()
|
||||
coe_by_key = {}
|
||||
|
||||
for test in tests:
|
||||
if test.get("interference") != "COE":
|
||||
continue
|
||||
|
||||
key = tuple(test.get(field) for field in pair_fields)
|
||||
device = test.get("device")
|
||||
test_id = test.get("test_id")
|
||||
if not device or not test_id:
|
||||
continue
|
||||
|
||||
coe_by_key.setdefault(key, []).append(f"{device}_{test_id}")
|
||||
|
||||
updated = 0
|
||||
for test in tests:
|
||||
if test.get("interference") != "P2P":
|
||||
continue
|
||||
|
||||
key = tuple(test.get(field) for field in pair_fields)
|
||||
pairs = sorted(set(coe_by_key.get(key, [])))
|
||||
set_coe_pair(test.get("id"), pairs)
|
||||
updated += 1
|
||||
|
||||
print(f"[scanner] coe_pair updated for {updated} P2P test(s)")
|
||||
|
||||
def update_p3p_throttle_pairs():
|
||||
tests = get_all_tests()
|
||||
|
||||
p3p_lookup = {}
|
||||
for test in tests:
|
||||
if test.get("interference") != "P3P":
|
||||
continue
|
||||
|
||||
device = test.get("device")
|
||||
test_id = test.get("test_id")
|
||||
if not device or not test_id:
|
||||
continue
|
||||
|
||||
p3p_lookup.setdefault(f"{str(device).upper()}_{str(test_id).upper()}", []).append(test)
|
||||
|
||||
def _pair_test_id(test_id, throttled):
|
||||
if not test_id or not throttled:
|
||||
return None
|
||||
|
||||
upper_id = str(test_id).upper()
|
||||
upper_throttled = str(throttled).upper()
|
||||
|
||||
if upper_throttled == "TH":
|
||||
return re.sub("TH", "UT", upper_id, count=1)
|
||||
if upper_throttled == "UT":
|
||||
return re.sub("UT", "TH", upper_id, count=1)
|
||||
return None
|
||||
|
||||
updated = 0
|
||||
for test in tests:
|
||||
if test.get("interference") != "P3P":
|
||||
continue
|
||||
|
||||
device = test.get("device")
|
||||
test_id = test.get("test_id")
|
||||
pair_test_id = _pair_test_id(test_id, test.get("throttled"))
|
||||
|
||||
pairs = []
|
||||
if device and pair_test_id:
|
||||
lookup_key = f"{str(device).upper()}_{str(pair_test_id).upper()}"
|
||||
matches = p3p_lookup.get(lookup_key, [])
|
||||
for match in matches:
|
||||
match_device = match.get("device")
|
||||
match_test_id = match.get("test_id")
|
||||
if match_device and match_test_id:
|
||||
pairs.append(f"{match_device}_{match_test_id}")
|
||||
|
||||
set_p3p_pair(test.get("id"), sorted(set(pairs)))
|
||||
updated += 1
|
||||
|
||||
print(f"[scanner] p3p_pair updated for {updated} P3P test(s)")
|
||||
coe_pairs = update_all_p2p_coe_pairs_sql()
|
||||
print(f"[scanner] coe_pair updated for {coe_pairs} P2P test(s)")
|
||||
p3p_pairs = update_all_p3p_pairs_sql()
|
||||
print(f"[scanner] p3p_pair updated for {p3p_pairs} P3P test(s)")
|
||||
finally:
|
||||
_scan_finished()
|
||||
|
||||
|
||||
def scan_targets(target_dir):
|
||||
@@ -247,7 +217,7 @@ def scan_targets(target_dir):
|
||||
]
|
||||
except OSError as exc:
|
||||
print(f"[scanner] Cannot read target dir: {exc}")
|
||||
return
|
||||
return False
|
||||
|
||||
print(f"[scanner] subdirectories found: {len(parent_entries)}")
|
||||
|
||||
@@ -296,6 +266,8 @@ def scan_targets(target_dir):
|
||||
}
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def scan_results(results_dir):
|
||||
results_dir = _normalize_input_path(results_dir)
|
||||
|
||||
Reference in New Issue
Block a user