Ruff linting.
This commit is contained in:
+1
-1
@@ -30,7 +30,7 @@ def create_startup_box(
|
||||
):
|
||||
"""Create a framed startup box with server information."""
|
||||
title = f"Cista {cista.__version__}"
|
||||
listen = unix if unix else url
|
||||
listen = unix or url
|
||||
location = f"{folder} @ {listen}"
|
||||
lines = [title, location]
|
||||
# Auth line: Paskia <url> or Password, with optional Public suffix
|
||||
|
||||
+8
-8
@@ -151,7 +151,7 @@ def setup_docker(confdir: Path | None = None) -> int:
|
||||
logger.info("Building OnlyOffice image")
|
||||
build_cmd = ["docker", "build", "-t", "onlyoffice-cista", str(docker_dir)]
|
||||
logger.info("%s", " ".join(build_cmd))
|
||||
result = subprocess.run(build_cmd)
|
||||
result = subprocess.run(build_cmd, check=False, shell=False) # noqa: S603
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError("Failed to build OnlyOffice image")
|
||||
|
||||
@@ -173,19 +173,19 @@ def setup_docker(confdir: Path | None = None) -> int:
|
||||
"onlyoffice-cista",
|
||||
]
|
||||
logger.info("%s", " ".join(run_cmd))
|
||||
result = subprocess.run(run_cmd)
|
||||
result = subprocess.run(run_cmd, check=False, shell=False) # noqa: S603
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError("Failed to start OnlyOffice container")
|
||||
logger.info("OnlyOffice is running on http://localhost:8988")
|
||||
return 0
|
||||
|
||||
|
||||
async def is_available_async(timeout: float = 2.0) -> bool:
|
||||
async def is_available_async(request_timeout: float = 2.0) -> bool:
|
||||
"""Return True if the configured OnlyOffice Document Server is reachable."""
|
||||
url = _get_onlyoffice_url().rstrip("/") + "/ConvertService.ashx"
|
||||
client = get_httpx_client()
|
||||
try:
|
||||
response = await client.get(url, timeout=timeout)
|
||||
response = await client.get(url, timeout=request_timeout)
|
||||
return response.status_code in (200, 405)
|
||||
except Exception:
|
||||
return False
|
||||
@@ -252,7 +252,7 @@ def _build_jwt_token(payload: dict) -> str | None:
|
||||
return jwt.encode(payload, secret, algorithm="HS256")
|
||||
|
||||
|
||||
async def convert_to_png_async(file_path: Path, timeout: float = 5.0) -> bytes:
|
||||
async def convert_to_png_async(file_path: Path, request_timeout: float = 5.0) -> bytes:
|
||||
"""Convert *file_path* to PNG using OnlyOffice Document Server (async).
|
||||
|
||||
Returns the PNG bytes. Raises RuntimeError on failure.
|
||||
@@ -268,7 +268,7 @@ async def convert_to_png_async(file_path: Path, timeout: float = 5.0) -> bytes:
|
||||
payload = {
|
||||
"async": False,
|
||||
"filetype": suffix,
|
||||
"key": f"cista_{file_path.stat().st_mtime_ns}",
|
||||
"key": f"cista_{(await asyncio.to_thread(file_path.stat)).st_mtime_ns}",
|
||||
"outputtype": "png",
|
||||
"title": file_path.name,
|
||||
"url": doc_url,
|
||||
@@ -286,7 +286,7 @@ async def convert_to_png_async(file_path: Path, timeout: float = 5.0) -> bytes:
|
||||
convert_url,
|
||||
content=json.dumps(payload).encode(),
|
||||
headers=headers,
|
||||
timeout=timeout,
|
||||
timeout=request_timeout,
|
||||
)
|
||||
response.raise_for_status()
|
||||
body = response.content
|
||||
@@ -309,7 +309,7 @@ async def convert_to_png_async(file_path: Path, timeout: float = 5.0) -> bytes:
|
||||
logger.debug("OnlyOffice converted in %.2fs: %s", t_end - t_start, file_url)
|
||||
|
||||
# Download converted PNG
|
||||
png_response = await client.get(file_url, timeout=timeout)
|
||||
png_response = await client.get(file_url, timeout=request_timeout)
|
||||
png_response.raise_for_status()
|
||||
return png_response.content
|
||||
finally:
|
||||
|
||||
+16
-12
@@ -32,7 +32,6 @@ from cista import auth, config, onlyoffice, sharefs
|
||||
from cista.preview_worker import PreviewRequest, PreviewResponse
|
||||
from cista.util.filename import sanitize
|
||||
|
||||
|
||||
bp = Blueprint("preview", url_prefix="/preview")
|
||||
|
||||
|
||||
@@ -179,14 +178,14 @@ class _PreviewWorkerPool:
|
||||
_active_procs.add(proc)
|
||||
try:
|
||||
ready = await asyncio.wait_for(proc.stdout.readexactly(1), timeout=30.0)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError as err:
|
||||
with contextlib.suppress(ProcessLookupError):
|
||||
proc.kill()
|
||||
raise WorkerProtocolError("preview worker failed to become ready")
|
||||
except asyncio.IncompleteReadError:
|
||||
raise WorkerProtocolError("preview worker failed to become ready") from err
|
||||
except asyncio.IncompleteReadError as err:
|
||||
raise WorkerProtocolError(
|
||||
"preview worker exited before signalling readiness"
|
||||
)
|
||||
) from err
|
||||
if ready != b"\x01":
|
||||
raise WorkerProtocolError(f"preview worker ready signal invalid: {ready!r}")
|
||||
return _PreviewWorker(proc)
|
||||
@@ -420,11 +419,12 @@ class OOConversionManager:
|
||||
def __init__(self, max_concurrent: int = OO_MAX_CONCURRENT):
|
||||
self._semaphore = asyncio.Semaphore(max_concurrent)
|
||||
self._in_flight: dict[str, asyncio.Future[bytes]] = {}
|
||||
self._tasks: set[asyncio.Task[None]] = set()
|
||||
self._lock = asyncio.Lock()
|
||||
|
||||
async def convert(self, filepath: Path) -> bytes:
|
||||
"""Return PNG bytes for *filepath*, deduplicating concurrent requests."""
|
||||
stat = filepath.stat()
|
||||
stat = await asyncio.to_thread(filepath.stat)
|
||||
key = f"{filepath}:{stat.st_mtime_ns}"
|
||||
|
||||
async with self._lock:
|
||||
@@ -433,7 +433,9 @@ class OOConversionManager:
|
||||
else:
|
||||
future = asyncio.get_running_loop().create_future()
|
||||
self._in_flight[key] = future
|
||||
asyncio.create_task(self._do_convert(filepath, key, future))
|
||||
task = asyncio.create_task(self._do_convert(filepath, key, future))
|
||||
self._tasks.add(task)
|
||||
task.add_done_callback(self._tasks.discard)
|
||||
|
||||
return await future
|
||||
|
||||
@@ -442,7 +444,9 @@ class OOConversionManager:
|
||||
) -> None:
|
||||
try:
|
||||
async with self._semaphore:
|
||||
png_bytes = await onlyoffice.convert_to_png_async(filepath, timeout=5.0)
|
||||
png_bytes = await onlyoffice.convert_to_png_async(
|
||||
filepath, request_timeout=5.0
|
||||
)
|
||||
except Exception as e:
|
||||
future.set_exception(e)
|
||||
async with self._lock:
|
||||
@@ -630,13 +634,13 @@ async def preview(req, path):
|
||||
_run_preview_process(filepath, quality, maxsize, maxzoom),
|
||||
timeout=PREVIEW_TIMEOUT,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
except TimeoutError:
|
||||
logger.warning("Preview timeout for %s", filepath)
|
||||
return empty(503)
|
||||
except PreviewTimeoutError:
|
||||
logger.warning("Preview worker timeout for %s", filepath)
|
||||
return empty(503)
|
||||
except httpx.HTTPStatusError as e:
|
||||
except httpx.HTTPStatusError:
|
||||
req.ctx._log_extra = "onlyoffice N/A"
|
||||
return empty(503)
|
||||
except httpx.RequestError:
|
||||
@@ -775,8 +779,8 @@ def _image_via_ffmpeg(path: Path, maxsize: int, quality: int) -> bytes:
|
||||
cmd.insert(4, "-s")
|
||||
cmd.insert(5, f"{new_w}x{new_h}")
|
||||
try:
|
||||
subprocess.run(cmd, capture_output=True, check=True)
|
||||
with open(tmp_path, "rb") as f:
|
||||
subprocess.run(cmd, capture_output=True, check=True, shell=False) # noqa: S603
|
||||
with Path(tmp_path).open("rb") as f:
|
||||
return f.read()
|
||||
finally:
|
||||
Path(tmp_path).unlink(missing_ok=True)
|
||||
|
||||
@@ -22,6 +22,8 @@ from pathlib import Path
|
||||
import msgspec
|
||||
from blake3 import blake3
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PreviewRequest(msgspec.Struct, omit_defaults=True):
|
||||
path: str
|
||||
@@ -121,7 +123,7 @@ def _run_loop() -> None:
|
||||
)
|
||||
_write_response(resp, result or b"")
|
||||
except Exception as e:
|
||||
logging.exception("Preview worker error for %s", req.path)
|
||||
logger.exception("Preview worker error for %s", req.path)
|
||||
captured = stderr_capture.getvalue().strip()
|
||||
_write_response(
|
||||
PreviewResponse(ok=False, error=str(e), stderr=captured or None), b""
|
||||
@@ -138,13 +140,13 @@ def main() -> None:
|
||||
from cista import config
|
||||
|
||||
config.load_config()
|
||||
logging.warning(
|
||||
logger.warning(
|
||||
"preview-worker config=%s master_secret=%s",
|
||||
config.conffile,
|
||||
config.config.secret,
|
||||
)
|
||||
except Exception:
|
||||
logging.exception("preview-worker failed to load config at startup")
|
||||
logger.exception("preview-worker failed to load config at startup")
|
||||
if len(sys.argv) > 1:
|
||||
_run_once()
|
||||
return
|
||||
|
||||
+1
-1
@@ -257,7 +257,7 @@ async def proxy_auth_request(request):
|
||||
method=request.method,
|
||||
url=url,
|
||||
headers=headers,
|
||||
content=request.body if request.body else None,
|
||||
content=request.body or None,
|
||||
) as response:
|
||||
raw_content = b"".join([chunk async for chunk in response.aiter_raw()])
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ def websocket_wrapper(handler):
|
||||
@wraps(handler)
|
||||
async def wrapper(request, ws, *args, **kwargs):
|
||||
username = getattr(request.ctx, "username", None)
|
||||
extra = username if username else None
|
||||
extra = username or None
|
||||
start = time.perf_counter()
|
||||
ws_id = log_ws_open(request, extra=extra)
|
||||
close_extra = None
|
||||
|
||||
Reference in New Issue
Block a user