UNPKG

mikser

Version:
446 lines (408 loc) 14.3 kB
'use strict' var Promise = require('bluebird'); var path = require('path'); var extend = require('node.extend'); var cluster = require('cluster'); var S = require('string'); var fs = require("fs-extra-promise"); var using = Promise.using; var constants = require('./constants.js'); var _ = require('lodash'); var NodeCache = require( "node-cache" ); module.exports = function(mikser) { var runtime = {}; var debug = mikser.debug('runtime'); runtime.cache = new NodeCache(); mikser.state.entities = mikser.state.entities || {}; mikser.state.sitemap = mikser.state.sitemap || {}; mikser.state.urlmap = mikser.state.urlmap || {}; runtime._link = function(entity) { if (cluster.isMaster) debug('Import in sitemap: M', entity._id); else debug('Import in sitemap: W['+ mikser.workerId +']', entity._id); mikser.state.entities[entity.collection] = mikser.state.entities[entity.collection] || {}; return runtime._unlink(entity).then(() => { mikser.state.entities[entity.collection][entity._id] = entity; mikser.state.urlmap[entity.url] = { entityId: entity._id, collection: entity.collection } if (entity.meta.href) { if (entity.meta.lang) { mikser.state.sitemap[entity.meta.href] = mikser.state.sitemap[entity.meta.href] || {}; let previous = mikser.state.sitemap[entity.meta.href][entity.meta.lang]; if (previous && (previous.entityId != entity._id || previous.collection != entity.collection)) { mikser.diagnostics.log('warning', 'Entity with equal href:', previous.collection, previous.entityId, 'and', entity.collection, entity._id); } mikser.state.sitemap[entity.meta.href][entity.meta.lang] = { entityId: entity._id, collection: entity.collection } } else { let previous = mikser.state.sitemap[entity.meta.href]; if (previous && (previous.entityId != entity._id || previous.collection != entity.collection)) { mikser.diagnostics.log('warning', 'Entity with equal href:', previous.collection, previous.entityId, 'and', entity.collection, entity._id); } mikser.state.sitemap[entity.meta.href] = { entityId: entity._id, collection: entity.collection } } } return mikser.emit('mikser.runtime.link', entity); }); }; runtime._unlink = function(entity) { if (cluster.isMaster) debug('Remove from sitemap: M', entity._id); else debug('Remove from sitemap: W['+ mikser.workerId +']', entity._id); let previous = mikser.state.entities[entity.collection][entity._id]; delete mikser.state.entities[entity.collection][entity._id]; if (previous) delete mikser.state.urlmap[previous.url]; for (let href of _.keys(mikser.state.sitemap)) { if (mikser.state.sitemap[href].entityId) { if (mikser.state.sitemap[href].entityId == entity._id) { delete mikser.state.sitemap[href]; } } else { for (let lang of _.keys(mikser.state.sitemap[href])) { if (mikser.state.sitemap[href][lang].entityId == entity._id) { delete mikser.state.sitemap[href][lang]; } } if (!_.keys(mikser.state.sitemap[href]).length) { delete mikser.state.sitemap[href]; } } } return mikser.emit('mikser.runtime.unlink', entity); }; runtime._clearCache = function() { if (cluster.isMaster) debug('Clear cache: M'); else debug('Clear cache: W['+ mikser.workerId +']'); mikser.runtime.cache.flushAll(); return Promise.resolve(); } runtime.clearCache = function() { return mikser.broker.broadcast('mikser.runtime._clearCache'); }; runtime.link = function(entity) { return mikser.broker.broadcast('mikser.runtime._link', entity); }; runtime.unlink = function(entity) { return mikser.broker.broadcast('mikser.runtime._unlink', entity); }; runtime.importDocument = function(document, strategy) { if (strategy != constants.RENDER_STRATEGY_PREVIEW) { return mikser.runtime.link(document).then(() => { return mikser.database.documents.save(document).then(() => { return mikser.scheduler.scheduleDocument(document._id, constants.RENDER_STRATEGY_FULL); }); }); } else { return mikser.runtime._link(document).then(() => { return mikser.database.documents.save(document); }); } }; runtime.addLinks = function(document, documentLinks, layoutLinks) { documentLinks = documentLinks || []; layoutLinks = layoutLinks || []; if (!documentLinks && !layoutLinks) return Promise.resolve(); let linkHistory = {}; return Promise.map(documentLinks, (link) => { let linkEntry = { _id: document._id + '->' + link, from: document._id, to: link }; if (!linkHistory[linkEntry._id]) { debug('Document link:', document._id + ' -> ' + link); linkHistory[linkEntry._id] = linkEntry; if (document.pageNumber) { let mainPageId = document._id.replace('.' + document.pageNumber, ''); return mikser.database.collection('documentLinks').save({ _id: mainPageId + '->' + link, from: mainPageId, to: link }); } else { return mikser.database.documentLinks.save(linkEntry); } } }).then(() => { return Promise.map(layoutLinks, (link) => { let linkEntry = { _id: document._id + '->' + link, from: document._id, to: link }; if (!linkHistory[linkEntry._id]) { debug('Layout link:', document._id + ' -> ' + link); linkHistory[linkEntry._id] = linkEntry; return mikser.database.collection('layoutLinks').save(linkEntry); } }); }); }; runtime.restoreLinks = function(links) { let documentId; if (links.documentLinks && links.documentLinks[0]) documentId = links.documentLinks[0].from; else if (links.layoutLinks && links.layoutLinks[0]) documentId = links.layoutLinks[0].from; if (!documentId) return Promise.resolve(); return runtime.cleanLinks({ _id: documentId }).then(() => { return Promise.join(() => { if (links.documentLinks) { return Promise.all(links.documentLinks.map((link) => { return mikser.database.documentLinks.save(link); })); } }, () => { if (links.layoutLinks) { return Promise.all(links.layoutLinks.map((link) => { return mikser.database.layoutLinks.save(link); })); } }); }); } runtime.cleanLinks = function(document) { let links = {}; return mikser.database.documentLinks.find({ from: document._id }).toArray().then((documentLinks) => { links.documentLinks = documentLinks; return mikser.database.layoutLinks.find({ from: document._id }).toArray().then((layoutLinks) => { links.layoutLinks = layoutLinks; }); }).then(() => { return mikser.database.documentLinks.remove({ from: document._id }).then(() => { return mikser.database.layoutLinks.remove({ from: document._id }); }); }).then(() => links); } runtime.followLinks = function(document, forcePaging) { return mikser.database.collection('documentLinks').find({ to: document._id }).toArray().then((links) => { return Promise.map(links, (link) => { let fromDocument = mikser.state.entities['documents'][link.from]; if (!fromDocument) return Promise.resolve(); return mikser.database.documents.find({ source: fromDocument.source }).toArray().then((otherPages) => { return Promise.map(otherPages, (otherPage) => { if (otherPage.pageNumber == 0) { return mikser.scheduler.scheduleDocument(otherPage._id, constants.RENDER_STRATEGY_STANDALONE); } }); }); }); }); } runtime.findHref = function(entity, href, lang) { if (!href || href[0] == '.') return href; if (href == '/' && entity.relativeBase) return entity.relativeBase; let refEntity; let url = href; href = href || entity.meta.href; lang = lang || entity.meta.lang; // Href parameter might take multiple types, it can be an absolute url string, // it can be a href formated string linking to a document or a document object if (href._id) { refEntity = href; } else { if (S(href).startsWith('http')) return href; if (lang) { let hrefLangs = mikser.state.sitemap[href]; if (hrefLangs) { refEntity = hrefLangs[lang]; } } else { refEntity = mikser.state.sitemap[href]; } if (refEntity) { refEntity = mikser.state.entities[refEntity.collection][refEntity.entityId]; } } if (refEntity) { url = refEntity.url; if (!url) return refEntity; } else { url = url.toString(); } // If we are lookig for a relative path to a resource that has been shared // remove replication path from the url let entityUrl = entity.url; for (let share of mikser.config.shared) { share = S(share).ensureLeft('/').replaceAll('\\','/').ensureRight('/').s; if (S(entityUrl).startsWith(share)) { entityUrl = entityUrl.replace(share, '/'); } if (S(url).startsWith(share)) { url = url.replace(share, '/'); } } url = '.' + S(path.relative(path.dirname(entityUrl), path.dirname(url))).replaceAll('\\','/').ensureLeft('/').ensureRight('/').s + S(path.basename(url)).replaceAll('\\','/').chompLeft('/').s; let relativeUrl = { link: url } if (refEntity) { relativeUrl = refEntity; relativeUrl.link = url; if (refEntity._id == entity._id) { relativeUrl.link = entity.url.split('/').pop(); } } else { Object.defineProperty(relativeUrl, 'meta', { get: function() { let error = "[runtime] Can't find "; if (lang) { error += 'hreflang (' + lang + '): '; } else { error += 'href: '; } error += href; mikser.diagnostics.log('error', error); return {}; } }); } if (mikser.config.cleanUrls) { let clean = new RegExp(mikser.config.cleanUrlDestination,"gi"); relativeUrl.link = relativeUrl.link.replace(clean, ''); } relativeUrl.toString = function() { return this.link; } return relativeUrl; } runtime.findHrefLang = function (href) { let refLang = mikser.state.sitemap[href]; if (refLang) { let refHrefLang = {}; for (let lang of _.keys(refLang)) { let refEntity = refLang[lang]; refHrefLang[lang] = mikser.state.entities[refEntity.collection][refEntity.entityId]; } return refHrefLang; } } runtime.findUrl = function (url) { // console.log(mikser.state.urlmap) let refEntity = mikser.state.urlmap[url]; if (refEntity) { refEntity = mikser.state.entities[refEntity.collection][refEntity.entityId]; } return refEntity; } runtime.findEntity = function (collection, entityId) { collection = mikser.state.entities[collection]; if (collection) { return collection[entityId]; } } runtime.fromCache = function(name, loadData) { let cached = runtime.cache.get(name); if (cached) return Promise.resolve(cached); return loadData().then((data) => { runtime.cache.set(name, data); return Promise.resolve(data); }) } runtime.findPlugin = function(plugin) { let privatePlugin = plugin; if (mikser.config) { plugin = plugin || mikser.config[plugin]; privatePlugin = path.join(mikser.config.pluginsFolder, S(plugin).ensureRight('.js').s); } if (!fs.existsSync(privatePlugin)) { let buildinPlugin = path.join(__dirname, '../plugins', S(plugin).ensureRight('.js').s); if (fs.existsSync(buildinPlugin)) { plugin = buildinPlugin; } else { let publicPlugin = path.join(mikser.options.workingFolder, 'node_modules', 'mikser-' + plugin); if (fs.existsSync(publicPlugin)) { return publicPlugin; } else { if (S(plugin).endsWith('/index.js')) throw 'Plugin ' + plugin + ' not found.'; return runtime.findPlugin(S(plugin).ensureRight('/index.js').s); } } } else { plugin = privatePlugin; } return plugin; } runtime.findBrowserPlugin = function(plugin) { let serverPlugin = runtime.findPlugin(plugin); if (S(serverPlugin).endsWith('.js')) { var browserPlugin = path.join(path.dirname(serverPlugin), 'browser','index.js'); } else { var browserPlugin = path.join(serverPlugin, 'browser','index.js'); } if (!fs.existsSync(browserPlugin)) { browserPlugin = path.join(path.dirname(serverPlugin), 'browser.js'); } return browserPlugin; } if (cluster.isMaster) { mikser.cli .option('-k, --keep', 'keep the runtime clean') .init(); mikser.options.keepRuntime = mikser.cli.keep; runtime.markDirty = function() { fs.ensureFileSync(path.join(mikser.config.runtimeFolder, 'dirty')); }; runtime.markClean = function() { fs.removeSync(path.join(mikser.config.runtimeFolder, 'dirty')); }; runtime.isDirty = function() { return fs.existsSync(path.join(mikser.config.runtimeFolder, 'dirty')); }; if (!mikser.options.keepRuntime && runtime.isDirty()) { console.log('Cleaning dirty state'); fs.emptyDirSync(mikser.config.outputFolder); runtime.markClean(); } // If version update clean output and runtime state fs.mkdirsSync(mikser.config.runtimeFolder); let currentPackge = path.join(__dirname,'../package.json'); let currentVersion = require(currentPackge).version; mikser.cli.version(currentVersion); currentVersion = currentVersion.split('.'); let recentPackage = path.join(mikser.config.runtimeFolder, 'recent.json'); if (fs.existsSync(recentPackage)) { let recentVersion = require(recentPackage).version.split('.'); runtime.versionChange = { major: recentVersion[0] != currentVersion[0], minor: recentVersion[1] != currentVersion[1] || recentVersion[0] != currentVersion[0], patch: recentVersion[2] != currentVersion[2] || recentVersion[1] != currentVersion[1] || recentVersion[0] != currentVersion[0] } if (runtime.versionChange.minor) { fs.emptyDirSync(mikser.config.outputFolder); } fs.copySync(currentPackge, recentPackage); } else { fs.copySync(currentPackge, recentPackage); } } mikser.runtime = runtime; return Promise.resolve(mikser); }