added exclusion, fixed time display
This commit is contained in:
+6
-5
@@ -18,6 +18,7 @@ ALLOWED_KEYS = {
|
||||
"avg_time_coe",
|
||||
"avg_time_p2p",
|
||||
"avg_time_p3p",
|
||||
"scan_exclusions",
|
||||
"smb_username",
|
||||
"smb_password",
|
||||
"smb_domain",
|
||||
@@ -48,7 +49,7 @@ def _start_full_scan_background(target_dir, results_dir, results_dir_ref, source
|
||||
def _job():
|
||||
try:
|
||||
full_scan(target_dir, results_dir, results_dir_ref)
|
||||
start_watching(target_dir, results_dir)
|
||||
start_watching(target_dir, results_dir, results_dir_ref)
|
||||
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")
|
||||
@@ -250,7 +251,7 @@ def set_config_route():
|
||||
if not isinstance(updates, dict):
|
||||
return jsonify({"error": "Request body must be a JSON object"}), 400
|
||||
|
||||
dirs_changed = False
|
||||
rescan_required = False
|
||||
|
||||
for key, value in updates.items():
|
||||
if key not in ALLOWED_KEYS:
|
||||
@@ -261,10 +262,10 @@ def set_config_route():
|
||||
else:
|
||||
set_config(key, str(value))
|
||||
|
||||
if key in {"target_dir", "results_dir", "results_dir_ref"}:
|
||||
dirs_changed = True
|
||||
if key in {"target_dir", "results_dir", "results_dir_ref", "scan_exclusions"}:
|
||||
rescan_required = True
|
||||
|
||||
if dirs_changed:
|
||||
if rescan_required:
|
||||
_apply_smb_env_from_config()
|
||||
target_dir = resolve_runtime_path(get_config("target_dir"))
|
||||
results_dir = resolve_runtime_path(get_config("results_dir"))
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
+126
-8
@@ -5,6 +5,7 @@ import smbclient
|
||||
|
||||
from db_py import (
|
||||
clear_tests,
|
||||
get_config,
|
||||
mark_completed,
|
||||
update_all_p2p_coe_pairs_sql,
|
||||
update_all_p3p_pairs_sql,
|
||||
@@ -23,6 +24,125 @@ _SMB_SESSIONS = set()
|
||||
_WIN_DRIVE_PATH_RE = re.compile(r"^[A-Za-z]:[\\/]")
|
||||
_SCAN_STATE_LOCK = threading.Lock()
|
||||
_ACTIVE_SCAN_COUNT = 0
|
||||
_DEFAULT_SCAN_EXCLUSIONS = ""
|
||||
|
||||
def _parse_scan_exclusions(value):
|
||||
raw = value if value not in (None, "") else _DEFAULT_SCAN_EXCLUSIONS
|
||||
if raw == "" or raw is None:
|
||||
return None
|
||||
parts = re.split(r"[,\n;]", str(raw))
|
||||
return {token.strip().upper() for token in parts if token.strip()}
|
||||
|
||||
|
||||
def _build_match_tokens(parsed):
|
||||
tokens = set()
|
||||
|
||||
def _add(value):
|
||||
if value in (None, ""):
|
||||
return
|
||||
tokens.add(str(value).upper())
|
||||
|
||||
_add(parsed.get("interference"))
|
||||
_add(parsed.get("device"))
|
||||
_add(parsed.get("rotation"))
|
||||
_add(parsed.get("test_point"))
|
||||
_add(parsed.get("rssi"))
|
||||
_add(parsed.get("station"))
|
||||
_add(parsed.get("band"))
|
||||
_add(parsed.get("channel"))
|
||||
_add(parsed.get("bandwidth"))
|
||||
_add(parsed.get("direction"))
|
||||
_add(parsed.get("throttled"))
|
||||
_add(parsed.get("test_id"))
|
||||
|
||||
for bw in parsed.get("extra_bandwidths") or []:
|
||||
_add(bw)
|
||||
|
||||
# Add numeric alias for devices, e.g. CGW453 -> 453.
|
||||
device = (parsed.get("device") or "").upper()
|
||||
device_digits = re.sub(r"\D", "", device)
|
||||
if device_digits:
|
||||
tokens.add(device_digits)
|
||||
|
||||
# SP can appear as flag or specific token variant (e.g., SP40).
|
||||
if parsed.get("sp"):
|
||||
tokens.add("SP")
|
||||
tokens.add(str(parsed.get("sp")).upper())
|
||||
|
||||
return tokens
|
||||
|
||||
|
||||
def _normalize_bandwidth_value(value):
|
||||
if value in (None, ""):
|
||||
return set()
|
||||
|
||||
raw = str(value).strip().upper()
|
||||
if not raw:
|
||||
return set()
|
||||
|
||||
normalized = {raw}
|
||||
digits = re.sub(r"\D", "", raw)
|
||||
if digits:
|
||||
normalized.add(digits)
|
||||
normalized.add(f"BW{digits}")
|
||||
|
||||
return normalized
|
||||
|
||||
|
||||
def _extract_coe_bandwidth_tokens(ini_path):
|
||||
keys = {"Bandwidth_fh2", "Bandwidth_fh5", "Bandwidth_fh6"}
|
||||
line_re = re.compile(r"^\s*([A-Za-z0-9_]+)\s*=\s*([^\r\n#;]+)")
|
||||
tokens = set()
|
||||
|
||||
try:
|
||||
for line in _read_text_lines(ini_path):
|
||||
match = line_re.match(line)
|
||||
if not match:
|
||||
continue
|
||||
|
||||
key = match.group(1).strip()
|
||||
if key not in keys:
|
||||
continue
|
||||
|
||||
raw_value = match.group(2).strip()
|
||||
for part in re.split(r"[,/\s]+", raw_value):
|
||||
part = part.strip()
|
||||
if not part:
|
||||
continue
|
||||
tokens.update(_normalize_bandwidth_value(part))
|
||||
except OSError as exc:
|
||||
print(f"[scanner] Cannot read ini file {ini_path}: {exc}")
|
||||
|
||||
return tokens
|
||||
|
||||
|
||||
def _rule_matches_target(rule, parsed_tokens):
|
||||
# Rule parts are AND-ed: CGW453_P3P_ROT2 => device AND interference AND rotation.
|
||||
# Support _, -, or spaces as condition separators.
|
||||
parts = [p for p in re.split(r"[_\-\s]+", rule) if p]
|
||||
if not parts:
|
||||
return False
|
||||
|
||||
def _part_matches(part):
|
||||
if part in parsed_tokens:
|
||||
return True
|
||||
|
||||
# Support tag variants (e.g., BW80 should match BW80M/BW80+80 in source names).
|
||||
# Keep this conservative for very short parts to avoid overmatching.
|
||||
if len(part) >= 3:
|
||||
for token in parsed_tokens:
|
||||
if token.startswith(part) or part in token:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
return all(_part_matches(part) for part in parts)
|
||||
|
||||
|
||||
def _should_exclude_target(parsed, exclusions):
|
||||
parsed_tokens = _build_match_tokens(parsed)
|
||||
# Any matching rule excludes the testcase.
|
||||
return any(_rule_matches_target(rule, parsed_tokens) for rule in exclusions)
|
||||
|
||||
|
||||
def _scan_started():
|
||||
@@ -210,6 +330,7 @@ def full_scan(target_dir, results_dir, results_dir_ref):
|
||||
|
||||
def scan_targets(target_dir):
|
||||
target_dir = _normalize_input_path(target_dir)
|
||||
exclusions = _parse_scan_exclusions(get_config("scan_exclusions"))
|
||||
|
||||
try:
|
||||
parent_entries = [
|
||||
@@ -245,14 +366,11 @@ def scan_targets(target_dir):
|
||||
#print(f"[scanner] skip (no test_id): {parent_name}/{filename}")
|
||||
continue
|
||||
|
||||
# Remove all SP testcases and BW320 testcases
|
||||
if parsed["sp"] or parsed["bandwidth"] == "BW320":
|
||||
#print(f"[scanner] skip (SP test or BW320): {parent_name}/{filename}")
|
||||
continue
|
||||
|
||||
#Remove P3P and COE testcases for CGW452
|
||||
if parsed["interference"] in ["P3P", "COE"] and parsed["device"] == "CGW452":
|
||||
#print(f"[scanner] skip ({parsed['interference']} CGW452): {parent_name}/{filename}")
|
||||
if (parsed.get("interference") or "").upper() == "COE":
|
||||
ini_path = _join_path(parent_path, filename)
|
||||
parsed["extra_bandwidths"] = sorted(_extract_coe_bandwidth_tokens(ini_path))
|
||||
|
||||
if exclusions and _should_exclude_target(parsed, exclusions):
|
||||
continue
|
||||
|
||||
#print( f"[scanner] found: {parent_name}/{filename} -> test_id={parsed['test_id']}")
|
||||
|
||||
+79
-23
@@ -5,15 +5,40 @@ from watchdog.events import FileSystemEventHandler
|
||||
from watchdog.observers import Observer
|
||||
|
||||
from db_py import reset_by_file_id_and_device
|
||||
from scanner import full_scan, parse_deleted_result_dir_name, process_result_dir
|
||||
from scanner import (
|
||||
full_scan,
|
||||
parse_deleted_result_dir_name,
|
||||
process_result_dir,
|
||||
_is_unc_path,
|
||||
_normalize_input_path,
|
||||
)
|
||||
from sse_py import broadcast
|
||||
|
||||
_target_observer = None
|
||||
_results_observer = None
|
||||
_results_observers = []
|
||||
_scan_timer = None
|
||||
_scan_lock = threading.Lock()
|
||||
|
||||
|
||||
def _path_exists(path):
|
||||
"""Check if path exists, handling UNC paths."""
|
||||
if _is_unc_path(path):
|
||||
try:
|
||||
import smbclient
|
||||
return smbclient.path.isdir(path)
|
||||
except Exception:
|
||||
return False
|
||||
return os.path.isdir(path)
|
||||
|
||||
|
||||
def _normalize_watcher_path(path):
|
||||
"""Normalize path for watcher comparison, handling UNC paths."""
|
||||
normalized = _normalize_input_path(path)
|
||||
if not _is_unc_path(normalized):
|
||||
normalized = os.path.normcase(os.path.abspath(normalized))
|
||||
return normalized
|
||||
|
||||
|
||||
def _schedule_full_scan(target_dir, results_dir, delay_seconds=1.0):
|
||||
global _scan_timer
|
||||
with _scan_lock:
|
||||
@@ -66,16 +91,29 @@ class _TargetHandler(FileSystemEventHandler):
|
||||
|
||||
class _ResultsHandler(FileSystemEventHandler):
|
||||
def __init__(self, results_dir):
|
||||
self.results_dir = os.path.normcase(os.path.abspath(results_dir))
|
||||
self.results_dir = _normalize_watcher_path(results_dir)
|
||||
|
||||
def _is_direct_child_dir(self, path):
|
||||
parent = os.path.normcase(os.path.abspath(os.path.dirname(path)))
|
||||
return parent == self.results_dir
|
||||
try:
|
||||
if _is_unc_path(path):
|
||||
parent = _normalize_watcher_path(os.path.dirname(path))
|
||||
else:
|
||||
parent = os.path.normcase(os.path.abspath(os.path.dirname(path)))
|
||||
return parent == self.results_dir
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _is_file_under_result_child(self, path):
|
||||
parent_dir = os.path.normcase(os.path.abspath(os.path.dirname(path)))
|
||||
grandparent = os.path.normcase(os.path.abspath(os.path.dirname(parent_dir)))
|
||||
return grandparent == self.results_dir
|
||||
try:
|
||||
if _is_unc_path(path):
|
||||
parent_dir = _normalize_watcher_path(os.path.dirname(path))
|
||||
grandparent = _normalize_watcher_path(os.path.dirname(parent_dir))
|
||||
else:
|
||||
parent_dir = os.path.normcase(os.path.abspath(os.path.dirname(path)))
|
||||
grandparent = os.path.normcase(os.path.abspath(os.path.dirname(parent_dir)))
|
||||
return grandparent == self.results_dir
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _result_child_name_for_file(self, path):
|
||||
return os.path.basename(os.path.dirname(path))
|
||||
@@ -164,7 +202,7 @@ class _ResultsHandler(FileSystemEventHandler):
|
||||
|
||||
|
||||
def stop_watching():
|
||||
global _target_observer, _results_observer, _scan_timer
|
||||
global _target_observer, _results_observers, _scan_timer
|
||||
|
||||
if _scan_timer:
|
||||
_scan_timer.cancel()
|
||||
@@ -175,27 +213,30 @@ def stop_watching():
|
||||
_target_observer.join(timeout=2)
|
||||
_target_observer = None
|
||||
|
||||
if _results_observer:
|
||||
_results_observer.stop()
|
||||
_results_observer.join(timeout=2)
|
||||
_results_observer = None
|
||||
for obs in _results_observers:
|
||||
try:
|
||||
obs.stop()
|
||||
obs.join(timeout=2)
|
||||
except Exception as e:
|
||||
print(f"[watcher] error stopping results observer: {e}")
|
||||
_results_observers.clear()
|
||||
|
||||
|
||||
def start_watching(target_dir, results_dir):
|
||||
global _target_observer, _results_observer
|
||||
def start_watching(target_dir, results_dir, results_dir_ref=None):
|
||||
global _target_observer, _results_observers
|
||||
|
||||
stop_watching()
|
||||
if not target_dir or not results_dir:
|
||||
print("[watcher] target_dir/results_dir not configured; watcher disabled.")
|
||||
return
|
||||
|
||||
if not os.path.isdir(target_dir):
|
||||
print(f"[watcher] target_dir does not exist inside runtime: {target_dir}")
|
||||
if not _path_exists(target_dir):
|
||||
print(f"[watcher] target_dir does not exist or is not accessible: {target_dir}")
|
||||
print("[watcher] watcher disabled until valid paths are configured.")
|
||||
return
|
||||
|
||||
if not os.path.isdir(results_dir):
|
||||
print(f"[watcher] results_dir does not exist inside runtime: {results_dir}")
|
||||
if not _path_exists(results_dir):
|
||||
print(f"[watcher] results_dir does not exist or is not accessible: {results_dir}")
|
||||
print("[watcher] watcher disabled until valid paths are configured.")
|
||||
return
|
||||
|
||||
@@ -204,11 +245,26 @@ def start_watching(target_dir, results_dir):
|
||||
_target_observer.schedule(_TargetHandler(target_dir, results_dir), target_dir, recursive=True)
|
||||
_target_observer.daemon = True
|
||||
_target_observer.start()
|
||||
print(f"[watcher] target observer started for: {target_dir}")
|
||||
|
||||
_results_observer = Observer()
|
||||
_results_observer.schedule(_ResultsHandler(results_dir), results_dir, recursive=True)
|
||||
_results_observer.daemon = True
|
||||
_results_observer.start()
|
||||
# Watch primary results directory
|
||||
results_obs = Observer()
|
||||
results_obs.schedule(_ResultsHandler(results_dir), results_dir, recursive=True)
|
||||
results_obs.daemon = True
|
||||
results_obs.start()
|
||||
_results_observers.append(results_obs)
|
||||
print(f"[watcher] results observer started for: {results_dir}")
|
||||
|
||||
# Watch reference results directory if provided and different
|
||||
if results_dir_ref and results_dir_ref != results_dir and _path_exists(results_dir_ref):
|
||||
ref_obs = Observer()
|
||||
ref_obs.schedule(_ResultsHandler(results_dir_ref), results_dir_ref, recursive=True)
|
||||
ref_obs.daemon = True
|
||||
ref_obs.start()
|
||||
_results_observers.append(ref_obs)
|
||||
print(f"[watcher] results observer started for reference: {results_dir_ref}")
|
||||
elif results_dir_ref and results_dir_ref != results_dir:
|
||||
print(f"[watcher] reference results_dir does not exist or is not accessible: {results_dir_ref}")
|
||||
except Exception as exc:
|
||||
print(f"[watcher] failed to start watchers: {exc}")
|
||||
stop_watching()
|
||||
|
||||
Reference in New Issue
Block a user