uchill/backend/apps/video/tasks.py

303 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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)}')