var fs = require("fs"); var http = require("http"); var crypto = require("crypto"); var pathlib = require("path"); var urllib = require("url"); var conf = JSON.parse(fs.readFileSync("conf.json")); var index = fs.readFileSync("index.html", "utf-8") .replace("{{transition_time}}", conf.transition_time); function error(res, err) { console.trace(err); res.end(err.toString()); } function mimetype(path) { var ext = pathlib.extname(path) .substring(1) .toLowerCase(); switch (ext) { case "html": case "xml": return "text/"+ext; case "png": case "jpg": case "jpeg": case "gif": return "image/"+ext; case "svg": return "image/svg+xml"; case "mov": case "mp4": case "ogv": case "webm": return "video/"+ext; case "mp3": case "ogg": case "flac": case "m4a": return "audio/"+ext; default: return "application/octet-stream"; } } function sendfile(res, path) { res.writeHead(200, { "content-type": mimetype(path) }); fs.createReadStream(path) .on("error", err => error(res, err)) .pipe(res); } // The individual slide function Slide(dir) { var self = {}; self.dir = dir; self.name = pathlib.parse(dir).name; self.serveSlide = function(parts, res) { sendfile(res, pathlib.join(dir, "index.html")); } self.serveFiles = function(parts, res) { // Redirect to / if /{name} is requested if (parts.pathname.replace(/\//g, "") === self.name) { res.writeHead(302, { location: "/" }); return res.end(); } var name = parts.pathname.substring(1).replace(self.name, ""); sendfile(res, pathlib.join(dir, name)); } return self; } // The slideshow, whose job it is to manage all slides // and tell the client whether it has to update or not function Slideshow(dir, changeInterval) { var self = {}; var currentSlide = null; var awaiters = []; var slides = []; self.sendEvent = function(evt, args) { awaiters.forEach(res => res.end(JSON.stringify({ evt: evt, args: args }))); } self.serve = function(req, res) { var parts = urllib.parse(req.url); // /: Send the base site to the client if (parts.pathname === "/") { res.end(index); // /init: send initial information about current slide } else if (parts.pathname === "/init") { res.end(currentSlide ? currentSlide.name : ""); // /await: long polling, request won't end before a new slide comes } else if (parts.pathname === "/await") { awaiters.push(res); // /slide: serve the current slide's html } else if (parts.pathname === "/slide" && currentSlide) { currentSlide.serveSlide(parts, res); // Serve other files } else { var served = false; for (var slide of slides) { // If client requests /{slide-name}/* if (slide.name === parts.pathname.substr(1, slide.name.length)) { slide.serveFiles(parts, res); served = true; break; } } if (!served) { res.writeHead(404); res.end("404"); } } } // This function starts the slideshow and goes through the slides // one by one. When done, it starts again by calling this function again. function init() { slides = fs.readdirSync(dir) .sort() .map(file => Slide(pathlib.join(dir, file))); var slideIndex = 0; currentSlide = slides[slideIndex]; var interval = setInterval(() => { slideIndex += 1; // Go to the next slide, or restart if (slideIndex >= slides.length) { clearInterval(interval); init(); } else { currentSlide = slides[slideIndex]; } // End all awaiting connections to notify slide change self.sendEvent("next", { name: currentSlide.name }); }, changeInterval); } init(); return self; } var slideshow = Slideshow(conf.slides, conf.interval); function onexit() { slideshow.sendEvent("reload"); process.exit(); } process.on("exit", onexit); process.on("SIGINT", onexit); process.on("SIGTERM", onexit); var server = http.createServer((req, res) => { slideshow.serve(req, res); }); server.on("error", err => { console.error(err.toString()); system.exit(1); }); server.listen(conf.port); console.log("Server running on port "+conf.port+".");