#106: tests: aircox_streamer #110
@ -3,13 +3,19 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from .metadata import Metadata, Request
 | 
					from .metadata import Metadata, Request
 | 
				
			||||||
from .streamer import Streamer
 | 
					from .streamer import Streamer
 | 
				
			||||||
 | 
					from .streamers import Streamers
 | 
				
			||||||
from .sources import Source, PlaylistSource, QueueSource
 | 
					from .sources import Source, PlaylistSource, QueueSource
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					streamers = Streamers()
 | 
				
			||||||
 | 
					"""Default controller used by views and viewsets."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
__all__ = (
 | 
					__all__ = (
 | 
				
			||||||
    "Metadata",
 | 
					    "Metadata",
 | 
				
			||||||
    "Request",
 | 
					    "Request",
 | 
				
			||||||
    "Streamer",
 | 
					    "Streamer",
 | 
				
			||||||
 | 
					    "Streamers",
 | 
				
			||||||
    "Source",
 | 
					    "Source",
 | 
				
			||||||
    "PlaylistSource",
 | 
					    "PlaylistSource",
 | 
				
			||||||
    "QueueSource",
 | 
					    "QueueSource",
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import itertools
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from datetime import datetime, time
 | 
					from datetime import datetime, time
 | 
				
			||||||
@ -74,21 +75,53 @@ def station():
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@pytest.fixture
 | 
					@pytest.fixture
 | 
				
			||||||
def station_ports(station):
 | 
					def stations(station):
 | 
				
			||||||
    items = [
 | 
					    objs = [
 | 
				
			||||||
        models.Port(
 | 
					        models.Station(
 | 
				
			||||||
            station=station,
 | 
					            name=f"test-{i}",
 | 
				
			||||||
            direction=models.Port.DIRECTION_INPUT,
 | 
					            slug=f"test-{i}",
 | 
				
			||||||
            type=models.Port.TYPE_HTTP,
 | 
					            path=working_dir,
 | 
				
			||||||
 | 
					            default=(i == 0),
 | 
				
			||||||
            active=True,
 | 
					            active=True,
 | 
				
			||||||
        ),
 | 
					        )
 | 
				
			||||||
        models.Port(
 | 
					        for i in range(0, 3)
 | 
				
			||||||
            station=station,
 | 
					 | 
				
			||||||
            direction=models.Port.DIRECTION_OUTPUT,
 | 
					 | 
				
			||||||
            type=models.Port.TYPE_FILE,
 | 
					 | 
				
			||||||
            active=True,
 | 
					 | 
				
			||||||
        ),
 | 
					 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					    models.Station.objects.bulk_create(objs)
 | 
				
			||||||
 | 
					    return [station] + objs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@pytest.fixture
 | 
				
			||||||
 | 
					def station_ports(station):
 | 
				
			||||||
 | 
					    return _stations_ports(station)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@pytest.fixture
 | 
				
			||||||
 | 
					def stations_ports(stations):
 | 
				
			||||||
 | 
					    return _stations_ports(*stations)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _stations_ports(*stations):
 | 
				
			||||||
 | 
					    items = list(
 | 
				
			||||||
 | 
					        itertools.chain(
 | 
				
			||||||
 | 
					            *[
 | 
				
			||||||
 | 
					                (
 | 
				
			||||||
 | 
					                    models.Port(
 | 
				
			||||||
 | 
					                        station=station,
 | 
				
			||||||
 | 
					                        direction=models.Port.DIRECTION_INPUT,
 | 
				
			||||||
 | 
					                        type=models.Port.TYPE_HTTP,
 | 
				
			||||||
 | 
					                        active=True,
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    models.Port(
 | 
				
			||||||
 | 
					                        station=station,
 | 
				
			||||||
 | 
					                        direction=models.Port.DIRECTION_OUTPUT,
 | 
				
			||||||
 | 
					                        type=models.Port.TYPE_FILE,
 | 
				
			||||||
 | 
					                        active=True,
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                for station in stations
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    models.Port.objects.bulk_create(items)
 | 
					    models.Port.objects.bulk_create(items)
 | 
				
			||||||
    return items
 | 
					    return items
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -180,3 +213,50 @@ def metadata_string(metadata_data):
 | 
				
			|||||||
        "\n".join(f"{key}={value}" for key, value in metadata_data.items())
 | 
					        "\n".join(f"{key}={value}" for key, value in metadata_data.items())
 | 
				
			||||||
        + "\nEND"
 | 
					        + "\nEND"
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# -- streamers
 | 
				
			||||||
 | 
					class FakeStreamer(controllers.Streamer):
 | 
				
			||||||
 | 
					    calls = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def fetch(self):
 | 
				
			||||||
 | 
					        self.calls["fetch"] = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FakeSource(controllers.Source):
 | 
				
			||||||
 | 
					    def __init__(self, id, *args, **kwargs):
 | 
				
			||||||
 | 
					        self.id = id
 | 
				
			||||||
 | 
					        self.args = args
 | 
				
			||||||
 | 
					        self.kwargs = kwargs
 | 
				
			||||||
 | 
					        self.calls = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def fetch(self):
 | 
				
			||||||
 | 
					        self.calls["sync"] = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def sync(self):
 | 
				
			||||||
 | 
					        self.calls["sync"] = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def push(self, path):
 | 
				
			||||||
 | 
					        self.calls["push"] = path
 | 
				
			||||||
 | 
					        return path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def skip(self):
 | 
				
			||||||
 | 
					        self.calls["skip"] = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def restart(self):
 | 
				
			||||||
 | 
					        self.calls["restart"] = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def seek(self, c):
 | 
				
			||||||
 | 
					        self.calls["seek"] = c
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@pytest.fixture
 | 
				
			||||||
 | 
					def streamers(stations, stations_ports):
 | 
				
			||||||
 | 
					    streamers = controllers.Streamers(streamer_class=FakeStreamer)
 | 
				
			||||||
 | 
					    # avoid unecessary db calls
 | 
				
			||||||
 | 
					    streamers.streamers = {
 | 
				
			||||||
 | 
					        station.pk: FakeStreamer(station) for station in stations
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    for streamer in streamers.values():
 | 
				
			||||||
 | 
					        streamer.sources = [FakeSource(i) for i in range(0, 3)]
 | 
				
			||||||
 | 
					    return streamers
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										37
									
								
								aircox_streamer/tests/test_controllers_streamers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								aircox_streamer/tests/test_controllers_streamers.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					from datetime import timedelta
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.utils import timezone as tz
 | 
				
			||||||
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestStreamers:
 | 
				
			||||||
 | 
					    @pytest.fixture
 | 
				
			||||||
 | 
					    def test___init__(self, streamers):
 | 
				
			||||||
 | 
					        assert isinstance(streamers.timeout, timedelta)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @pytest.fixture
 | 
				
			||||||
 | 
					    def test_reset(self, streamers, stations):
 | 
				
			||||||
 | 
					        streamers.reset()
 | 
				
			||||||
 | 
					        assert all(
 | 
				
			||||||
 | 
					            streamers.streamers[station.pk] == station for station in stations
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @pytest.fixture
 | 
				
			||||||
 | 
					    def test_fetch(self, streamers):
 | 
				
			||||||
 | 
					        streamers.next_date = tz.now() - tz.timedelta(seconds=30)
 | 
				
			||||||
 | 
					        streamers.streamers = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        now = tz.now()
 | 
				
			||||||
 | 
					        streamers.fetch()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert all(streamer.calls.get("fetch") for streamer in streamers)
 | 
				
			||||||
 | 
					        assert streamers.next_date > now
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @pytest.fixture
 | 
				
			||||||
 | 
					    def test_fetch_timeout_not_reached(self, streamers):
 | 
				
			||||||
 | 
					        next_date = tz.now() + tz.timedelta(seconds=30)
 | 
				
			||||||
 | 
					        streamers.next_date = next_date
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        streamers.fetch()
 | 
				
			||||||
 | 
					        assert all(not streamer.calls.get("fetch") for streamer in streamers)
 | 
				
			||||||
 | 
					        assert streamers.next_date == next_date
 | 
				
			||||||
							
								
								
									
										185
									
								
								aircox_streamer/tests/test_viewsets.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								aircox_streamer/tests/test_viewsets.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,185 @@
 | 
				
			|||||||
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.http import Http404
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from rest_framework.exceptions import ValidationError
 | 
				
			||||||
 | 
					from aircox_streamer.viewsets import (
 | 
				
			||||||
 | 
					    ControllerViewSet,
 | 
				
			||||||
 | 
					    SourceViewSet,
 | 
				
			||||||
 | 
					    StreamerViewSet,
 | 
				
			||||||
 | 
					    QueueSourceViewSet,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FakeSerializer:
 | 
				
			||||||
 | 
					    def __init__(self, instance, *args, **kwargs):
 | 
				
			||||||
 | 
					        self.instance = instance
 | 
				
			||||||
 | 
					        self.data = {"instance": self.instance}
 | 
				
			||||||
 | 
					        self.args = args
 | 
				
			||||||
 | 
					        self.kwargs = kwargs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FakeRequest:
 | 
				
			||||||
 | 
					    def __init__(self, **kwargs):
 | 
				
			||||||
 | 
					        self.__dict__.update(**kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@pytest.fixture
 | 
				
			||||||
 | 
					def controller_viewset(streamers, station):
 | 
				
			||||||
 | 
					    return ControllerViewSet(
 | 
				
			||||||
 | 
					        streamers=streamers,
 | 
				
			||||||
 | 
					        streamer=streamers[station.pk],
 | 
				
			||||||
 | 
					        serializer_class=FakeSerializer,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@pytest.fixture
 | 
				
			||||||
 | 
					def streamer_viewset(streamers, station):
 | 
				
			||||||
 | 
					    return StreamerViewSet(
 | 
				
			||||||
 | 
					        streamers=streamers,
 | 
				
			||||||
 | 
					        streamer=streamers[station.pk],
 | 
				
			||||||
 | 
					        serializer_class=FakeSerializer,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@pytest.fixture
 | 
				
			||||||
 | 
					def source_viewset(streamers, station):
 | 
				
			||||||
 | 
					    return SourceViewSet(
 | 
				
			||||||
 | 
					        streamers=streamers,
 | 
				
			||||||
 | 
					        streamer=streamers[station.pk],
 | 
				
			||||||
 | 
					        serializer_class=FakeSerializer,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@pytest.fixture
 | 
				
			||||||
 | 
					def queue_source_viewset(streamers, station):
 | 
				
			||||||
 | 
					    return QueueSourceViewSet(
 | 
				
			||||||
 | 
					        streamers=streamers,
 | 
				
			||||||
 | 
					        streamer=streamers[station.pk],
 | 
				
			||||||
 | 
					        serializer_class=FakeSerializer,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestControllerViewSet:
 | 
				
			||||||
 | 
					    @pytest.mark.django_db
 | 
				
			||||||
 | 
					    def test_get_streamer(self, controller_viewset, stations):
 | 
				
			||||||
 | 
					        station = stations[0]
 | 
				
			||||||
 | 
					        streamer = controller_viewset.get_streamer(station.pk)
 | 
				
			||||||
 | 
					        assert streamer.station.pk == station.pk
 | 
				
			||||||
 | 
					        assert streamer.calls.get("fetch")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @pytest.mark.django_db
 | 
				
			||||||
 | 
					    def test_get_streamer_station_not_found(self, controller_viewset):
 | 
				
			||||||
 | 
					        controller_viewset.streamers.streamers = {}
 | 
				
			||||||
 | 
					        with pytest.raises(Http404):
 | 
				
			||||||
 | 
					            controller_viewset.get_streamer(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @pytest.mark.django_db
 | 
				
			||||||
 | 
					    def test_get_serializer(self, controller_viewset):
 | 
				
			||||||
 | 
					        controller_viewset.object = {"object": "value"}
 | 
				
			||||||
 | 
					        serializer = controller_viewset.get_serializer(test=True)
 | 
				
			||||||
 | 
					        assert serializer.kwargs["test"]
 | 
				
			||||||
 | 
					        assert serializer.instance == controller_viewset.object
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @pytest.mark.django_db
 | 
				
			||||||
 | 
					    def test_serialize(self, controller_viewset):
 | 
				
			||||||
 | 
					        instance = {}
 | 
				
			||||||
 | 
					        data = controller_viewset.serialize(instance, test=True)
 | 
				
			||||||
 | 
					        assert data == {"instance": instance}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestStreamerViewSet:
 | 
				
			||||||
 | 
					    @pytest.mark.django_db
 | 
				
			||||||
 | 
					    def test_retrieve(self, streamer_viewset):
 | 
				
			||||||
 | 
					        streamer_viewset.streamer = {"streamer": "test"}
 | 
				
			||||||
 | 
					        resp = streamer_viewset.retrieve(None, None)
 | 
				
			||||||
 | 
					        assert resp.data == {"instance": streamer_viewset.streamer}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @pytest.mark.django_db
 | 
				
			||||||
 | 
					    def test_list(self, streamer_viewset):
 | 
				
			||||||
 | 
					        streamers = {"a": 1, "b": 2}
 | 
				
			||||||
 | 
					        streamer_viewset.streamers.streamers = streamers
 | 
				
			||||||
 | 
					        resp = streamer_viewset.list(None)
 | 
				
			||||||
 | 
					        assert set(resp.data["results"]["instance"]) == set(streamers.values())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestSourceViewSet:
 | 
				
			||||||
 | 
					    @pytest.mark.django_db
 | 
				
			||||||
 | 
					    def test_get_sources(self, source_viewset, streamers):
 | 
				
			||||||
 | 
					        source_viewset.streamer.sources.append(45)
 | 
				
			||||||
 | 
					        sources = source_viewset.get_sources()
 | 
				
			||||||
 | 
					        assert 45 not in set(sources)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @pytest.mark.django_db
 | 
				
			||||||
 | 
					    def test_get_source(self, source_viewset):
 | 
				
			||||||
 | 
					        source = source_viewset.get_source(1)
 | 
				
			||||||
 | 
					        assert source.id == 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @pytest.mark.django_db
 | 
				
			||||||
 | 
					    def test_get_source_not_found(self, source_viewset):
 | 
				
			||||||
 | 
					        with pytest.raises(Http404):
 | 
				
			||||||
 | 
					            source_viewset.get_source(1000)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @pytest.mark.django_db
 | 
				
			||||||
 | 
					    def test_retrieve(self, source_viewset, station):
 | 
				
			||||||
 | 
					        resp = source_viewset.retrieve(None, 0)
 | 
				
			||||||
 | 
					        source = source_viewset.streamers[station.pk].sources[0]
 | 
				
			||||||
 | 
					        # this is FakeSerializer being used which provides us the proof
 | 
				
			||||||
 | 
					        assert resp.data["instance"] == source
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @pytest.mark.django_db
 | 
				
			||||||
 | 
					    def test_list(self, source_viewset, station):
 | 
				
			||||||
 | 
					        sources = source_viewset.streamers[station.pk].sources
 | 
				
			||||||
 | 
					        resp = source_viewset.list(None)
 | 
				
			||||||
 | 
					        assert list(resp.data["results"]["instance"]) == sources
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @pytest.mark.django_db
 | 
				
			||||||
 | 
					    def test__run(self, source_viewset):
 | 
				
			||||||
 | 
					        calls = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def action(x):
 | 
				
			||||||
 | 
					            return calls.setdefault("action", True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        source_viewset._run(0, action)
 | 
				
			||||||
 | 
					        assert calls.get("action")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @pytest.mark.django_db
 | 
				
			||||||
 | 
					    def test_all_api_source_actions(self, source_viewset, station):
 | 
				
			||||||
 | 
					        source = source_viewset.streamers[station.pk].sources[0]
 | 
				
			||||||
 | 
					        request = FakeRequest(POST={"seek": 1})
 | 
				
			||||||
 | 
					        source_viewset.get_source = lambda x: source
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for action in ("sync", "skip", "restart", "seek"):
 | 
				
			||||||
 | 
					            func = getattr(source_viewset, action)
 | 
				
			||||||
 | 
					            func(request, 1)
 | 
				
			||||||
 | 
					            assert source.calls.get(action)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestQueueSourceViewSet:
 | 
				
			||||||
 | 
					    @pytest.mark.django_db
 | 
				
			||||||
 | 
					    def test_get_sound_queryset(self, queue_source_viewset, station, sounds):
 | 
				
			||||||
 | 
					        ids = {sound.pk for sound in sounds}
 | 
				
			||||||
 | 
					        request = FakeRequest(station=station)
 | 
				
			||||||
 | 
					        query = queue_source_viewset.get_sound_queryset(request)
 | 
				
			||||||
 | 
					        assert set(query.values_list("pk", flat=True)) == ids
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @pytest.mark.django_db
 | 
				
			||||||
 | 
					    def test_push(self, queue_source_viewset, station, sounds):
 | 
				
			||||||
 | 
					        calls = {}
 | 
				
			||||||
 | 
					        sound = sounds[0]
 | 
				
			||||||
 | 
					        request = FakeRequest(station=station, data={"sound_id": sound.pk})
 | 
				
			||||||
 | 
					        queue_source_viewset._run = lambda pk, func: calls.setdefault(
 | 
				
			||||||
 | 
					            "_run", (pk, func)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        result = queue_source_viewset.push(request, 13)
 | 
				
			||||||
 | 
					        assert "_run" in calls
 | 
				
			||||||
 | 
					        assert result[0] == 13
 | 
				
			||||||
 | 
					        assert callable(result[1])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @pytest.mark.django_db
 | 
				
			||||||
 | 
					    def test_push_missing_sound_in_request_post(
 | 
				
			||||||
 | 
					        self, queue_source_viewset, station
 | 
				
			||||||
 | 
					    ):
 | 
				
			||||||
 | 
					        request = FakeRequest(station=station, data={})
 | 
				
			||||||
 | 
					        with pytest.raises(ValidationError):
 | 
				
			||||||
 | 
					            queue_source_viewset.push(request, 0)
 | 
				
			||||||
@ -4,11 +4,11 @@ from django.utils.translation import gettext_lazy as _
 | 
				
			|||||||
from aircox.viewsets import SoundViewSet
 | 
					from aircox.viewsets import SoundViewSet
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from . import viewsets
 | 
					from . import viewsets
 | 
				
			||||||
from .views import StreamerAdminMixin
 | 
					from .views import StreamerAdminView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
admin.site.route_view(
 | 
					admin.site.route_view(
 | 
				
			||||||
    "tools/streamer",
 | 
					    "tools/streamer",
 | 
				
			||||||
    StreamerAdminMixin.as_view(),
 | 
					    StreamerAdminView.as_view(),
 | 
				
			||||||
    "tools-streamer",
 | 
					    "tools-streamer",
 | 
				
			||||||
    label=_("Streamer Monitor"),
 | 
					    label=_("Streamer Monitor"),
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
				
			|||||||
@ -2,8 +2,17 @@ from django.utils.translation import gettext_lazy as _
 | 
				
			|||||||
from django.views.generic import TemplateView
 | 
					from django.views.generic import TemplateView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from aircox.views.admin import AdminMixin
 | 
					from aircox.views.admin import AdminMixin
 | 
				
			||||||
 | 
					from .controllers import streamers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class StreamerAdminMixin(AdminMixin, TemplateView):
 | 
					class StreamerAdminView(AdminMixin, TemplateView):
 | 
				
			||||||
    template_name = "aircox_streamer/streamer.html"
 | 
					    template_name = "aircox_streamer/streamer.html"
 | 
				
			||||||
    title = _("Streamer Monitor")
 | 
					    title = _("Streamer Monitor")
 | 
				
			||||||
 | 
					    streamers = streamers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def dispatch(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        # Note: this might raise concurrency racing problem with viewsets,
 | 
				
			||||||
 | 
					        # since streamers.streamers is reset to a new dict. Still am i not
 | 
				
			||||||
 | 
					        # sure, and needs analysis.
 | 
				
			||||||
 | 
					        self.streamers.reset()
 | 
				
			||||||
 | 
					        return super().dispatch(*args, **kwargs)
 | 
				
			||||||
 | 
				
			|||||||
@ -1,15 +1,15 @@
 | 
				
			|||||||
from django.http import Http404
 | 
					from django.http import Http404
 | 
				
			||||||
from django.shortcuts import get_object_or_404
 | 
					from django.shortcuts import get_object_or_404
 | 
				
			||||||
from django.utils import timezone as tz
 | 
					 | 
				
			||||||
from rest_framework import viewsets
 | 
					from rest_framework import viewsets
 | 
				
			||||||
from rest_framework.decorators import action
 | 
					from rest_framework.decorators import action
 | 
				
			||||||
from rest_framework.exceptions import ValidationError
 | 
					from rest_framework.exceptions import ValidationError
 | 
				
			||||||
from rest_framework.permissions import IsAdminUser
 | 
					from rest_framework.permissions import IsAdminUser
 | 
				
			||||||
from rest_framework.response import Response
 | 
					from rest_framework.response import Response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from aircox.models import Sound, Station
 | 
					from aircox.models import Sound
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from . import controllers
 | 
					from . import controllers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .serializers import (
 | 
					from .serializers import (
 | 
				
			||||||
    PlaylistSerializer,
 | 
					    PlaylistSerializer,
 | 
				
			||||||
    QueueSourceSerializer,
 | 
					    QueueSourceSerializer,
 | 
				
			||||||
@ -19,8 +19,7 @@ from .serializers import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
__all__ = [
 | 
					__all__ = [
 | 
				
			||||||
    "Streamers",
 | 
					    "ControllerViewSet",
 | 
				
			||||||
    "BaseControllerAPIView",
 | 
					 | 
				
			||||||
    "RequestViewSet",
 | 
					    "RequestViewSet",
 | 
				
			||||||
    "StreamerViewSet",
 | 
					    "StreamerViewSet",
 | 
				
			||||||
    "SourceViewSet",
 | 
					    "SourceViewSet",
 | 
				
			||||||
@ -29,94 +28,45 @@ __all__ = [
 | 
				
			|||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Streamers:
 | 
					class ControllerViewSet(viewsets.ViewSet):
 | 
				
			||||||
    date = None
 | 
					 | 
				
			||||||
    """Next update datetime."""
 | 
					 | 
				
			||||||
    streamers = None
 | 
					 | 
				
			||||||
    """Stations by station id."""
 | 
					 | 
				
			||||||
    timeout = None
 | 
					 | 
				
			||||||
    """Timedelta to next update."""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __init__(self, timeout=None):
 | 
					 | 
				
			||||||
        self.timeout = timeout or tz.timedelta(seconds=2)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def load(self, force=False):
 | 
					 | 
				
			||||||
        # FIXME: cf. TODO in aircox.controllers about model updates
 | 
					 | 
				
			||||||
        stations = Station.objects.active()
 | 
					 | 
				
			||||||
        if self.streamers is None or force:
 | 
					 | 
				
			||||||
            self.streamers = {
 | 
					 | 
				
			||||||
                station.pk: controllers.Streamer(station)
 | 
					 | 
				
			||||||
                for station in stations
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        streamers = self.streamers
 | 
					 | 
				
			||||||
        self.streamers = {
 | 
					 | 
				
			||||||
            station.pk: controllers.Streamer(station)
 | 
					 | 
				
			||||||
            if station.pk in streamers
 | 
					 | 
				
			||||||
            else streamers[station.pk]
 | 
					 | 
				
			||||||
            for station in stations
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def fetch(self):
 | 
					 | 
				
			||||||
        if self.streamers is None:
 | 
					 | 
				
			||||||
            self.load()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        now = tz.now()
 | 
					 | 
				
			||||||
        if self.date is not None and now < self.date:
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for streamer in self.streamers.values():
 | 
					 | 
				
			||||||
            streamer.fetch()
 | 
					 | 
				
			||||||
        self.date = now + self.timeout
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get(self, key, default=None):
 | 
					 | 
				
			||||||
        return self.streamers.get(key, default)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def values(self):
 | 
					 | 
				
			||||||
        return self.streamers.values()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __getitem__(self, key):
 | 
					 | 
				
			||||||
        return self.streamers[key]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __contains__(self, key):
 | 
					 | 
				
			||||||
        return key in self.streamers
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
streamers = Streamers()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class BaseControllerAPIView(viewsets.ViewSet):
 | 
					 | 
				
			||||||
    permission_classes = (IsAdminUser,)
 | 
					    permission_classes = (IsAdminUser,)
 | 
				
			||||||
    serializer_class = None
 | 
					    serializer_class = None
 | 
				
			||||||
 | 
					    streamers = controllers.streamers
 | 
				
			||||||
 | 
					    """Streamers controller instance."""
 | 
				
			||||||
    streamer = None
 | 
					    streamer = None
 | 
				
			||||||
 | 
					    """User's Streamer instance."""
 | 
				
			||||||
    object = None
 | 
					    object = None
 | 
				
			||||||
 | 
					    """Object to serialize."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_streamer(self, request, station_pk=None, **kwargs):
 | 
					    def get_streamer(self, station_pk=None):
 | 
				
			||||||
        streamers.fetch()
 | 
					        """Get user's streamer."""
 | 
				
			||||||
        id = int(request.station.pk if station_pk is None else station_pk)
 | 
					        if station_pk is None:
 | 
				
			||||||
        if id not in streamers:
 | 
					            station_pk = self.request.station.pk
 | 
				
			||||||
 | 
					        self.streamers.fetch()
 | 
				
			||||||
 | 
					        if station_pk not in self.streamers:
 | 
				
			||||||
            raise Http404("station not found")
 | 
					            raise Http404("station not found")
 | 
				
			||||||
        return streamers[id]
 | 
					        return self.streamers[station_pk]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_serializer(self, **kwargs):
 | 
					    def get_serializer(self, **kwargs):
 | 
				
			||||||
 | 
					        """Get serializer instance."""
 | 
				
			||||||
        return self.serializer_class(self.object, **kwargs)
 | 
					        return self.serializer_class(self.object, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def serialize(self, obj, **kwargs):
 | 
					    def serialize(self, obj, **kwargs):
 | 
				
			||||||
 | 
					        """Serializer controller data."""
 | 
				
			||||||
        self.object = obj
 | 
					        self.object = obj
 | 
				
			||||||
        serializer = self.get_serializer(**kwargs)
 | 
					        serializer = self.get_serializer(**kwargs)
 | 
				
			||||||
        return serializer.data
 | 
					        return serializer.data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def dispatch(self, request, *args, station_pk=None, **kwargs):
 | 
					    def dispatch(self, request, *args, station_pk=None, **kwargs):
 | 
				
			||||||
        self.streamer = self.get_streamer(request, station_pk, **kwargs)
 | 
					        self.streamer = self.get_streamer(station_pk)
 | 
				
			||||||
        return super().dispatch(request, *args, **kwargs)
 | 
					        return super().dispatch(request, *args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class RequestViewSet(BaseControllerAPIView):
 | 
					class RequestViewSet(ControllerViewSet):
 | 
				
			||||||
    serializer_class = RequestSerializer
 | 
					    serializer_class = RequestSerializer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class StreamerViewSet(BaseControllerAPIView):
 | 
					class StreamerViewSet(ControllerViewSet):
 | 
				
			||||||
    serializer_class = StreamerSerializer
 | 
					    serializer_class = StreamerSerializer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def retrieve(self, request, pk=None):
 | 
					    def retrieve(self, request, pk=None):
 | 
				
			||||||
@ -124,7 +74,7 @@ class StreamerViewSet(BaseControllerAPIView):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def list(self, request, pk=None):
 | 
					    def list(self, request, pk=None):
 | 
				
			||||||
        return Response(
 | 
					        return Response(
 | 
				
			||||||
            {"results": self.serialize(streamers.values(), many=True)}
 | 
					            {"results": self.serialize(self.streamers.values(), many=True)}
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def dispatch(self, request, *args, pk=None, **kwargs):
 | 
					    def dispatch(self, request, *args, pk=None, **kwargs):
 | 
				
			||||||
@ -135,7 +85,7 @@ class StreamerViewSet(BaseControllerAPIView):
 | 
				
			|||||||
        return super().dispatch(request, *args, **kwargs)
 | 
					        return super().dispatch(request, *args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SourceViewSet(BaseControllerAPIView):
 | 
					class SourceViewSet(ControllerViewSet):
 | 
				
			||||||
    serializer_class = SourceSerializer
 | 
					    serializer_class = SourceSerializer
 | 
				
			||||||
    model = controllers.Source
 | 
					    model = controllers.Source
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -151,8 +101,8 @@ class SourceViewSet(BaseControllerAPIView):
 | 
				
			|||||||
        return source
 | 
					        return source
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def retrieve(self, request, pk=None):
 | 
					    def retrieve(self, request, pk=None):
 | 
				
			||||||
        self.object = self.get_source(pk)
 | 
					        source = self.get_source(pk)
 | 
				
			||||||
        return Response(self.serialize())
 | 
					        return Response(self.serialize(source))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def list(self, request):
 | 
					    def list(self, request):
 | 
				
			||||||
        return Response(
 | 
					        return Response(
 | 
				
			||||||
@ -192,8 +142,8 @@ class QueueSourceViewSet(SourceViewSet):
 | 
				
			|||||||
    serializer_class = QueueSourceSerializer
 | 
					    serializer_class = QueueSourceSerializer
 | 
				
			||||||
    model = controllers.QueueSource
 | 
					    model = controllers.QueueSource
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_sound_queryset(self):
 | 
					    def get_sound_queryset(self, request):
 | 
				
			||||||
        return Sound.objects.station(self.request.station).archive()
 | 
					        return Sound.objects.station(request.station).archive()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @action(detail=True, methods=["POST"])
 | 
					    @action(detail=True, methods=["POST"])
 | 
				
			||||||
    def push(self, request, pk):
 | 
					    def push(self, request, pk):
 | 
				
			||||||
@ -201,7 +151,7 @@ class QueueSourceViewSet(SourceViewSet):
 | 
				
			|||||||
            raise ValidationError('missing "sound_id" POST data')
 | 
					            raise ValidationError('missing "sound_id" POST data')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        sound = get_object_or_404(
 | 
					        sound = get_object_or_404(
 | 
				
			||||||
            self.get_sound_queryset(), pk=request.data["sound_id"]
 | 
					            self.get_sound_queryset(request), pk=request.data["sound_id"]
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        return self._run(
 | 
					        return self._run(
 | 
				
			||||||
            pk, lambda s: s.push(sound.file.path) if sound.file.path else None
 | 
					            pk, lambda s: s.push(sound.file.path) if sound.file.path else None
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user