You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Migrate GeoReel from a monolithic desktop app to a client-server architecture:
FastAPI backend + PySide6 GUI client.
Goals
georeel (GUI) remains the user-facing entry point
All pipeline logic moves to a FastAPI service (georeel-server)
GUI communicates with service via REST (HTTP/JSON + multipart file uploads)
Service can run standalone (georeel-server) or be spawned as a subprocess by GUI
Long-running pipeline stages report progress via job polling or SSE
Default Port
8765 — fixed, configurable via --port on georeel-server and --server-port on georeel.
Server Startup Logic (GUI side)
GUI starts
│
├─ GET http://localhost:8765/health
│ ├─ 200 OK → attach to existing server (use port 8765)
│ └─ connection refused
│ └─ bind random OS port
│ └─ spawn subprocess: georeel-server --port <random>
│ └─ poll /health until ready (timeout 10 s)
│ └─ use random port for session
│
└─ at GUI exit: if subprocess was spawned → terminate it
External georeel-server (user-started) takes precedence. GUI never kills a
pre-existing server.
The server manages a workspace concept: uploaded files are stored server-side and
referenced by workspace_id. Since GUI and server run on the same machine, local file
paths are also accepted as an alternative to upload (avoids copying large files).
SSE stream: data: {"progress":42,"message":"..."} per tick
Job Registry (server/jobs.py)
@dataclassclassJobRecord:
job_id: strstatus: Literal["pending", "running", "done", "error"]
progress: int# 0–100message: strresult: Any# set when doneerror: str|Nonecancel_event: asyncio.Eventcreated_at: datetime
In-memory dict, keyed by UUID
Long-running pipeline functions accept a progress_cb(pct, msg) callback
Background tasks (FastAPI BackgroundTasks or asyncio.create_task) run pipeline in thread pool
Completed jobs retained for 30 min, then evicted
DELETE /jobs/{job_id} sets cancel_event; pipeline stages check it periodically
Progress Reporting Strategy
Two options exposed to clients:
Polling — GET /jobs/{job_id} every ~500 ms. Simple, works everywhere.
SSE — GET /jobs/{job_id}/events. Real-time stream, closes when job ends. GUI uses this.
GUI replaces current QThread workers with:
HTTP POST to start job → get job_id
SSE connection to /jobs/{job_id}/events → update progress bar
On done event: fetch result endpoint
GUI Refactor Plan
ui/server_client.py
Thin wrapper over httpx.AsyncClient:
Base URL from ServerManager
JSON serialisation helpers
Retry on 503 (server still starting)
Raises typed exceptions mapped from HTTP error codes
ui/server_manager.py
classServerManager:
asyncdefconnect_or_spawn(self, preferred_port=8765) ->int:
"""Try preferred_port; if unavailable, spawn subprocess on random port."""asyncdefshutdown(self):
"""Terminate subprocess if we own it."""
Uses asyncio.subprocess to launch georeel-server --port <port>.
Worker thread replacement
Current ScenePrepWorker and KeyframeCalcWorker (QThread) are replaced by:
QNetworkAccessManager or httpx async calls wrapped in asyncio-aware Qt integration
Progress dialogs subscribe to SSE job events via a lightweight Qt-compatible SSE reader
Existing ui/ dialogs keep their signatures; only the trigger mechanism changes
from "call core function in thread" to "POST to server + poll SSE".
New Dependencies
# runtime (server)
fastapi>=0.115
uvicorn[standard]>=0.32
httpx>=0.28 # GUI HTTP client (already useful for nominatim/osrm, add here)
pydantic>=2.10 # already pulled by fastapi
# dev/test
httpx # TestClient for API tests
pytest-asyncio>=0.25
Add via uv add fastapi uvicorn httpx.
Migration Phases
Phase 1 — Server scaffold (no GUI changes)
Create src/georeel/server/ package
Implement app.py, jobs.py, health route
Add georeel-server entry point with --host/--port args
Write API tests for health endpoint
Verify basedpyright passes
Phase 2 — Sync/fast pipeline endpoints
/gpx/parse, /gpx/clean
/photos/upload, /photos/match
/camera/keyframes
Pydantic models for all request/response schemas
Tests for each route (target: routes covered ≥ 85%)