const cmn = require("../$common.js"); const fs = require("fs"); const https = require("https"); const child_process = require("child_process"); const generateTemplate = require("./template.js"); let moddir = null; let repodir = null; const repoRemote = "https://git.tomsmeding.com/blog"; let templateCache = new Map(); function fetch(url) { return new Promise((resolve, reject) => { https.get(url, res => { if (res.statusCode != 200) { reject(`HTTP status code ${res.statusCode}`); return; } let buffers = []; res.on("data", d => buffers.push(d)); res.on("end", () => { resolve(Buffer.concat(buffers)); }); }).on("error", err => { reject(err); }); }); } function runCommand(cmd, args) { console.log(`blog: ${cmd} ${JSON.stringify(args)}`); child_process.execFileSync( cmd, args, { stdio: "inherit", timeout: 20000 } ); } function runCommandOutput(cmd, args) { return child_process.execFileSync( cmd, args, { timeout: 20000 } ); } function ensureRepo() { try { const stats = fs.statSync(repodir); if (stats.isDirectory()) return; } catch (e) {} runCommand("git", ["clone", "https://git.tomsmeding.com/blog", repodir]); } function currentCommit() { return runCommandOutput("git", ["-C", repodir, "rev-parse", "HEAD"]).toString().trim(); } async function upstreamCommit() { const body = await fetch(repoRemote + "/info/refs"); return body.toString().split("\t")[0]; } function updateRepo() { try { runCommand("git", ["-C", repodir, "fetch", "--all"]); runCommand("git", ["-C", repodir, "reset", "origin/master", "--hard"]); // Reset cache _after_ the commands succeeded; if anything failed, we // might at least still have stale cache data templateCache = new Map(); } catch (e) { console.error("Cannot update blog git repo!"); console.error(e); } } async function periodicUpdateCheck() { let shouldUpdate = false; try { const local = currentCommit(); const remote = await upstreamCommit(); if (local != remote) shouldUpdate = true; } catch (e) { // Also update if our pre-check fails for some reason shouldUpdate = true; } if (shouldUpdate) updateRepo(); } setInterval(function () { periodicUpdateCheck().catch(err => console.error(err)); }, 3600 * 1000); module.exports = (app, io, _moddir) => { moddir = _moddir; repodir = moddir + "/repo"; ensureRepo(); updateRepo(); app.get("/blog", (req, res, next) => { req.url = "/blog/"; next(); }); app.get("/blog/*", (req, res) => { if (req.path.indexOf("/.") != -1) { res.sendStatus(404); return; } const path = req.path.slice(6).replace(/\.html$/, ""); if (templateCache.has(path)) { res.send(templateCache.get(path)); } else { generateTemplate(repodir, path ? path + ".html" : undefined) .then(rendered => { // TODO: fix rendering race condition templateCache.set(path, rendered); res.send(rendered); }) .catch(err => { if (err.code && err.code == "ENOENT") { res.sendStatus(404); } else { console.error(err); res.sendStatus(500); } }); } }); };