303 lines
12 KiB
Python
303 lines
12 KiB
Python
"""
|
||
Celery задачи для видеоконференций.
|
||
"""
|
||
import logging
|
||
from celery import shared_task
|
||
from django.utils import timezone
|
||
from datetime import timedelta
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
@shared_task(name='apps.video.tasks.process_recording')
|
||
def process_recording(recording_id):
|
||
"""
|
||
Обработка записи видео.
|
||
|
||
Args:
|
||
recording_id: ID записи для обработки
|
||
"""
|
||
from .models import ScreenRecording
|
||
|
||
try:
|
||
recording = ScreenRecording.objects.get(id=recording_id)
|
||
|
||
logger.info(f'Начало обработки записи {recording_id}')
|
||
|
||
# ПРИМЕЧАНИЕ: Запись видео не используется в проекте
|
||
# Эта функция оставлена для возможного использования в будущем
|
||
|
||
# Для примера просто помечаем как готовую
|
||
recording.mark_as_ready()
|
||
|
||
logger.info(f'Запись {recording_id} успешно обработана')
|
||
|
||
# Отправляем уведомление
|
||
from apps.notifications.services import NotificationService
|
||
NotificationService.send_notification(
|
||
user=recording.room.mentor,
|
||
notification_type='recording_ready',
|
||
message=f'Запись занятия "{recording.room.lesson.title}" готова',
|
||
link=f'/video/recordings/{recording.id}/'
|
||
)
|
||
|
||
except ScreenRecording.DoesNotExist:
|
||
logger.error(f'Запись {recording_id} не найдена')
|
||
except Exception as e:
|
||
logger.error(f'Ошибка обработки записи {recording_id}: {str(e)}')
|
||
try:
|
||
recording = ScreenRecording.objects.get(id=recording_id)
|
||
recording.mark_as_failed(str(e))
|
||
except:
|
||
pass
|
||
|
||
|
||
@shared_task(name='apps.video.tasks.cleanup_old_recordings')
|
||
def cleanup_old_recordings():
|
||
"""
|
||
Очистка старых записей видео.
|
||
Удаляет записи, срок действия которых истек.
|
||
"""
|
||
from .models import ScreenRecording
|
||
|
||
try:
|
||
# Находим истекшие записи
|
||
expired_recordings = ScreenRecording.objects.filter(
|
||
expires_at__lt=timezone.now()
|
||
)
|
||
|
||
count = expired_recordings.count()
|
||
|
||
if count > 0:
|
||
logger.info(f'Найдено {count} истекших записей')
|
||
|
||
# ПРИМЕЧАНИЕ: Запись видео не используется в проекте
|
||
# Эта функция оставлена для возможного использования в будущем
|
||
for recording in expired_recordings:
|
||
if recording.file_path:
|
||
# Удаление файла из хранилища (не реализовано)
|
||
pass
|
||
|
||
# Удаляем записи из БД
|
||
expired_recordings.delete()
|
||
|
||
logger.info(f'Удалено {count} истекших записей')
|
||
|
||
except Exception as e:
|
||
logger.error(f'Ошибка очистки старых записей: {str(e)}')
|
||
|
||
|
||
@shared_task(name='apps.video.tasks.generate_call_log')
|
||
def generate_call_log(room_id):
|
||
"""
|
||
Генерация лога видеозвонка.
|
||
|
||
Args:
|
||
room_id: ID видеокомнаты
|
||
"""
|
||
from .models import VideoRoom, VideoCallLog, VideoParticipant
|
||
|
||
try:
|
||
room = VideoRoom.objects.get(id=room_id)
|
||
|
||
# Собираем статистику
|
||
# Оптимизация: используем select_related для избежания N+1 запросов
|
||
participants = VideoParticipant.objects.filter(room=room).select_related('user')
|
||
participants_list = list(participants)
|
||
total_participants = len(participants_list)
|
||
total_duration = room.actual_duration
|
||
|
||
# Создаем лог
|
||
call_log = VideoCallLog.objects.create(
|
||
room=room,
|
||
total_participants=total_participants,
|
||
total_duration=total_duration,
|
||
metadata={
|
||
'participants': [
|
||
{
|
||
'user_id': p.user.id,
|
||
'email': p.user.email,
|
||
'joined_at': p.joined_at.isoformat() if p.joined_at else None,
|
||
'left_at': p.left_at.isoformat() if p.left_at else None,
|
||
'duration': p.total_duration,
|
||
'reconnections': p.reconnection_count
|
||
}
|
||
for p in participants_list
|
||
],
|
||
'room_info': {
|
||
'started_at': room.started_at.isoformat() if room.started_at else None,
|
||
'ended_at': room.ended_at.isoformat() if room.ended_at else None,
|
||
'recording_enabled': room.is_recording,
|
||
'quality_rating': room.quality_rating
|
||
}
|
||
}
|
||
)
|
||
|
||
logger.info(f'Создан лог для видеозвонка {room_id}')
|
||
|
||
except VideoRoom.DoesNotExist:
|
||
logger.error(f'Видеокомната {room_id} не найдена')
|
||
except Exception as e:
|
||
logger.error(f'Ошибка генерации лога для комнаты {room_id}: {str(e)}')
|
||
|
||
|
||
@shared_task(name='apps.video.tasks.send_call_reminder')
|
||
def send_call_reminder(room_id):
|
||
"""
|
||
Отправка напоминания о предстоящем видеозвонке.
|
||
|
||
Args:
|
||
room_id: ID видеокомнаты
|
||
"""
|
||
from .models import VideoRoom
|
||
from apps.notifications.services import NotificationService
|
||
|
||
try:
|
||
room = VideoRoom.objects.get(id=room_id)
|
||
|
||
# Проверяем что занятие скоро начнется
|
||
lesson = room.lesson
|
||
time_until_start = (lesson.start_time - timezone.now()).total_seconds()
|
||
|
||
# Если до начала осталось 5-15 минут
|
||
if 300 <= time_until_start <= 900:
|
||
# Отправляем уведомление ментору
|
||
NotificationService.send_notification(
|
||
user=room.mentor,
|
||
notification_type='lesson_reminder',
|
||
message=f'Занятие "{lesson.title}" начнется через {int(time_until_start/60)} минут',
|
||
link=f'/video/rooms/{room.room_id}/join/'
|
||
)
|
||
|
||
# Отправляем уведомление клиенту
|
||
NotificationService.send_notification(
|
||
user=room.client,
|
||
notification_type='lesson_reminder',
|
||
message=f'Занятие "{lesson.title}" начнется через {int(time_until_start/60)} минут',
|
||
link=f'/video/rooms/{room.room_id}/join/'
|
||
)
|
||
|
||
logger.info(f'Отправлены напоминания о звонке {room_id}')
|
||
|
||
except VideoRoom.DoesNotExist:
|
||
logger.error(f'Видеокомната {room_id} не найдена')
|
||
except Exception as e:
|
||
logger.error(f'Ошибка отправки напоминания о звонке {room_id}: {str(e)}')
|
||
|
||
|
||
@shared_task(name='apps.video.tasks.end_inactive_rooms')
|
||
def end_inactive_rooms():
|
||
"""
|
||
Завершение неактивных видеокомнат.
|
||
Находит активные комнаты без участников и завершает их.
|
||
"""
|
||
from .models import VideoRoom, VideoParticipant
|
||
|
||
try:
|
||
# Находим активные комнаты
|
||
active_rooms = VideoRoom.objects.filter(status='active')
|
||
|
||
for room in active_rooms:
|
||
# Проверяем есть ли подключенные участники
|
||
connected_participants = VideoParticipant.objects.filter(
|
||
room=room,
|
||
is_connected=True
|
||
).count()
|
||
|
||
# Если нет подключенных участников больше 10 минут
|
||
if connected_participants == 0:
|
||
if room.started_at:
|
||
time_since_start = (timezone.now() - room.started_at).total_seconds()
|
||
if time_since_start > 600: # 10 минут
|
||
room.end()
|
||
logger.info(f'Завершена неактивная комната {room.room_id}')
|
||
|
||
# Генерируем лог
|
||
generate_call_log.delay(room.id)
|
||
|
||
except Exception as e:
|
||
logger.error(f'Ошибка завершения неактивных комнат: {str(e)}')
|
||
|
||
|
||
@shared_task(name='apps.video.tasks.auto_end_long_rooms')
|
||
def auto_end_long_rooms():
|
||
"""
|
||
Автоматическое завершение видеокомнат, которые идут более 10 минут.
|
||
|
||
Запускается каждые 10 минут через Celery Beat.
|
||
Находит активные комнаты, которые начались более 10 минут назад,
|
||
и автоматически завершает их.
|
||
"""
|
||
from .models import VideoRoom
|
||
|
||
try:
|
||
now = timezone.now()
|
||
# Время 10 минут назад
|
||
cutoff_time = now - timedelta(minutes=10)
|
||
|
||
# Находим активные комнаты, которые начались более 10 минут назад
|
||
long_running_rooms = VideoRoom.objects.filter(
|
||
status='active',
|
||
started_at__lt=cutoff_time
|
||
)
|
||
|
||
ended_count = 0
|
||
for room in long_running_rooms:
|
||
try:
|
||
room.end()
|
||
ended_count += 1
|
||
logger.info(
|
||
f'Автоматически завершена видеокомната {room.room_id} '
|
||
f'(занятие {room.lesson.id}), длительность: более 10 минут'
|
||
)
|
||
|
||
# Генерируем лог звонка (сигнал также должен это сделать, но для надежности)
|
||
generate_call_log.delay(room.id)
|
||
|
||
except Exception as e:
|
||
logger.error(
|
||
f'Ошибка при автоматическом завершении комнаты {room.room_id}: {str(e)}'
|
||
)
|
||
|
||
if ended_count > 0:
|
||
logger.info(f'Автоматически завершено {ended_count} видеокомнат, идущих более 10 минут')
|
||
|
||
return f'Завершено {ended_count} видеокомнат'
|
||
|
||
except Exception as e:
|
||
logger.error(f'Ошибка автоматического завершения длительных видеокомнат: {str(e)}')
|
||
raise
|
||
|
||
|
||
@shared_task(name='apps.video.tasks.delete_expired_video_rooms')
|
||
def delete_expired_video_rooms():
|
||
"""
|
||
Удаление истекших видеокомнат.
|
||
Удаляет завершенные комнаты, которые были завершены более 30 дней назад.
|
||
"""
|
||
from .models import VideoRoom
|
||
|
||
try:
|
||
# Находим завершенные комнаты старше 30 дней
|
||
expired_date = timezone.now() - timedelta(days=30)
|
||
expired_rooms = VideoRoom.objects.filter(
|
||
status='ended',
|
||
ended_at__lt=expired_date
|
||
)
|
||
|
||
count = expired_rooms.count()
|
||
|
||
if count > 0:
|
||
logger.info(f'Найдено {count} истекших видеокомнат для удаления')
|
||
|
||
# Удаляем комнаты (CASCADE удалит связанные записи)
|
||
expired_rooms.delete()
|
||
|
||
logger.info(f'Удалено {count} истекших видеокомнат')
|
||
else:
|
||
logger.debug('Нет истекших видеокомнат для удаления')
|
||
|
||
except Exception as e:
|
||
logger.error(f'Ошибка удаления истекших видеокомнат: {str(e)}')
|
||
|