Add explicit Documents & Texts tab and native Image-to-PDF converter (/api/convert_pdf)

This commit is contained in:
drjones
2026-07-20 21:52:36 -07:00
parent 2d3ef6a06c
commit 031fccb3ff
2 changed files with 154 additions and 0 deletions

View File

@@ -1407,6 +1407,95 @@ class Handler(BaseHTTPRequestHandler):
paths = [r["path"] for r in filtered[:MAX_SELECT]]
self._json({"paths": paths, "total": len(filtered), "capped": len(filtered) > MAX_SELECT})
elif self.path.startswith("/api/documents"):
parsed_url = urllib.parse.urlparse(self.path)
params = urllib.parse.parse_qs(parsed_url.query)
try:
limit = max(1, min(200, int(params.get("limit", ["60"])[0])))
except ValueError:
limit = 60
try:
offset = max(0, int(params.get("offset", ["0"])[0]))
except ValueError:
offset = 0
records = load_search_index()
docs = [r for r in records if r.get("category") == "Screenshots & Documents" or r.get("route") == "screenshot" or "text:" in (r.get("desc") or "").lower()]
total = len(docs)
page = [{k: v for k, v in r.items() if k not in ("_hay", "_mtime", "_ext")} for r in docs[offset:offset + limit]]
self._json({"results": page, "total": total, "offset": offset, "limit": limit})
elif self.path.startswith("/api/convert_pdf"):
parsed_url = urllib.parse.urlparse(self.path)
params = urllib.parse.parse_qs(parsed_url.query)
paths_param = params.get("paths", [""])[0] or params.get("path", [""])[0]
if not paths_param:
self._json({"error": "path parameter is required"}, 400); return
paths = [p for p in paths_param.split(",") if p and os.path.exists(p)]
if not paths:
self._json({"error": "files not found"}, 404); return
if len(paths) == 1:
src = paths[0]
base_name = os.path.splitext(os.path.basename(src))[0] + ".pdf"
tmp_pdf = tempfile.NamedTemporaryFile(suffix=".pdf", delete=False)
tmp_pdf.close()
try:
cmd = ["sips", "-s", "format", "pdf", src, "--out", tmp_pdf.name]
r = subprocess.run(cmd, capture_output=True, timeout=30)
if r.returncode != 0 or not os.path.exists(tmp_pdf.name):
raise RuntimeError(f"sips failed: {r.stderr.decode(errors='replace')}")
with open(tmp_pdf.name, "rb") as f:
pdf_data = f.read()
self.send_response(200)
self.send_header("Content-Type", "application/pdf")
self.send_header("Content-Disposition", f'attachment; filename="{base_name}"')
self.send_header("Content-Length", str(len(pdf_data)))
self.end_headers()
self.wfile.write(pdf_data)
except Exception as e:
self._json({"error": f"Failed converting image to PDF: {e}"}, 500)
finally:
try: os.remove(tmp_pdf.name)
except Exception: pass
else:
tmp_zip = tempfile.NamedTemporaryFile(suffix=".zip", delete=False)
tmp_zip.close()
tmp_dir = tempfile.mkdtemp(prefix="photon_pdf_zip_")
try:
used_names = set()
with zipfile.ZipFile(tmp_zip.name, "w", zipfile.ZIP_STORED, allowZip64=True) as zf:
for src in paths:
base = os.path.splitext(os.path.basename(src))[0]
pdf_name = base + ".pdf"
i = 1
while pdf_name in used_names:
pdf_name = f"{base}_{i}.pdf"
i += 1
used_names.add(pdf_name)
out_pdf = os.path.join(tmp_dir, pdf_name)
cmd = ["sips", "-s", "format", "pdf", src, "--out", out_pdf]
r = subprocess.run(cmd, capture_output=True, timeout=30)
if r.returncode == 0 and os.path.exists(out_pdf):
zf.write(out_pdf, arcname=pdf_name)
size = os.path.getsize(tmp_zip.name)
self.send_response(200)
self.send_header("Content-Type", "application/zip")
self.send_header("Content-Disposition", f'attachment; filename="photon_documents_pdf.zip"')
self.send_header("Content-Length", str(size))
self.end_headers()
with open(tmp_zip.name, "rb") as f:
while True:
chunk = f.read(1024 * 1024)
if not chunk: break
self.wfile.write(chunk)
except Exception as e:
self._json({"error": f"Failed converting batch PDFs: {e}"}, 500)
finally:
try: os.remove(tmp_zip.name)
except Exception: pass
try: shutil.rmtree(tmp_dir)
except Exception: pass
elif self.path.startswith("/api/search"):
parsed_url = urllib.parse.urlparse(self.path)
params = urllib.parse.parse_qs(parsed_url.query)