Initial commit
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
import logging
|
||||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from fastapi import BackgroundTasks, FastAPI, Form, Request
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
import config
|
||||
import db
|
||||
import sync
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s — %(message)s")
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
_scheduler = BackgroundScheduler(timezone="UTC")
|
||||
|
||||
|
||||
def _reschedule_auto_sync() -> None:
|
||||
_scheduler.remove_all_jobs()
|
||||
try:
|
||||
interval = int(db.get_setting("scheduler_interval_minutes", "0") or "0")
|
||||
except ValueError:
|
||||
interval = 0
|
||||
if interval > 0:
|
||||
_scheduler.add_job(sync.run_sync, "interval", minutes=interval, id="auto_sync")
|
||||
log.info("Auto-sync scheduled every %d minute(s)", interval)
|
||||
else:
|
||||
log.info("Auto-sync disabled")
|
||||
|
||||
|
||||
def next_run_time() -> str | None:
|
||||
job = _scheduler.get_job("auto_sync")
|
||||
if job and job.next_run_time:
|
||||
return job.next_run_time.strftime("%Y-%m-%d %H:%M UTC")
|
||||
return None
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
db.init_db()
|
||||
_scheduler.start()
|
||||
_reschedule_auto_sync()
|
||||
yield
|
||||
_scheduler.shutdown(wait=False)
|
||||
|
||||
|
||||
app = FastAPI(title="CalibreSync", lifespan=lifespan)
|
||||
app.mount("/static", StaticFiles(directory=Path(__file__).parent / "static"), name="static")
|
||||
templates = Jinja2Templates(directory=Path(__file__).parent / "templates")
|
||||
|
||||
|
||||
# --- Dashboard ---
|
||||
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
async def dashboard(request: Request):
|
||||
stats = db.get_stats()
|
||||
runs = [dict(r) for r in db.get_recent_runs(10)]
|
||||
zips = [dict(z) for z in db.get_recent_zips(20)]
|
||||
interval = int(db.get_setting("scheduler_interval_minutes", "0") or "0")
|
||||
return templates.TemplateResponse(
|
||||
"index.html",
|
||||
{
|
||||
"request": request,
|
||||
"stats": stats,
|
||||
"runs": runs,
|
||||
"zips": zips,
|
||||
"sync_running": sync.is_running(),
|
||||
"next_run": next_run_time(),
|
||||
"interval": interval,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
# --- Books ---
|
||||
|
||||
@app.get("/books", response_class=HTMLResponse)
|
||||
async def books_page(request: Request, page: int = 1):
|
||||
per_page = 50
|
||||
offset = (page - 1) * per_page
|
||||
books = [dict(b) for b in db.get_books(limit=per_page, offset=offset)]
|
||||
total = db.get_books_count()
|
||||
pages = max(1, (total + per_page - 1) // per_page)
|
||||
return templates.TemplateResponse(
|
||||
"books.html",
|
||||
{"request": request, "books": books, "page": page, "pages": pages, "total": total},
|
||||
)
|
||||
|
||||
|
||||
# --- Settings ---
|
||||
|
||||
@app.get("/settings", response_class=HTMLResponse)
|
||||
async def settings_page(request: Request):
|
||||
s = db.get_all_settings()
|
||||
has_key = bool(s.get("sftp_key", "").strip())
|
||||
return templates.TemplateResponse(
|
||||
"settings.html",
|
||||
{"request": request, "s": s, "has_key": has_key},
|
||||
)
|
||||
|
||||
|
||||
@app.post("/settings")
|
||||
async def save_settings(
|
||||
request: Request,
|
||||
sftp_host: str = Form(""),
|
||||
sftp_port: str = Form("22"),
|
||||
sftp_user: str = Form(""),
|
||||
sftp_auth_method: str = Form("key"),
|
||||
sftp_key: str = Form(""),
|
||||
sftp_password: str = Form(""),
|
||||
sftp_remote_path: str = Form(""),
|
||||
calibre_url: str = Form(""),
|
||||
calibre_user: str = Form(""),
|
||||
calibre_pass: str = Form(""),
|
||||
local_work_dir: str = Form("/tmp/calibresync"),
|
||||
scheduler_interval_minutes: str = Form("0"),
|
||||
):
|
||||
config.save({
|
||||
"sftp_host": sftp_host,
|
||||
"sftp_port": sftp_port,
|
||||
"sftp_user": sftp_user,
|
||||
"sftp_auth_method": sftp_auth_method,
|
||||
"sftp_key": sftp_key,
|
||||
"sftp_password": sftp_password,
|
||||
"sftp_remote_path": sftp_remote_path,
|
||||
"calibre_url": calibre_url,
|
||||
"calibre_user": calibre_user,
|
||||
"calibre_pass": calibre_pass,
|
||||
"local_work_dir": local_work_dir,
|
||||
"scheduler_interval_minutes": scheduler_interval_minutes,
|
||||
})
|
||||
_reschedule_auto_sync()
|
||||
return RedirectResponse("/settings?saved=1", status_code=303)
|
||||
|
||||
|
||||
# --- Sync trigger ---
|
||||
|
||||
@app.post("/sync")
|
||||
async def trigger_sync(background_tasks: BackgroundTasks):
|
||||
if sync.is_running():
|
||||
return RedirectResponse("/?already_running=1", status_code=303)
|
||||
background_tasks.add_task(sync.run_sync)
|
||||
return RedirectResponse("/?started=1", status_code=303)
|
||||
|
||||
|
||||
# --- JSON status API ---
|
||||
|
||||
@app.get("/api/status")
|
||||
async def api_status():
|
||||
return {
|
||||
"sync_running": sync.is_running(),
|
||||
"next_run": next_run_time(),
|
||||
"stats": db.get_stats(),
|
||||
"last_run": [dict(r) for r in db.get_recent_runs(1)],
|
||||
}
|
||||
Reference in New Issue
Block a user