UNPKG

alchemymvc

Version:
1,715 lines (1,374 loc) 34.7 kB
let mkdirp = alchemy.use('mkdirp')?.mkdirp, ncp = alchemy.use('ncp').ncp, fs = alchemy.use('fs'), fsp = fs.promises, libpath = alchemy.use('path'), child = alchemy.use('child_process'), crypto = alchemy.use('crypto'), mongo = alchemy.use('mongodb'), queues = {}, timers = {}, lpQueue = 0, moduledirs, spawnQueue; const HAS_EXPOSED = Symbol('has_exposed'); // Create a queue for functions opening files spawnQueue = Function.createQueue(); // Limit to 500 open files spawnQueue.limit = 500; // Start the queue spawnQueue.start(); /** * Attach a conduit to a certain instance * * @author Jelle De Loecker <jelle@elevenways.be> * @since 0.2.0 * @version 0.2.0 * * @param {Conduit} conduit */ Informer.setMethod(function attachConduit(conduit) { this.conduit = conduit; }); /** * Get a model, attach a conduit if possible * * @author Jelle De Loecker <jelle@elevenways.be> * @since 0.3.0 * @version 1.1.8 * * @param {string} name The name of the model to get * @param {boolean} init Initialize the class [true] * @param {Object} options * * @return {Model} */ Informer.setMethod(function getModel(name, init, options) { return Blast.Classes.Alchemy.Base.prototype.getModel.apply(this, arguments); }); /** * Create a debug entry * * @author Jelle De Loecker <jelle@elevenways.be> * @since 0.2.0 * @version 0.2.0 * * @param {number} verbosity */ Informer.setMethod(function debug(verbosity) { var duplicate, options, item, args, i = 0; if (typeof verbosity == 'object' && verbosity && (verbosity.label || verbosity.id || verbosity.unique)) { options = verbosity; verbosity = null; i = 1; } if (options == null) { options = {}; } if (options.verbosity == null) { if (typeof verbosity == 'number') { i = 1; options.verbosity = verbosity; } else { options.verbosity = log.INFO; } } // Do nothing if debugging is off and verbosity is too high if (!alchemy.settings.debugging.debug && options.verbosity > log.ERROR) { return; } if (options.data == null) { args = []; for (; i < arguments.length; i++) { args.push(arguments[i]); } options.data = {args: args}; } if (!options.namespace) { options.namespace = this.constructor.name; } if (options.unique) { // Generate fowler hash options.id = (options.data.args + '').fowler(); } if (options.id) { if (!this._debug_seen_items) { this._debug_seen_items = {}; } if (!this._debug_seen_items[options.id]) { this._debug_seen_items[options.id] = options; options.seen_count = 1; } else { // Do nothing if it has already been seen duplicate = this._debug_seen_items[options.id]; duplicate.seen_count++; } } if (options.label) { if (this._debugObject) { item = this._debugObject.debug(options.label, options.data, options.verbosity); } else if (!this.conduit) { } else { item = new Debugger(this.conduit, options.label, options.verbosity); item.data = options.data; if (options.data && options.data.title) { item.title = options.data.title; } if (this.conduit.debuglog) this.conduit.debuglog.push(item); } return item; } if (!duplicate && alchemy.settings.debugging.debug && options.data && options.data.args) { if (typeof options.level != 'number') { options.level = 1; } alchemy.Janeway.print('debug', options.data.args, options); } return item; }); /** * Create a debug mark * * @author Jelle De Loecker <jelle@elevenways.be> * @since 0.2.0 * @version 0.2.0 * * @param {string} message */ Informer.setMethod(function debugMark(message) { if (this._debugObject) { this._debugObject.mark(message); } }); var Debugger = Function.inherits('Alchemy.Base', function Debugger(conduit, type, verbosity, level) { // Store the conduit this.conduit = conduit; // When this started during the request this.start = this.time(); // The identifier of this debug line this.id = null; // The title of this line this.title = null; // Labels this.labels = []; // When this ended this.end = null; // The type of debug this.type = type; // Children this.children = []; // The active sub mark this.activeMark = null; if (typeof verbosity != 'number') { verbosity = alchemy.Janeway.LEVELS.INFO; } this.verbosity = verbosity; // The recursive level this.level = level || 0; }); Debugger.setMethod(function toJSON() { return { id : this.id, type: this.type, title: this.title, start: this.start, end: this.end || this._possible_end, duration: this.duration, labels: this.labels, children: this.children, level: this.level, data: this.data, verbosity: this.verbosity }; }); Debugger.setMethod(function time() { return this.conduit.time(); }); Debugger.setMethod(function mark(stop, message, id) { var temp, i; if (typeof stop == 'string') { id = message; message = stop; stop = null; } if (!id) { id = message; } // If stop is false and a message has been supplied, // look for the original object with that title and // only stop that mark if (stop === false && (message || id)) { if (arguments.length == 2) { id = message; } for (i = 0; i < this.children.length; i++) { temp = this.children[i]; if (temp.data.id == id) { return temp.stop(); } } return; } if (this.activeMark != null) { this.activeMark.stop(); this.activeMark._possible_end = this.time(); } // If the message is false, we just wanted to end the previous mark if (!message) { return; } this.activeMark = this.debug('mark', {title: message, id: id}); }); Debugger.setMethod(function debug(type, data, verbosity) { var item; if (typeof verbosity != 'number') { verbosity = log.INFO; } if (!data) { data = {}; } item = new Debugger(this.conduit, type, verbosity, this.level + 1); item.data = data; item.parent = this; if (!data.id) { data.id = data.title; } if (data.title) { item.title = data.title; } this.children.push(item); return item; }); Debugger.setMethod(function stop(message) { // Don't end something twice if (this.end) { return; } if (!message) { message = 'end'; } this.end = this.time(); this.duration = this.end - this.start; this.labels.push({message: message, time: this.end}); if (this.activeMark) { this.activeMark.stop('parent-end'); } }); /** * Schedule a function in a low priority queue * * @author Jelle De Loecker <jelle@elevenways.be> * @since 0.0.1 * @version 0.4.0 * * @param {Function} fnc * @param {number} wait How long to wait before executing */ Alchemy.setMethod(function lowPriority(fnc, wait) { var start, total, exec, lag = alchemy.toobusy.lag(); // Set the default wait to 20 ms if (!wait) { wait = 20; } // The function that will do the executing if (fnc.name == 'execLowPriority') { exec = fnc; } else { // Increase the low priority queue count lpQueue++; // When we queued the function start = Date.now(); exec = function execLowPriority() { // Decrease the low priority queue count lpQueue--; // When we're executing the function total = Date.now() - start; fnc(total); }; exec.rescheduled = 0; } if (lag > 0 || (lpQueue > 100 && exec.rescheduled < 3)) { exec.rescheduled++; wait = 500 + ~~(lpQueue/(4*exec.rescheduled)); if (wait > 1000) { wait = 999; } if (exec.rescheduled > 3) { return exec(); } setTimeout(function(){alchemy.lowPriority(exec)}, wait); } else { setTimeout(exec, wait); } }); var _duplicateCheck = {}; /** * Copy a directory * * @author Jelle De Loecker <jelle@elevenways.be> * @since 0.0.1 * @version 0.4.0 * * @param {string} source The source path * @param {string} target The target path * @param {Function} callback The function to call when done */ Alchemy.setMethod(function copyDir(source, target, callback) { // Create the target directory if needed alchemy.createDir(target, function(err, made) { ncp(source, target, callback); }); }); /** * Create a directory * * @author Jelle De Loecker <jelle@elevenways.be> * @since 0.0.1 * @version 1.1.0 * * @param {string} target The target path * @param {Function} callback The function to call when done * * @return {Promise} */ Alchemy.setMethod(function createDir(target, callback) { let promise = mkdirp(target); if (callback) { Pledge.done(promise, callback); } return promise; }); /** * Return the key:items of the first object that are no longer in the second * * @author Jelle De Loecker <jelle@elevenways.be> * @since 0.0.1 * @version 1.3.22 * * @param {Object|Map} first * @param {Object|Map} second * * @return {Object} The items in the first item that are not in the second */ Alchemy.setMethod(function getDifference(first, second) { let first_is_map = first instanceof Map, second_is_map = second instanceof Map, first_keys, result = {}, first_val, key, val; if (first_is_map) { first_keys = [...first.keys()]; } else { first_keys = Object.keys(first); } for (key of first_keys) { if (second_is_map) { val = second.get(key); } else { val = second[key]; } // If the key in the first item doesn't show up in the second item, // add it to the result if (typeof val === 'undefined') { if (first_is_map) { first_val = first.get(key); } else { first_val = first[key]; } result[key] = first_val; } } return result; }); /** * Return the shared items * * @author Jelle De Loecker <jelle@elevenways.be> * @since 0.0.1 * @version 1.3.22 * * @param {Object|Map} first * @param {Object|Map} second * * @return {Object} The items in the second item that are also in the first */ Alchemy.setMethod(function getShared(first, second) { let first_is_map = first instanceof Map, second_is_map = second instanceof Map, first_keys, result = {}, key, val; if (first_is_map) { first_keys = [...first.keys()]; } else { first_keys = Object.keys(first); } for (key of first_keys) { if (second_is_map) { val = second.get(key); } else { val = second[key]; } // If the key in the first item doesn't show up in the second item, // add it to the result if (typeof val !== 'undefined') { result[key] = val; } } return result; }); /** * Make JSON-Dry handle ObjectIDs when drying * (Old class name) * * @author Jelle De Loecker <jelle@elevenways.be> * @since 0.2.0 * @version 0.2.0 */ JSON.registerDrier('ObjectID', function dryOI(holder, key, value) { return ''+value; }, {add_path: false}); /** * Correctly un-dry ObjectIDs * (Old class name) * * @author Jelle De Loecker <jelle@elevenways.be> * @since 0.2.0 * @version 0.2.0 */ JSON.registerUndrier('ObjectID', function undryOI(holder, key, value) { return alchemy.castObjectId(value); }); /** * Make JSON-Dry handle ObjectIds when drying * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.3.16 * @version 1.3.16 */ JSON.registerDrier('ObjectId', function dryOI(holder, key, value) { return ''+value; }, {add_path: false}); /** * Correctly un-dry ObjectIds * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.3.16 * @version 1.3.16 */ JSON.registerUndrier('ObjectId', function undryOI(holder, key, value) { return alchemy.castObjectId(value); }); /** * Monkey-patch checksum support to the ObjectId class * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.3.16 * @version 1.4.0 */ if (typeof mongo?.ObjectId == 'function') { Function.setMethod(mongo.ObjectId, Blast.JANEWAY_RIGHT, function right() { return this.toString(); }); Function.setMethod(mongo.ObjectId, Blast.checksumSymbol, function checksum() { return this.toString(); }); } /** * Create a new ObjectId * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.3.16 * @version 1.3.16 * * @param {*} object_id * * @return {ObjectId} */ Alchemy.setMethod(function ObjectId(object_id) { return new mongo.ObjectId(object_id); }); /** * Get mimetype info of a file * * @deprecated Use Classes.Alchemy.Inode.File.getMimetype(path) * * @author Jelle De Loecker <jelle@elevenways.be> * @since 0.2.0 * @version 1.3.0 * * @param {string} filepath A path to the file * @param {Function} callback * * @return {Pledge<string>} */ Alchemy.setMethod(function getMimetype(filepath, callback) { const pledge = Classes.Alchemy.Inode.File.getMimetype(filepath); pledge.done(callback); return pledge; }); /** * Get a medhash * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.0.7 * @version 1.0.7 * * @param {string} file_path A path to the file * @param {Object} options */ Alchemy.setMethod(function getMedhash(file_path, options) { var do_full, sample_size, sample, digest, stat, size, fd; if (!options) { options = {}; } if (!options.threshold) { options.threshold = 128 * 1024; } if (!options.sample) { options.sample = 16 * 1024; } sample_size = options.sample; return Function.series(function getStat(next) { if (options.stat) { if (options.stat.then) { options.stat.then(function gotValue(val) { stat = val; next(); }); } else { stat = options.stat; next(); } return; } fs.stat(file_path, function gotStat(err, result) { if (err) { return doClose(next, err); } stat = result; next(); }); }, function gotSize(next) { // The size of the file size = stat.size; if (size < options.threshold || sample_size < 1) { do_full = true; } // Open the file descriptor fs.open(file_path, 'r', function gotFd(err, result) { if (err) { return doClose(next, err); } fd = result; next(); }); }, function firstSample(next) { if (do_full) { return alchemy.hashFile(fd).done(next); } sample = new Buffer(sample_size * 3); fs.read(fd, sample, 0, sample_size, 0, next); }, function secondSample(next, first) { if (do_full) { digest = first; return next(); } fs.read(fd, sample, sample_size, sample_size, ~~(size / 2), next); }, function thirdSample(next) { if (do_full) { return next(); } fs.read(fd, sample, sample_size, sample_size, size - sample_size, next); }, function doHash(next) { if (do_full) { return doClose(next); } alchemy.hashFile(sample).done(function gotResult(err, result) { if (err) { return doClose(next, err); } digest = result; doClose(next); }); }, function done(err) { if (err) { return; } return size.toString(16) + digest; }); // Function to close the descriptor function doClose(next, err) { if (fd > 0 && file_path !== fd) { fs.close(fd, function closed() { // Ignore errors next(err); }); } else { next(err); } } }); /** * Hash the given file_path using the given hash * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.0.7 * @version 1.3.0 * * @param {string} file_path * @param {Object} options */ Alchemy.setMethod(function hashFile(file_path, options) { var stream_options = {}, digest_type, hash_algorithm; if (!options) { options = {}; } if (typeof options == 'string') { options = { type : options }; } hash_algorithm = options.algorithm || options.type || alchemy.settings.data_management.file_hash_algorithm || 'sha1'; digest_type = options.digest_type || 'hex'; let checksum = crypto.createHash(hash_algorithm), pledge = new Pledge(), stream; if (typeof file_path == 'number') { stream_options.fd = file_path; stream_options.autoClose = false; file_path = ''; } if (Buffer.isBuffer(file_path)) { checksum.update(file_path); pledge.resolve(checksum.digest(digest_type)); } else { // Start reading the file stream = fs.createReadStream(file_path, stream_options); // Update the checksum on data stream.on('data', function updateDigest(d) { checksum.update(d); }); // When it's done, send the hexadecimal digest stream.on('end', function finalizeDigest() { pledge.resolve(checksum.digest(digest_type)); }); } return pledge; }); /** * Get file statistics * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.0.7 * @version 1.0.7 * * @param {string} path Path to stat * @param {Object} options * * @return {Pledge} */ Alchemy.setMethod(function statPath(path, options) { if (options && options.stat && typeof options.stat == 'object') { return Pledge.resolve(options.stat); } let pledge = new Pledge(); fs.stat(path, function gotStats(err, stats) { if (err) { return pledge.reject(err); } pledge.resolve(stats); }); return pledge; }); /** * Get basic file information * * @author Jelle De Loecker <jelle@elevenways.be> * @since 0.0.1 * @version 1.4.0 * * @param {string} filePath A path to the file * @param {Object} options * @param {Function} callback */ Alchemy.setMethod(function getFileInfo(filePath, options, callback) { var tasks = {}; if (typeof filePath !== 'string') { return callback(new Error('Unable to get file info: given path is empty')); } if (typeof options == 'function') { callback = options; options = {}; } // Use sha1 as hashing default if (typeof options.hash == 'undefined' || options.hash === true) { options.hash = alchemy.settings.data_management.file_hash_algorithm || 'sha1'; } // Get basic file information tasks.stat = this.statPath(filePath); // Prepare a function to calculate the hash if it's wanted if (options.hash == 'medhash') { tasks.hash = function getMedHash(next) { alchemy.getMedhash(filePath, {stat: tasks.stat}).done(next); }; } else if (options.hash) { tasks.hash = this.hashFile(filePath, options.hash); } if (options.filename) { tasks.filename = options.filename; } else { // Get the filename tasks.filename = next => { let pieces = filePath.split('/'); next(null, pieces[pieces.length-1]); }; } // Get the extension tasks.extension = function getExtension(next) { var pieces = filePath.split('.'), extension = null; if (pieces.length > 1) { extension = pieces[pieces.length-1]; } next(null, extension); }; // Get the mime type tasks.mimetype = function getMimetype(next) { alchemy.getMimetype(filePath, next); }; Function.parallel(tasks, function doneInfoTasks(err, result) { if (err) { return callback(err); } let pieces, stat = result.stat; // Remove the stat object from the result object delete result.stat; // Set the size result.size = stat.size; // Uid result.uid = stat.uid; result.gid = stat.gid; // Dates result.atime = stat.atime; result.mtime = stat.mtime; result.ctime = stat.ctime; result.birthtime = stat.birthtime; // Get the name without extension pieces = result.filename.split('.'); // Remove the last piece, the extension pieces.splice(pieces.length-1, 1); // Join again result.name = pieces.join('.'); callback(null, result); }); }); /** * Move or copy a file * * @author Jelle De Loecker <jelle@elevenways.be> * @since 0.0.1 * @version 1.0.5 * * @param {string} source Origin path * @param {string} target Target path * @param {Function} cb */ function mcFile(type, source, target, cb) { var targetDir, bin, cmd; if (type == 'cp' || type == 'copy') { bin = '/bin/cp'; } else if (type == 'mv' || type == 'move') { bin = '/bin/mv'; } // We assume the last piece of the target is the file name // Split the string by slashes targetDir = target.split('/'); // Remove the last piece targetDir.splice(targetDir.length-1, 1); // Join it again targetDir = targetDir.join('/'); // Make sure the target directory exists alchemy.createDir(targetDir, function ensuredDir(err) { if (err) { return cb(err); } spawnQueue.add(function queuecp(done) { var options = []; if (process.platform == 'linux') { options = ['--no-target-directory', source, target]; } else { if (source.endsWith('/')) { source = source.slice(0, -1); } if (target.endsWith('/')) { target = target.slice(0, -1); } options = [source, target]; } cmd = child.execFile(bin, options); cmd.on('exit', function onExit(code, signal) { var message; done(); if (code > 0) { message = 'File "' + type + '" operation failed:\n'; message += bin + ' ' + options.join(' '); cb(new Error(message)); } else { cb(null); } }); }); }); }; /** * Copy a file * * @author Jelle De Loecker <jelle@elevenways.be> * @since 0.0.1 * @version 0.4.0 * * @param {string} source Origin path * @param {string} target Target path * @param {Function} cb */ Alchemy.setMethod(function copyFile(source, target, cb) { mcFile('cp', source, target, cb); }); /** * Make an HTTP(S) request * * @author Jelle De Loecker <jelle@elevenways.be> * @since 0.2.0 * @version 1.1.0 * * @param {string} url The url * @param {Object} options * @param {Function} callback Callback * * @return {Pledge} */ Alchemy.setMethod(function request(url, options, callback) { if (typeof options == 'function') { callback = options; options = null; } if (url && options) { options.url = url; } else { if (typeof url == 'string' || url instanceof Classes.RURL) { options = { url : url }; } else { options = url; } } url = Classes.RURL.parse(options.url); options.url = url; return Blast.fetch(options, callback); }); /** * Turn a `data:` uri into a file * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.4.0 * @version 1.4.0 * * @param {string} data_uri * * @return {Pledge<File>} */ function convertDataUriToFile(data_uri) { // Split the data uri into its 2 parts let pieces = data_uri.slice(5).split(','); // Split the info bit let info = pieces[0].split(';'); // Get the expected mime type let mime_type = info[0]; // And is it base64? let is_b64 = info[1] && info[1].toLowerCase() == 'base64'; let buffer; if (is_b64) { buffer = Buffer.from(pieces[1], 'base64'); } else { buffer = Buffer.from(String.decodeURI(pieces[1])); } return Pledge.Swift.waterfall( () => Blast.createTempDir({prefix: 'aldl'}), async (temp_dir) => { let full_path = libpath.resolve(temp_dir, 'buffer_' + alchemy.ObjectId()); await fsp.writeFile(full_path, buffer); return full_path; }, (full_path) => Classes.Alchemy.Inode.Inode.from(full_path) ); } /** * Download a file * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.3.0 * @version 1.3.0 * * @param {string} url * * @return {Pledge<File>} */ Alchemy.setMethod(function download(url, options) { if (url.startsWith('data:')) { try { return convertDataUriToFile(url, options); } catch (err) { return Pledge.reject(err); } } const pledge = new Pledge(); // Get the file Blast.fetch({url, get_stream: true}, async function gotStream(err, res, output) { if (err) { return pledge.reject(err); } if (res.statusCode == 404) { err = new Error('Path "' + url + '" does not exist'); err.number = 404; return pledge.reject(err); } if (options?.type && res.headers['content-type']) { if (res.headers['content-type'].indexOf(options.type) < 0) { err = new Error('Received unexpected filetype'); err.number = 500; return pledge.reject(err); } } let name; if (res.headers['content-disposition']) { let disposition = res.headers['content-disposition'], pieces = disposition.split(';'), temp, i; for (i = 0; i < pieces.length; i++) { temp = String.decodeAttributes(pieces[i], ','); if (temp && temp.filename) { name = temp.filename; break; } } } if (!name) { name = libpath.parse(url).base; if (name && name.indexOf('?') > -1) { name = name.before('?'); } if (!name) { name = alchemy.ObjectId(); } } if (name.indexOf('/') > -1) { name = name.replaceAll('/', '-'); } let temp_dir = await Blast.createTempDir({prefix: 'aldl'}), full_path = libpath.resolve(temp_dir, name); let write_stream = fs.createWriteStream(full_path); if (output) { // Pipe the response stream into the file output.pipe(write_stream); } else { write_stream.end(''); } // Wait for it to finish writing to the temp file write_stream.on('finish', async function writeFinished() { try { let file = await Classes.Alchemy.Inode.Inode.from(full_path); pledge.resolve(file); } catch (err) { pledge.reject(err); } }); }); return pledge; }); /** * Download a url to a temporary location * * @author Jelle De Loecker <jelle@elevenways.be> * @since 0.1.0 * @version 1.3.0 * * @deprecated Use `download` instead * * @param {string} url The url * @param {Object} options * @param {Function} callback Callback */ Alchemy.setMethod(function downloadFile(url, options, callback) { if (typeof options == 'function') { callback = options; options = null; } let pledge = this.download(url, options); pledge.done(callback); return pledge; }); /** * Move a file * * @author Jelle De Loecker <jelle@elevenways.be> * @since 0.0.1 * @version 0.4.0 * * @param {string} source Origin path * @param {string} target Target path * @param {Function} cb */ Alchemy.setMethod(function moveFile(source, target, cb) { mcFile('mv', source, target, cb); }); /** * Return the first path that works * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.0.5 * @version 1.0.7 * * @param {string} name The name of the binary * @param {string} preferred_path The preferred path * @param {Array} fallbacks Optional fallbacks * * @return {string} */ Alchemy.setMethod(function findPathToBinarySync(name, preferred_path, fallbacks) { var temp, i; if (preferred_path) { if (fs.existsSync(preferred_path)) { return preferred_path; } } fallbacks = Array.cast(fallbacks); temp = '/usr/local/bin/' + name; if (fallbacks.indexOf(temp) == -1) { fallbacks.push(temp); } temp = '/usr/bin/' + name; if (fallbacks.indexOf(temp) == -1) { fallbacks.push(temp); } temp = '/bin/' + name; if (fallbacks.indexOf(temp) == -1) { fallbacks.push(temp); } for (i = 0; i < fallbacks.length; i++) { if (fs.existsSync(fallbacks[i])) { return fallbacks[i]; } } return false; }); var shown_list_warning = false; /** * Create a list, an array with extra methods * * @author Jelle De Loecker <jelle@elevenways.be> * @since 0.0.1 * @version 1.0.7 * @deprecated */ Alchemy.setMethod(function List() { if (!shown_list_warning) { log.warn('Alchemy#List() is deprecated'); shown_list_warning = true; } // Create a new array var result = Array.apply(Array, arguments); // Add our modified toString method result.toString = listToString; return result; }); function listToString() { return this[0]; } /** * Get the current time in nanoseconds * * @author Jelle De Loecker <jelle@elevenways.be> * @since 0.0.1 * @version 0.4.0 * * @return {number} */ Alchemy.setMethod(function nanotime() { var hrTime = process.hrtime(); return hrTime[0] * 1000000000 + hrTime[1]; }); /** * Start a very precise timer * * @author Jelle De Loecker <jelle@elevenways.be> * @since 0.0.1 * @version 0.4.0 * * @return {string} name */ Alchemy.setMethod(function time(name) { // Prepare the entry first, before starting the clock if (!timers[name]) { timers[name] = { total: 0, first: this.nanotime() }; } // Start the timer timers[name].start = this.nanotime(); }); /** * End a very precise timer, * output the duration since the start to the console * and return the duration too. * * @author Jelle De Loecker <jelle@elevenways.be> * @since 0.0.1 * @version 0.4.0 * * @return {string} name */ Alchemy.setMethod(function timeEnd(name, showNano, extra, iterated) { // Immediately stop the timer var stop = this.nanotime(), diff = stop - timers[name].start, extra = extra || ''; if (iterated) { diff = diff / iterated; } // Increase the total timers[name].total += diff; if (!showNano) { diff = ~~(diff / 1000); console.log('Timer ' + name + ' in ' + diff + ' µs ' + extra); } else { console.log('Timer ' + name + ' in ' + diff + ' ns ' + extra); } return diff; }); /** * Get the total of all the registered durations for the given name * * @author Jelle De Loecker <jelle@elevenways.be> * @since 0.0.1 * @version 0.4.0 * * @return {string} name */ Alchemy.setMethod(function timeTotal(name, showNano) { var total = timers[name].total; if (!showNano) { total = ~~(total / 1000); console.log('Timer ' + name + ' total spent ' + total + ' µs'); } else { console.log('Timer ' + name + ' total spent ' + total + ' ns'); } return total; }); /** * Set some variable that should always be sent to the client. * (The value will be serialized once and then cached, so it should not change) * Alias method for Hawkejs#exposeStatic() * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.1.0 * @version 1.1.0 * * @param {string} name * @param {Object} value */ Alchemy.setMethod(function exposeStatic(name, value) { alchemy.hawkejs.exposeStatic(name, value); }); /** * Expose the default static variables * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.1.0 * @version 1.3.1 */ Alchemy.setMethod(function exposeDefaultStaticVariables() { let model_info = [], model_code = '', hawkejs = alchemy.hawkejs, models = Model.getAllChildren(), info, i; for (i = 0; i < models.length; i++) { info = models[i].getClientConfig(); model_info.push(info); } // Sort the models by their ancestor count model_info.sortByPath(1, 'ancestors'); model_code = `function inheritModel(parent_model_name, child_model_name) { return Classes.Hawkejs.Model.getClass(child_model_name, true, parent_model_name); }\n`; for (let info of model_info) { model_code += 'inheritModel(' + JSON.stringify(info.parent) + ', ' + JSON.stringify(info.model_name) + ')\n'; } // Expose the model configuration hawkejs.exposeStatic('model_info', model_info); Blast.require('client_models', { after : 'helper_model/model', resolver : async function getExportedFunction() { return model_code; }, client : true, server : false, }); this.exposeRouteData(); // Expose breadcrumb info hawkejs.exposeStatic('breadcrumb_info', Router.getBreadcrumbInfo()); // Expose prefixes hawkejs.exposeStatic('prefixes', Prefix.all()); const websockets = alchemy.settings.network.use_websockets; // Are websockets enabled? if (!websockets || websockets == 'optional' || websockets == 'never') { hawkejs.exposeStatic('enable_websockets', false); } else { hawkejs.exposeStatic('enable_websockets', true); } // Expose the layout settings hawkejs.exposeStatic('alchemy_layout', alchemy.settings.frontend.ui.layout); let app_version = alchemy.package.version; // The current app version if (alchemy.environment == 'dev') { app_version = ''+Date.now(); } hawkejs.exposeStatic('app_version', app_version); hawkejs.app_version = app_version; // Emit as a global event so plugins can also expose their data this.emit('generate_static_variables', hawkejs); this[HAS_EXPOSED] = true; }); /** * Regenerate exposed route data * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.3.0 * @version 1.3.0 */ Alchemy.setMethod(function exposeRouteData() { const hawkejs = alchemy.hawkejs; // Expose router options hawkejs.exposeStatic('router_options', Router.getOptions()); // Expose basic HTTP routes hawkejs.exposeStatic('routes', Router.getRoutes()); // Expose socket routes hawkejs.exposeStatic('socket_routes', Router.getSocketRoutes()); }); /** * Flush the exposed route data * (Will only regenerate if default static variables haven't been generated yet) * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.3.0 * @version 1.3.0 */ Alchemy.setMethod(function checkExposedRouteData() { if (!this[HAS_EXPOSED]) { return; } this.exposeRouteData(); }); /** * Read directory contents * * @author Jelle De Loecker <jelle@elevenways.be> * @since 1.1.0 * @version 1.1.0 * * @param {string} path * @param {Object} options * * @return {Pledge} */ Alchemy.setMethod(function readDir(path, options) { var fs_config = {}, list; if (!options) { options = {}; } if (options.encoding) { fs_config.encoding = options.encoding; } if (options.recursive === true) { options.recursive = Infinity; } if (options.recursive == null) { options.recursive = 0; } let pledge = Function.series(function getEntries(next) { fs.readdir(path, fs_config, next); }, function gotEntries(next, entries) { if (options.simple) { return next(null, entries); } let tasks = []; for (let name of entries) { tasks.push(function gotFileInfo(next) { Classes.Alchemy.Inode.Inode.process([path, name], null, next); }); } Function.parallel(4, tasks, next); }, function gotInodes(next, inodes) { if (options.simple) { list = inodes; return next(null); } list = new Classes.Alchemy.Inode.List(inodes); if (!options.recursive) { return next(null); } list.loadDirContents({recursive: options.recursive}).then(function done() { next(); }).catch(function caught(err) { next(err); }); }, function done(err) { if (err) { return; } return list; }); return pledge; }); /** * Connect to another alchemy instance * * @author Jelle De Loecker <jelle@elevenways.be> * @since 0.1.0 * @version 0.4.0 */ Alchemy.setMethod(function callServer(address, data, callback) { var server = new Classes.ClientSocket(); server.reconnect = false; server.connect(address, data, callback); return server; });