'use strict'; /** * file-locking.js - file system locking (replaces fs-ext) */ const async = require('async'); const locker = require('lockfile'); const fs = require('fs'); const path = require('path'); // locks a file by creating a lock file const lockFile = function(name, next) { const lockFileName = `${name}.lock`; const lockOpts = { wait: 1000, // time (ms) to wait when checking for stale locks pollPeriod: 100, // how often (ms) to re-check stale locks stale: 5 * 60 * 1000, // locks are considered stale after 5 minutes retries: 100, // number of times to attempt to create a lock retryWait: 100, // time (ms) between tries }; async.series({ statdir: function(callback) { // test to see if the directory exists fs.stat(path.dirname(name), function(err, stats) { if (err) { callback(err); } else if (!stats.isDirectory()) { callback(new Error(path.dirname(name) + ' is not a directory')); } else { callback(null); } }); }, statfile: function(callback) { // test to see if the file to lock exists fs.stat(name, function(err, stats) { if (err) { callback(err); } else if (!stats.isFile()) { callback(new Error(path.dirname(name) + ' is not a file')); } else { callback(null); } }); }, lockfile: function(callback) { // try to lock the file locker.lock(lockFileName, lockOpts, callback); }, }, function(err) { if (err) { // lock failed return next(err); } // lock succeeded return next(null); }); }; // unlocks file by removing existing lock file const unlockFile= function(name, next) { const lockFileName = `${name}.lock`; locker.unlock(lockFileName, function(err) { if (err) { return next(err); } return next(null); }); }; /** * Reads a local file, which involves * optionally taking a lock * reading the file contents * optionally parsing JSON contents * @param {*} name * @param {*} options * @param {*} next */ function readFile(name, options, next) { if (typeof options === 'function' && next === null) { next = options; options = {}; } options = options || {}; options.lock = options.lock || false; options.parse = options.parse || false; const lock = function(callback) { if (!options.lock) { return callback(null); } lockFile(name, function(err) { if (err) { return callback(err); } return callback(null); }); }; const read = function(callback) { fs.readFile(name, 'utf8', function(err, contents) { if (err) { return callback(err); } callback(null, contents); }); }; const parseJSON = function(contents, callback) { if (!options.parse) { return callback(null, contents); } try { contents = JSON.parse(contents); return callback(null, contents); } catch (err) { return callback(err); } }; async.waterfall([ lock, read, parseJSON, ], function(err, result) { if (err) { return next(err); } else { return next(null, result); } }); } exports.lockFile = lockFile; exports.unlockFile = unlockFile; exports.readFile = readFile;