Skip to content

Commit

Permalink
fs: Buffer and encoding enhancements to fs API
Browse files Browse the repository at this point in the history
This makes several changes:

1. Allow path/filename to be passed in as a Buffer on fs methods
2. Add `options.encoding` to fs.readdir, fs.readdirSync, fs.readlink,
   fs.readlinkSync and fs.watch.
3. Documentation updates

For 1... it's now possible to do:

```js
fs.open(Buffer('/fs/foo/bar'), 'w+', (err, fd) => { });
```

For 2...
```js
fs.readdir('/fs/foo/bar', {encoding:'hex'}, (err,list) => { });

fs.readdir('/fs/foo/bar', {encoding:'buffer'}, (err, list) => { });
```

encoding can also be passed as a string

```js
fs.readdir('/fs/foo/bar', 'hex', (err,list) => { });
```

The default encoding is set to UTF8 so this addresses the
discrepency that existed previously between fs.readdir and
fs.watch handling filenames differently.

Fixes: #2088
Refs: #3519
PR-URL: #5616
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
  • Loading branch information
jasnell committed Mar 25, 2016
1 parent 4d4f353 commit 060e5f0
Show file tree
Hide file tree
Showing 15 changed files with 654 additions and 228 deletions.
189 changes: 129 additions & 60 deletions doc/api/fs.markdown

Large diffs are not rendered by default.

106 changes: 79 additions & 27 deletions lib/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -903,17 +903,32 @@ fs.mkdirSync = function(path, mode) {
modeNum(mode, 0o777));
};

