Skip to content

Commit

Permalink
Reload all playlists from backend
Browse files Browse the repository at this point in the history
  • Loading branch information
dtcooper committed Jul 29, 2024
1 parent 2f7494f commit 11bcc60
Show file tree
Hide file tree
Showing 9 changed files with 113 additions and 17 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ Changes for 2024 based on real world usage in 2023 and feedback
- [ ] Skip track in stopset when we get to it
- [ ] ~~Mark a bad asset? Interesting. Not sure how it would work.~~ – too difficult
to implement, too many opinions, person wishing to flag talks to manager-of-the-moment
- [ ] Refresh playlist from backend. Connected client status from backend? Communicate what's in the playlist?
- [x] Refresh playlist from backend. ~~Connected client status from backend? Communicate what's in the playlist?~~
- [x] Minimally completed via `RELOAD_PLAYLIST_AFTER_DATA_CHANGES` setting
- [ ] ~~Status updates, ie news and traffic? Fullscreen thing?~~ / ~~Custom UI labels unused labels that play every N times (ie for telling the DJ to do something)~~ -- Probably not
- [x] Round-robin (or "cycle evenly") rotator scheduling, ignoring weight as an option for a rotator
Expand Down
20 changes: 14 additions & 6 deletions server/api/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ async def receive(self):

async def receive_message(self):
data = await self.receive()
return (data["type"], data["data"])
return (data["type"], data.get("data", {}))

async def message(self, message_type, message=None):
await self.send({"type": message_type, "data": message})
Expand Down Expand Up @@ -81,6 +81,10 @@ def __init__(self):
def is_admin(self):
raise NotImplementedError()

@property
def num_connections(self):
return len(self.connections)

async def hello(self, connection: Connection):
raise NotImplementedError()

Expand All @@ -99,9 +103,9 @@ async def authorize(self, greeting: dict, websocket: WebSocket) -> Connection:
)
raise TomatoAuthError(f"Server running {what} protocol than you. You'll need to {action} Tomato.")

is_session = greeting["method"] == "session"

if is_session:
if greeting["method"] == "secret-key":
lookup = {"id": greeting["user_id"]}
elif greeting["method"] == "session":
if "sessionid" not in websocket.cookies:
raise TomatoAuthError("No sessionid cookie, can't complete session auth!")
store = SessionStore(session_key=websocket.cookies["sessionid"])
Expand All @@ -114,12 +118,16 @@ async def authorize(self, greeting: dict, websocket: WebSocket) -> Connection:
except User.DoesNotExist:
pass
else:
if user.is_active and (not self.is_admin or user.is_superuser):
if is_session:
if user.is_active and (not self.is_admin or user.is_superuser or greeting["method"] == "secret-key"):
if greeting["method"] == "session":
session_hash = store.get(HASH_SESSION_KEY)
if session_hash and constant_time_compare(session_hash, user.get_session_auth_hash()):
logger.info(f"Authorized admin session for {user}")
return Connection(websocket, user)
elif greeting["method"] == "secret-key":
if constant_time_compare(greeting["key"], settings.SECRET_KEY):
logger.info(f"Authorized admin via secret key for {user}")
return Connection(websocket, user)
elif await user.acheck_password(greeting["password"]):
logger.info(f"Authorized user connection for {user}")
return Connection(websocket, user)
Expand Down
8 changes: 6 additions & 2 deletions server/api/connections.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from tomato.models import ClientLogEntry, get_config_async, serialize_for_api

from .base import Connection, ConnectionsBase
from .schemas import AdminMessageTypes, OutgoingUserMessageTypes, UserMessageTypes
from .schemas import AdminMessageTypes, OutgoingAdminMessageTypes, OutgoingUserMessageTypes, UserMessageTypes
from .utils import retry_on_failure


Expand All @@ -16,7 +16,11 @@ class AdminConnections(ConnectionsBase):
is_admin = True

async def hello(self, connection: Connection):
await connection.send({"test": "You are an admin!"})
await connection.message(OutgoingAdminMessageTypes.HELLO, {"num_connected_users": users.num_connections})

async def process_reload_playlist(self, connection: Connection, data):
await users.broadcast(OutgoingAdminMessageTypes.RELOAD_PLAYLIST)
await connection.message(OutgoingAdminMessageTypes.RELOAD_PLAYLIST, {"success": True})


class UserConnections(ConnectionsBase):
Expand Down
13 changes: 11 additions & 2 deletions server/api/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ class OutgoingUserMessageTypes(enum.StrEnum):


