import asyncio import os import os.path import shutil import time import uuid from typing import AsyncIterator, Optional import aiofiles import aiofiles.os import aiofiles.ospath import sentry_sdk from fastapi import APIRouter, FastAPI, File, Form, HTTPException, UploadFile, status from fastapi.responses import StreamingResponse from fastapi_utils.tasks import repeat_every from config import env_config if env_config.SENTRY_DSN: sentry_sdk.init( env_config.SENTRY_DSN, ) router = APIRouter(tags=["converter"]) @router.post("/") async def convert( file: Optional[UploadFile] = File(None), format: Optional[str] = Form(None), ): if file is None or format is None: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="File and format required!" ) format_lower = format.lower() if format_lower not in ["epub", "mobi"]: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Bad format!" ) temp_uuid = uuid.uuid1() temp_filename = str(temp_uuid) + ".fb2" converted_temp_filename = str(temp_uuid) + "." + format_lower try: async with aiofiles.open(temp_filename, "wb") as f: while content := await file.read(1024): if isinstance(content, str): content = content.encode() await f.write(content) proc = await asyncio.create_subprocess_exec( "./bin/fb2c", "convert", "--to", format, temp_filename, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) _, stderr = await proc.communicate() finally: await aiofiles.os.remove(temp_filename) if proc.returncode != 0 or len(stderr) != 0: try: await aiofiles.os.remove(converted_temp_filename) except FileNotFoundError: pass raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Can't convert!" ) async def result_iterator() -> AsyncIterator[bytes]: try: async with aiofiles.open(converted_temp_filename, "rb") as f: while data := await f.read(2048): yield data finally: await aiofiles.os.remove(converted_temp_filename) return StreamingResponse(result_iterator()) @router.get("/healthcheck") async def healthcheck(): return "Ok!" app = FastAPI() app.include_router(router) @app.on_event("startup") @repeat_every(seconds=5 * 60, raise_exceptions=True) def remove_temp_files(): current_time = time.time() try: os.remove("./conversion.log") except IOError: pass for f in os.listdir("/tmp/"): target_path = f"/tmp/{f}" is_file = os.path.isfile(target_path) try: creation_time = os.path.getctime(target_path) except FileNotFoundError: continue if (current_time - creation_time) // 3600 >= 3: if is_file: os.remove(target_path) else: shutil.rmtree(target_path)