Compare commits
11 Commits
2347f8405d
...
adc29d8c0a
Author | SHA1 | Date | |
---|---|---|---|
adc29d8c0a | |||
49c7a62948 | |||
e3aef70f53 | |||
1c442bec41 | |||
96c41150f9 | |||
9411b21d0f | |||
44402c3907 | |||
997d1260c8 | |||
8aba4f8529 | |||
564d79e831 | |||
93e521aca0 |
1
description
Normal file
1
description
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Track fear and phase in multiplayer games of the board game Spirit Island.
|
16
fear-tracker-gunicorn.service
Normal file
16
fear-tracker-gunicorn.service
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# Based on https://docs.gunicorn.org/en/stable/deploy.html#systemd
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/bin/gunicorn3 fear_tracker_site.asgi:application --config=/home/anyoneeb/sites/apps/fear-tracker/gnuicorn.conf.py
|
||||||
|
ExecReload=/bin/kill -s HUP $MAINPID
|
||||||
|
Restart=always
|
||||||
|
StandardOutput=syslog
|
||||||
|
StandardError=syslog
|
||||||
|
SyslogIdentifier=fear-tracker
|
||||||
|
User=fear-tracker
|
||||||
|
Group=fear-tracker
|
||||||
|
KillMode=mixed
|
||||||
|
PrivateTmp=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
11
fear-tracker-uwsgi.service
Normal file
11
fear-tracker-uwsgi.service
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/bin/uwsgi --ini /home/anyoneeb/sites/apps/fear-tracker/uwsgi.ini
|
||||||
|
Restart=always
|
||||||
|
StandardOutput=syslog
|
||||||
|
StandardError=syslog
|
||||||
|
SyslogIdentifier=fear-tracker
|
||||||
|
User=fear-tracker
|
||||||
|
Group=fear-tracker
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
16
fear_tracker/routing.py
Normal file
16
fear_tracker/routing.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
from channels.http import AsgiHandler
|
||||||
|
from channels.routing import ProtocolTypeRouter, URLRouter
|
||||||
|
|
||||||
|
from django.conf.urls import url
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
application = ProtocolTypeRouter({
|
||||||
|
"http": URLRouter([
|
||||||
|
url(r'^(?P<access_code>[a-zA-Z]{6})/', URLRouter([
|
||||||
|
url(r"status/(?P<hashcode>[a-z0-9]{64})/",
|
||||||
|
views.StatusLongPollConsumer, name='status'),
|
||||||
|
])),
|
||||||
|
url(r"", AsgiHandler),
|
||||||
|
]),
|
||||||
|
})
|
|
@ -360,13 +360,23 @@
|
||||||
el.addEventListener("change", formElementChanged);
|
el.addEventListener("change", formElementChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
setInterval(function() {
|
function checkStatus() {
|
||||||
if(activeRequests.size != 0) return;
|
if(activeRequests.size != 0) return;
|
||||||
|
// From https://stackoverflow.com/a/50101022
|
||||||
|
|
||||||
|
const abort = new AbortController();
|
||||||
|
const signal = abort.signal;
|
||||||
|
|
||||||
|
// 50 second timeout:
|
||||||
|
const timeoutId = setTimeout(() => abort.abort(), 50000);
|
||||||
|
|
||||||
fetch(new Request("{% url 'status' access_code=access_code %}"
|
fetch(new Request("{% url 'status' access_code=access_code %}"
|
||||||
+ (statusObj.hash != ""
|
+ (statusObj.hash != ""
|
||||||
? statusObj.hash + "/"
|
? statusObj.hash + "/"
|
||||||
: "")))
|
: "")),
|
||||||
|
{signal})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
if(response.status === 304) {
|
if(response.status === 304) {
|
||||||
// TODO Just skip the next step?
|
// TODO Just skip the next step?
|
||||||
return statusObj;
|
return statusObj;
|
||||||
|
@ -381,8 +391,14 @@
|
||||||
} else {
|
} else {
|
||||||
statusObj = data;
|
statusObj = data;
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.then(checkStatus)
|
||||||
|
.catch(() => {
|
||||||
|
// If something went wrong, wait a few seconds before retrying.
|
||||||
|
setTimeout(checkStatus, 5000);
|
||||||
});
|
});
|
||||||
}, 5000);
|
}
|
||||||
|
checkStatus();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -27,7 +27,5 @@ urlpatterns = [
|
||||||
path('update/', views.update_game, name='update_game'),
|
path('update/', views.update_game, name='update_game'),
|
||||||
path('qr/', views.qr_code, name='qr_code'),
|
path('qr/', views.qr_code, name='qr_code'),
|
||||||
path('status/', views.status, name='status'),
|
path('status/', views.status, name='status'),
|
||||||
url('^status/(?P<hashcode>[a-z0-9]{64})/',
|
|
||||||
views.status, name='status'),
|
|
||||||
])),
|
])),
|
||||||
]
|
]
|
||||||
|
|
|
@ -5,6 +5,11 @@ import hashlib
|
||||||
import json
|
import json
|
||||||
import qrcode
|
import qrcode
|
||||||
|
|
||||||
|
from asgiref.sync import async_to_sync
|
||||||
|
|
||||||
|
from channels.generic.http import AsyncHttpConsumer
|
||||||
|
from channels.layers import get_channel_layer
|
||||||
|
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
|
@ -288,12 +293,22 @@ def handle_game_request(request, game, update):
|
||||||
res['value'] = current_value
|
res['value'] = current_value
|
||||||
else:
|
else:
|
||||||
res = {'success': True}
|
res = {'success': True}
|
||||||
|
async_to_sync(get_channel_layer().group_send)(
|
||||||
|
"%s_status" % game.access_code,
|
||||||
|
{"type": "fear_tracker.invalidate_status"})
|
||||||
return HttpResponse(json.dumps(res))
|
return HttpResponse(json.dumps(res))
|
||||||
|
|
||||||
players = get_players_with_fear(game, current_phase, players)
|
players = get_players_with_fear(game, current_phase, players)
|
||||||
status_obj = game_status_object(game, current_phase, players)
|
status_obj = game_status_object(game, current_phase, players)
|
||||||
status_string = json.dumps(status_obj)
|
status_string = json.dumps(status_obj)
|
||||||
|
|
||||||
|
async_to_sync(get_channel_layer().group_send)(
|
||||||
|
"%s_status" % game.access_code, {
|
||||||
|
"type": "fear_tracker.hashcode_seen",
|
||||||
|
"hashcode": status_obj['hash'],
|
||||||
|
"status_string": status_string,
|
||||||
|
})
|
||||||
|
|
||||||
for player in players.values():
|
for player in players.values():
|
||||||
info = player.fear
|
info = player.fear
|
||||||
if not info:
|
if not info:
|
||||||
|
@ -324,4 +339,57 @@ def status(request, game, hashcode=None):
|
||||||
return HttpResponse(status=HTTPStatus.NOT_MODIFIED)
|
return HttpResponse(status=HTTPStatus.NOT_MODIFIED)
|
||||||
else:
|
else:
|
||||||
status_string = json.dumps(status_obj)
|
status_string = json.dumps(status_obj)
|
||||||
|
async_to_sync(get_channel_layer().group_send)(
|
||||||
|
"%s_status" % game.access_code, {
|
||||||
|
"type": "fear_tracker.hashcode_seen",
|
||||||
|
"hashcode": status_obj['hash'],
|
||||||
|
"status_string": status_string,
|
||||||
|
})
|
||||||
return HttpResponse(status_string)
|
return HttpResponse(status_string)
|
||||||
|
|
||||||
|
|
||||||
|
class StatusLongPollConsumer(AsyncHttpConsumer):
|
||||||
|
async def handle(self, body):
|
||||||
|
self.access_code = self.scope["url_route"]["kwargs"]["access_code"]
|
||||||
|
self.hashcode = self.scope["url_route"]["kwargs"]["hashcode"]
|
||||||
|
|
||||||
|
await self.channel_layer.group_add("%s_status" % self.access_code,
|
||||||
|
self.channel_name)
|
||||||
|
await self.channel_layer.group_send(
|
||||||
|
"%s_status" % self.access_code, {
|
||||||
|
"type": "fear_tracker.hashcode_seen",
|
||||||
|
"hashcode": self.hashcode,
|
||||||
|
})
|
||||||
|
|
||||||
|
async def http_request(self, message):
|
||||||
|
"""
|
||||||
|
Async entrypoint - concatenates body fragments and hands off control
|
||||||
|
to ``self.handle`` when the body has been completely received.
|
||||||
|
"""
|
||||||
|
if "body" in message:
|
||||||
|
self.body.append(message["body"])
|
||||||
|
if not message.get("more_body"):
|
||||||
|
await self.handle(b"".join(self.body))
|
||||||
|
|
||||||
|
async def disconnect(self):
|
||||||
|
await self.channel_layer.group_discard("%s_status" % self.access_code,
|
||||||
|
self.channel_name)
|
||||||
|
|
||||||
|
async def fear_tracker_hashcode_seen(self, event):
|
||||||
|
if self.hashcode != event["hashcode"]:
|
||||||
|
if event["status_string"]:
|
||||||
|
body = event["status_string"].encode('utf-8')
|
||||||
|
await self.send_response(200, body)
|
||||||
|
await self.disconnect()
|
||||||
|
await self.channel_layer.group_send(
|
||||||
|
"%s_status" % self.access_code, {
|
||||||
|
"type": "fear_tracker.invalidate_status",
|
||||||
|
})
|
||||||
|
|
||||||
|
async def fear_tracker_invalidate_status(self, event):
|
||||||
|
no_hash_status = reverse('status',
|
||||||
|
kwargs={'access_code': self.access_code})
|
||||||
|
await self.send_response(302, b'', headers=[
|
||||||
|
(b"Location", no_hash_status.encode('utf-8'))
|
||||||
|
])
|
||||||
|
await self.http_disconnect(None)
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
from channels.routing import ProtocolTypeRouter
|
import fear_tracker.routing
|
||||||
|
|
||||||
# No async routing currently; just default sync http.
|
application = fear_tracker.routing.application
|
||||||
application = ProtocolTypeRouter({})
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ ALLOWED_HOSTS = []
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
|
'channels',
|
||||||
'fear_tracker',
|
'fear_tracker',
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
|
@ -82,6 +83,12 @@ DATABASES = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CHANNEL_LAYERS = {
|
||||||
|
"default": {
|
||||||
|
"BACKEND": "channels.layers.InMemoryChannelLayer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# Password validation
|
# Password validation
|
||||||
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
|
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
|
||||||
|
|
14
gnuicorn.conf.py
Normal file
14
gnuicorn.conf.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import multiprocessing
|
||||||
|
|
||||||
|
bind = "127.0.0.1:8043"
|
||||||
|
worker_class = "uvicorn.workers.UvicornWorker"
|
||||||
|
workers = multiprocessing.cpu_count() * 2 + 1
|
||||||
|
chdir = "/home/anyoneeb/sites/apps/fear-tracker"
|
||||||
|
raw_env = "DJANGO_SETTINGS_MODULE=fear_tracker_site.local_settings"
|
||||||
|
proc_name = "gnuicorn-fear-tracker"
|
||||||
|
pidfile = "/tmp/gnuicorn-fear-tracker.pid"
|
||||||
|
user = "fear-tracker"
|
||||||
|
group = "fear-tracker"
|
||||||
|
max_requests=5000
|
||||||
|
max_requests_jitter=100
|
||||||
|
preload_app=True
|
14
uwsgi.ini
Normal file
14
uwsgi.ini
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
[uwsgi]
|
||||||
|
socket=127.0.0.1:8043
|
||||||
|
mount = /fear-tracker=fear_tracker_site.wsgi:application
|
||||||
|
manage-script-name = true
|
||||||
|
chdir=/home/anyoneeb/sites/apps/fear-tracker
|
||||||
|
plugin=python3
|
||||||
|
module=fear_tracker_site.wsgi:application
|
||||||
|
env=DJANGO_SETTINGS_MODULE=fear_tracker_site.local_settings
|
||||||
|
master=True
|
||||||
|
pidfile=/tmp/fear-tracker-master.pid
|
||||||
|
vacuum=True
|
||||||
|
max-requests=5000
|
||||||
|
uid=fear-tracker
|
||||||
|
gid=fear-tracker
|
Loading…
Reference in New Issue
Block a user