Редактировать file_handler.py
This commit is contained in:
@@ -9,8 +9,8 @@
|
|||||||
- Для личных чатов – поведение зависит от режима обучения (learning_mode) и флага --global.
|
- Для личных чатов – поведение зависит от режима обучения (learning_mode) и флага --global.
|
||||||
|
|
||||||
ДОБАВЛЕНО:
|
ДОБАВЛЕНО:
|
||||||
- Удалён прямой вызов GigaChat для изображений (теперь это делает RAG-сервер)
|
- Гарантированное удаление временных файлов через tempfile.TemporaryDirectory.
|
||||||
- Удалён прямой вызов транскрибации (теперь это делает RAG-сервер)
|
- Обработка ошибок скачивания с очисткой.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@@ -18,6 +18,8 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import requests
|
import requests
|
||||||
|
import tempfile
|
||||||
|
import shutil
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@@ -35,11 +37,6 @@ class FileHandler:
|
|||||||
async def _wait_for_queue(self):
|
async def _wait_for_queue(self):
|
||||||
"""
|
"""
|
||||||
Ожидает создания очереди индексации (защита от гонки при старте бота).
|
Ожидает создания очереди индексации (защита от гонки при старте бота).
|
||||||
|
|
||||||
Проблема: очередь `self.bot.indexing_queue` создаётся в `session_start` после
|
|
||||||
подключения к БД. Если сообщение с файлом придёт ДО этого момента,
|
|
||||||
`indexing_queue` будет `None`, и `put()` вызовет исключение.
|
|
||||||
Решение: ждём, пока очередь станет доступна, проверяя каждые 0.5 секунды.
|
|
||||||
"""
|
"""
|
||||||
while self.bot.indexing_queue is None:
|
while self.bot.indexing_queue is None:
|
||||||
logger.debug("Очередь индексации ещё не создана, ожидание...")
|
logger.debug("Очередь индексации ещё не создана, ожидание...")
|
||||||
@@ -48,34 +45,20 @@ class FileHandler:
|
|||||||
async def handle_file(self, msg, url: str, user_jid: str, room_jid: str = None):
|
async def handle_file(self, msg, url: str, user_jid: str, room_jid: str = None):
|
||||||
"""
|
"""
|
||||||
Скачивает файл и создаёт задачу для indexing_worker.
|
Скачивает файл и создаёт задачу для indexing_worker.
|
||||||
|
Использует временную директорию для гарантированной очистки.
|
||||||
Аргументы:
|
|
||||||
msg: stanza сообщения (нужна для получения user, ответных сообщений).
|
|
||||||
url (str): HTTP/HTTPS ссылка на файл (из атрибута url элемента oob).
|
|
||||||
user_jid (str): JID отправителя (без ресурса).
|
|
||||||
room_jid (str | None): JID комнаты, если сообщение из groupchat; иначе None.
|
|
||||||
|
|
||||||
Логика:
|
|
||||||
1. Извлечь имя файла из URL, декодировать, сгенерировать уникальное имя во временной папке.
|
|
||||||
2. Скачать файл (синхронно, в отдельном потоке, с таймаутом из конфига).
|
|
||||||
3. Определить режим индексации:
|
|
||||||
- Если room_jid не None -> mode = 'room', is_global = False (всегда).
|
|
||||||
- Если личный чат -> проверить learning_mode пользователя.
|
|
||||||
4. Извлечь --title из тела сообщения (если есть).
|
|
||||||
5. Положить задачу в очередь индексации (asyncio.Queue).
|
|
||||||
6. Ответить пользователю, что файл принят в обработку.
|
|
||||||
"""
|
"""
|
||||||
self.bot.metrics_handler.increment("files_processed")
|
self.bot.metrics_handler.increment("files_processed")
|
||||||
try:
|
try:
|
||||||
# --- 1. Извлечение имени файла из URL ---
|
|
||||||
raw_name = url.split('/')[-1].split('?')[0]
|
raw_name = url.split('/')[-1].split('?')[0]
|
||||||
original_fname = urllib.parse.unquote(raw_name)
|
original_fname = urllib.parse.unquote(raw_name)
|
||||||
ext = os.path.splitext(original_fname)[1].lower()
|
ext = os.path.splitext(original_fname)[1].lower()
|
||||||
fname = f"{msg['from'].user}_{int(datetime.now().timestamp())}{ext}"
|
|
||||||
target_path = os.path.join(self.bot.config.temp_dir, fname)
|
|
||||||
|
|
||||||
# --- 2. Скачивание файла ---
|
# Создаём временную директорию (автоматически удалится)
|
||||||
# HEAD-запрос для получения размера
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
fname = f"{msg['from'].user}_{int(datetime.now().timestamp())}{ext}"
|
||||||
|
target_path = os.path.join(tmpdir, fname)
|
||||||
|
|
||||||
|
# Проверка размера через HEAD-запрос
|
||||||
try:
|
try:
|
||||||
head_resp = requests.head(url, timeout=self.bot.config.http_download_timeout)
|
head_resp = requests.head(url, timeout=self.bot.config.http_download_timeout)
|
||||||
content_length = head_resp.headers.get('Content-Length')
|
content_length = head_resp.headers.get('Content-Length')
|
||||||
@@ -87,29 +70,22 @@ class FileHandler:
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Скачивание с прерыванием по лимиту
|
# Скачивание с лимитом
|
||||||
def download_with_limit():
|
def download_with_limit():
|
||||||
r = requests.get(url, stream=True, timeout=self.bot.config.http_download_timeout)
|
r = requests.get(url, stream=True, timeout=self.bot.config.http_download_timeout)
|
||||||
if r.status_code != 200:
|
if r.status_code != 200:
|
||||||
return False
|
raise Exception("HTTP error")
|
||||||
downloaded = 0
|
|
||||||
limit = self.bot.config.max_file_size_mb * 1024 * 1024
|
limit = self.bot.config.max_file_size_mb * 1024 * 1024
|
||||||
with open(target_path, 'wb') as f:
|
with open(target_path, 'wb') as f:
|
||||||
|
downloaded = 0
|
||||||
for chunk in r.iter_content(8192):
|
for chunk in r.iter_content(8192):
|
||||||
f.write(chunk)
|
f.write(chunk)
|
||||||
downloaded += len(chunk)
|
downloaded += len(chunk)
|
||||||
if downloaded > limit:
|
if downloaded > limit:
|
||||||
os.unlink(target_path)
|
|
||||||
raise Exception("File too large")
|
raise Exception("File too large")
|
||||||
return True
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
success = await asyncio.to_thread(download_with_limit)
|
await asyncio.to_thread(download_with_limit)
|
||||||
if not success:
|
|
||||||
reply = msg.reply("❌ Не удалось загрузить файл. Проверьте доступность ссылки.")
|
|
||||||
if reply:
|
|
||||||
reply.send()
|
|
||||||
return
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if "File too large" in str(e):
|
if "File too large" in str(e):
|
||||||
reply = msg.reply(f"❌ Файл превышает максимальный размер {self.bot.config.max_file_size_mb} МБ.")
|
reply = msg.reply(f"❌ Файл превышает максимальный размер {self.bot.config.max_file_size_mb} МБ.")
|
||||||
@@ -119,17 +95,15 @@ class FileHandler:
|
|||||||
reply.send()
|
reply.send()
|
||||||
return
|
return
|
||||||
|
|
||||||
# --- 3. Определение режима индексации ---
|
# Определение режима индексации
|
||||||
if room_jid:
|
if room_jid:
|
||||||
mode = 'room'
|
mode = 'room'
|
||||||
is_global = False
|
is_global = False
|
||||||
title = None
|
|
||||||
else:
|
else:
|
||||||
mode_info = self.bot.learning_mode.get(user_jid)
|
mode_info = self.bot.learning_mode.get(user_jid)
|
||||||
if mode_info is None:
|
if mode_info is None:
|
||||||
mode = None
|
mode = None
|
||||||
is_global = False
|
is_global = False
|
||||||
title = None
|
|
||||||
else:
|
else:
|
||||||
if isinstance(mode_info, tuple):
|
if isinstance(mode_info, tuple):
|
||||||
mode, saved_room = mode_info
|
mode, saved_room = mode_info
|
||||||
@@ -137,16 +111,15 @@ class FileHandler:
|
|||||||
else:
|
else:
|
||||||
mode = mode_info
|
mode = mode_info
|
||||||
is_global = (mode == 'global')
|
is_global = (mode == 'global')
|
||||||
title = None
|
|
||||||
|
|
||||||
# --- 4. Парсинг --title из тела сообщения ---
|
title = None
|
||||||
raw_body = msg['body'].strip() if msg['body'] else ""
|
raw_body = msg['body'].strip() if msg['body'] else ""
|
||||||
if raw_body:
|
if raw_body:
|
||||||
match = re.search(r'--title "([^"]+)"', raw_body)
|
match = re.search(r'--title "([^"]+)"', raw_body)
|
||||||
if match:
|
if match:
|
||||||
title = match.group(1)
|
title = match.group(1)
|
||||||
|
|
||||||
# --- 5. Создание задачи для очереди ---
|
# Создаём задачу для фонового индексатора
|
||||||
await self._wait_for_queue()
|
await self._wait_for_queue()
|
||||||
task = {
|
task = {
|
||||||
"user_jid": user_jid,
|
"user_jid": user_jid,
|
||||||
@@ -160,11 +133,7 @@ class FileHandler:
|
|||||||
}
|
}
|
||||||
await self.bot.indexing_queue.put(task)
|
await self.bot.indexing_queue.put(task)
|
||||||
|
|
||||||
# --- 6. Уведомление пользователя ---
|
scope = "комнатную" if room_jid else ("глобальную" if is_global else "личную")
|
||||||
if room_jid:
|
|
||||||
reply = msg.reply(f"📥 Файл '{original_fname}' принят в обработку для КОМНАТНОЙ БЗ.")
|
|
||||||
else:
|
|
||||||
scope = "глобальную" if is_global else "личную"
|
|
||||||
reply = msg.reply(f"📥 Файл '{original_fname}' принят в обработку для {scope} БЗ.")
|
reply = msg.reply(f"📥 Файл '{original_fname}' принят в обработку для {scope} БЗ.")
|
||||||
if reply:
|
if reply:
|
||||||
reply.send()
|
reply.send()
|
||||||
|
|||||||
Reference in New Issue
Block a user