Upload app.py
Browse files
app.py
CHANGED
|
@@ -57,10 +57,14 @@ API_CACHE_TTL_SECONDS = 120
|
|
| 57 |
SEARCH_CACHE_TTL_SECONDS = 300
|
| 58 |
API_CACHE_MAX_ENTRIES = 500
|
| 59 |
SEARCH_CACHE_MAX_ENTRIES = 500
|
|
|
|
|
|
|
| 60 |
api_response_cache: dict[tuple, tuple[float, object]] = {}
|
| 61 |
api_response_cache_lock = threading.Lock()
|
| 62 |
search_response_cache: dict[tuple, tuple[float, object]] = {}
|
| 63 |
search_response_cache_lock = threading.Lock()
|
|
|
|
|
|
|
| 64 |
ZIP_TOKEN_TTL_SECONDS = 600
|
| 65 |
ZIP_TOKEN_MAX_ENTRIES = 32
|
| 66 |
zip_download_tokens: dict[str, tuple[float, list[str]]] = {}
|
|
@@ -1119,6 +1123,23 @@ def fulltext_search(q="", sources_filter=None, folders=None, min_size=None, max_
|
|
| 1119 |
def get_doc_storage_path(doc: dict) -> Path:
|
| 1120 |
return BASE_DIR / doc["storage_root"] / doc["storage_rel_path"]
|
| 1121 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1122 |
def get_folder_contents(source_slug: str, path: str) -> dict:
|
| 1123 |
source_browser = folder_browser_data.get(source_slug, {})
|
| 1124 |
if path in source_browser:
|
|
@@ -1412,7 +1433,7 @@ def api_preview(doc_id: str):
|
|
| 1412 |
file_path = get_doc_storage_path(rec)
|
| 1413 |
if not file_path.exists():
|
| 1414 |
return JSONResponse({"error": f"missing file: {file_path.name}"}, status_code=404)
|
| 1415 |
-
text =
|
| 1416 |
return JSONResponse({"doc_id": doc_id, "title": rec["display_name"], "path": rec["display_rel_path"], "source": rec["source_name"], "text": text})
|
| 1417 |
@app.get("/api/snippet/{doc_id}")
|
| 1418 |
|
|
@@ -1445,6 +1466,7 @@ def api_download(doc_id: str):
|
|
| 1445 |
media_type="text/plain; charset=utf-8",
|
| 1446 |
filename=f"{rec['display_name']}.txt",
|
| 1447 |
content_disposition_type="attachment",
|
|
|
|
| 1448 |
)
|
| 1449 |
@app.post("/api/zip")
|
| 1450 |
|
|
@@ -1505,14 +1527,22 @@ app.mount("/static", StaticFiles(directory=str(BASE_DIR / "static"), html=True),
|
|
| 1505 |
app.mount("/icons", StaticFiles(directory=str(BASE_DIR / "static/icons")), name="icons")
|
| 1506 |
app.mount("/data/initial", StaticFiles(directory=str(BASE_DIR / "data/initial")), name="initial-data")
|
| 1507 |
app.mount("/data/sidebar", StaticFiles(directory=str(BASE_DIR / "data/sidebar")), name="sidebar-data")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1508 |
@app.get("/manifest.json")
|
| 1509 |
|
| 1510 |
def serve_manifest():
|
| 1511 |
-
return JSONResponse(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1512 |
@app.get("/sw.js")
|
| 1513 |
|
| 1514 |
def serve_sw():
|
| 1515 |
-
return PlainTextResponse((
|
| 1516 |
|
| 1517 |
def refresh_fulltext_loop() -> None:
|
| 1518 |
while not fulltext_refresh_stop.wait(FULLTEXT_REFRESH_INTERVAL_SECONDS):
|
|
@@ -1544,6 +1574,7 @@ def sidebar_payload_for_path(rest_of_path: str) -> str | None:
|
|
| 1544 |
return None
|
| 1545 |
return payload_path.read_text(encoding="utf-8")
|
| 1546 |
|
|
|
|
| 1547 |
def render_index_html(rest_of_path: str = "") -> str:
|
| 1548 |
html = (BASE_DIR / "static/index.html").read_text(encoding="utf-8")
|
| 1549 |
initial_payload = initial_payload_for_path(rest_of_path)
|
|
|
|
| 57 |
SEARCH_CACHE_TTL_SECONDS = 300
|
| 58 |
API_CACHE_MAX_ENTRIES = 500
|
| 59 |
SEARCH_CACHE_MAX_ENTRIES = 500
|
| 60 |
+
PREVIEW_CACHE_MAX_ENTRIES = 64
|
| 61 |
+
PREVIEW_CACHE_MAX_ENTRY_BYTES = 2 * 1024 * 1024
|
| 62 |
api_response_cache: dict[tuple, tuple[float, object]] = {}
|
| 63 |
api_response_cache_lock = threading.Lock()
|
| 64 |
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]]] = {}
|
|
|
|
| 1123 |
def get_doc_storage_path(doc: dict) -> Path:
|
| 1124 |
return BASE_DIR / doc["storage_root"] / doc["storage_rel_path"]
|
| 1125 |
|
| 1126 |
+
def read_preview_text(doc_id: str, file_path: Path) -> str:
|
| 1127 |
+
with preview_text_cache_lock:
|
| 1128 |
+
cached = preview_text_cache.get(doc_id)
|
| 1129 |
+
if cached is not None:
|
| 1130 |
+
return cached
|
| 1131 |
+
try:
|
| 1132 |
+
size = file_path.stat().st_size
|
| 1133 |
+
except OSError:
|
| 1134 |
+
size = PREVIEW_CACHE_MAX_ENTRY_BYTES + 1
|
| 1135 |
+
text = file_path.read_text(encoding="utf-8", errors="ignore")
|
| 1136 |
+
if size <= PREVIEW_CACHE_MAX_ENTRY_BYTES:
|
| 1137 |
+
with preview_text_cache_lock:
|
| 1138 |
+
preview_text_cache[doc_id] = text
|
| 1139 |
+
while len(preview_text_cache) > PREVIEW_CACHE_MAX_ENTRIES:
|
| 1140 |
+
preview_text_cache.pop(next(iter(preview_text_cache)), None)
|
| 1141 |
+
return text
|
| 1142 |
+
|
| 1143 |
def get_folder_contents(source_slug: str, path: str) -> dict:
|
| 1144 |
source_browser = folder_browser_data.get(source_slug, {})
|
| 1145 |
if path in source_browser:
|
|
|
|
| 1433 |
file_path = get_doc_storage_path(rec)
|
| 1434 |
if not file_path.exists():
|
| 1435 |
return JSONResponse({"error": f"missing file: {file_path.name}"}, status_code=404)
|
| 1436 |
+
text = read_preview_text(doc_id, file_path)
|
| 1437 |
return JSONResponse({"doc_id": doc_id, "title": rec["display_name"], "path": rec["display_rel_path"], "source": rec["source_name"], "text": text})
|
| 1438 |
@app.get("/api/snippet/{doc_id}")
|
| 1439 |
|
|
|
|
| 1466 |
media_type="text/plain; charset=utf-8",
|
| 1467 |
filename=f"{rec['display_name']}.txt",
|
| 1468 |
content_disposition_type="attachment",
|
| 1469 |
+
headers={"Content-Encoding": "identity"},
|
| 1470 |
)
|
| 1471 |
@app.post("/api/zip")
|
| 1472 |
|
|
|
|
| 1527 |
app.mount("/icons", StaticFiles(directory=str(BASE_DIR / "static/icons")), name="icons")
|
| 1528 |
app.mount("/data/initial", StaticFiles(directory=str(BASE_DIR / "data/initial")), name="initial-data")
|
| 1529 |
app.mount("/data/sidebar", StaticFiles(directory=str(BASE_DIR / "data/sidebar")), name="sidebar-data")
|
| 1530 |
+
@lru_cache(maxsize=1)
|
| 1531 |
+
def _manifest_payload() -> dict:
|
| 1532 |
+
return json.loads((BASE_DIR / "static/manifest.json").read_text(encoding="utf-8"))
|
| 1533 |
+
|
| 1534 |
@app.get("/manifest.json")
|
| 1535 |
|
| 1536 |
def serve_manifest():
|
| 1537 |
+
return JSONResponse(_manifest_payload())
|
| 1538 |
+
@lru_cache(maxsize=1)
|
| 1539 |
+
def _sw_source() -> str:
|
| 1540 |
+
return (BASE_DIR / "static/sw.js").read_text(encoding="utf-8")
|
| 1541 |
+
|
| 1542 |
@app.get("/sw.js")
|
| 1543 |
|
| 1544 |
def serve_sw():
|
| 1545 |
+
return PlainTextResponse(_sw_source(), media_type="application/javascript")
|
| 1546 |
|
| 1547 |
def refresh_fulltext_loop() -> None:
|
| 1548 |
while not fulltext_refresh_stop.wait(FULLTEXT_REFRESH_INTERVAL_SECONDS):
|
|
|
|
| 1574 |
return None
|
| 1575 |
return payload_path.read_text(encoding="utf-8")
|
| 1576 |
|
| 1577 |
+
@lru_cache(maxsize=64)
|
| 1578 |
def render_index_html(rest_of_path: str = "") -> str:
|
| 1579 |
html = (BASE_DIR / "static/index.html").read_text(encoding="utf-8")
|
| 1580 |
initial_payload = initial_payload_for_path(rest_of_path)
|