|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 "use strict"; |
|
5 |
|
6 module.metadata = { |
|
7 "stability": "experimental" |
|
8 }; |
|
9 |
|
10 const { Cc, Ci, CC } = require("chrome"); |
|
11 |
|
12 const { setTimeout } = require("../timers"); |
|
13 const { Stream, InputStream, OutputStream } = require("./stream"); |
|
14 const { emit, on } = require("../event/core"); |
|
15 const { Buffer } = require("./buffer"); |
|
16 const { ns } = require("../core/namespace"); |
|
17 const { Class } = require("../core/heritage"); |
|
18 |
|
19 |
|
20 const nsILocalFile = CC("@mozilla.org/file/local;1", "nsILocalFile", |
|
21 "initWithPath"); |
|
22 const FileOutputStream = CC("@mozilla.org/network/file-output-stream;1", |
|
23 "nsIFileOutputStream", "init"); |
|
24 const FileInputStream = CC("@mozilla.org/network/file-input-stream;1", |
|
25 "nsIFileInputStream", "init"); |
|
26 const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", |
|
27 "nsIBinaryInputStream", "setInputStream"); |
|
28 const BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1", |
|
29 "nsIBinaryOutputStream", "setOutputStream"); |
|
30 const StreamPump = CC("@mozilla.org/network/input-stream-pump;1", |
|
31 "nsIInputStreamPump", "init"); |
|
32 |
|
33 const { createOutputTransport, createInputTransport } = |
|
34 Cc["@mozilla.org/network/stream-transport-service;1"]. |
|
35 getService(Ci.nsIStreamTransportService); |
|
36 |
|
37 const { OPEN_UNBUFFERED } = Ci.nsITransport; |
|
38 |
|
39 |
|
40 const { REOPEN_ON_REWIND, DEFER_OPEN } = Ci.nsIFileInputStream; |
|
41 const { DIRECTORY_TYPE, NORMAL_FILE_TYPE } = Ci.nsIFile; |
|
42 const { NS_SEEK_SET, NS_SEEK_CUR, NS_SEEK_END } = Ci.nsISeekableStream; |
|
43 |
|
44 const FILE_PERMISSION = parseInt("0666", 8); |
|
45 const PR_UINT32_MAX = 0xfffffff; |
|
46 // Values taken from: |
|
47 // http://mxr.mozilla.org/mozilla-central/source/nsprpub/pr/include/prio.h#615 |
|
48 const PR_RDONLY = 0x01; |
|
49 const PR_WRONLY = 0x02; |
|
50 const PR_RDWR = 0x04; |
|
51 const PR_CREATE_FILE = 0x08; |
|
52 const PR_APPEND = 0x10; |
|
53 const PR_TRUNCATE = 0x20; |
|
54 const PR_SYNC = 0x40; |
|
55 const PR_EXCL = 0x80; |
|
56 |
|
57 const FLAGS = { |
|
58 "r": PR_RDONLY, |
|
59 "r+": PR_RDWR, |
|
60 "w": PR_CREATE_FILE | PR_TRUNCATE | PR_WRONLY, |
|
61 "w+": PR_CREATE_FILE | PR_TRUNCATE | PR_RDWR, |
|
62 "a": PR_APPEND | PR_CREATE_FILE | PR_WRONLY, |
|
63 "a+": PR_APPEND | PR_CREATE_FILE | PR_RDWR |
|
64 }; |
|
65 |
|
66 function accessor() { |
|
67 let map = new WeakMap(); |
|
68 return function(fd, value) { |
|
69 if (value === null) map.delete(fd); |
|
70 if (value !== undefined) map.set(fd, value); |
|
71 return map.get(fd); |
|
72 } |
|
73 } |
|
74 |
|
75 let nsIFile = accessor(); |
|
76 let nsIFileInputStream = accessor(); |
|
77 let nsIFileOutputStream = accessor(); |
|
78 let nsIBinaryInputStream = accessor(); |
|
79 let nsIBinaryOutputStream = accessor(); |
|
80 |
|
81 // Just a contstant object used to signal that all of the file |
|
82 // needs to be read. |
|
83 const ALL = new String("Read all of the file"); |
|
84 |
|
85 function isWritable(mode) !!(mode & PR_WRONLY || mode & PR_RDWR) |
|
86 function isReadable(mode) !!(mode & PR_RDONLY || mode & PR_RDWR) |
|
87 |
|
88 function isString(value) typeof(value) === "string" |
|
89 function isFunction(value) typeof(value) === "function" |
|
90 |
|
91 function toArray(enumerator) { |
|
92 let value = []; |
|
93 while(enumerator.hasMoreElements()) |
|
94 value.push(enumerator.getNext()) |
|
95 return value |
|
96 } |
|
97 |
|
98 function getFileName(file) file.QueryInterface(Ci.nsIFile).leafName |
|
99 |
|
100 |
|
101 function remove(path, recursive) { |
|
102 let fd = new nsILocalFile(path) |
|
103 if (fd.exists()) { |
|
104 fd.remove(recursive || false); |
|
105 } |
|
106 else { |
|
107 throw FSError("remove", "ENOENT", 34, path); |
|
108 } |
|
109 } |
|
110 |
|
111 /** |
|
112 * Utility function to convert either an octal number or string |
|
113 * into an octal number |
|
114 * 0777 => 0777 |
|
115 * "0644" => 0644 |
|
116 */ |
|
117 function Mode(mode, fallback) { |
|
118 return isString(mode) ? parseInt(mode, 8) : mode || fallback; |
|
119 } |
|
120 function Flags(flag) { |
|
121 return !isString(flag) ? flag : |
|
122 FLAGS[flag] || Error("Unknown file open flag: " + flag); |
|
123 } |
|
124 |
|
125 |
|
126 function FSError(op, code, errno, path, file, line) { |
|
127 let error = Error(code + ", " + op + " " + path, file, line); |
|
128 error.code = code; |
|
129 error.path = path; |
|
130 error.errno = errno; |
|
131 return error; |
|
132 } |
|
133 |
|
134 const ReadStream = Class({ |
|
135 extends: InputStream, |
|
136 initialize: function initialize(path, options) { |
|
137 this.position = -1; |
|
138 this.length = -1; |
|
139 this.flags = "r"; |
|
140 this.mode = FILE_PERMISSION; |
|
141 this.bufferSize = 64 * 1024; |
|
142 |
|
143 options = options || {}; |
|
144 |
|
145 if ("flags" in options && options.flags) |
|
146 this.flags = options.flags; |
|
147 if ("bufferSize" in options && options.bufferSize) |
|
148 this.bufferSize = options.bufferSize; |
|
149 if ("length" in options && options.length) |
|
150 this.length = options.length; |
|
151 if ("position" in options && options.position !== undefined) |
|
152 this.position = options.position; |
|
153 |
|
154 let { flags, mode, position, length } = this; |
|
155 let fd = isString(path) ? openSync(path, flags, mode) : path; |
|
156 this.fd = fd; |
|
157 |
|
158 let input = nsIFileInputStream(fd); |
|
159 // Setting a stream position, unless it"s `-1` which means current position. |
|
160 if (position >= 0) |
|
161 input.QueryInterface(Ci.nsISeekableStream).seek(NS_SEEK_SET, position); |
|
162 // We use `nsIStreamTransportService` service to transform blocking |
|
163 // file input stream into a fully asynchronous stream that can be written |
|
164 // without blocking the main thread. |
|
165 let transport = createInputTransport(input, position, length, false); |
|
166 // Open an input stream on a transport. We don"t pass flags to guarantee |
|
167 // non-blocking stream semantics. Also we use defaults for segment size & |
|
168 // count. |
|
169 InputStream.prototype.initialize.call(this, { |
|
170 asyncInputStream: transport.openInputStream(null, 0, 0) |
|
171 }); |
|
172 |
|
173 // Close file descriptor on end and destroy the stream. |
|
174 on(this, "end", _ => { |
|
175 this.destroy(); |
|
176 emit(this, "close"); |
|
177 }); |
|
178 |
|
179 this.read(); |
|
180 }, |
|
181 destroy: function() { |
|
182 closeSync(this.fd); |
|
183 InputStream.prototype.destroy.call(this); |
|
184 } |
|
185 }); |
|
186 exports.ReadStream = ReadStream; |
|
187 exports.createReadStream = function createReadStream(path, options) { |
|
188 return new ReadStream(path, options); |
|
189 }; |
|
190 |
|
191 const WriteStream = Class({ |
|
192 extends: OutputStream, |
|
193 initialize: function initialize(path, options) { |
|
194 this.drainable = true; |
|
195 this.flags = "w"; |
|
196 this.position = -1; |
|
197 this.mode = FILE_PERMISSION; |
|
198 |
|
199 options = options || {}; |
|
200 |
|
201 if ("flags" in options && options.flags) |
|
202 this.flags = options.flags; |
|
203 if ("mode" in options && options.mode) |
|
204 this.mode = options.mode; |
|
205 if ("position" in options && options.position !== undefined) |
|
206 this.position = options.position; |
|
207 |
|
208 let { position, flags, mode } = this; |
|
209 // If pass was passed we create a file descriptor out of it. Otherwise |
|
210 // we just use given file descriptor. |
|
211 let fd = isString(path) ? openSync(path, flags, mode) : path; |
|
212 this.fd = fd; |
|
213 |
|
214 let output = nsIFileOutputStream(fd); |
|
215 // Setting a stream position, unless it"s `-1` which means current position. |
|
216 if (position >= 0) |
|
217 output.QueryInterface(Ci.nsISeekableStream).seek(NS_SEEK_SET, position); |
|
218 // We use `nsIStreamTransportService` service to transform blocking |
|
219 // file output stream into a fully asynchronous stream that can be written |
|
220 // without blocking the main thread. |
|
221 let transport = createOutputTransport(output, position, -1, false); |
|
222 // Open an output stream on a transport. We don"t pass flags to guarantee |
|
223 // non-blocking stream semantics. Also we use defaults for segment size & |
|
224 // count. |
|
225 OutputStream.prototype.initialize.call(this, { |
|
226 asyncOutputStream: transport.openOutputStream(OPEN_UNBUFFERED, 0, 0), |
|
227 output: output |
|
228 }); |
|
229 |
|
230 // For write streams "finish" basically means close. |
|
231 on(this, "finish", _ => { |
|
232 this.destroy(); |
|
233 emit(this, "close"); |
|
234 }); |
|
235 }, |
|
236 destroy: function() { |
|
237 OutputStream.prototype.destroy.call(this); |
|
238 closeSync(this.fd); |
|
239 } |
|
240 }); |
|
241 exports.WriteStream = WriteStream; |
|
242 exports.createWriteStream = function createWriteStream(path, options) { |
|
243 return new WriteStream(path, options); |
|
244 }; |
|
245 |
|
246 const Stats = Class({ |
|
247 initialize: function initialize(path) { |
|
248 let file = new nsILocalFile(path); |
|
249 if (!file.exists()) throw FSError("stat", "ENOENT", 34, path); |
|
250 nsIFile(this, file); |
|
251 }, |
|
252 isDirectory: function() nsIFile(this).isDirectory(), |
|
253 isFile: function() nsIFile(this).isFile(), |
|
254 isSymbolicLink: function() nsIFile(this).isSymlink(), |
|
255 get mode() nsIFile(this).permissions, |
|
256 get size() nsIFile(this).fileSize, |
|
257 get mtime() nsIFile(this).lastModifiedTime, |
|
258 isBlockDevice: function() nsIFile(this).isSpecial(), |
|
259 isCharacterDevice: function() nsIFile(this).isSpecial(), |
|
260 isFIFO: function() nsIFile(this).isSpecial(), |
|
261 isSocket: function() nsIFile(this).isSpecial(), |
|
262 // non standard |
|
263 get exists() nsIFile(this).exists(), |
|
264 get hidden() nsIFile(this).isHidden(), |
|
265 get writable() nsIFile(this).isWritable(), |
|
266 get readable() nsIFile(this).isReadable() |
|
267 }); |
|
268 exports.Stats = Stats; |
|
269 |
|
270 const LStats = Class({ |
|
271 extends: Stats, |
|
272 get size() this.isSymbolicLink() ? nsIFile(this).fileSizeOfLink : |
|
273 nsIFile(this).fileSize, |
|
274 get mtime() this.isSymbolicLink() ? nsIFile(this).lastModifiedTimeOfLink : |
|
275 nsIFile(this).lastModifiedTime, |
|
276 // non standard |
|
277 get permissions() this.isSymbolicLink() ? nsIFile(this).permissionsOfLink : |
|
278 nsIFile(this).permissions |
|
279 }); |
|
280 |
|
281 const FStat = Class({ |
|
282 extends: Stats, |
|
283 initialize: function initialize(fd) { |
|
284 nsIFile(this, nsIFile(fd)); |
|
285 } |
|
286 }); |
|
287 |
|
288 function noop() {} |
|
289 function Async(wrapped) { |
|
290 return function (path, callback) { |
|
291 let args = Array.slice(arguments); |
|
292 callback = args.pop(); |
|
293 // If node is not given a callback argument |
|
294 // it just does not calls it. |
|
295 if (typeof(callback) !== "function") { |
|
296 args.push(callback); |
|
297 callback = noop; |
|
298 } |
|
299 setTimeout(function() { |
|
300 try { |
|
301 var result = wrapped.apply(this, args); |
|
302 if (result === undefined) callback(null); |
|
303 else callback(null, result); |
|
304 } catch (error) { |
|
305 callback(error); |
|
306 } |
|
307 }, 0); |
|
308 } |
|
309 } |
|
310 |
|
311 |
|
312 /** |
|
313 * Synchronous rename(2) |
|
314 */ |
|
315 function renameSync(oldPath, newPath) { |
|
316 let source = new nsILocalFile(oldPath); |
|
317 let target = new nsILocalFile(newPath); |
|
318 if (!source.exists()) throw FSError("rename", "ENOENT", 34, oldPath); |
|
319 return source.moveTo(target.parent, target.leafName); |
|
320 }; |
|
321 exports.renameSync = renameSync; |
|
322 |
|
323 /** |
|
324 * Asynchronous rename(2). No arguments other than a possible exception are |
|
325 * given to the completion callback. |
|
326 */ |
|
327 let rename = Async(renameSync); |
|
328 exports.rename = rename; |
|
329 |
|
330 /** |
|
331 * Test whether or not the given path exists by checking with the file system. |
|
332 */ |
|
333 function existsSync(path) { |
|
334 return new nsILocalFile(path).exists(); |
|
335 } |
|
336 exports.existsSync = existsSync; |
|
337 |
|
338 let exists = Async(existsSync); |
|
339 exports.exists = exists; |
|
340 |
|
341 /** |
|
342 * Synchronous ftruncate(2). |
|
343 */ |
|
344 function truncateSync(path, length) { |
|
345 let fd = openSync(path, "w"); |
|
346 ftruncateSync(fd, length); |
|
347 closeSync(fd); |
|
348 } |
|
349 exports.truncateSync = truncateSync; |
|
350 |
|
351 /** |
|
352 * Asynchronous ftruncate(2). No arguments other than a possible exception are |
|
353 * given to the completion callback. |
|
354 */ |
|
355 function truncate(path, length, callback) { |
|
356 open(path, "w", function(error, fd) { |
|
357 if (error) return callback(error); |
|
358 ftruncate(fd, length, function(error) { |
|
359 if (error) { |
|
360 closeSync(fd); |
|
361 callback(error); |
|
362 } |
|
363 else { |
|
364 close(fd, callback); |
|
365 } |
|
366 }); |
|
367 }); |
|
368 } |
|
369 exports.truncate = truncate; |
|
370 |
|
371 function ftruncate(fd, length, callback) { |
|
372 write(fd, new Buffer(length), 0, length, 0, function(error) { |
|
373 callback(error); |
|
374 }); |
|
375 } |
|
376 exports.ftruncate = ftruncate; |
|
377 |
|
378 function ftruncateSync(fd, length = 0) { |
|
379 writeSync(fd, new Buffer(length), 0, length, 0); |
|
380 } |
|
381 exports.ftruncateSync = ftruncateSync; |
|
382 |
|
383 function chownSync(path, uid, gid) { |
|
384 throw Error("Not implemented yet!!"); |
|
385 } |
|
386 exports.chownSync = chownSync; |
|
387 |
|
388 let chown = Async(chownSync); |
|
389 exports.chown = chown; |
|
390 |
|
391 function lchownSync(path, uid, gid) { |
|
392 throw Error("Not implemented yet!!"); |
|
393 } |
|
394 exports.lchownSync = chownSync; |
|
395 |
|
396 let lchown = Async(lchown); |
|
397 exports.lchown = lchown; |
|
398 |
|
399 /** |
|
400 * Synchronous chmod(2). |
|
401 */ |
|
402 function chmodSync (path, mode) { |
|
403 let file; |
|
404 try { |
|
405 file = new nsILocalFile(path); |
|
406 } catch(e) { |
|
407 throw FSError("chmod", "ENOENT", 34, path); |
|
408 } |
|
409 |
|
410 file.permissions = Mode(mode); |
|
411 } |
|
412 exports.chmodSync = chmodSync; |
|
413 /** |
|
414 * Asynchronous chmod(2). No arguments other than a possible exception are |
|
415 * given to the completion callback. |
|
416 */ |
|
417 let chmod = Async(chmodSync); |
|
418 exports.chmod = chmod; |
|
419 |
|
420 /** |
|
421 * Synchronous chmod(2). |
|
422 */ |
|
423 function fchmodSync(fd, mode) { |
|
424 throw Error("Not implemented yet!!"); |
|
425 }; |
|
426 exports.fchmodSync = fchmodSync; |
|
427 /** |
|
428 * Asynchronous chmod(2). No arguments other than a possible exception are |
|
429 * given to the completion callback. |
|
430 */ |
|
431 let fchmod = Async(fchmodSync); |
|
432 exports.fchmod = fchmod; |
|
433 |
|
434 |
|
435 /** |
|
436 * Synchronous stat(2). Returns an instance of `fs.Stats` |
|
437 */ |
|
438 function statSync(path) { |
|
439 return new Stats(path); |
|
440 }; |
|
441 exports.statSync = statSync; |
|
442 |
|
443 /** |
|
444 * Asynchronous stat(2). The callback gets two arguments (err, stats) where |
|
445 * stats is a `fs.Stats` object. It looks like this: |
|
446 */ |
|
447 let stat = Async(statSync); |
|
448 exports.stat = stat; |
|
449 |
|
450 /** |
|
451 * Synchronous lstat(2). Returns an instance of `fs.Stats`. |
|
452 */ |
|
453 function lstatSync(path) { |
|
454 return new LStats(path); |
|
455 }; |
|
456 exports.lstatSync = lstatSync; |
|
457 |
|
458 /** |
|
459 * Asynchronous lstat(2). The callback gets two arguments (err, stats) where |
|
460 * stats is a fs.Stats object. lstat() is identical to stat(), except that if |
|
461 * path is a symbolic link, then the link itself is stat-ed, not the file that |
|
462 * it refers to. |
|
463 */ |
|
464 let lstat = Async(lstatSync); |
|
465 exports.lstat = lstat; |
|
466 |
|
467 /** |
|
468 * Synchronous fstat(2). Returns an instance of `fs.Stats`. |
|
469 */ |
|
470 function fstatSync(fd) { |
|
471 return new FStat(fd); |
|
472 }; |
|
473 exports.fstatSync = fstatSync; |
|
474 |
|
475 /** |
|
476 * Asynchronous fstat(2). The callback gets two arguments (err, stats) where |
|
477 * stats is a fs.Stats object. |
|
478 */ |
|
479 let fstat = Async(fstatSync); |
|
480 exports.fstat = fstat; |
|
481 |
|
482 /** |
|
483 * Synchronous link(2). |
|
484 */ |
|
485 function linkSync(source, target) { |
|
486 throw Error("Not implemented yet!!"); |
|
487 }; |
|
488 exports.linkSync = linkSync; |
|
489 |
|
490 /** |
|
491 * Asynchronous link(2). No arguments other than a possible exception are given |
|
492 * to the completion callback. |
|
493 */ |
|
494 let link = Async(linkSync); |
|
495 exports.link = link; |
|
496 |
|
497 /** |
|
498 * Synchronous symlink(2). |
|
499 */ |
|
500 function symlinkSync(source, target) { |
|
501 throw Error("Not implemented yet!!"); |
|
502 }; |
|
503 exports.symlinkSync = symlinkSync; |
|
504 |
|
505 /** |
|
506 * Asynchronous symlink(2). No arguments other than a possible exception are |
|
507 * given to the completion callback. |
|
508 */ |
|
509 let symlink = Async(symlinkSync); |
|
510 exports.symlink = symlink; |
|
511 |
|
512 /** |
|
513 * Synchronous readlink(2). Returns the resolved path. |
|
514 */ |
|
515 function readlinkSync(path) { |
|
516 return new nsILocalFile(path).target; |
|
517 }; |
|
518 exports.readlinkSync = readlinkSync; |
|
519 |
|
520 /** |
|
521 * Asynchronous readlink(2). The callback gets two arguments |
|
522 * `(error, resolvedPath)`. |
|
523 */ |
|
524 let readlink = Async(readlinkSync); |
|
525 exports.readlink = readlink; |
|
526 |
|
527 /** |
|
528 * Synchronous realpath(2). Returns the resolved path. |
|
529 */ |
|
530 function realpathSync(path) { |
|
531 return new nsILocalFile(path).path; |
|
532 }; |
|
533 exports.realpathSync = realpathSync; |
|
534 |
|
535 /** |
|
536 * Asynchronous realpath(2). The callback gets two arguments |
|
537 * `(err, resolvedPath)`. |
|
538 */ |
|
539 let realpath = Async(realpathSync); |
|
540 exports.realpath = realpath; |
|
541 |
|
542 /** |
|
543 * Synchronous unlink(2). |
|
544 */ |
|
545 let unlinkSync = remove; |
|
546 exports.unlinkSync = unlinkSync; |
|
547 |
|
548 /** |
|
549 * Asynchronous unlink(2). No arguments other than a possible exception are |
|
550 * given to the completion callback. |
|
551 */ |
|
552 let unlink = Async(remove); |
|
553 exports.unlink = unlink; |
|
554 |
|
555 /** |
|
556 * Synchronous rmdir(2). |
|
557 */ |
|
558 let rmdirSync = remove; |
|
559 exports.rmdirSync = rmdirSync; |
|
560 |
|
561 /** |
|
562 * Asynchronous rmdir(2). No arguments other than a possible exception are |
|
563 * given to the completion callback. |
|
564 */ |
|
565 let rmdir = Async(rmdirSync); |
|
566 exports.rmdir = rmdir; |
|
567 |
|
568 /** |
|
569 * Synchronous mkdir(2). |
|
570 */ |
|
571 function mkdirSync(path, mode) { |
|
572 try { |
|
573 return nsILocalFile(path).create(DIRECTORY_TYPE, Mode(mode)); |
|
574 } catch (error) { |
|
575 // Adjust exception thorw to match ones thrown by node. |
|
576 if (error.name === "NS_ERROR_FILE_ALREADY_EXISTS") { |
|
577 let { fileName, lineNumber } = error; |
|
578 error = FSError("mkdir", "EEXIST", 47, path, fileName, lineNumber); |
|
579 } |
|
580 throw error; |
|
581 } |
|
582 }; |
|
583 exports.mkdirSync = mkdirSync; |
|
584 |
|
585 /** |
|
586 * Asynchronous mkdir(2). No arguments other than a possible exception are |
|
587 * given to the completion callback. |
|
588 */ |
|
589 let mkdir = Async(mkdirSync); |
|
590 exports.mkdir = mkdir; |
|
591 |
|
592 /** |
|
593 * Synchronous readdir(3). Returns an array of filenames excluding `"."` and |
|
594 * `".."`. |
|
595 */ |
|
596 function readdirSync(path) { |
|
597 try { |
|
598 return toArray(new nsILocalFile(path).directoryEntries).map(getFileName); |
|
599 } |
|
600 catch (error) { |
|
601 // Adjust exception thorw to match ones thrown by node. |
|
602 if (error.name === "NS_ERROR_FILE_TARGET_DOES_NOT_EXIST" || |
|
603 error.name === "NS_ERROR_FILE_NOT_FOUND") |
|
604 { |
|
605 let { fileName, lineNumber } = error; |
|
606 error = FSError("readdir", "ENOENT", 34, path, fileName, lineNumber); |
|
607 } |
|
608 throw error; |
|
609 } |
|
610 }; |
|
611 exports.readdirSync = readdirSync; |
|
612 |
|
613 /** |
|
614 * Asynchronous readdir(3). Reads the contents of a directory. The callback |
|
615 * gets two arguments `(error, files)` where `files` is an array of the names |
|
616 * of the files in the directory excluding `"."` and `".."`. |
|
617 */ |
|
618 let readdir = Async(readdirSync); |
|
619 exports.readdir = readdir; |
|
620 |
|
621 /** |
|
622 * Synchronous close(2). |
|
623 */ |
|
624 function closeSync(fd) { |
|
625 let input = nsIFileInputStream(fd); |
|
626 let output = nsIFileOutputStream(fd); |
|
627 |
|
628 // Closing input stream and removing reference. |
|
629 if (input) input.close(); |
|
630 // Closing output stream and removing reference. |
|
631 if (output) output.close(); |
|
632 |
|
633 nsIFile(fd, null); |
|
634 nsIFileInputStream(fd, null); |
|
635 nsIFileOutputStream(fd, null); |
|
636 nsIBinaryInputStream(fd, null); |
|
637 nsIBinaryOutputStream(fd, null); |
|
638 }; |
|
639 exports.closeSync = closeSync; |
|
640 /** |
|
641 * Asynchronous close(2). No arguments other than a possible exception are |
|
642 * given to the completion callback. |
|
643 */ |
|
644 let close = Async(closeSync); |
|
645 exports.close = close; |
|
646 |
|
647 /** |
|
648 * Synchronous open(2). |
|
649 */ |
|
650 function openSync(path, flags, mode) { |
|
651 let [ fd, flags, mode, file ] = |
|
652 [ { path: path }, Flags(flags), Mode(mode), nsILocalFile(path) ]; |
|
653 |
|
654 nsIFile(fd, file); |
|
655 |
|
656 // If trying to open file for just read that does not exists |
|
657 // need to throw exception as node does. |
|
658 if (!file.exists() && !isWritable(flags)) |
|
659 throw FSError("open", "ENOENT", 34, path); |
|
660 |
|
661 // If we want to open file in read mode we initialize input stream. |
|
662 if (isReadable(flags)) { |
|
663 let input = FileInputStream(file, flags, mode, DEFER_OPEN); |
|
664 nsIFileInputStream(fd, input); |
|
665 } |
|
666 |
|
667 // If we want to open file in write mode we initialize output stream for it. |
|
668 if (isWritable(flags)) { |
|
669 let output = FileOutputStream(file, flags, mode, DEFER_OPEN); |
|
670 nsIFileOutputStream(fd, output); |
|
671 } |
|
672 |
|
673 return fd; |
|
674 } |
|
675 exports.openSync = openSync; |
|
676 /** |
|
677 * Asynchronous file open. See open(2). Flags can be |
|
678 * `"r", "r+", "w", "w+", "a"`, or `"a+"`. mode defaults to `0666`. |
|
679 * The callback gets two arguments `(error, fd). |
|
680 */ |
|
681 let open = Async(openSync); |
|
682 exports.open = open; |
|
683 |
|
684 /** |
|
685 * Synchronous version of buffer-based fs.write(). Returns the number of bytes |
|
686 * written. |
|
687 */ |
|
688 function writeSync(fd, buffer, offset, length, position) { |
|
689 if (length + offset > buffer.length) { |
|
690 throw Error("Length is extends beyond buffer"); |
|
691 } |
|
692 else if (length + offset !== buffer.length) { |
|
693 buffer = buffer.slice(offset, offset + length); |
|
694 } |
|
695 let writeStream = new WriteStream(fd, { position: position, |
|
696 length: length }); |
|
697 |
|
698 let output = BinaryOutputStream(nsIFileOutputStream(fd)); |
|
699 nsIBinaryOutputStream(fd, output); |
|
700 // We write content as a byte array as this will avoid any transcoding |
|
701 // if content was a buffer. |
|
702 output.writeByteArray(buffer.valueOf(), buffer.length); |
|
703 output.flush(); |
|
704 }; |
|
705 exports.writeSync = writeSync; |
|
706 |
|
707 /** |
|
708 * Write buffer to the file specified by fd. |
|
709 * |
|
710 * `offset` and `length` determine the part of the buffer to be written. |
|
711 * |
|
712 * `position` refers to the offset from the beginning of the file where this |
|
713 * data should be written. If `position` is `null`, the data will be written |
|
714 * at the current position. See pwrite(2). |
|
715 * |
|
716 * The callback will be given three arguments `(error, written, buffer)` where |
|
717 * written specifies how many bytes were written into buffer. |
|
718 * |
|
719 * Note that it is unsafe to use `fs.write` multiple times on the same file |
|
720 * without waiting for the callback. |
|
721 */ |
|
722 function write(fd, buffer, offset, length, position, callback) { |
|
723 if (!Buffer.isBuffer(buffer)) { |
|
724 // (fd, data, position, encoding, callback) |
|
725 let encoding = null; |
|
726 [ position, encoding, callback ] = Array.slice(arguments, 1); |
|
727 buffer = new Buffer(String(buffer), encoding); |
|
728 offset = 0; |
|
729 } else if (length + offset > buffer.length) { |
|
730 throw Error("Length is extends beyond buffer"); |
|
731 } else if (length + offset !== buffer.length) { |
|
732 buffer = buffer.slice(offset, offset + length); |
|
733 } |
|
734 |
|
735 let writeStream = new WriteStream(fd, { position: position, |
|
736 length: length }); |
|
737 writeStream.on("error", callback); |
|
738 writeStream.write(buffer, function onEnd() { |
|
739 writeStream.destroy(); |
|
740 if (callback) |
|
741 callback(null, buffer.length, buffer); |
|
742 }); |
|
743 }; |
|
744 exports.write = write; |
|
745 |
|
746 /** |
|
747 * Synchronous version of string-based fs.read. Returns the number of |
|
748 * bytes read. |
|
749 */ |
|
750 function readSync(fd, buffer, offset, length, position) { |
|
751 let input = nsIFileInputStream(fd); |
|
752 // Setting a stream position, unless it"s `-1` which means current position. |
|
753 if (position >= 0) |
|
754 input.QueryInterface(Ci.nsISeekableStream).seek(NS_SEEK_SET, position); |
|
755 // We use `nsIStreamTransportService` service to transform blocking |
|
756 // file input stream into a fully asynchronous stream that can be written |
|
757 // without blocking the main thread. |
|
758 let binaryInputStream = BinaryInputStream(input); |
|
759 let count = length === ALL ? binaryInputStream.available() : length; |
|
760 if (offset === 0) binaryInputStream.readArrayBuffer(count, buffer.buffer); |
|
761 else { |
|
762 let chunk = new Buffer(count); |
|
763 binaryInputStream.readArrayBuffer(count, chunk.buffer); |
|
764 chunk.copy(buffer, offset); |
|
765 } |
|
766 |
|
767 return buffer.slice(offset, offset + count); |
|
768 }; |
|
769 exports.readSync = readSync; |
|
770 |
|
771 /** |
|
772 * Read data from the file specified by `fd`. |
|
773 * |
|
774 * `buffer` is the buffer that the data will be written to. |
|
775 * `offset` is offset within the buffer where writing will start. |
|
776 * |
|
777 * `length` is an integer specifying the number of bytes to read. |
|
778 * |
|
779 * `position` is an integer specifying where to begin reading from in the file. |
|
780 * If `position` is `null`, data will be read from the current file position. |
|
781 * |
|
782 * The callback is given the three arguments, `(error, bytesRead, buffer)`. |
|
783 */ |
|
784 function read(fd, buffer, offset, length, position, callback) { |
|
785 let bytesRead = 0; |
|
786 let readStream = new ReadStream(fd, { position: position, length: length }); |
|
787 readStream.on("data", function onData(data) { |
|
788 data.copy(buffer, offset + bytesRead); |
|
789 bytesRead += data.length; |
|
790 }); |
|
791 readStream.on("end", function onEnd() { |
|
792 callback(null, bytesRead, buffer); |
|
793 readStream.destroy(); |
|
794 }); |
|
795 }; |
|
796 exports.read = read; |
|
797 |
|
798 /** |
|
799 * Asynchronously reads the entire contents of a file. |
|
800 * The callback is passed two arguments `(error, data)`, where data is the |
|
801 * contents of the file. |
|
802 */ |
|
803 function readFile(path, encoding, callback) { |
|
804 if (isFunction(encoding)) { |
|
805 callback = encoding |
|
806 encoding = null |
|
807 } |
|
808 |
|
809 let buffer = null; |
|
810 try { |
|
811 let readStream = new ReadStream(path); |
|
812 readStream.on("data", function(data) { |
|
813 if (!buffer) buffer = data; |
|
814 else buffer = Buffer.concat([buffer, data], 2); |
|
815 }); |
|
816 readStream.on("error", function onError(error) { |
|
817 callback(error); |
|
818 }); |
|
819 readStream.on("end", function onEnd() { |
|
820 // Note: Need to destroy before invoking a callback |
|
821 // so that file descriptor is released. |
|
822 readStream.destroy(); |
|
823 callback(null, buffer); |
|
824 }); |
|
825 } catch (error) { |
|
826 setTimeout(callback, 0, error); |
|
827 } |
|
828 }; |
|
829 exports.readFile = readFile; |
|
830 |
|
831 /** |
|
832 * Synchronous version of `fs.readFile`. Returns the contents of the path. |
|
833 * If encoding is specified then this function returns a string. |
|
834 * Otherwise it returns a buffer. |
|
835 */ |
|
836 function readFileSync(path, encoding) { |
|
837 let fd = openSync(path, "r"); |
|
838 let size = fstatSync(fd).size; |
|
839 let buffer = new Buffer(size); |
|
840 try { |
|
841 readSync(fd, buffer, 0, ALL, 0); |
|
842 } |
|
843 finally { |
|
844 closeSync(fd); |
|
845 } |
|
846 return buffer; |
|
847 }; |
|
848 exports.readFileSync = readFileSync; |
|
849 |
|
850 /** |
|
851 * Asynchronously writes data to a file, replacing the file if it already |
|
852 * exists. data can be a string or a buffer. |
|
853 */ |
|
854 function writeFile(path, content, encoding, callback) { |
|
855 if (!isString(path)) |
|
856 throw new TypeError('path must be a string'); |
|
857 |
|
858 try { |
|
859 if (isFunction(encoding)) { |
|
860 callback = encoding |
|
861 encoding = null |
|
862 } |
|
863 if (isString(content)) |
|
864 content = new Buffer(content, encoding); |
|
865 |
|
866 let writeStream = new WriteStream(path); |
|
867 let error = null; |
|
868 |
|
869 writeStream.end(content, function() { |
|
870 writeStream.destroy(); |
|
871 callback(error); |
|
872 }); |
|
873 |
|
874 writeStream.on("error", function onError(reason) { |
|
875 error = reason; |
|
876 writeStream.destroy(); |
|
877 }); |
|
878 } catch (error) { |
|
879 callback(error); |
|
880 } |
|
881 }; |
|
882 exports.writeFile = writeFile; |
|
883 |
|
884 /** |
|
885 * The synchronous version of `fs.writeFile`. |
|
886 */ |
|
887 function writeFileSync(filename, data, encoding) { |
|
888 throw Error("Not implemented"); |
|
889 }; |
|
890 exports.writeFileSync = writeFileSync; |
|
891 |
|
892 |
|
893 function utimesSync(path, atime, mtime) { |
|
894 throw Error("Not implemented"); |
|
895 } |
|
896 exports.utimesSync = utimesSync; |
|
897 |
|
898 let utimes = Async(utimesSync); |
|
899 exports.utimes = utimes; |
|
900 |
|
901 function futimesSync(fd, atime, mtime, callback) { |
|
902 throw Error("Not implemented"); |
|
903 } |
|
904 exports.futimesSync = futimesSync; |
|
905 |
|
906 let futimes = Async(futimesSync); |
|
907 exports.futimes = futimes; |
|
908 |
|
909 function fsyncSync(fd, atime, mtime, callback) { |
|
910 throw Error("Not implemented"); |
|
911 } |
|
912 exports.fsyncSync = fsyncSync; |
|
913 |
|
914 let fsync = Async(fsyncSync); |
|
915 exports.fsync = fsync; |
|
916 |
|
917 |
|
918 /** |
|
919 * Watch for changes on filename. The callback listener will be called each |
|
920 * time the file is accessed. |
|
921 * |
|
922 * The second argument is optional. The options if provided should be an object |
|
923 * containing two members a boolean, persistent, and interval, a polling value |
|
924 * in milliseconds. The default is { persistent: true, interval: 0 }. |
|
925 */ |
|
926 function watchFile(path, options, listener) { |
|
927 throw Error("Not implemented"); |
|
928 }; |
|
929 exports.watchFile = watchFile; |
|
930 |
|
931 |
|
932 function unwatchFile(path, listener) { |
|
933 throw Error("Not implemented"); |
|
934 } |
|
935 exports.unwatchFile = unwatchFile; |
|
936 |
|
937 function watch(path, options, listener) { |
|
938 throw Error("Not implemented"); |
|
939 } |
|
940 exports.watch = watch; |