Skip to content

Commit

Permalink
integrate amazing-graceful-fs with graceful-fs
Browse files Browse the repository at this point in the history
all credit goes to ben
https://github.com/bnoordhuis/amazing-graceful-fs
and nodejs/node#2026

formatting was modified to match
  • Loading branch information
heavyk committed Jun 25, 2015
1 parent 460834a commit 3fd8df5
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 136 deletions.
32 changes: 21 additions & 11 deletions fs.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
// eeeeeevvvvviiiiiiillllll
// more evil than monkey-patching the native builtin?
// Not sure.

var mod = require("module")
var pre = '(function (exports, require, module, __filename, __dirname) { '
var post = '});'
var src = pre + process.binding('natives').fs + post
var vm = require('vm')
var fn = vm.runInThisContext(src)
fn(exports, require, module, __filename, __dirname)
'use strict'

var fs = require('fs')

module.exports = clone(fs)

function clone (obj) {
if (obj === null || typeof obj !== 'object')
return obj

if ('__proto__' in obj)
var copy = { __proto__: obj.__proto__ }
else
var copy = Object.create(null)

Object.getOwnPropertyNames(obj).forEach(function (key) {
Object.defineProperty(copy, key, Object.getOwnPropertyDescriptor(obj, key))
})

return copy
}
226 changes: 108 additions & 118 deletions graceful-fs.js
Original file line number Diff line number Diff line change
@@ -1,158 +1,148 @@
// Monkey-patching the fs module.
// It's ugly, but there is simply no other way to do this.
var fs = module.exports = require('./fs.js')

var assert = require('assert')
var fs = require('fs')
module.exports = require('./fs.js')

// fix up some busted stuff, mostly on windows and old nodes
require('./polyfills.js')

var util = require('util')
module.exports.FileReadStream = ReadStream; // Legacy name.
module.exports.FileWriteStream = WriteStream; // Legacy name.
module.exports.ReadStream = ReadStream
module.exports.WriteStream = WriteStream
module.exports.close = close
module.exports.closeSync = closeSync
module.exports.createReadStream = createReadStream
module.exports.createWriteStream = createWriteStream
module.exports.open = open
module.exports.readFile = readFile
module.exports.readdir = readdir

function noop () {}
ReadStream.prototype = Object.create(fs.ReadStream.prototype)
ReadStream.prototype.open = ReadStream$open

var debug = noop
if (util.debuglog)
debug = util.debuglog('gfs')
else if (/\bgfs\b/i.test(process.env.NODE_DEBUG || ''))
debug = function() {
var m = util.format.apply(util, arguments)
m = 'GFS: ' + m.split(/\n/).join('\nGFS: ')
console.error(m)
}
WriteStream.prototype = Object.create(fs.WriteStream.prototype)
WriteStream.prototype.open = WriteStream$open

if (/\bgfs\b/i.test(process.env.NODE_DEBUG || '')) {
process.on('exit', function() {
debug('fds', fds)
debug(queue)
assert.equal(queue.length, 0)
})
}


var originalOpen = fs.open
fs.open = open
var queue = []

