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