diff --git a/config.json b/config.json index 32c2a79..ac5e1f9 100644 --- a/config.json +++ b/config.json @@ -7,6 +7,7 @@ "__comment::changes_to_the_config": "the program needs to be restarted if you change this file since it is stored in memory on startup", "log_level": 20, "idle_polling_interval": 5, + "idle_polling_interval_with_trigger": 300, "tokenizer_file": "spiece.model", "loader": { "model_name": "Nextcloud-AI/madlad400-3b-mt-ct2-int8_float32", diff --git a/lib/main.py b/lib/main.py index 592e59a..c7be29d 100644 --- a/lib/main.py +++ b/lib/main.py @@ -10,9 +10,8 @@ import threading import traceback from contextlib import asynccontextmanager, suppress -from time import sleep +from threading import Event -import httpx import uvicorn.logging from dotenv import load_dotenv from fastapi import FastAPI, Request, responses @@ -20,6 +19,7 @@ from nc_py_api.ex_app import LogLvl, run_app, set_handlers, setup_nextcloud_logging from nc_py_api.ex_app.integration_fastapi import fetch_models_task from nc_py_api.ex_app.providers.task_processing import ShapeEnumValue, TaskProcessingProvider +from niquests import RequestException from Service import Service, ServiceException, TranslateRequest from util import load_config_file, save_config_file @@ -61,6 +61,7 @@ async def lifespan(_: FastAPI): fast_api_app=APP, enabled_handler=enabled_handler, # type: ignore models_to_fetch=models_to_fetch, # type: ignore + trigger_handler=trigger_handler, ) print(f"Config loaded: {json.dumps(config, indent=4)}", flush=True) @@ -81,6 +82,7 @@ async def lifespan(_: FastAPI): APP_ID = "translate2" TASK_TYPE_ID = "core:text2text:translate" IDLE_POLLING_INTERVAL = config["idle_polling_interval"] +IDLE_POLLING_INTERVAL_WITH_TRIGGER = config["idle_polling_interval_with_trigger"] DETECT_LANGUAGE = ShapeEnumValue(name="Detect Language", value="detect_language") APP = FastAPI(lifespan=lifespan) @@ -95,7 +97,7 @@ def report_error(task: dict | None, exc: Exception): task["task"]["id"], error_message=f"Error translating the input text: {exc}" ) - except (NextcloudException, httpx.NetworkError) as e: + except (NextcloudException, RequestException) as e: logger.error(f"Error reporting error to the server: {e}") @@ -130,21 +132,16 @@ def task_fetch_thread(service: Service): task = nc.providers.task_processing.next_task([APP_ID], [TASK_TYPE_ID]) except (NextcloudException, json.JSONDecodeError) as e: logger.error("Error fetching the next task", exc_info=e) - sleep(IDLE_POLLING_INTERVAL) + wait_for_task() continue - except ( - httpx.RemoteProtocolError, - httpx.ReadError, - httpx.LocalProtocolError, - httpx.PoolTimeout, - ) as e: + except RequestException as e: logger.debug("Ignored error during task polling", exc_info=e) - sleep(IDLE_POLLING_INTERVAL / 2) + wait_for_task(IDLE_POLLING_INTERVAL / 2) continue if not task: logger.debug("No tasks found") - sleep(IDLE_POLLING_INTERVAL) + wait_for_task() continue logger.debug(f"Processing task: {task}") @@ -218,6 +215,25 @@ async def enabled_handler(enabled: bool, nc: AsyncNextcloudApp) -> str: return "" +TRIGGER = Event() +# Trigger is only available in nc >= 33 +def trigger_handler(providerId: str): + global TRIGGER + TRIGGER.set() + +# Waits for interval seconds or IDLE_POLLING_INTERVAL seconds +# if TRIGGER is received, IDLE_POLLING_INTERVAL is set to IDLE_POLLING_INTERVAL_WITH_TRIGGER +def wait_for_task(interval = None): + global TRIGGER + global IDLE_POLLING_INTERVAL + global IDLE_POLLING_INTERVAL_WITH_TRIGGER + if interval is None: + interval = IDLE_POLLING_INTERVAL + if TRIGGER.wait(timeout=interval): + IDLE_POLLING_INTERVAL = IDLE_POLLING_INTERVAL_WITH_TRIGGER + TRIGGER.clear() + + if __name__ == "__main__": uvicorn_log_level = ( uvicorn.logging.TRACE_LOG_LEVEL diff --git a/requirements.in.txt b/requirements.in.txt index 24d099b..298671b 100644 --- a/requirements.in.txt +++ b/requirements.in.txt @@ -1,5 +1,5 @@ fastapi ctranslate2 huggingface_hub -nc_py_api[app]>=0.20.0 +nc_py_api[app]>=0.22.0 sentencepiece diff --git a/requirements.txt b/requirements.txt index 97843e4..ba52b15 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,38 +1,43 @@ +annotated-doc==0.0.3 annotated-types==0.7.0 -anyio==4.9.0 -certifi==2025.6.15 -charset-normalizer==3.4.2 -click==8.2.1 -cmake==4.0.3 +anyio==4.11.0 +certifi==2025.10.5 +charset-normalizer==3.4.4 +click==8.3.0 ctranslate2==4.6.0 -fastapi==0.116.0 -filelock==3.18.0 -fsspec==2025.5.1 +fastapi==0.120.1 +filelock==3.20.0 +fsspec==2025.9.0 h11==0.16.0 -hf-xet==1.1.5 +hf-xet==1.2.0 httpcore==1.0.9 -httptools==0.6.4 +httptools==0.7.1 httpx==0.28.1 -huggingface-hub==0.33.2 -idna==3.10 -nc-py-api==0.20.2 -numpy==2.3.1 +huggingface-hub==1.0.0 +idna==3.11 +jh2==5.0.10 +nc-py-api==0.22.0 +niquests==3.15.2 +numpy==2.3.4 packaging==25.0 -pydantic==2.11.7 -pydantic_core==2.33.2 -python-dotenv==1.1.1 -PyYAML==6.0.2 -requests==2.32.4 -sentencepiece==0.2.0 +pydantic==2.12.3 +pydantic_core==2.41.4 +python-dotenv==1.2.1 +PyYAML==6.0.3 +qh3==1.5.5 +sentencepiece==0.2.1 +setuptools==80.9.0 +shellingham==1.5.4 sniffio==1.3.1 -starlette==0.46.2 +starlette==0.48.0 tqdm==4.67.1 -truststore==0.10.0 -typing-inspection==0.4.1 -typing_extensions==4.14.1 -urllib3==2.5.0 -uvicorn==0.35.0 -uvloop==0.21.0 -watchfiles==1.1.0 +typer-slim==0.20.0 +typing-inspection==0.4.2 +typing_extensions==4.15.0 +urllib3-future==2.14.905 +uvicorn==0.38.0 +uvloop==0.22.1 +wassima==2.0.2 +watchfiles==1.1.1 websockets==15.0.1 -xmltodict==0.14.2 +xmltodict==1.0.2