class AdminMessageTypes(enum.StrEnum):
pass
RELOAD_PLAYLIST = "reload-playlist"


class OutgoingAdminMessageTypes(enum.StrEnum):
pass
RELOAD_PLAYLIST = "reload-playlist"
HELLO = "hello"


greeting_schema = Schema(
Expand All @@ -44,5 +45,13 @@ class OutgoingAdminMessageTypes(enum.StrEnum):
Optional("admin_mode", default=False): Use(bool),
"method": "session",
},
{
"key": str,
"user_id": int,
"tomato": "radio-automation",
"protocol_version": Use(int),
"admin_mode": True,
"method": "secret-key",
},
)
)
3 changes: 2 additions & 1 deletion server/tomato/admin/views/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from .asset_data import AdminAssetDataView
from .configure_live_clients import AdminConfigureLiveClientsView


extra_views = (AdminAssetDataView,)
extra_views = (AdminAssetDataView, AdminConfigureLiveClientsView)


__all__ = (extra_views,)
61 changes: 61 additions & 0 deletions server/tomato/admin/views/configure_live_clients.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import json
import logging

from websockets.sync.client import connect as websocket_connect

from django.conf import settings
from django.contrib import messages
from django.shortcuts import redirect
from django.views.generic import TemplateView

from ...constants import PROTOCOL_VERSION
from .base import AdminViewMixin


logger = logging.getLogger(__name__)


class AdminConfigureLiveClientsView(AdminViewMixin, TemplateView):
name = "configure_live_clients"
perms = ("tomato.immediate_play_asset",)
title = "Configure live clients"

def post(self, request, *args, **kwargs):
# Probably needs a refactor but okay for now
try:
with websocket_connect("ws://api:8000/api") as ws:
ws.send(
json.dumps({
"user_id": request.user.id,
"tomato": "radio-automation",
"protocol_version": PROTOCOL_VERSION,
"admin_mode": True,
"method": "secret-key",
"key": settings.SECRET_KEY,
})
)
response = json.loads(ws.recv())
if not response["success"]:
raise Exception(f"Error connecting: {response}")

response = json.loads(ws.recv())
if not response["type"] == "hello":
raise Exception(f"Invalid hello response type: {response}")
num_connected_users = response["data"]["num_connected_users"]

ws.send(json.dumps({"type": "reload-playlist"}))
response = json.loads(ws.recv())
if not response["type"] == "reload-playlist":
raise Exception(f"Invalid reload-playlist response type: {response}")
if not response["data"]["success"]:
raise Exception(f"Failure reloading playlist: {response}")

except Exception:
logger.exception("Error while connecting to api")
self.message_user(
"An error occurred while connecting to the server. Check logs for more information.", messages.ERROR
)
else:
self.message_user(f"Reloaded the playlist of {num_connected_users} connected desktop client(s)!")

return redirect("admin:extra_configure_live_clients")
2 changes: 1 addition & 1 deletion server/tomato/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def create_groups(self, using=None, *args, **kwargs):
group.permissions.add(Permission.objects.get(codename="change_config"))
all_groups.append(group)

group, _ = Group.objects.get_or_create(name="Can immediately schedule audio assets to play")
group, _ = Group.objects.get_or_create(name="Can manage connected desktop clients")
group.permissions.add(Permission.objects.get(codename="immediate_play_asset"))
all_groups.append(group)

Expand Down
4 changes: 0 additions & 4 deletions server/tomato/templates/admin/base_site.html
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,6 @@
cell.append(link)
})

{% if perms.tomato.immediate_play_asset %}
{# add immediate play link here #}
{% endif %}

Array.from(document.querySelectorAll('.dismiss-message')).forEach(dismissLink => {
dismissLink.addEventListener('click', async event => {
event.preventDefault()
Expand Down
17 changes: 17 additions & 0 deletions server/tomato/templates/admin/extra/configure_live_clients.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{% extends 'admin/extra/base.html' %}

{% block content %}
<form method="POST">
{% csrf_token %}
<fieldset class="module aligned">
<h2>Reload all connected clients</h2>
<input type="hidden" name="action" value="export">
<div class="form-row">
<p>Click the button below to reload the plalists in all connected clients.</p>
</div>
</fieldset>
<div class="submit-row">
<input type="submit" class="default" value="Reload playlists of all connected desktop clients">
</div>
</form>
{% endblock %}

0 comments on commit 11bcc60

Please sign in to comment.