You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

temp.js 7.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. let fs = require('fs');
  2. let path = require('path');
  3. let cnst = require('constants');
  4. let os = require('os');
  5. let rimraf = require('rimraf');
  6. let mkdirp = require('mkdirp');
  7. let osTmpdir = require('os').tmpdir();
  8. const rimrafSync = rimraf.sync;
  9. //== helpers
  10. //
  11. let dir = path.resolve(os.tmpdir());
  12. let RDWR_EXCL = cnst.O_CREAT | cnst.O_TRUNC | cnst.O_RDWR | cnst.O_EXCL;
  13. let promisify = function(callback) {
  14. if (typeof callback === 'function') {
  15. return [undefined, callback];
  16. }
  17. var promiseCallback;
  18. var promise = new Promise(function(resolve, reject) {
  19. promiseCallback = function() {
  20. var args = Array.from(arguments);
  21. var err = args.shift();
  22. process.nextTick(function() {
  23. if (err) {
  24. reject(err);
  25. } else if (args.length === 1) {
  26. resolve(args[0]);
  27. } else {
  28. resolve(args);
  29. }
  30. });
  31. };
  32. });
  33. return [promise, promiseCallback];
  34. };
  35. var generateName = function(rawAffixes, defaultPrefix) {
  36. var affixes = parseAffixes(rawAffixes, defaultPrefix);
  37. var now = new Date();
  38. var name = [affixes.prefix,
  39. now.getFullYear(), now.getMonth(), now.getDate(),
  40. '-',
  41. process.pid,
  42. '-',
  43. (Math.random() * 0x100000000 + 1).toString(36),
  44. affixes.suffix].join('');
  45. return path.join(affixes.dir || dir, name);
  46. };
  47. var parseAffixes = function(rawAffixes, defaultPrefix) {
  48. var affixes = {prefix: null, suffix: null};
  49. if(rawAffixes) {
  50. switch (typeof(rawAffixes)) {
  51. case 'string':
  52. affixes.prefix = rawAffixes;
  53. break;
  54. case 'object':
  55. affixes = rawAffixes;
  56. break;
  57. default:
  58. throw new Error("Unknown affix declaration: " + affixes);
  59. }
  60. } else {
  61. affixes.prefix = defaultPrefix;
  62. }
  63. return affixes;
  64. };
  65. /* -------------------------------------------------------------------------
  66. * Don't forget to call track() if you want file tracking and exit handlers!
  67. * -------------------------------------------------------------------------
  68. * When any temp file or directory is created, it is added to filesToDelete
  69. * or dirsToDelete. The first time any temp file is created, a listener is
  70. * added to remove all temp files and directories at exit.
  71. */
  72. var tracking = false;
  73. var track = function(value) {
  74. tracking = (value !== false);
  75. return module.exports; // chainable
  76. };
  77. var exitListenerAttached = false;
  78. var filesToDelete = [];
  79. var dirsToDelete = [];
  80. function deleteFileOnExit(filePath) {
  81. if (!tracking) return false;
  82. attachExitListener();
  83. filesToDelete.push(filePath);
  84. }
  85. function deleteDirOnExit(dirPath) {
  86. if (!tracking) return false;
  87. attachExitListener();
  88. dirsToDelete.push(dirPath);
  89. }
  90. function attachExitListener() {
  91. if (!tracking) return false;
  92. if (!exitListenerAttached) {
  93. process.addListener('exit', function() {
  94. try {
  95. cleanupSync();
  96. } catch(err) {
  97. console.warn("Fail to clean temporary files on exit : ", err);
  98. throw err;
  99. }
  100. });
  101. exitListenerAttached = true;
  102. }
  103. }
  104. function cleanupFilesSync() {
  105. if (!tracking) {
  106. return false;
  107. }
  108. var count = 0;
  109. var toDelete;
  110. while ((toDelete = filesToDelete.shift()) !== undefined) {
  111. rimrafSync(toDelete, { maxBusyTries: 6 });
  112. count++;
  113. }
  114. return count;
  115. }
  116. function cleanupFiles(callback) {
  117. var p = promisify(callback);
  118. var promise = p[0];
  119. callback = p[1];
  120. if (!tracking) {
  121. callback(new Error("not tracking"));
  122. return promise;
  123. }
  124. var count = 0;
  125. var left = filesToDelete.length;
  126. if (!left) {
  127. callback(null, count);
  128. return promise;
  129. }
  130. var toDelete;
  131. var rimrafCallback = function(err) {
  132. if (!left) {
  133. // Prevent processing if aborted
  134. return;
  135. }
  136. if (err) {
  137. // This shouldn't happen; pass error to callback and abort
  138. // processing
  139. callback(err);
  140. left = 0;
  141. return;
  142. } else {
  143. count++;
  144. }
  145. left--;
  146. if (!left) {
  147. callback(null, count);
  148. }
  149. };
  150. while ((toDelete = filesToDelete.shift()) !== undefined) {
  151. rimraf(toDelete, { maxBusyTries: 6 }, rimrafCallback);
  152. }
  153. return promise;
  154. }
  155. function cleanupDirsSync() {
  156. if (!tracking) {
  157. return false;
  158. }
  159. var count = 0;
  160. var toDelete;
  161. while ((toDelete = dirsToDelete.shift()) !== undefined) {
  162. rimrafSync(toDelete, { maxBusyTries: 6 });
  163. count++;
  164. }
  165. return count;
  166. }
  167. function cleanupDirs(callback) {
  168. var p = promisify(callback);
  169. var promise = p[0];
  170. callback = p[1];
  171. if (!tracking) {
  172. callback(new Error("not tracking"));
  173. return promise;
  174. }
  175. var count = 0;
  176. var left = dirsToDelete.length;
  177. if (!left) {
  178. callback(null, count);
  179. return promise;
  180. }
  181. var toDelete;
  182. var rimrafCallback = function (err) {
  183. if (!left) {
  184. // Prevent processing if aborted
  185. return;
  186. }
  187. if (err) {
  188. // rimraf handles most "normal" errors; pass the error to the
  189. // callback and abort processing
  190. callback(err, count);
  191. left = 0;
  192. return;
  193. } else {
  194. count++;
  195. }
  196. left--;
  197. if (!left) {
  198. callback(null, count);
  199. }
  200. };
  201. while ((toDelete = dirsToDelete.shift()) !== undefined) {
  202. rimraf(toDelete, { maxBusyTries: 6 }, rimrafCallback);
  203. }
  204. return promise;
  205. }
  206. function cleanupSync() {
  207. if (!tracking) {
  208. return false;
  209. }
  210. var fileCount = cleanupFilesSync();
  211. var dirCount = cleanupDirsSync();
  212. return {files: fileCount, dirs: dirCount};
  213. }
  214. function cleanup(callback) {
  215. var p = promisify(callback);
  216. var promise = p[0];
  217. callback = p[1];
  218. if (!tracking) {
  219. callback(new Error("not tracking"));
  220. return promise;
  221. }
  222. cleanupFiles(function(fileErr, fileCount) {
  223. if (fileErr) {
  224. callback(fileErr, {files: fileCount});
  225. } else {
  226. cleanupDirs(function(dirErr, dirCount) {
  227. callback(dirErr, {files: fileCount, dirs: dirCount});
  228. });
  229. }
  230. });
  231. return promise;
  232. }
  233. //== directories
  234. //
  235. const mkdir = (affixes, callback) => {
  236. const p = promisify(callback);
  237. const promise = p[0];
  238. callback = p[1];
  239. let dirPath = generateName(affixes, 'd-');
  240. mkdirp(dirPath, 0o700, (err) => {
  241. if (!err) {
  242. deleteDirOnExit(dirPath);
  243. }
  244. callback(err, dirPath);
  245. });
  246. return promise;
  247. }
  248. const mkdirSync = (affixes) => {
  249. let dirPath = generateName(affixes, 'd-');
  250. mkdirp.sync(dirPath, 0o700);
  251. deleteDirOnExit(dirPath);
  252. return dirPath;
  253. }
  254. //== files
  255. //
  256. const open = (affixes, callback) => {
  257. const p = promisify(callback);
  258. const promise = p[0];
  259. callback = p[1];
  260. const path = generateName(affixes, 'f-');
  261. fs.open(path, RDWR_EXCL, 0o600, (err, fd) => {
  262. if (!err) {
  263. deleteFileOnExit(path);
  264. }
  265. callback(err, { path, fd });
  266. });
  267. return promise;
  268. }
  269. const openSync = (affixes) => {
  270. const path = generateName(affixes, 'f-');
  271. let fd = fs.openSync(path, RDWR_EXCL, 0o600);
  272. deleteFileOnExit(path);
  273. return { path, fd };
  274. }
  275. const createWriteStream = (affixes) => {
  276. const path = generateName(affixes, 's-');
  277. let stream = fs.createWriteStream(path, { flags: RDWR_EXCL, mode: 0o600 });
  278. deleteFileOnExit(path);
  279. return stream;
  280. }
  281. //== settings
  282. //
  283. exports.dir = dir;
  284. exports.track = track;
  285. //== functions
  286. //
  287. exports.mkdir = mkdir;
  288. exports.mkdirSync = mkdirSync;
  289. exports.open = open;
  290. exports.openSync = openSync;
  291. exports.path = generateName;
  292. exports.cleanup = cleanup;
  293. exports.cleanupSync = cleanupSync;
  294. exports.createWriteStream = createWriteStream;