Eerste commit

This commit is contained in:
Chris Josten 2020-12-12 17:14:09 +01:00
commit f84a4f321c
13 changed files with 438 additions and 0 deletions

8
dub.sdl Normal file
View file

@ -0,0 +1,8 @@
name "kerstezel"
description "A simple vibe.d server application."
authors "vercie"
copyright "Copyright © 2020, vercie"
license "MIT"
dependency "vibe-d" version="~>0.9"
stringImportPaths "views" "translations"
versions "VibeOutputCompactHTML"

17
dub.selections.json Normal file
View file

@ -0,0 +1,17 @@
{
"fileVersion": 1,
"versions": {
"botan": "1.12.18",
"botan-math": "1.0.3",
"diet-ng": "1.7.4",
"eventcore": "0.9.11",
"libasync": "0.8.6",
"memutils": "1.0.4",
"mir-linux-kernel": "1.0.1",
"openssl": "1.1.6+1.0.1g",
"stdx-allocator": "2.77.5",
"taggedalgebraic": "0.11.18",
"vibe-core": "1.11.1",
"vibe-d": "0.9.2"
}
}

76
source/app.d Normal file
View file

@ -0,0 +1,76 @@
import std.functional;
import vibe.vibe;
import kerstezelprotocol;
struct VertaalContext {
import std.typetuple;
// Throw an error when an translation string is missing/mistyped.
enum enforceExistingKeys = true;
// The list of supported languages (the same family of languages will
// automatically be matched to the closest candidate, e.g. en_GB->en_US)
alias languages = TypeTuple!("en_GB", "nl_NL");
// The base name of the translation files - the full names will be
// kerstman.en_US.po and kerstman.de_DE.po. Any number of these mixin
// statements can be used.
mixin translationModule!"kerstman";
}
@translationContext!VertaalContext
class KerstezelService {
@path("/")
void getIndex() {
render!"index.dt";
}
@path("/arreslee")
void getArreslee(scope WebSocket bus) {
runTask((WebSocket bus) {
while(bus.connected) {
string commando = bus.receiveText;
if (commando == "trommel") {
kerstmanTaak.send(TrommelCommando());
} else {
logInfo("Onbekend commando:" ~ commando);
}
}
}, bus);
while (bus.connected) {
int uitzend_aantal = staatGebeurtenis.emitCount;
bus.send(staat.serializeToJsonString());
// Wordt elke 30 seconden wakker om de verbinding in leven
// te houden en/of te detecteren dat deze juist gestopt is.
staatGebeurtenis.wait(30.seconds, uitzend_aantal);
}
}
}
void main() {
import vibe.http.router;
auto settings = new HTTPServerSettings;
settings.port = 8090;
settings.bindAddresses = ["::", "0.0.0.0"];
auto router = new URLRouter;
router.registerWebInterface(new KerstezelService);
auto fsettings = new HTTPFileServerSettings;
fsettings.serverPathPrefix = "/statisch";
router.get("*", serveStaticFiles("statisch/", fsettings));
listenHTTP(settings, router);
logInfo("Please open http://127.0.0.1:8080/ in your browser.");
if (!listenTCP(9305, toDelegate(&kerstezelProtocolLuister), "0.0.0.0")) {
logWarn("Kon kerstezelProtocol niet starten");
}
runApplication();
}

142
source/kerstezelprotocol.d Normal file
View file

