From 4291c845d8e0fe11ec94ba1f90f93cb75cd4aaf7 Mon Sep 17 00:00:00 2001 From: Tris Forster Date: Mon, 29 Sep 2025 22:44:17 +1000 Subject: [PATCH] Rewrite using fastapi --- .gitignore | 2 + Dockerfile | 12 + README.md | 0 app/static/css/search-source.css | 14 + app/static/css/search.css | 368 ++++++++++++++++++++++ app/static/fonts/beyond_the_mountains.ttf | Bin 0 -> 59224 bytes app/static/images/squirrel_32.png | Bin 0 -> 4552 bytes app/templates/base.html | 13 + app/templates/home.html | 17 + app/templates/search_results.html | 54 ++++ pyproject.toml | 19 ++ squirrel/__init__.py | 0 squirrel/api.py | 101 ++++++ squirrel/config.py | 12 + squirrel/server.py | 9 + 15 files changed, 621 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 app/static/css/search-source.css create mode 100644 app/static/css/search.css create mode 100644 app/static/fonts/beyond_the_mountains.ttf create mode 100644 app/static/images/squirrel_32.png create mode 100644 app/templates/base.html create mode 100644 app/templates/home.html create mode 100644 app/templates/search_results.html create mode 100644 pyproject.toml create mode 100644 squirrel/__init__.py create mode 100644 squirrel/api.py create mode 100644 squirrel/config.py create mode 100644 squirrel/server.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8d35cb3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +*.pyc diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..965f289 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM recoll + +RUN apt-get install breeze-icon-theme + +COPY . /opt/squirrel +WORKDIR /opt/squirrel + +RUN pip install --break-system-packages -e . + +EXPOSE 8000 + +ENTRYPOINT ["/bin/bash"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/app/static/css/search-source.css b/app/static/css/search-source.css new file mode 100644 index 0000000..59dcbe2 --- /dev/null +++ b/app/static/css/search-source.css @@ -0,0 +1,14 @@ +@import "tailwindcss"; + +SPAN.rclmatch { + font-weight: bold; + color: var(--color-red-900); +} +@font-face { + font-family: beyondTheMountains; /* set name */ + src: url(/static/fonts/beyond_the_mountains.ttf); /* url of the font */ +} + +.font-beyond-the-mountains { + font-family: beyondTheMountains; +} diff --git a/app/static/css/search.css b/app/static/css/search.css new file mode 100644 index 0000000..37d68df --- /dev/null +++ b/app/static/css/search.css @@ -0,0 +1,368 @@ +/*! tailwindcss v4.0.9 | MIT License | https://tailwindcss.com */ +@layer theme, base, components, utilities; +@layer theme { + :root, :host { + --font-sans: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', + 'Noto Color Emoji'; + --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', + monospace; + --color-red-900: oklch(0.396 0.141 25.723); + --color-slate-600: oklch(0.446 0.043 257.281); + --color-gray-700: oklch(0.373 0.034 259.733); + --spacing: 0.25rem; + --text-sm: 0.875rem; + --text-sm--line-height: calc(1.25 / 0.875); + --text-xl: 1.25rem; + --text-xl--line-height: calc(1.75 / 1.25); + --text-2xl: 1.5rem; + --text-2xl--line-height: calc(2 / 1.5); + --text-8xl: 6rem; + --text-8xl--line-height: 1; + --leading-tight: 1.25; + --default-font-family: var(--font-sans); + --default-font-feature-settings: var(--font-sans--font-feature-settings); + --default-font-variation-settings: var(--font-sans--font-variation-settings); + --default-mono-font-family: var(--font-mono); + --default-mono-font-feature-settings: var(--font-mono--font-feature-settings); + --default-mono-font-variation-settings: var(--font-mono--font-variation-settings); + } +} +@layer base { + *, ::after, ::before, ::backdrop, ::file-selector-button { + box-sizing: border-box; + margin: 0; + padding: 0; + border: 0 solid; + } + html, :host { + line-height: 1.5; + -webkit-text-size-adjust: 100%; + tab-size: 4; + font-family: var( --default-font-family, ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji' ); + font-feature-settings: var(--default-font-feature-settings, normal); + font-variation-settings: var(--default-font-variation-settings, normal); + -webkit-tap-highlight-color: transparent; + } + body { + line-height: inherit; + } + hr { + height: 0; + color: inherit; + border-top-width: 1px; + } + abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + } + h1, h2, h3, h4, h5, h6 { + font-size: inherit; + font-weight: inherit; + } + a { + color: inherit; + -webkit-text-decoration: inherit; + text-decoration: inherit; + } + b, strong { + font-weight: bolder; + } + code, kbd, samp, pre { + font-family: var( --default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace ); + font-feature-settings: var(--default-mono-font-feature-settings, normal); + font-variation-settings: var(--default-mono-font-variation-settings, normal); + font-size: 1em; + } + small { + font-size: 80%; + } + sub, sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; + } + sub { + bottom: -0.25em; + } + sup { + top: -0.5em; + } + table { + text-indent: 0; + border-color: inherit; + border-collapse: collapse; + } + :-moz-focusring { + outline: auto; + } + progress { + vertical-align: baseline; + } + summary { + display: list-item; + } + ol, ul, menu { + list-style: none; + } + img, svg, video, canvas, audio, iframe, embed, object { + display: block; + vertical-align: middle; + } + img, video { + max-width: 100%; + height: auto; + } + button, input, select, optgroup, textarea, ::file-selector-button { + font: inherit; + font-feature-settings: inherit; + font-variation-settings: inherit; + letter-spacing: inherit; + color: inherit; + border-radius: 0; + background-color: transparent; + opacity: 1; + } + :where(select:is([multiple], [size])) optgroup { + font-weight: bolder; + } + :where(select:is([multiple], [size])) optgroup option { + padding-inline-start: 20px; + } + ::file-selector-button { + margin-inline-end: 4px; + } + ::placeholder { + opacity: 1; + color: color-mix(in oklab, currentColor 50%, transparent); + } + textarea { + resize: vertical; + } + ::-webkit-search-decoration { + -webkit-appearance: none; + } + ::-webkit-date-and-time-value { + min-height: 1lh; + text-align: inherit; + } + ::-webkit-datetime-edit { + display: inline-flex; + } + ::-webkit-datetime-edit-fields-wrapper { + padding: 0; + } + ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field { + padding-block: 0; + } + :-moz-ui-invalid { + box-shadow: none; + } + button, input:where([type='button'], [type='reset'], [type='submit']), ::file-selector-button { + appearance: button; + } + ::-webkit-inner-spin-button, ::-webkit-outer-spin-button { + height: auto; + } + [hidden]:where(:not([hidden='until-found'])) { + display: none !important; + } +} +@layer utilities { + .m-2 { + margin: calc(var(--spacing) * 2); + } + .m-4 { + margin: calc(var(--spacing) * 4); + } + .mx-10 { + margin-inline: calc(var(--spacing) * 10); + } + .mx-auto { + margin-inline: auto; + } + .my-10 { + margin-block: calc(var(--spacing) * 10); + } + .mt-50 { + margin-top: calc(var(--spacing) * 50); + } + .block { + display: block; + } + .flex { + display: flex; + } + .h-16 { + height: calc(var(--spacing) * 16); + } + .w-1\/2 { + width: calc(1/2 * 100%); + } + .w-2\/3 { + width: calc(2/3 * 100%); + } + .w-16 { + width: calc(var(--spacing) * 16); + } + .w-full { + width: 100%; + } + .grow { + flex-grow: 1; + } + .appearance-none { + appearance: none; + } + .items-center { + align-items: center; + } + .rounded-full { + border-radius: calc(infinity * 1px); + } + .border-b { + border-bottom-style: var(--tw-border-style); + border-bottom-width: 1px; + } + .border-red-900 { + border-color: var(--color-red-900); + } + .border-slate-600 { + border-color: var(--color-slate-600); + } + .p-4 { + padding: calc(var(--spacing) * 4); + } + .px-3 { + padding-inline: calc(var(--spacing) * 3); + } + .px-5 { + padding-inline: calc(var(--spacing) * 5); + } + .py-2 { + padding-block: calc(var(--spacing) * 2); + } + .py-3 { + padding-block: calc(var(--spacing) * 3); + } + .pb-2 { + padding-bottom: calc(var(--spacing) * 2); + } + .text-center { + text-align: center; + } + .text-right { + text-align: right; + } + .text-2xl { + font-size: var(--text-2xl); + line-height: var(--tw-leading, var(--text-2xl--line-height)); + } + .text-8xl { + font-size: var(--text-8xl); + line-height: var(--tw-leading, var(--text-8xl--line-height)); + } + .text-sm { + font-size: var(--text-sm); + line-height: var(--tw-leading, var(--text-sm--line-height)); + } + .text-xl { + font-size: var(--text-xl); + line-height: var(--tw-leading, var(--text-xl--line-height)); + } + .leading-tight { + --tw-leading: var(--leading-tight); + line-height: var(--leading-tight); + } + .text-gray-700 { + color: var(--color-gray-700); + } + .text-red-900 { + color: var(--color-red-900); + } + .shadow { + --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + .focus\:outline-none { + &:focus { + --tw-outline-style: none; + outline-style: none; + } + } +} +SPAN.rclmatch { + font-weight: bold; + color: var(--color-red-900); +} +@font-face { + font-family: beyondTheMountains; + src: url(/static/fonts/beyond_the_mountains.ttf); +} +.font-beyond-the-mountains { + font-family: beyondTheMountains; +} +@property --tw-border-style { + syntax: "*"; + inherits: false; + initial-value: solid; +} +@property --tw-leading { + syntax: "*"; + inherits: false; +} +@property --tw-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-shadow-color { + syntax: "*"; + inherits: false; +} +@property --tw-inset-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-inset-shadow-color { + syntax: "*"; + inherits: false; +} +@property --tw-ring-color { + syntax: "*"; + inherits: false; +} +@property --tw-ring-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-inset-ring-color { + syntax: "*"; + inherits: false; +} +@property --tw-inset-ring-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-ring-inset { + syntax: "*"; + inherits: false; +} +@property --tw-ring-offset-width { + syntax: ""; + inherits: false; + initial-value: 0px; +} +@property --tw-ring-offset-color { + syntax: "*"; + inherits: false; + initial-value: #fff; +} +@property --tw-ring-offset-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} diff --git a/app/static/fonts/beyond_the_mountains.ttf b/app/static/fonts/beyond_the_mountains.ttf new file mode 100644 index 0000000000000000000000000000000000000000..c8e5c1542c440d83836bbef444648531b5aad1fa GIT binary patch literal 59224 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 Ar~m)} literal 0 HcmV?d00001 diff --git a/app/static/images/squirrel_32.png b/app/static/images/squirrel_32.png new file mode 100644 index 0000000000000000000000000000000000000000..4039a2de96a79b3d8bd8d32a8164e0a82eefee0b GIT binary patch literal 4552 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 me*Py13Z*$XJG&MB@A5ZG*y!X`M76E}0000 + + Squirrel + + + + {% block scripts %}{% endblock %} + + + + {% block content %}CONTENT{% endblock %} + + diff --git a/app/templates/home.html b/app/templates/home.html new file mode 100644 index 0000000..8025ef5 --- /dev/null +++ b/app/templates/home.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} + +{% block content %} + +
+ + +{% endblock %} diff --git a/app/templates/search_results.html b/app/templates/search_results.html new file mode 100644 index 0000000..d5776bd --- /dev/null +++ b/app/templates/search_results.html @@ -0,0 +1,54 @@ +{% extends "base.html" %} + +{% block content %} +
+
+ Squirrel +
+ +
+
+ Foo +
+
+ +
+
+

+

Page of , + results.

+
+ +
+ +
+ + +
+
+ + +{% endblock %} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..667053f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,19 @@ +[project] +name = "squirrel" +version = "0.3.0" +description = "Find your nuts" +authors = [ + {name = "Your Name",email = "you@example.com"} +] +readme = "README.md" +requires-python = ">=3.10" +dependencies = [ + "fastapi[standard] (>=0.118.0,<0.119.0)" +] + +[project.scripts] +"squirrel" = "squirrel.server:daemon" + +[build-system] +requires = ["poetry-core>=2.0.0,<3.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/squirrel/__init__.py b/squirrel/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/squirrel/api.py b/squirrel/api.py new file mode 100644 index 0000000..92ad5e2 --- /dev/null +++ b/squirrel/api.py @@ -0,0 +1,101 @@ +from math import ceil +from fastapi import FastAPI, Request +from fastapi.staticfiles import StaticFiles +from fastapi.templating import Jinja2Templates +from typing import Any +import logging +import subprocess + +from recoll import recoll + +from fastapi import BackgroundTasks + +# from .config import settings + +logger = logging.getLogger() +logging.basicConfig(level=logging.DEBUG) + +DOC_FOLDER = "/mnt/docs" + +STRIP_CHARS = len(DOC_FOLDER) + len("file:///") + + +class Recoll(object): + def __init__(self): + self._indexing = False + + def reindex(self): + if self._indexing: + raise RuntimeError("Reindex already underway") + self._indexing = True + subprocess.call(["recollindex"]) + self._indexing = False + + +repo = Recoll() + +app = FastAPI() +app.mount("/static", StaticFiles(directory="app/static")) +app.mount("/docs", StaticFiles(directory=DOC_FOLDER)) +app.mount( + "/icons", + StaticFiles(directory="/usr/share/icons/breeze/mimetypes/32", follow_symlink=True), +) + +templates = Jinja2Templates(directory="app/templates") + + +@app.get("/") +def home(request: Request): + ctx = {"request": request} + return templates.TemplateResponse("home.html", context=ctx) + + +@app.get("/search") +def search_results(request: Request, q: str, count: int = 20, page: int = 1): + ctx: dict[str, Any] = {"request": request} + ctx["search"] = search(q, count, page) + return templates.TemplateResponse("search_results.html", context=ctx) + + +@app.get("/api/reindex") +async def get_reindex(tasks: BackgroundTasks) -> dict[str, str]: + tasks.add_task(repo.reindex) + return {"message": "Recoll index started"} + + +@app.get("/api/search") +def get_search(q: str, count: int = 20, page: int = 1) -> dict[str, str]: + return search(q, count, page) + + +def search(q: str, count: int, page: int): + db = recoll.connect() + query = db.query() + nres = query.execute(q) + result = { + "query": q, + "count": count, + "page": page, + "pages": ceil(nres / count), + "total": nres, + "xquery": query.getxquery(), + "results": [], + } + if page > result["pages"] or page < 1: + return result + + query.scroll((page - 1) * count) + docs = query.fetchmany(count) + for doc in docs: + d = dict(doc) + d["snippets"] = query.getsnippets(doc, ctxwords=20) + d["fpath"] = d["url"][STRIP_CHARS:] + result["results"].append(d) + return result + + +@app.get("/api/info") +def get_info(): + db = recoll.connect() + return dir(db) diff --git a/squirrel/config.py b/squirrel/config.py new file mode 100644 index 0000000..c4d81f3 --- /dev/null +++ b/squirrel/config.py @@ -0,0 +1,12 @@ +from pydantic_settings import BaseSettings +from os.path import abspath + +class Settings(BaseSettings): + app_name: str = "Squirrel" + items_per_user: int = 50 + archive: str = "./archive" + + +settings = Settings() + +settings.archive = abspath(settings.archive) diff --git a/squirrel/server.py b/squirrel/server.py new file mode 100644 index 0000000..7d7fbc4 --- /dev/null +++ b/squirrel/server.py @@ -0,0 +1,9 @@ +import uvicorn + + +def daemon(reload=False): + uvicorn.run("squirrel.api:app", host="0.0.0.0", port=8000, reload=reload) + + +if __name__ == "__main__": + daemon(True)