Upload app.py
Browse files
app.py
CHANGED
|
@@ -65,6 +65,8 @@ search_response_cache: dict[tuple, tuple[float, object]] = {}
|
|
| 65 |
search_response_cache_lock = threading.Lock()
|
| 66 |
preview_text_cache: dict[str, str] = {}
|
| 67 |
preview_text_cache_lock = threading.Lock()
|
|
|
|
|
|
|
| 68 |
ZIP_TOKEN_TTL_SECONDS = 600
|
| 69 |
ZIP_TOKEN_MAX_ENTRIES = 32
|
| 70 |
zip_download_tokens: dict[str, tuple[float, list[str]]] = {}
|
|
@@ -330,7 +332,10 @@ class FulltextDatabases:
|
|
| 330 |
tokens = query_tokens(query) if version == TOKENIZER_VERSION else query_terms(query)
|
| 331 |
if version == TOKENIZER_VERSION:
|
| 332 |
tokens = [*tokens, *literal_query_tokens(query)]
|
| 333 |
-
|
|
|
|
|
|
|
|
|
|
| 334 |
rows = connection.execute(
|
| 335 |
"SELECT rowid FROM content_fts WHERE instr(content, ?) > 0", (normalized_query,)
|
| 336 |
)
|
|
@@ -403,21 +408,6 @@ class FulltextDatabases:
|
|
| 403 |
return set()
|
| 404 |
return matched or set()
|
| 405 |
|
| 406 |
-
def verify_exact_content(self, connection: sqlite3.Connection, candidates: set[int], query: str) -> set[int]:
|
| 407 |
-
if not candidates:
|
| 408 |
-
return set()
|
| 409 |
-
matched: set[int] = set()
|
| 410 |
-
ordered = sorted(candidates)
|
| 411 |
-
for offset in range(0, len(ordered), 500):
|
| 412 |
-
batch = ordered[offset:offset + 500]
|
| 413 |
-
placeholders = ",".join("?" for _ in batch)
|
| 414 |
-
rows = connection.execute(
|
| 415 |
-
f"SELECT rowid FROM content_fts WHERE rowid IN ({placeholders}) AND instr(content, ?) > 0",
|
| 416 |
-
[*batch, query],
|
| 417 |
-
)
|
| 418 |
-
matched.update(int(row[0]) for row in rows)
|
| 419 |
-
return matched
|
| 420 |
-
|
| 421 |
def verify_wildcard_content(
|
| 422 |
self,
|
| 423 |
connection: sqlite3.Connection,
|
|
@@ -455,15 +445,6 @@ class FulltextDatabases:
|
|
| 455 |
matched.update(int(row[0]) for row in rows)
|
| 456 |
return matched
|
| 457 |
|
| 458 |
-
def can_trust_exact_content(self, source: str, query: str) -> bool:
|
| 459 |
-
with self.lock:
|
| 460 |
-
return (
|
| 461 |
-
self.tokenizer_versions.get(source) == TOKENIZER_VERSION
|
| 462 |
-
and self.has_content_fts.get(source, False)
|
| 463 |
-
and "*" not in query
|
| 464 |
-
and "?" not in query
|
| 465 |
-
)
|
| 466 |
-
|
| 467 |
def summaries(self, doc_ids: list[str]) -> dict[str, str]:
|
| 468 |
grouped = {}
|
| 469 |
for doc_id in doc_ids:
|
|
@@ -986,25 +967,6 @@ def metadata_matches(q: str, exact: bool, search_paths: bool) -> set[int]:
|
|
| 986 |
if all(term in records[idx][field] for term in terms)
|
| 987 |
}
|
| 988 |
|
| 989 |
-
def file_matches_exact_query(idx: int, query: str) -> bool:
|
| 990 |
-
file_path = get_doc_storage_path(records[idx])
|
| 991 |
-
if not file_path.exists():
|
| 992 |
-
return False
|
| 993 |
-
payload = file_path.read_bytes()
|
| 994 |
-
if "*" not in query and "?" not in query and query.encode("utf-8") in payload:
|
| 995 |
-
return True
|
| 996 |
-
return matches_exact_query(payload.decode("utf-8", errors="ignore"), query)
|
| 997 |
-
|
| 998 |
-
def verify_fulltext_matches(indices: set[int], query: str) -> set[int]:
|
| 999 |
-
return set(cached_verified_fulltext_matches(tuple(sorted(indices)), query))
|
| 1000 |
-
|
| 1001 |
-
@lru_cache(maxsize=32)
|
| 1002 |
-
def cached_verified_fulltext_matches(indices: tuple[int, ...], query: str) -> frozenset[int]:
|
| 1003 |
-
return frozenset(
|
| 1004 |
-
idx for idx in indices
|
| 1005 |
-
if file_matches_exact_query(idx, query)
|
| 1006 |
-
)
|
| 1007 |
-
|
| 1008 |
def search(q="", sources_filter=None, folders=None, min_size=None, max_size=None, page=1, page_size=100, sort="relevance", exact=False, search_paths=True, timings=None):
|
| 1009 |
started = time.perf_counter()
|
| 1010 |
q = q.strip()
|
|
@@ -1161,14 +1123,29 @@ def _cached_payload(cache, lock, key: tuple, builder, ttl: int, max_entries: int
|
|
| 1161 |
if timings is not None:
|
| 1162 |
timings["cache"] = "miss"
|
| 1163 |
timings["cache_lookup"] = (time.perf_counter() - cache_started) * 1000
|
| 1164 |
-
|
| 1165 |
-
|
| 1166 |
-
|
| 1167 |
-
|
| 1168 |
-
|
| 1169 |
-
|
| 1170 |
-
|
| 1171 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1172 |
|
| 1173 |
def cached_payload(key: tuple, builder, timings=None):
|
| 1174 |
return _cached_payload(
|
|
|
|
| 65 |
search_response_cache_lock = threading.Lock()
|
| 66 |
preview_text_cache: dict[str, str] = {}
|
| 67 |
preview_text_cache_lock = threading.Lock()
|
| 68 |
+
cache_key_locks: dict[tuple, threading.Lock] = {}
|
| 69 |
+
cache_key_locks_guard = threading.Lock()
|
| 70 |
ZIP_TOKEN_TTL_SECONDS = 600
|
| 71 |
ZIP_TOKEN_MAX_ENTRIES = 32
|
| 72 |
zip_download_tokens: dict[str, tuple[float, list[str]]] = {}
|
|
|
|
| 332 |
tokens = query_tokens(query) if version == TOKENIZER_VERSION else query_terms(query)
|
| 333 |
if version == TOKENIZER_VERSION:
|
| 334 |
tokens = [*tokens, *literal_query_tokens(query)]
|
| 335 |
+
# Short non-CJK literals (Latin substrings or punctuation-separated
|
| 336 |
+
# characters) are not provable from whole-token/unigram postings, so
|
| 337 |
+
# verify them against the normalized FTS content directly.
|
| 338 |
+
if not tokens or not re.fullmatch(r"[\u4e00-\u9fff\u3400-\u4dbf]+", normalized_query):
|
| 339 |
rows = connection.execute(
|
| 340 |
"SELECT rowid FROM content_fts WHERE instr(content, ?) > 0", (normalized_query,)
|
| 341 |
)
|
|
|
|
| 408 |
return set()
|
| 409 |
return matched or set()
|
| 410 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 411 |
def verify_wildcard_content(
|
| 412 |
self,
|
| 413 |
connection: sqlite3.Connection,
|
|
|
|
| 445 |
matched.update(int(row[0]) for row in rows)
|
| 446 |
return matched
|
| 447 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 448 |
def summaries(self, doc_ids: list[str]) -> dict[str, str]:
|
| 449 |
grouped = {}
|
| 450 |
for doc_id in doc_ids:
|
|
|
|
| 967 |
if all(term in records[idx][field] for term in terms)
|
| 968 |
}
|
| 969 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 970 |
def search(q="", sources_filter=None, folders=None, min_size=None, max_size=None, page=1, page_size=100, sort="relevance", exact=False, search_paths=True, timings=None):
|
| 971 |
started = time.perf_counter()
|
| 972 |
q = q.strip()
|
|
|
|
| 1123 |
if timings is not None:
|
| 1124 |
timings["cache"] = "miss"
|
| 1125 |
timings["cache_lookup"] = (time.perf_counter() - cache_started) * 1000
|
| 1126 |
+
# Serialize identical cold keys so concurrent requests compute once instead
|
| 1127 |
+
# of stampeding the builder. The per-key lock is removed once it is no longer
|
| 1128 |
+
# contended so the map stays bounded to the number of in-flight keys.
|
| 1129 |
+
with cache_key_locks_guard:
|
| 1130 |
+
key_lock = cache_key_locks.setdefault(key, threading.Lock())
|
| 1131 |
+
try:
|
| 1132 |
+
with key_lock:
|
| 1133 |
+
with lock:
|
| 1134 |
+
cached = cache.get(key)
|
| 1135 |
+
if cached and now - cached[0] < ttl:
|
| 1136 |
+
return cached[1]
|
| 1137 |
+
value = builder()
|
| 1138 |
+
inserted_at = time.monotonic()
|
| 1139 |
+
with lock:
|
| 1140 |
+
cache[key] = (inserted_at, value)
|
| 1141 |
+
while len(cache) > max_entries:
|
| 1142 |
+
oldest_key = min(cache, key=lambda item: cache[item][0])
|
| 1143 |
+
cache.pop(oldest_key, None)
|
| 1144 |
+
return value
|
| 1145 |
+
finally:
|
| 1146 |
+
with cache_key_locks_guard:
|
| 1147 |
+
if cache_key_locks.get(key) is key_lock:
|
| 1148 |
+
cache_key_locks.pop(key, None)
|
| 1149 |
|
| 1150 |
def cached_payload(key: tuple, builder, timings=None):
|
| 1151 |
return _cached_payload(
|