@ -0,0 +1,142 @@
import std.array;
import std.algorithm;
import std.datetime;
import std.exception;
import std.format;
import std.random;
import std.range;
import vibe.vibe;
immutable size_t BUF_SIZE = 2048;
struct Staat {
bool verbondenMetKerstman = false;
long trommelenVerbodenTot = 0;
}
shared Staat staat;
shared ManualEvent staatGebeurtenis;
shared Task kerstmanTaak;
__gshared TaskMutex mutex;
shared static this() {
staatGebeurtenis = createSharedManualEvent();
mutex = new TaskMutex();
}
struct TrommelCommando {};
@trusted
void kerstezelProtocolLuister(TCPConnection verbinding) nothrow {
try {
auto lock = scopedMutexLock(mutex);
new Sessie(verbinding).voerUit();
} catch (Exception e) {
logException(e, "Kon mutex niet verkrijgen");
}
}
class UnicodeUitzondering : Exception {
mixin basicExceptionCtors;
}
class ProtocolUitzondering : Exception {
mixin basicExceptionCtors;
}
class Sessie {
private TCPConnection l_verbinding;
private bool blijfBezig = true;
private SysTime trommelenVerbodenTot;
private bool trommelenVerboden = false;
@safe
this(TCPConnection verbinding) nothrow {
this.l_verbinding = verbinding;
}
@trusted
public void voerUit() nothrow {
logInfo("Nieuwe sessie probeert te verbinden");
try {
handGeven();
logInfo("Sessie is verbonden");
staat.verbondenMetKerstman = true;
kerstmanTaak = Task.getThis();
staatGebeurtenis.emit();
while(true) {
auto tijdsduur = trommelenVerboden
? min(trommelenVerbodenTot - Clock.currTime, seconds(30))
: seconds(30);
bool received = receiveTimeout(tijdsduur,
(TrommelCommando com) {
logInfo("Trommelen");
auto now = Clock.currTime();
if (trommelenVerboden) return;
l_verbinding.write("trommel\n");
l_verbinding.flush();
trommelenVerboden = true;
trommelenVerbodenTot = now + seconds(30);
staat.trommelenVerbodenTot = trommelenVerbodenTot.toUnixTime;
staatGebeurtenis.emit();
ubyte[] read = l_verbinding.readLine(BUF_SIZE, "\n");
enforce!ProtocolUitzondering(read.equal("oké"), "Kerstman deed geen erkenning, maar deed %s".format(read));
}
);
if (Clock.currTime() >= trommelenVerbodenTot) {
trommelenVerboden = false;
staatGebeurtenis.emit();
}
if (!received) {
l_verbinding.write("ping\n");
l_verbinding.flush();
ubyte[] read = l_verbinding.readLine(BUF_SIZE, "\n");
enforce!ProtocolUitzondering(read.equal("pong"), "Kerstman pongde niet terug, maar deed %s".format(read));
}
}
} catch (ProtocolUitzondering u) {
logException(u, "Protocoluitzondering");
} catch (Exception e) {
logException(e, "Andere uitzondering");
} finally {
logInfo("Sessie verbroken");
staat.verbondenMetKerstman = false;
staatGebeurtenis.emit();
sluit();
}
}
@safe
private void handGeven () {
// Maak een begroeting
string begroeting2 = "e gaat 'ie?\n";
// Maak het irritant om het met de hand te doen :P
int aantal = cast(int) (rndGen.front % 200) + 100;
logInfo("Aantal: %d".format(aantal));
rndGen.popFront();
string begroeting = "ho".replicate(aantal) ~ begroeting2;
l_verbinding.write(begroeting);
l_verbinding.flush();
// Wacht op een antwoord in de vorm van (aantal * "go" + "ed!")
ubyte[] data = l_verbinding.readLine(BUF_SIZE, "\n");
string expected = "go".replicate(aantal) ~ "ed";
enforce!ProtocolUitzondering(equal(data, expected), "Ongeldige handdruk");
}
@safe
private void sluit() nothrow {
try {
l_verbinding.close();
} catch(Exception e) {
}
}
}

BIN
statisch/kerstman.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

BIN
statisch/rand-klein.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
statisch/rand.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 MiB

1
statisch/sneeuw2.min.js vendored Normal file
View file

