UNPKG

git-gallery

Version:

A gallery app for showing work based on Git commits.

268 lines (224 loc) 6.64 kB
const fs = require('fs'); const chokidar = require('chokidar'); const path = require('path'); const debug = require('debug')('git-gallery'); const pageUtils = require('./pageUtils'); const fsUtils = require('./fsUtils'); const galleryRoot = fsUtils.galleryRoot; const exportRoot = fsUtils.exportRoot; const isPageDir = pageUtils.isPageDir; var watcher; // gallery filesystem watcher const transientPageProperties = ['isHead', 'isClean', 'prevPage', 'nextPage', 'prevCommit', 'nextCommit', '_locals']; // page properties not to be written to disk var pages; var commit2Page; var dirties = {}; // a queue of objects that have been marked dirty and need to be written to disk var dirtyIgnores = {}; // page properties not to mark dirty on for (prop of transientPageProperties) { dirtyIgnores[prop] = true; } var pageJsonReplacer = function(key, value) { if (transientPageProperties.indexOf(key) >= 0) { return undefined; } return value; } var DirtyHandler = function(root) { this.root = root; this.set = (obj, prop, value) => { if (!dirtyIgnores[prop]) { debug('Dirty: ' + prop + ' of ' + obj.commitId); markDirty(this.root); } if (value && typeof value === 'object') { value = new Proxy(value, this); } obj[prop] = value; return true; }; } function markDirty(page) { let id = page.commitId; if (!id) { return; } dirties[id] = page; setTimeout(clean, 10); } function clean() { // console.log('PagesDB::cleaning: A'); let keys = Object.keys(dirties); if (!keys.length) return; let id = keys[0]; let page = dirties[id]; debug('cleaning: ' + id); delete dirties[id]; // console.log('PagesDB::cleaning: B'); pageUtils.writePage(page, pageJsonReplacer, (error) => { if (error) { console.log("Problem saving page " + id + ": " + error); } // console.log('PagesDB::cleaning: C'); clean(); // process the next item }); } /** Returns the list of pages ordered by time */ function getPages() { return pages; } /** Returns the page.json for the given commit */ function getPage(commitId) { return commit2Page[commitId]; } function createPage(commitId, callback) { pageUtils.createRawPageForId(commitId, (error, obj) => { if (error) { return callback(error); } let page = addRawPage(obj); markDirty(page); callback(null, page); }); } function addRawPage(obj) { // wrap page with proxies let dh = new DirtyHandler(obj); for (let i=0; i < obj.images.length; i++) { obj.images[i] = new Proxy(obj.images[i], dh); } obj.images = new Proxy(obj.images, dh); let page = new Proxy(obj, dh); // remove old version of page let old = commit2Page[page.commitId]; if (old) { pages.splice(pages.indexOf(old), 1); } pages.push(page); commit2Page[page.commitId] = page; sortPages(); return page; } function buildDB() { pages = []; commit2Page = {}; // find all gallery directories with page.json files and create an in memory index of them var files = fs.readdirSync(galleryRoot); for (let f of files) { debug("Processing file: " + f); let p = path.join(galleryRoot, f); // skip the HEAD symlink dir and any directory that is not a valid page if (f === 'HEAD' || !isPageDir(p)) { debug("...skipping"); continue; } // add the page to the db processPageDir(p); } sortPages(); console.log("PageDB: " + JSON.stringify(pages, null, 2)); } function processPageDir(dir) { let obj = pageUtils.readPageSync(dir); addRawPage(obj); } function removePage(commitId) { delete dirties[commitId]; let index = pages.findIndex(item => item.commitId === commitId); if (index >= 0) { pages.splice(index, 1); } delete commit2Page[commitId]; } function sortPages() { // sort all the entries by time pages.sort((a, b) => { return b.date - a.date; }); for (let i=0; i < pages.length; i++) { let prevInd = i - 1; let nextInd = i + 1; let p = pages[i]; p.nextPage = (prevInd >= 0 ? pages[prevInd].commitId : null); p.prevPage = (nextInd < pages.length ? pages[nextInd].commitId : null); } } //--------------------------------------------------------------------- function watchGallery() { watcher = chokidar.watch(galleryRoot, { ignoreInitial: true }); // watcher.on('all', (event, path) => { console.log(event, path); }); watcher.on('addDir', onDirAdded) // directory added .on('unlinkDir', onDirRemoved) // directory removed .on('add', onFileAdded) // file added .on('change', onFileChanged) // file changed .on('unlink', onFileRemoved) // file removed .on('error', error => console.log(`Watcher error: ${error}`)); } function shouldWatch(f) { if (path.dirname(f).endsWith(path.sep + 'HEAD') || f.includes(exportRoot)) { // console.log('pagesDB::shouldWatch ' + f + '? NO'); return false; } // console.log('pagesDB::shouldWatch ' + f + '? YES'); return true; } function onDirAdded(dir) { console.log("Watcher.onDirAdded: " + dir); if (!shouldWatch(dir)) { return; } if (path.basename(dir) === 'HEAD') { console.log('ignoring') return; } if (isPageDir(dir)) { processPageDir(dir); } } function onDirRemoved(dir) { console.log("Watcher.onDirRemoved: " + dir); if (!shouldWatch(dir)) { return; } if (path.basename(dir) === 'HEAD') { return; } buildDB(); } function onFileAdded(f) { console.log("Watcher.onFileAdded: " + f); if (!shouldWatch(f)) { return; } if (path.basename(f) === 'page.json') { let dir = path.dirname(f); if (isPageDir(dir)) { processPageDir(dir); } } } function onFileChanged(f) { // console.log('PagesDB::onFileChanged: ' + f); if (shouldWatch(f) && path.basename(f) === 'page.json') { let dir = path.dirname(f); let commitId = path.basename(dir); try { processPageDir(dir); sortPages(); } catch (ex) { console.log('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'); console.log(ex.name + ': ' + ex.message); console.log('Ignoring error and continuing. (Happens sometimes, but not a problem)'); // sometimes we seem to get called in the middle of the page writing with an empty file } } } function onFileRemoved(f) { if (shouldWatch(f) && path.basename(f) == 'page.json') { let dir = path.dirname(f); let commitId = path.basename(dir); removePage(commitId); } } buildDB(); // build the db watchGallery(); // Start the watcher exports.getPages = getPages; exports.getPage = getPage; exports.createPage = createPage;