1 | var fs = require('fs')
|
2 |
|
3 | var wx = 'wx'
|
4 | if (process.version.match(/^v0.[456]/)) {
|
5 | var c = require('constants')
|
6 | wx = c.O_TRUNC | c.O_CREAT | c.O_WRONLY | c.O_EXCL
|
7 | }
|
8 |
|
9 | var locks = {}
|
10 |
|
11 | function hasOwnProperty (obj, prop) {
|
12 | return Object.prototype.hasOwnProperty.call(obj, prop)
|
13 | }
|
14 |
|
15 | process.on('exit', function () {
|
16 |
|
17 | Object.keys(locks).forEach(exports.unlockSync)
|
18 | })
|
19 |
|
20 |
|
21 |
|
22 | process.on('uncaughtException', function H (er) {
|
23 | var l = process.listeners('uncaughtException').filter(function (h) {
|
24 | return h !== H
|
25 | })
|
26 | if (!l.length) {
|
27 |
|
28 | try { Object.keys(locks).forEach(exports.unlockSync) } catch (e) {}
|
29 | process.removeListener('uncaughtException', H)
|
30 | throw er
|
31 | }
|
32 | })
|
33 |
|
34 | exports.unlock = function (path, cb) {
|
35 |
|
36 | if (hasOwnProperty(locks, path))
|
37 | fs.close(locks[path], unlink)
|
38 | else
|
39 | unlink()
|
40 |
|
41 | function unlink () {
|
42 | delete locks[path]
|
43 | fs.unlink(path, function (unlinkEr) { cb() })
|
44 | }
|
45 | }
|
46 |
|
47 | exports.unlockSync = function (path) {
|
48 |
|
49 | try { fs.closeSync(locks[path]) } catch (er) {}
|
50 | try { fs.unlinkSync(path) } catch (er) {}
|
51 | delete locks[path]
|
52 | }
|
53 |
|
54 |
|
55 |
|
56 |
|
57 | exports.check = function (path, opts, cb) {
|
58 | if (typeof opts === 'function') cb = opts, opts = {}
|
59 | fs.open(path, 'r', function (er, fd) {
|
60 | if (er) {
|
61 | if (er.code !== 'ENOENT') return cb(er)
|
62 | return cb(null, false)
|
63 | }
|
64 |
|
65 | if (!opts.stale) {
|
66 | return fs.close(fd, function (er) {
|
67 | return cb(er, true)
|
68 | })
|
69 | }
|
70 |
|
71 | fs.fstat(fd, function (er, st) {
|
72 | if (er) return fs.close(fd, function (er2) {
|
73 | return cb(er)
|
74 | })
|
75 |
|
76 | fs.close(fd, function (er) {
|
77 | var age = Date.now() - st.ctime.getTime()
|
78 | return cb(er, age <= opts.stale)
|
79 | })
|
80 | })
|
81 | })
|
82 | }
|
83 |
|
84 | exports.checkSync = function (path, opts) {
|
85 | opts = opts || {}
|
86 | if (opts.wait) {
|
87 | throw new Error('opts.wait not supported sync for obvious reasons')
|
88 | }
|
89 |
|
90 | try {
|
91 | var fd = fs.openSync(path, 'r')
|
92 | } catch (er) {
|
93 | if (er.code !== 'ENOENT') throw er
|
94 | return false
|
95 | }
|
96 |
|
97 | if (!opts.stale) {
|
98 | fs.closeSync(fd)
|
99 | return true
|
100 | }
|
101 |
|
102 |
|
103 | if (opts.stale) {
|
104 | try {
|
105 | var st = fs.fstatSync(fd)
|
106 | } finally {
|
107 | fs.closeSync(fd)
|
108 | }
|
109 | var age = Date.now() - st.ctime.getTime()
|
110 | return (age <= opts.stale)
|
111 | }
|
112 | }
|
113 |
|
114 |
|
115 |
|
116 | exports.lock = function (path, opts, cb) {
|
117 | if (typeof opts === 'function') cb = opts, opts = {}
|
118 |
|
119 | if (typeof opts.retries === 'number' && opts.retries > 0) {
|
120 | cb = (function (orig) { return function (er, fd) {
|
121 | if (!er) return orig(er, fd)
|
122 | var newRT = opts.retries - 1
|
123 | opts_ = Object.create(opts, { retries: { value: newRT }})
|
124 | if (opts.retryWait) setTimeout(function() {
|
125 | exports.lock(path, opts_, orig)
|
126 | }, opts.retryWait)
|
127 | else exports.lock(path, opts_, orig)
|
128 | }})(cb)
|
129 | }
|
130 |
|
131 |
|
132 |
|
133 | fs.open(path, wx, function (er, fd) {
|
134 | if (!er) {
|
135 | locks[path] = fd
|
136 | return cb(null, fd)
|
137 | }
|
138 |
|
139 |
|
140 |
|
141 | if (er.code !== 'EEXIST') return cb(er)
|
142 |
|
143 |
|
144 | if (opts.stale) fs.stat(path, function (statEr, st) {
|
145 | if (statEr) {
|
146 | if (statEr.code === 'ENOENT') {
|
147 |
|
148 | var opts_ = Object.create(opts, { stale: { value: false }})
|
149 | exports.lock(path, opts_, cb)
|
150 | return
|
151 | }
|
152 | return cb(statEr)
|
153 | }
|
154 |
|
155 | var age = Date.now() - st.ctime.getTime()
|
156 | if (age > opts.stale) {
|
157 | exports.unlock(path, function (er) {
|
158 | if (er) return cb(er)
|
159 | var opts_ = Object.create(opts, { stale: { value: false }})
|
160 | exports.lock(path, opts_, cb)
|
161 | })
|
162 | } else notStale(er, path, opts, cb)
|
163 | })
|
164 | else notStale(er, path, opts, cb)
|
165 | })
|
166 | }
|
167 |
|
168 | function notStale (er, path, opts, cb) {
|
169 |
|
170 | if (typeof opts.wait !== 'number' || opts.wait <= 0)
|
171 | return cb(er)
|
172 |
|
173 |
|
174 |
|
175 | var start = Date.now()
|
176 | var end = start + opts.wait
|
177 |
|
178 | function retry () {
|
179 | var now = Date.now()
|
180 | var newWait = end - now
|
181 | var newOpts = Object.create(opts, { wait: { value: newWait }})
|
182 | exports.lock(path, newOpts, cb)
|
183 | }
|
184 |
|
185 | var timer = setTimeout(retry, 10)
|
186 | }
|
187 |
|
188 | exports.lockSync = function (path, opts) {
|
189 | opts = opts || {}
|
190 | if (opts.wait || opts.retryWait) {
|
191 | throw new Error('opts.wait not supported sync for obvious reasons')
|
192 | }
|
193 |
|
194 | try {
|
195 | var fd = fs.openSync(path, wx)
|
196 | locks[path] = fd
|
197 | return fd
|
198 | } catch (er) {
|
199 | if (er.code !== 'EEXIST') return retryThrow(path, opts, er)
|
200 |
|
201 | if (opts.stale) {
|
202 | var st = fs.statSync(path)
|
203 | var ct = st.ctime.getTime()
|
204 | if (!(ct % 1000) && (opts.stale % 1000)) {
|
205 |
|
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 | opts.stale = 1000 * Math.ceil(opts.stale / 1000)
|
212 | }
|
213 | var age = Date.now() - ct
|
214 | if (age > opts.stale) {
|
215 | exports.unlockSync(path)
|
216 | return exports.lockSync(path, opts)
|
217 | }
|
218 | }
|
219 |
|
220 |
|
221 | return retryThrow(path, opts, er)
|
222 | }
|
223 | }
|
224 |
|
225 | function retryThrow (path, opts, er) {
|
226 | if (typeof opts.retries === 'number' && opts.retries > 0) {
|
227 | var newRT = opts.retries - 1
|
228 | var opts_ = Object.create(opts, { retries: { value: newRT }})
|
229 | return exports.lockSync(path, opts_)
|
230 | }
|
231 | throw er
|
232 | }
|
233 |
|