@ -0,0 +1 @@
snow={count:60,delay:20,flutter:0.2,wobble:0.5,spin:1,wind:1,w1:1,minSpeed:0.3,maxSpeed:4,cv:null,flakes:[],toggle:function(){if(window.snowtimer){snow.stop()}else{snow.start()}},resize:function(){snow.cv.width=innerWidth;snow.cv.height=innerHeight;snow.gt=snow.ct.createLinearGradient(0,0,0,snow.cv.height);snow.gt.addColorStop(0,"#6666ff");snow.gt.addColorStop(1,"#ffffff");snow.ct.fillStyle=snow.gt},start:function(){snow.cv=document.createElement("canvas");snow.cv.width=snow.cv.height=10;snow.cv.id="backgroundSnowCanvas";document.body.appendChild(snow.cv);snow.createFlake();snow.ct=snow.cv.getContext("2d"),snow.cv.style.position="fixed";snow.cv.style.top=0;snow.cv.style.left=0;snow.cv.style.zIndex=-1;snow.resize();var a=snow.count;snow.flakes=[];do{snow.flakes.push(new snow.flake())}while(--a);snow.ct.clearRect(0,0,snow.cv.width,snow.cv.height);window.snowtimer=window.setInterval(snow.draw,snow.delay);window.addEventListener("resize",snow.resize)},stop:function(){window.clearInterval(window.snowtimer);var a=document.getElementById("backgroundSnowCanvas");a.parentNode.removeChild(a);window.snowtimer=snow=null},draw:function(){var a=snow.ct,b=snow.flakes,d=snow.count;a.clearRect(0,0,snow.cv.width,snow.cv.height);do{if(b[--d].draw(a)&&++fdone){}}while(d);snow.wind+=Math.cos(snow.w1++/180)},flake:function(){this.draw=function(c){var b=this.x+snow.wind,e=this.y,a=b+this.sz/2,d=e+this.sz/2;c.translate(a,d);c.rotate(this.a);c.translate(-a,-d);c.drawImage(snow.flakeImages[this.flake],b,e,this.sz,this.sz);if(this.flakebits>=0){c.drawImage(snow.flakeBits[this.flakebits],b,e,this.sz,this.sz)}c.setTransform(1,0,0,1,0,0);this.animate()};this.animate=function(){this.y+=this.speed;this.x+=this.flutter*Math.cos(snow.flutter*snow.flutter*this.y);this.a=(this.spin*this.y)+(this.wobble*Math.sin(this.y/this.sz));if(this.y>innerHeight){this.init(1)}};this.init=function(a){this.speed=snow.minSpeed+(Math.random()*(snow.maxSpeed-snow.minSpeed));this.sz=~~(Math.random()*40)+20;this.flutter=~~(Math.random()*snow.flutter*(60-this.sz));this.wobble=Math.random()*snow.wobble;this.spin=snow.spin*0.1*(Math.random()-0.5);this.a=0;this.x=(Math.random()*(innerWidth+this.sz))-this.sz;this.y=a?-this.sz:Math.random()*innerHeight;this.flake=~~(Math.random()*snow.flakeImages.length);this.flakebits=~~(Math.random()*(snow.flakeBits.length+1))-1};this.init()},createFlake:function(){var A,z,B,s,r,j=document.createElement("canvas"),m,d;j.width=j.height=40;snow.flakeImages=[];snow.flakeBits=[];for(A=0;A<6;++A){snow.flakeImages[A]=s=j.cloneNode();B=s.getContext("2d");B.fillStyle="#fff";B.translate(20,20);B.beginPath();r=1+(A/2);B.rect(-r,-20,r*2,40);B.rotate(Math.PI/3);B.rect(-r,-20,r*2,40);B.rotate(Math.PI/3);B.rect(-r,-20,r*2,40);B.closePath();B.fill()}function x(f){f.arc(0,-16,4,0,7,0)}function b(I,F,f,H,G,g){I.moveTo(0,-f);I.lineTo(F,-H);I.lineTo(F,-G);I.lineTo(0,-H-g);I.lineTo(-F,-G);I.lineTo(-F,-H);I.lineTo(0,-f)}function i(f){b(f,5,6,8,10,0)}function v(f){b(f,5,10,12,14,0)}function u(f){b(f,4,6,8,12,2)}function k(f){b(f,9,6,8,11,0)}function y(f){b(f,4,16,16,18,2)}function E(f){b(f,5,12,12,14,2)}function e(f){x(f);u(f)}function t(f){v(f);u(f)}function l(f){i(f);v(f)}function o(f){x(f);i(f)}function a(f){y(f);E(f)}function D(f){y(f);k(f)}function n(f){y(f);u(f)}function C(f){y(f);i(f)}function q(f){E(f);i(f)}function p(f){y(f);v(f)}function h(f){x(f);k(f)}d=[h,q,p,C,n,D,y,E,a,k,x,i,v,u,e,t,l,o];for(A=0;A<d.length;++A){snow.flakeBits[A]=s=j.cloneNode();m=d[A];B=s.getContext("2d");B.translate(20,20);B.fillStyle="#fff";for(z=0;z<6;++z){B.beginPath();m(B);B.closePath();B.fill();B.rotate(Math.PI/3)}}}};

45
statisch/stijl.css Normal file
View file

@ -0,0 +1,45 @@
html {
background-color: red;
color: green;
}
body {
font-family: "sans-serif";
}
main {
margin: 2em auto;
max-width: 600px;
padding: 1.5em;
border-image: url("rand-klein.png") 70 65 / 80px / 30px round;
border-style: solid;
background-color: cyan;
}
button {
border: 10px outset green;
background-color: green;
color: white;
font-size: 1.5em;
text-transform: uppercase;
font-weight: bold;
width: 100%;
}
img {
width: 100%;
}
p {
font-weight: bold;
}
button:disabled {
background-color: darkgreen;
border-style: groove;
color: red;
cursor: wait;
}
button:active, button:active:not(:disabled) {
border-style: inset;
}

69
statisch/webbus.js Normal file
View file

