Browse Source

Cleaned up the code a bunch, and added a slide specific meta.json

master
mortie 7 years ago
parent
commit
e8f37a9004
8 changed files with 266 additions and 191 deletions
  1. 8
    0
      README.md
  2. 3
    0
      exampleSlides/1/meta.json
  3. 1
    2
      index.html
  4. 7
    0
      js/error.js
  5. 40
    0
      js/mimetype.js
  6. 15
    0
      js/sendfile.js
  7. 148
    0
      js/slideshow.js
  8. 44
    189
      server.js

+ 8
- 0
README.md View File

@@ -12,6 +12,14 @@ display the website hosted by the pipic server.
2. Run `node server.js`.
3. Point your clients to the site hosted by the pipic server.

## meta.json

Each slide has a file called meta.json, with some metadata about the slide. It
has the following options:

* **disabled**: Set to `true` to disable the slide.
* **interval**: Override the interval setting in conf.json.

## Automatic fullscreen

There are multiple ways for a client to automatically display the website in

+ 3
- 0
exampleSlides/1/meta.json View File

@@ -0,0 +1,3 @@
{
"interval": 8000
}

+ 1
- 2
index.html View File

@@ -64,7 +64,7 @@
._content h2 { font-size: 1.4em }
._content h3 { font-size: 1.2em }

._content p { font-size: 2.2em }
._content p { font-size: 1.4em }

._content .fullscreen {
position: absolute;
@@ -84,7 +84,6 @@
width: 100%;
}

._content p,
._content ul,
._content ol {
text-align: left;

+ 7
- 0
js/error.js View File

@@ -0,0 +1,7 @@
module.exports = error;

function error(err, res) {
console.trace(err);
if (res)
res.end(err.toString());
}

+ 40
- 0
js/mimetype.js View File

@@ -0,0 +1,40 @@
var pathlib = require("path");

module.exports = mimetype;

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";
}
}

+ 15
- 0
js/sendfile.js View File

@@ -0,0 +1,15 @@
var fs = require("fs");

var mimetype = require("./mimetype");

module.exports = sendfile;

function sendfile(res, path) {
res.writeHead(200, {
"content-type": mimetype(path)
});

fs.createReadStream(path)
.on("error", err => error(err, res))
.pipe(res);
}

+ 148
- 0
js/slideshow.js View File

@@ -0,0 +1,148 @@
var fs = require("fs");
var pathlib = require("path");

var sendfile = require("./sendfile");
var error = require("./error");

module.exports = Slideshow;

// 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));
}

self.indexExists = function() {
try {
fs.accessSync(pathlib.join(dir, "index.html"));
return true;
} catch (err) {
return false;
}
}

try {
self.meta = JSON.parse(
fs.readFileSync(pathlib.join(dir, "meta.json")));
} catch (err) {
if (err.code !== "ENOENT")
error(err);

self.meta = {};
}

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 = [];
var slideIndex = 0;
var nextTimeout;

self.sendEvent = function(evt, args) {
var str = JSON.stringify({ evt: evt, args: args });
awaiters.forEach(res => res.end(str));
awaiters = [];
}

// Get the current slide's interval
function currentInterval() {
var itv = currentSlide.meta.interval;
if (itv === undefined)
itv = changeInterval;
return itv;
}

// Return if the current slide is enabled and valid
function currentEnabled() {
return currentSlide.indexExists() && !currentSlide.disabled;
}

self.getSlideName = getSlideName;
function getSlideName() {
return currentSlide ? currentSlide.name : ""
}

self.pushAwaiter = pushAwaiter;
function pushAwaiter(res) {
awaiters.push(res);
}

self.serveCurrentSlide = serveCurrentSlide;
function serveCurrentSlide(parts, res) {
if (currentSlide)
currentSlide.serveSlide(parts, res);
else
error("Current slide requested while no current slide exists", res);
}

self.getSlides = getSlides;
function getSlides() {
return slides;
}

self.next = next;
function next() {
slideIndex += 1;

// Go to the next slide, or restart
if (slideIndex >= slides.length) {
clearTimeout(nextTimeout);
init();
} else {
currentSlide = slides[slideIndex];

nextTimeout = setTimeout(next, currentInterval());
}

// End all awaiting connections to notify slide change,
if (currentEnabled()) {
self.sendEvent("next", { name: currentSlide.name });

// Or go to the next slide if the current one doesn't have an index.html
// or if the slide is disabled
} else {
clearTimeout(nextTimeout);
setTimeout(next, 0);
}
}