function open(path, flags, mode, cb) {
if (typeof mode === "function") cb = mode, mode = null
if (typeof cb !== "function") cb = noop
new OpenReq(path, flags, mode, cb)
function ReadStream (path, options) {
if (this instanceof ReadStream)
return fs.ReadStream.apply(this, arguments), this
else
return ReadStream.apply(Object.create(ReadStream.prototype), arguments)
}

function OpenReq(path, flags, mode, cb) {
this.path = path
this.flags = flags
this.mode = mode
this.cb = cb
Req.call(this)
function ReadStream$open () {
var that = this
open(that.path, that.flags, that.mode, function (err, fd) {
if (err) {
if (that.autoClose)
that.destroy()

that.emit('error', err)
} else {
that.fd = fd
that.emit('open', fd)
that.read()
}
})
}

util.inherits(OpenReq, Req)

OpenReq.prototype.process = function() {
originalOpen.call(fs, this.path, this.flags, this.mode, this.done)
function WriteStream (path, options) {
if (this instanceof WriteStream)
return fs.WriteStream.apply(this, arguments), this
else
return WriteStream.apply(Object.create(WriteStream.prototype), arguments)
}

var fds = {}
OpenReq.prototype.done = function(er, fd) {
debug('open done', er, fd)
if (fd)
fds['fd' + fd] = this.path
Req.prototype.done.call(this, er, fd)
function WriteStream$open () {
var that = this
open(that.path, that.flags, that.mode, function (err, fd) {
if (err) {
that.destroy()
that.emit('error', err)
} else {
that.fd = fd
that.emit('open', fd)
}
})
}

function close (fd, cb) {
return fs.close(fd, function (err) {
if (!err)
retry()

var originalReaddir = fs.readdir
fs.readdir = readdir

function readdir(path, cb) {
if (typeof cb !== "function") cb = noop
new ReaddirReq(path, cb)
if (typeof cb === 'function')
cb.apply(this, arguments)
})
}

function ReaddirReq(path, cb) {
this.path = path
this.cb = cb
Req.call(this)
function closeSync () {
// Note that graceful-fs also retries when fs.closeSync() fails.
// Looks like a bug to me, although it's probably a harmless one.
var rval = fs.closeSync.apply(fs, arguments)
retry()
return rval
}

util.inherits(ReaddirReq, Req)

ReaddirReq.prototype.process = function() {
originalReaddir.call(fs, this.path, this.done)
function createReadStream (path, options) {
return new ReadStream(path, options)
}

ReaddirReq.prototype.done = function(er, files) {
if (files && files.sort)
files = files.sort()
Req.prototype.done.call(this, er, files)
onclose()
function createWriteStream (path, options) {
return new WriteStream(path, options)
}

function open (path, flags, mode, cb) {
if (typeof mode === 'function')
cb = mode, mode = null

var originalClose = fs.close
fs.close = close
return go(path, flags, mode, cb)

function close (fd, cb) {
debug('close', fd)
if (typeof cb !== "function") cb = noop
delete fds['fd' + fd]
originalClose.call(fs, fd, function(er) {
onclose()
cb(er)
})
function go (path, flags, mode, cb) {
return fs.open(path, flags, mode, function (err, fd) {
if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
queue.push([go, [path, flags, mode, cb]])
else if (typeof cb === 'function')
cb.apply(this, arguments)
})
}
}

function readFile (path, options, cb) {
if (typeof options === 'function')
cb = options, options = null

var originalCloseSync = fs.closeSync
fs.closeSync = closeSync
return go(path, options, cb)

function closeSync (fd) {
try {
return originalCloseSync(fd)
} finally {
onclose()
function go (path, flags, mode, cb) {
return fs.readFile(path, options, function (err, fd) {
if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
queue.push([go, [path, options, cb]])
else if (typeof cb === 'function')
cb.apply(this, arguments)
})
}
}

function readdir (path, cb) {
return go(path, cb)

// Req class
function Req () {
// start processing
this.done = this.done.bind(this)
this.failures = 0
this.process()
}

Req.prototype.done = function (er, result) {
var tryAgain = false
if (er) {
var code = er.code
var tryAgain = code === "EMFILE" || code === "ENFILE"
if (process.platform === "win32")
tryAgain = tryAgain || code === "OK"
}
function go () {
return fs.readdir(path, function (err, files) {
if (files && files.sort)
files.sort(); // Backwards compatibility with graceful-fs.

if (tryAgain) {
this.failures ++
enqueue(this)
} else {
var cb = this.cb
cb(er, result)
if (err && (err.code === 'EMFILE' || err.code === 'ENFILE'))
queue.push([go, [path, cb]])
else if (typeof cb === 'function')
cb.apply(this, arguments)
})
}
}

var queue = []

function enqueue(req) {
queue.push(req)
debug('enqueue %d %s', queue.length, req.constructor.name, req)
}

function onclose() {
var req = queue.shift()
if (req) {
debug('process', req.constructor.name, req)
req.process()
}
function retry () {
var elem = queue.shift()
if (elem)
elem[0].apply(null, elem[1])
}
7 changes: 1 addition & 6 deletions test/open.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
var test = require('tap').test
var fs = require('../graceful-fs.js')

test('graceful fs is monkeypatched fs', function (t) {
t.equal(fs, require('../fs.js'))
t.end()
})
var fs = require('../')

test('open an existing file works', function (t) {
var fd = fs.openSync(__filename, 'r')
Expand Down
58 changes: 58 additions & 0 deletions test/read-write-stream.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
'use strict'

var fs = require('../')
var rimraf = require('rimraf')
var mkdirp = require('mkdirp')
var test = require('tap').test
var p = require('path').resolve(__dirname, 'files')

process.chdir(__dirname)

// Make sure to reserve the stderr fd
process.stderr.write('')

var num = 4097
var paths = new Array(num)

test('write files', function (t) {
rimraf.sync(p)
mkdirp.sync(p)

var done = 0
for (var i = 0; i < num; ++i) {
paths[i] = 'files/file-' + i
var stream = fs.createWriteStream(paths[i])
stream.on('end', function () {
++done
if (done === num) {
t.pass('success')
t.end()
}
})
stream.write('content')
stream.end()
}

t.end()
})

test('read files', function (t) {
// now read them
var done = 0
for (var i = 0; i < num; ++i) {
var stream = fs.createReadStream(paths[i])
stream.on('data', function (data) {})
stream.on('end', function () {
++done
if (done === num) {
t.pass('success')
t.end()
}
})
}
})

test('cleanup', function (t) {
rimraf.sync(p)
t.end()
})
2 changes: 1 addition & 1 deletion test/readdir-sort.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
var test = require("tap").test
var fs = require("../fs.js")
var fs = require("fs")

var readdir = fs.readdir
fs.readdir = function(path, cb) {
Expand Down

0 comments on commit 3fd8df5

Please sign in to comment.