@ -0,0 +1,69 @@
var knop;
var bus;
function werkKnopBij(tekst, aan) {
knop.innerText = tekst;
knop.disabled = !aan;
}
function trommel() {
bus.send("trommel");
}
function aftellen(tijd) {
if (bus.readyState !== bus.OPEN) return;
werkKnopBij(v9n["knop.afkoelen"].replace("{seconden}", Math.ceil((tijd - Date.now()) / 1000)), false);
if (tijd - Date.now() > 0) {
window.setTimeout(function() { aftellen(tijd) }, 1000);
} else {
werkKnopBij(v9n["knop.activeer"], true);
}
}
function verbind() {
werkKnopBij(v9n["knop.initialiseren"], false);
var bestemming =( window.location.protocol == "http:"
? "ws://" : "wss://") + window.location.hostname + ":" + window.location.port + "/arreslee";
bus = new WebSocket(bestemming);
bus.onopen = function() {
werkKnopBij(v9n["knop.initialiseren2"], false);
knop.removeEventListener("click", verbind);
knop.addEventListener("click", trommel);
}
bus.onmessage = function(bericht) {
var gegevens = JSON.parse(bericht.data);
if (gegevens.verbondenMetKerstman) {
var doelTijd = gegevens.trommelenVerbodenTot * 1000;
var tijdVerschil = Date.now() - doelTijd;
if (tijdVerschil > 0) {
werkKnopBij(v9n["knop.activeer"], true);
} else {
aftellen(doelTijd);
}
} else {
werkKnopBij(v9n["knop.initialiseren2"], false);
}
}
bus.onclose = function() {
werkKnopBij("Opnieuw verbinden", true);
knop.removeEventListener("click", trommel);
knop.addEventListener("click", verbind);
}
}
window.addEventListener("load", function() {
knop = document.getElementById("knop");
verbind();
try {
snow.count = 100; // number of flakes
snow.delay = 20; // timer interval
snow.minSpeed = 0.2; // minimum movement/time slice
snow.maxSpeed = 2; // maximum movement/time slice
snow.wobble = 2;
snow.flutter = 0.1;
snow.start();
} catch(e) {
console.log(e);
}
});

View file

@ -0,0 +1,29 @@
msgid ""
msgstr ""
"Plural-Forms: nplurals=2; plural=n != 1;\\n"
msgid "pagina.titel"
msgstr "Father Christmas of IAPC"
msgid "pagina.kop1"
msgstr "Our Beloved Father Christmas"
msgid "pagina.tekst"
msgstr ""
"At IAPC we have a wonderful miniature Father Christmas. "
"This Father Christmas is not your avarage Father Christmas. This Father Christmas is able to drum! He has brought the volunteers at IAPC a lot of <abbr title=\"pain\">joy</abbr> "
"over the last serveral years, drumming his way around. "
"But this year, due to covid-19, the amount of people around around our store has been historically low, which is why we "
"are looking for you! You can save Christmas at IAPC! All you have to do is press the big green button below."
msgid "pagina.knop.activeer"
msgstr "Drum!"
msgid "pagina.knop.afkoelen"
msgstr "Drumming ({seconden}s)"
msgid "pagina.knop.initialiseren"
msgstr "Contacting the North Pole"
msgid "pagina.knop.initialiseren2"
msgstr "Asking the elves about the whereabouts of Santa Clause"

View file

@ -0,0 +1,29 @@
msgid ""
msgstr ""
"Plural-Forms: nplurals=2; plural=n != 1;\\n"
msgid "pagina.titel"
msgstr "IAPC kerstman"
msgid "pagina.kop1"
msgstr "Onze Lieve Kerstman"
msgid "pagina.tekst"
msgstr ""
"Bij IAPC staat er een prachtige kerstman die kan trommelen als je op een knopje drukt. "
"Deze kerstman heeft de vrijwilligers bij IAPC veel <abbr title=\"pijn\">vreugd</abbr> bezorgd. Helaas komen er "
"vanwege covid-19 niet zo veel mensen meer bij IAPC langs, waardoor er minder mensen "
"op het knopje van de kerstman drukken. Dit is zo zielig, dat de maker van deze webstek "
"de knop naar jullie heeft toegebracht, over het internet."
msgid "pagina.knop.activeer"
msgstr "Trommel!"
msgid "pagina.knop.afkoelen"
msgstr "Druk met trommelen ({seconden}s)"
msgid "pagina.knop.initialiseren"
msgstr "Contact opnemen met de Noordpool"
msgid "pagina.knop.initialiseren2"
msgstr "De elfjes vragen waar de kerstman uithangt"

22
views/index.dt Normal file
View file

@ -0,0 +1,22 @@
doctype html
html
head
title& pagina.titel
meta(name="viewport", content="width=device-width")
- import vibe.web.web;
:javascript
var v9n = {
"knop.activeer": "!{trWeb("pagina.knop.activeer")}",
"knop.afkoelen": "!{trWeb("pagina.knop.afkoelen")}",
"knop.initialiseren": "!{trWeb("pagina.knop.initialiseren")}",
"knop.initialiseren2": "!{trWeb("pagina.knop.initialiseren2")}",
}
script(type="text/javascript", src="/statisch/sneeuw2.min.js")
script(type="text/javascript", src="/statisch/webbus.js")
link(href="/statisch/stijl.css", rel="stylesheet")
body
main
h1& pagina.kop1
img(src="statisch/kerstman.jpg")
p& pagina.tekst
button#knop& pagina.knop.initialiseren