commit f84a4f321c276d2828f0c7de787253961698d39b Author: Chris Josten Date: Sat Dec 12 17:14:09 2020 +0100 Eerste commit diff --git a/dub.sdl b/dub.sdl new file mode 100644 index 0000000..bb0e955 --- /dev/null +++ b/dub.sdl @@ -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" diff --git a/dub.selections.json b/dub.selections.json new file mode 100644 index 0000000..98352bc --- /dev/null +++ b/dub.selections.json @@ -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" + } +} diff --git a/source/app.d b/source/app.d new file mode 100644 index 0000000..b52e941 --- /dev/null +++ b/source/app.d @@ -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(); +} diff --git a/source/kerstezelprotocol.d b/source/kerstezelprotocol.d new file mode 100644 index 0000000..bb5c585 --- /dev/null +++ b/source/kerstezelprotocol.d @@ -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) { + + } + } + +} diff --git a/statisch/kerstman.jpg b/statisch/kerstman.jpg new file mode 100644 index 0000000..c60c967 Binary files /dev/null and b/statisch/kerstman.jpg differ diff --git a/statisch/rand-klein.png b/statisch/rand-klein.png new file mode 100644 index 0000000..0800331 Binary files /dev/null and b/statisch/rand-klein.png differ diff --git a/statisch/rand.png b/statisch/rand.png new file mode 100644 index 0000000..293d673 Binary files /dev/null and b/statisch/rand.png differ diff --git a/statisch/sneeuw2.min.js b/statisch/sneeuw2.min.js new file mode 100644 index 0000000..df7fe33 --- /dev/null +++ b/statisch/sneeuw2.min.js @@ -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 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); + } +}); diff --git a/translations/kerstman.en_GB.po b/translations/kerstman.en_GB.po new file mode 100644 index 0000000..8e4c674 --- /dev/null +++ b/translations/kerstman.en_GB.po @@ -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 joy " +"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" diff --git a/translations/kerstman.nl_NL.po b/translations/kerstman.nl_NL.po new file mode 100644 index 0000000..2ed255f --- /dev/null +++ b/translations/kerstman.nl_NL.po @@ -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 vreugd 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" diff --git a/views/index.dt b/views/index.dt new file mode 100644 index 0000000..c91ea3d --- /dev/null +++ b/views/index.dt @@ -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