index.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. 'use strict'
  2. const EE = require('events').EventEmitter
  3. const cons = require('constants')
  4. const fs = require('fs')
  5. module.exports = (f, options, cb) => {
  6. if (typeof options === 'function')
  7. cb = options, options = {}
  8. const p = new Promise((res, rej) => {
  9. new Touch(validOpts(options, f, null))
  10. .on('done', res).on('error', rej)
  11. })
  12. return cb ? p.then(res => cb(null, res), cb) : p
  13. }
  14. module.exports.sync = module.exports.touchSync = (f, options) =>
  15. (new TouchSync(validOpts(options, f, null)), undefined)
  16. module.exports.ftouch = (fd, options, cb) => {
  17. if (typeof options === 'function')
  18. cb = options, options = {}
  19. const p = new Promise((res, rej) => {
  20. new Touch(validOpts(options, null, fd))
  21. .on('done', res).on('error', rej)
  22. })
  23. return cb ? p.then(res => cb(null, res), cb) : p
  24. }
  25. module.exports.ftouchSync = (fd, opt) =>
  26. (new TouchSync(validOpts(opt, null, fd)), undefined)
  27. const validOpts = (options, path, fd) => {
  28. options = Object.create(options || {})
  29. options.fd = fd
  30. options.path = path
  31. // {mtime: true}, {ctime: true}
  32. // If set to something else, then treat as epoch ms value
  33. const now = parseInt(new Date(options.time || Date.now()).getTime() / 1000)
  34. if (!options.atime && !options.mtime)
  35. options.atime = options.mtime = now
  36. else {
  37. if (true === options.atime)
  38. options.atime = now
  39. if (true === options.mtime)
  40. options.mtime = now
  41. }
  42. let oflags = 0
  43. if (!options.force)
  44. oflags = oflags | cons.O_RDWR
  45. if (!options.nocreate)
  46. oflags = oflags | cons.O_CREAT
  47. options.oflags = oflags
  48. return options
  49. }
  50. class Touch extends EE {
  51. constructor (options) {
  52. super(options)
  53. this.fd = options.fd
  54. this.path = options.path
  55. this.atime = options.atime
  56. this.mtime = options.mtime
  57. this.ref = options.ref
  58. this.nocreate = !!options.nocreate
  59. this.force = !!options.force
  60. this.closeAfter = options.closeAfter
  61. this.oflags = options.oflags
  62. this.options = options
  63. if (typeof this.fd !== 'number') {
  64. this.closeAfter = true
  65. this.open()
  66. } else
  67. this.onopen(null, this.fd)
  68. }
  69. emit (ev, data) {
  70. // we only emit when either done or erroring
  71. // in both cases, need to close
  72. this.close()
  73. return super.emit(ev, data)
  74. }
  75. close () {
  76. if (typeof this.fd === 'number' && this.closeAfter)
  77. fs.close(this.fd, () => {})
  78. }
  79. open () {
  80. fs.open(this.path, this.oflags, (er, fd) => this.onopen(er, fd))
  81. }
  82. onopen (er, fd) {
  83. if (er) {
  84. if (er.code === 'EISDIR')
  85. this.onopen(null, null)
  86. else if (er.code === 'ENOENT' && this.nocreate)
  87. this.emit('done')
  88. else
  89. this.emit('error', er)
  90. } else {
  91. this.fd = fd
  92. if (this.ref)
  93. this.statref()
  94. else if (!this.atime || !this.mtime)
  95. this.fstat()
  96. else
  97. this.futimes()
  98. }
  99. }
  100. statref () {
  101. fs.stat(this.ref, (er, st) => {
  102. if (er)
  103. this.emit('error', er)
  104. else
  105. this.onstatref(st)
  106. })
  107. }
  108. onstatref (st) {
  109. this.atime = this.atime && parseInt(st.atime.getTime()/1000, 10)
  110. this.mtime = this.mtime && parseInt(st.mtime.getTime()/1000, 10)
  111. if (!this.atime || !this.mtime)
  112. this.fstat()
  113. else
  114. this.futimes()
  115. }
  116. fstat () {
  117. const stat = this.fd ? 'fstat' : 'stat'
  118. const target = this.fd || this.path
  119. fs[stat](target, (er, st) => {
  120. if (er)
  121. this.emit('error', er)
  122. else
  123. this.onfstat(st)
  124. })
  125. }
  126. onfstat (st) {
  127. if (typeof this.atime !== 'number')
  128. this.atime = parseInt(st.atime.getTime()/1000, 10)
  129. if (typeof this.mtime !== 'number')
  130. this.mtime = parseInt(st.mtime.getTime()/1000, 10)
  131. this.futimes()
  132. }
  133. futimes () {
  134. const utimes = this.fd ? 'futimes' : 'utimes'
  135. const target = this.fd || this.path
  136. fs[utimes](target, ''+this.atime, ''+this.mtime, er => {
  137. if (er)
  138. this.emit('error', er)
  139. else
  140. this.emit('done')
  141. })
  142. }
  143. }
  144. class TouchSync extends Touch {
  145. open () {
  146. try {
  147. this.onopen(null, fs.openSync(this.path, this.oflags))
  148. } catch (er) {
  149. this.onopen(er)
  150. }
  151. }
  152. statref () {
  153. let threw = true
  154. try {
  155. this.onstatref(fs.statSync(this.ref))
  156. threw = false
  157. } finally {
  158. if (threw)
  159. this.close()
  160. }
  161. }
  162. fstat () {
  163. let threw = true
  164. const stat = this.fd ? 'fstatSync' : 'statSync'
  165. const target = this.fd || this.path
  166. try {
  167. this.onfstat(fs[stat](target))
  168. threw = false
  169. } finally {
  170. if (threw)
  171. this.close()
  172. }
  173. }
  174. futimes () {
  175. let threw = true
  176. const utimes = this.fd ? 'futimesSync' : 'utimesSync'
  177. const target = this.fd || this.path
  178. try {
  179. fs[utimes](target, this.atime, this.mtime)
  180. threw = false
  181. } finally {
  182. if (threw)
  183. this.close()
  184. }
  185. this.emit('done')
  186. }
  187. close () {
  188. if (typeof this.fd === 'number' && this.closeAfter)
  189. try { fs.closeSync(this.fd) } catch (er) {}
  190. }
  191. }