// 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)));

slideIndex = 0;
currentSlide = slides[slideIndex];

nextTimeout = setTimeout(next, currentInterval());
}
init();

return self;
}

+ 44
- 189
server.js View File

@@ -4,218 +4,73 @@ var crypto = require("crypto");
var pathlib = require("path");
var urllib = require("url");

var Slideshow = require("./js/slideshow");
var error = require("./js/error");

var conf = JSON.parse(fs.readFileSync("conf.json"));
var index = fs.readFileSync("index.html", "utf-8")
.replace(/<<transition_time>>/g, 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));
}

self.indexExists = function() {
try {
fs.accessSync(pathlib.join(dir, "index.html"));
return true;
} catch (err) {
return false;
}
}
var slideshow = Slideshow(conf.slides, conf.interval);

return self;
function onexit(code) {
console.log("exiting", code);
slideshow.sendEvent("reload");
process.exit();
}
process.on("exit", onexit);
process.on("SIGINT", onexit);
process.on("SIGTERM", onexit);

// 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 = [];
var slideIndex = 0;
var nextTimeout;

self.sendEvent = function(evt, args) {
var str = JSON.stringify({ evt: evt, args: args });
awaiters.forEach(res => res.end(str));
awaiters = [];
}

self.serve = function(req, res) {
var parts = urllib.parse(req.url);
process.on("uncaughtException", onexit);

// /: Send the base site to the client
if (parts.pathname === "/") {
res.end(index);
function handler(req, res) {
var parts = urllib.parse(req.url);

// /polyfills.js: JavaScript polyfills
} else if (parts.pathname === "/polyfills.js") {
fs.createReadStream("polyfills.js")
.pipe(res)
.on("error", err => res.end(err.toString()));
// /: 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 : "");
// /polyfills.js: JavaScript polyfills
} else if (parts.pathname === "polyfills.js") {
fs.createReadStream("polyfills.js")
.pipe(res)
.on("error", err => error(err, res));

// /await: long polling, request won't end before a new slide comes
} else if (parts.pathname === "/await") {
awaiters.push(res);
// /init: send initial information about current slide
} else if (parts.pathname === "/init") {
res.end(slideshow.getSlideName());

// /slide: serve the current slide's html
} else if (parts.pathname === "/slide" && currentSlide) {
currentSlide.serveSlide(parts, res);
// /await: long polling, request won't end before a new slide comes
} else if (parts.pathname === "/await") {
slideshow.pushAwaiter(res);

// Serve other files
} else {
var served = false;
// /slide: serve the current slide's html
} else if (parts.pathname === "/slide") {
slideshow.serveCurrentSlide(parts, res);

for (var slide of slides) {
// Serve other files
} else {
var served = false;

// If client requests /{slide-name}/*
if (slide.name === parts.pathname.substr(1, slide.name.length)) {
slide.serveFiles(parts, res);
served = true;
break;
}
}
for (var slide of slideshow.getSlides()) {

if (!served) {
res.writeHead(404);
res.end("404");
// If client requests /{slide-name}/*
if (slide.name === parts.pathname.substr(1, slide.name.length)) {
slide.serveFiles(parts, res);
served = true;
break;
}
}
}

function next() {
slideIndex += 1;

// Go to the next slide, or restart
if ((slideIndex >= slides.length)) {
clearTimeout(nextTimeout);
init();
} else {
currentSlide = slides[slideIndex];
nextTimeout = setTimeout(next, changeInterval);
}

// End all awaiting connections to notify slide change,
if (currentSlide.indexExists()) {
self.sendEvent("next", { name: currentSlide.name });

// Or go to the next slide if the current one doesn't have an index.html
} else {
clearTimeout(nextTimeout);
setTimeout(next, 0);
if (!served) {
res.writeHead(404);
res.end("404");
}
}

self.next = next;

// 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)));

slideIndex = 0;
currentSlide = slides[slideIndex];

nextTimeout = setTimeout(next, changeInterval);
}
init();

return self;
}

var slideshow = Slideshow(conf.slides, conf.interval);

function onexit(code) {
console.log("exiting", code);
slideshow.sendEvent("reload");
process.exit();
}
process.on("exit", onexit);
process.on("SIGINT", onexit);
process.on("SIGTERM", onexit);

process.on("uncaughtException", onexit);

var server = http.createServer((req, res) => {
slideshow.serve(req, res);
});
var server = http.createServer(handler);
server.on("error", err => {
console.error(err);
system.exit(1);

Loading…
Cancel
Save