fs.readdir = function(path, callback) {
fs.readdir = function(path, options, callback) {
options = options || {};
if (typeof options === 'function') {
callback = options;
options = {};
} else if (typeof options === 'string') {
options = {encoding: options};
}
if (typeof options !== 'object')
throw new TypeError('"options" must be a string or an object');

callback = makeCallback(callback);
if (!nullCheck(path, callback)) return;
var req = new FSReqWrap();
req.oncomplete = callback;
binding.readdir(pathModule._makeLong(path), req);
binding.readdir(pathModule._makeLong(path), options.encoding, req);
};

fs.readdirSync = function(path) {
fs.readdirSync = function(path, options) {
options = options || {};
if (typeof options === 'string')
options = {encoding: options};
if (typeof options !== 'object')
throw new TypeError('"options" must be a string or an object');
nullCheck(path);
return binding.readdir(pathModule._makeLong(path));
return binding.readdir(pathModule._makeLong(path), options.encoding);
};

fs.fstat = function(fd, callback) {
Expand Down Expand Up @@ -952,17 +967,31 @@ fs.statSync = function(path) {
return binding.stat(pathModule._makeLong(path));
};

fs.readlink = function(path, callback) {
fs.readlink = function(path, options, callback) {
options = options || {};
if (typeof options === 'function') {
callback = options;
options = {};
} else if (typeof options === 'string') {
options = {encoding: options};
}
if (typeof options !== 'object')
throw new TypeError('"options" must be a string or an object');
callback = makeCallback(callback);
if (!nullCheck(path, callback)) return;
var req = new FSReqWrap();
req.oncomplete = callback;
binding.readlink(pathModule._makeLong(path), req);
binding.readlink(pathModule._makeLong(path), options.encoding, req);
};

fs.readlinkSync = function(path) {
fs.readlinkSync = function(path, options) {
options = options || {};
if (typeof options === 'string')
options = {encoding: options};
if (typeof options !== 'object')
throw new TypeError('"options" must be a string or an object');
nullCheck(path);
return binding.readlink(pathModule._makeLong(path));
return binding.readlink(pathModule._makeLong(path), options.encoding);
};

function preprocessSymlinkDestination(path, type, linkPath) {
Expand Down Expand Up @@ -1363,11 +1392,15 @@ function FSWatcher() {
}
util.inherits(FSWatcher, EventEmitter);

FSWatcher.prototype.start = function(filename, persistent, recursive) {
FSWatcher.prototype.start = function(filename,
persistent,
recursive,
encoding) {
nullCheck(filename);
var err = this._handle.start(pathModule._makeLong(filename),
persistent,
recursive);
recursive,
encoding);
if (err) {
this._handle.close();
const error = errnoException(err, `watch ${filename}`);
Expand All @@ -1380,25 +1413,27 @@ FSWatcher.prototype.close = function() {
this._handle.close();
};

fs.watch = function(filename) {
fs.watch = function(filename, options, listener) {
nullCheck(filename);
var watcher;
var options;
var listener;

if (arguments[1] !== null && typeof arguments[1] === 'object') {
options = arguments[1];
listener = arguments[2];
} else {
options = options || {};
if (typeof options === 'function') {
listener = options;
options = {};
listener = arguments[1];
} else if (typeof options === 'string') {
options = {encoding: options};
}
if (typeof options !== 'object')
throw new TypeError('"options" must be a string or an object');

if (options.persistent === undefined) options.persistent = true;
if (options.recursive === undefined) options.recursive = false;

watcher = new FSWatcher();
watcher.start(filename, options.persistent, options.recursive);
const watcher = new FSWatcher();
watcher.start(filename,
options.persistent,
options.recursive,
options.encoding);

if (listener) {
watcher.addListener('change', listener);
Expand Down Expand Up @@ -2139,10 +2174,19 @@ SyncWriteStream.prototype.destroy = function() {

SyncWriteStream.prototype.destroySoon = SyncWriteStream.prototype.destroy;

fs.mkdtemp = function(prefix, callback) {
if (typeof callback !== 'function') {
throw new TypeError('"callback" argument must be a function');
fs.mkdtemp = function(prefix, options, callback) {
if (!prefix || typeof prefix !== 'string')
throw new TypeError('filename prefix is required');

options = options || {};
if (typeof options === 'function') {
callback = options;
options = {};
} else if (typeof options === 'string') {
options = {encoding: options};
}
if (typeof options !== 'object')
throw new TypeError('"options" must be a string or an object');

if (!nullCheck(prefix, callback)) {
return;
Expand All @@ -2151,11 +2195,19 @@ fs.mkdtemp = function(prefix, callback) {
var req = new FSReqWrap();
req.oncomplete = callback;

binding.mkdtemp(prefix + 'XXXXXX', req);
binding.mkdtemp(prefix + 'XXXXXX', options.encoding, req);
};

fs.mkdtempSync = function(prefix) {
fs.mkdtempSync = function(prefix, options) {
if (!prefix || typeof prefix !== 'string')
throw new TypeError('filename prefix is required');

options = options || {};
if (typeof options === 'string')
options = {encoding: options};
if (typeof options !== 'object')
throw new TypeError('"options" must be a string or an object');
nullCheck(prefix);

return binding.mkdtemp(prefix + 'XXXXXX');
return binding.mkdtemp(prefix + 'XXXXXX', options.encoding);
};
27 changes: 22 additions & 5 deletions src/fs_event_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "util-inl.h"
#include "node.h"
#include "handle_wrap.h"
#include "string_bytes.h"

#include <stdlib.h>

Expand Down Expand Up @@ -41,6 +42,7 @@ class FSEventWrap: public HandleWrap {

uv_fs_event_t handle_;
bool initialized_;
enum encoding encoding_;
};


Expand Down Expand Up @@ -86,16 +88,20 @@ void FSEventWrap::Start(const FunctionCallbackInfo<Value>& args) {

FSEventWrap* wrap = Unwrap<FSEventWrap>(args.Holder());

if (args.Length() < 1 || !args[0]->IsString()) {
return env->ThrowTypeError("filename must be a valid string");
}
static const char kErrMsg[] = "filename must be a string or Buffer";
if (args.Length() < 1)
return env->ThrowTypeError(kErrMsg);

node::Utf8Value path(env->isolate(), args[0]);
BufferValue path(env->isolate(), args[0]);
if (*path == nullptr)
return env->ThrowTypeError(kErrMsg);

unsigned int flags = 0;
if (args[2]->IsTrue())
flags |= UV_FS_EVENT_RECURSIVE;

wrap->encoding_ = ParseEncoding(env->isolate(), args[3], UTF8);

int err = uv_fs_event_init(wrap->env()->event_loop(), &wrap->handle_);
if (err == 0) {
wrap->initialized_ = true;
Expand Down Expand Up @@ -156,7 +162,18 @@ void FSEventWrap::OnEvent(uv_fs_event_t* handle, const char* filename,
};

if (filename != nullptr) {
argv[2] = OneByteString(env->isolate(), filename);
Local<Value> fn = StringBytes::Encode(env->isolate(),
filename,
wrap->encoding_);
if (fn.IsEmpty()) {
argv[0] = Integer::New(env->isolate(), UV_EINVAL);
argv[2] = StringBytes::Encode(env->isolate(),
filename,
strlen(filename),
BUFFER);
} else {
argv[2] = fn;
}
}

wrap->MakeCallback(env->onchange_string(), ARRAY_SIZE(argv), argv);
Expand Down
Loading

0 comments on commit 060e5f0

Please sign in to comment.