()
| 749 | return 10 |
| 750 | |
| 751 | |
| 752 | _REPORT_RECORD_CACHE_LOCK = threading.Lock() |
| 753 | # path -> (mtime_ns, size, parsed record). Parsing a report means reading the |
| 754 | # whole file and extracting frontmatter, and the reports view rescans every |
| 755 | # markdown file under the configured directories on each call: 1752 files and |
| 756 | # 22.8MB in local testing, taking 1.6-2.3s and getting slower as the OS cache |
| 757 | # churns. list_reports, get_report_content and delete_report each triggered a |
| 758 | # full scan, so opening one report paid for two. |
| 759 | _REPORT_RECORD_CACHE: Dict[str, Tuple[int, int, Dict[str, Any]]] = {} |
| 760 | |
| 761 | |
| 762 | def _cached_report_record(path: Path, resolved_key: str) -> Optional[Dict[str, Any]]: |
| 763 | """Return the parsed record for path, re-reading only when it changed. |
| 764 | |
| 765 | Keyed on mtime_ns plus size so an edit is always picked up; a stale entry |
| 766 | would show the user outdated report content, which is worse than the scan |
| 767 | cost this avoids. |
| 768 | """ |
| 769 | try: |
| 770 | stat = path.stat() |
| 771 | except OSError: |
| 772 | with _REPORT_RECORD_CACHE_LOCK: |
| 773 | _REPORT_RECORD_CACHE.pop(resolved_key, None) |
| 774 | return None |
| 775 | |
| 776 | signature = (stat.st_mtime_ns, stat.st_size) |
| 777 | with _REPORT_RECORD_CACHE_LOCK: |
| 778 | cached = _REPORT_RECORD_CACHE.get(resolved_key) |
| 779 | if cached is not None and cached[0] == signature[0] and cached[1] == signature[1]: |
| 780 | # deepcopy: callers mutate the record (e.g. _source_priority) and |
| 781 | # must not corrupt the cached copy. |
no test coverage detected