Delete the Recording and ensure any active DVR client connection is closed. Also removes the associated file(s) from disk if present. Operation order matters for correctness: 1. Delete the DB record first — run_recording's cancellation guard (Recording.object
(self, request, *args, **kwargs)
| 3745 | return Response({"success": True}) |
| 3746 | |
| 3747 | def destroy(self, request, *args, **kwargs): |
| 3748 | """Delete the Recording and ensure any active DVR client connection is closed. |
| 3749 | |
| 3750 | Also removes the associated file(s) from disk if present. |
| 3751 | |
| 3752 | Operation order matters for correctness: |
| 3753 | 1. Delete the DB record first — run_recording's cancellation guard |
| 3754 | (Recording.objects.filter(id=...).exists()) will now return False, |
| 3755 | preventing it from saving 'interrupted' status or sending |
| 3756 | recording_ended after the stream is torn down. |
| 3757 | 2. Send recording_cancelled WebSocket immediately so the frontend |
| 3758 | removes the card without waiting for the slow DVR client teardown. |
| 3759 | 3. Spawn a background thread to stop the DVR client and delete files. |
| 3760 | This mirrors the stop() endpoint's approach and avoids the 5-15 s |
| 3761 | delay that _stop_dvr_clients() can introduce. |
| 3762 | """ |
| 3763 | instance = self.get_object() |
| 3764 | recording_id = instance.pk |
| 3765 | channel_name = instance.channel.name |
| 3766 | |
| 3767 | # Attempt to close the DVR client connection for this channel if active |
| 3768 | try: |
| 3769 | channel_uuid = str(instance.channel.uuid) |
| 3770 | # Lazy imports to avoid module overhead if proxy isn't used |
| 3771 | from core.utils import RedisClient |
| 3772 | from apps.proxy.live_proxy.redis_keys import RedisKeys |
| 3773 | from apps.proxy.live_proxy.services.channel_service import ChannelService |
| 3774 | |
| 3775 | r = RedisClient.get_client() |
| 3776 | if r: |
| 3777 | client_set_key = RedisKeys.clients(channel_uuid) |
| 3778 | client_ids = r.smembers(client_set_key) or [] |
| 3779 | stopped = 0 |
| 3780 | for cid in client_ids: |
| 3781 | try: |
| 3782 | meta_key = RedisKeys.client_metadata(channel_uuid, cid) |
| 3783 | ua = r.hget(meta_key, "user_agent") |
| 3784 | # Identify DVR recording client by its user agent |
| 3785 | if ua and "Dispatcharr-DVR" in ua: |
| 3786 | try: |
| 3787 | ChannelService.stop_client(channel_uuid, cid) |
| 3788 | stopped += 1 |
| 3789 | except Exception as inner_e: |
| 3790 | logger.debug(f"Failed to stop DVR client {cid} for channel {channel_uuid}: {inner_e}") |
| 3791 | except Exception as inner: |
| 3792 | logger.debug(f"Error while checking client metadata: {inner}") |
| 3793 | if stopped: |
| 3794 | logger.info(f"Stopped {stopped} DVR client(s) for channel {channel_uuid} due to recording cancellation") |
| 3795 | # If no clients remain after stopping DVR clients, proactively stop the channel |
| 3796 | try: |
| 3797 | remaining = r.scard(client_set_key) or 0 |
| 3798 | except Exception: |
| 3799 | remaining = 0 |
| 3800 | if remaining == 0: |
| 3801 | try: |
| 3802 | ChannelService.stop_channel(channel_uuid) |
| 3803 | logger.info(f"Stopped channel {channel_uuid} (no clients remain)") |
| 3804 | except Exception as sc_e: |
nothing calls this directly
no test coverage detected