|
1 | 1 | """Utility functions for the server."""
|
2 | 2 |
|
3 |
| -import asyncio |
4 |
| -import shutil |
5 |
| -import time |
6 |
| -from contextlib import asynccontextmanager, suppress |
7 |
| -from pathlib import Path |
8 |
| -from typing import AsyncGenerator |
9 |
| - |
10 |
| -from fastapi import FastAPI, Request |
| 3 | +from fastapi import Request |
11 | 4 | from fastapi.responses import Response
|
12 | 5 | from slowapi import Limiter, _rate_limit_exceeded_handler
|
13 | 6 | from slowapi.errors import RateLimitExceeded
|
14 | 7 | from slowapi.util import get_remote_address
|
15 | 8 |
|
16 |
| -from gitingest.config import TMP_BASE_PATH |
17 | 9 | from gitingest.utils.logging_config import get_logger
|
18 |
| -from server.server_config import DELETE_REPO_AFTER |
19 | 10 |
|
20 | 11 | # Initialize logger for this module
|
21 | 12 | logger = get_logger(__name__)
|
@@ -52,118 +43,6 @@ async def rate_limit_exception_handler(request: Request, exc: Exception) -> Resp
|
52 | 43 | raise exc
|
53 | 44 |
|
54 | 45 |
|
55 |
| -@asynccontextmanager |
56 |
| -async def lifespan(_: FastAPI) -> AsyncGenerator[None, None]: |
57 |
| - """Manage startup & graceful-shutdown tasks for the FastAPI app. |
58 |
| -
|
59 |
| - Returns |
60 |
| - ------- |
61 |
| - AsyncGenerator[None, None] |
62 |
| - Yields control back to the FastAPI application while the background task runs. |
63 |
| -
|
64 |
| - """ |
65 |
| - task = asyncio.create_task(_remove_old_repositories()) |
66 |
| - |
67 |
| - yield # app runs while the background task is alive |
68 |
| - |
69 |
| - task.cancel() # ask the worker to stop |
70 |
| - with suppress(asyncio.CancelledError): |
71 |
| - await task # swallow the cancellation signal |
72 |
| - |
73 |
| - |
74 |
| -async def _remove_old_repositories( |
75 |
| - base_path: Path = TMP_BASE_PATH, |
76 |
| - scan_interval: int = 60, |
77 |
| - delete_after: int = DELETE_REPO_AFTER, |
78 |
| -) -> None: |
79 |
| - """Periodically delete old repositories/directories. |
80 |
| -
|
81 |
| - Every ``scan_interval`` seconds the coroutine scans ``base_path`` and deletes directories older than |
82 |
| - ``delete_after`` seconds. The repository URL is extracted from the first ``.txt`` file in each directory |
83 |
| - and appended to ``history.txt``, assuming the filename format: "owner-repository.txt". Filesystem errors are |
84 |
| - logged and the loop continues. |
85 |
| -
|
86 |
| - Parameters |
87 |
| - ---------- |
88 |
| - base_path : Path |
89 |
| - The path to the base directory where repositories are stored (default: ``TMP_BASE_PATH``). |
90 |
| - scan_interval : int |
91 |
| - The number of seconds between scans (default: 60). |
92 |
| - delete_after : int |
93 |
| - The number of seconds after which a repository is considered old and will be deleted |
94 |
| - (default: ``DELETE_REPO_AFTER``). |
95 |
| -
|
96 |
| - """ |
97 |
| - while True: |
98 |
| - if not base_path.exists(): |
99 |
| - await asyncio.sleep(scan_interval) |
100 |
| - continue |
101 |
| - |
102 |
| - now = time.time() |
103 |
| - try: |
104 |
| - for folder in base_path.iterdir(): |
105 |
| - if now - folder.stat().st_ctime <= delete_after: # Not old enough |
106 |
| - continue |
107 |
| - |
108 |
| - await _process_folder(folder) |
109 |
| - |
110 |
| - except (OSError, PermissionError): |
111 |
| - logger.exception("Error in repository cleanup", extra={"base_path": str(base_path)}) |
112 |
| - |
113 |
| - await asyncio.sleep(scan_interval) |
114 |
| - |
115 |
| - |
116 |
| -async def _process_folder(folder: Path) -> None: |
117 |
| - """Append the repo URL (if discoverable) to ``history.txt`` and delete ``folder``. |
118 |
| -
|
119 |
| - Parameters |
120 |
| - ---------- |
121 |
| - folder : Path |
122 |
| - The path to the folder to be processed. |
123 |
| -
|
124 |
| - """ |
125 |
| - history_file = Path("history.txt") |
126 |
| - loop = asyncio.get_running_loop() |
127 |
| - |
128 |
| - try: |
129 |
| - first_txt_file = next(folder.glob("*.txt")) |
130 |
| - except StopIteration: # No .txt file found |
131 |
| - return |
132 |
| - |
133 |
| - # Append owner/repo to history.txt |
134 |
| - try: |
135 |
| - filename = first_txt_file.stem # "owner-repo" |
136 |
| - if "-" in filename: |
137 |
| - owner, repo = filename.split("-", 1) |
138 |
| - repo_url = f"{owner}/{repo}" |
139 |
| - await loop.run_in_executor(None, _append_line, history_file, repo_url) |
140 |
| - except (OSError, PermissionError): |
141 |
| - logger.exception("Error logging repository URL", extra={"folder": str(folder)}) |
142 |
| - |
143 |
| - # Delete the cloned repo |
144 |
| - try: |
145 |
| - await loop.run_in_executor(None, shutil.rmtree, folder) |
146 |
| - except PermissionError: |
147 |
| - logger.exception("No permission to delete folder", extra={"folder": str(folder)}) |
148 |
| - except OSError: |
149 |
| - logger.exception("Could not delete folder", extra={"folder": str(folder)}) |
150 |
| - |
151 |
| - |
152 |
| -def _append_line(path: Path, line: str) -> None: |
153 |
| - """Append a line to a file. |
154 |
| -
|
155 |
| - Parameters |
156 |
| - ---------- |
157 |
| - path : Path |
158 |
| - The path to the file to append the line to. |
159 |
| - line : str |
160 |
| - The line to append to the file. |
161 |
| -
|
162 |
| - """ |
163 |
| - with path.open("a", encoding="utf-8") as fp: |
164 |
| - fp.write(f"{line}\n") |
165 |
| - |
166 |
| - |
167 | 46 | ## Color printing utility
|
168 | 47 | class Colors:
|
169 | 48 | """ANSI color codes."""
|
|
0 commit comments