Created
August 11, 2011 16:51
-
-
Save richardms/1140156 to your computer and use it in GitHub Desktop.
ignite.js rimraf implementation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var ignite = require('ignite'), | |
path = require('path') ; | |
var rmrfFactory ; | |
function rmrf (fire, dir, opts) | |
{ | |
opts = opts || {} ; | |
var gently = opts.gently, maxBusyRetries = opts.maxBusyRetries || 3 ; | |
var emfTimeout = 5 ; | |
var subDirCount = 0 ; | |
var linkList = [] ; | |
var toDeleteList = [] ; | |
return { | |
startState: "ReadDir", | |
states: { | |
// Use async fs.readdir to get the items in the directory | |
ReadDir: { | |
async: { | |
fn: 'fs.readdir', | |
fnArgs: [dir] | |
}, | |
actions: { | |
'.done': 'StatEntities', | |
'.err': '@error' | |
} | |
}, | |
// If items exist, iterate over them with fs.lstat. Otherwise jump to DeleteDir | |
StatEntities: { | |
guard: function (dirList) { | |
if (dirList.length===0) | |
return "DeleteDir" ; | |
}, | |
map: { | |
fn: 'fs.lstat', | |
fnArgs: function (localPath) { | |
return [path.join(dir, localPath)] ; | |
}, | |
replace: true | |
}, | |
actions: { | |
".done": "HandleEntities" | |
} | |
}, | |
// Loop through the entities, using their type to decide their fate. | |
// If a directory is found, then we spawn a new rmrf machine to handle it | |
// and raise our reference count. Note this state is a closure. | |
HandleEntities: function () { | |
// This is the main logic to decide what to do with each entity | |
function handleEntity (fullPath, stat) { | |
if (stat.isFile()) { | |
toDeleteList.push(fullPath) ; | |
return ; | |
} else if (stat.isDirectory()) { | |
if (gently && fullPath.indexOf(gently) !== 0) { | |
// Don't descend - it doesn't match the gently path | |
} else { | |
var subdir = rmrfFactory.spawn(fullPath, opts) ; | |
fire.$regEmitter(subDirCount+".subdir", subdir, true) ; | |
subDirCount += 1 ; | |
} | |
} else if (stat.isSymbolicLink()) { | |
linkList.push(fullPath) ; | |
} | |
} | |
return { | |
entry: function (stats, filenames, errors) { | |
var i ; | |
for (i=0;i<filenames.length;i++) { | |
var stat = stats[i], fname = filenames[i], err = errors[i] ; | |
if (err) { | |
if (err.message.match(/^ENOENT/)) | |
continue ; // It's gone - ignore error | |
// If not exit this state machine with the error | |
return ["@error", err, fname]; | |
} | |
handleEntity(fname, stat) ; | |
} | |
return "QueryLinks" ; | |
} | |
} ; | |
}, | |
// If any links were found we resolve them | |
QueryLinks: { | |
guard: function () { | |
if (linkList.length === 0) | |
return "DeleteFiles" ; | |
}, | |
detect: { | |
fn: 'fs.readlink', | |
over: linkList, | |
iterator: function (pos, err, resolvedPath) { | |
if (err) return err ; | |
if (opts.gently && resolvedPath.indexOf(opts.gently) !== 0) { | |
return ; | |
} | |
toDeleteList.push(linkList[pos]); | |
} | |
}, | |
actions: { | |
'.done': function (err) { | |
if (err) | |
return "@error" ; | |
return "DeleteFiles" ; | |
} | |
} | |
}, | |
// This state attempts to delete all items in the current delete list | |
DeleteFiles: { | |
guard: function () { | |
if (toDeleteList.length === 0) | |
return "SubDirWait" ; | |
}, | |
filter: { | |
fn: 'fs.unlink', | |
over: toDeleteList | |
}, | |
actions: { | |
'.done': function (brokeList, origList, errList) { | |
if (brokeList.length === 0) | |
return "SubDirWait" ; | |
return ["HandleErrors", brokeList, errList] ; | |
} | |
} | |
}, | |
// If errors were encountered then we look at them to decide what to do. | |
HandleErrors: { | |
guard: function (brokeList, errList) { | |
if (opts.maxBusyRetries === 0 || emfTimeout > 1000) | |
return ["@error", errList[0]] ; | |
}, | |
entry: function (brokeList, errList) { | |
var i = 0, emf = false, ebusy = false ; | |
for (i=0;i<brokeList.length;i++) { | |
if (errList[i].message.match(/^EMFILE/)) { | |
emf = true ; | |
} else if (errList[i].message.match(/^EBUSY/)) { | |
ebusy = true ; | |
} else { | |
return ["@error", errList[i]] ; | |
} | |
} | |
toDeleteList = brokeList ; | |
if (ebusy) { | |
opts.maxBusyRetries -= 1; | |
} | |
if (emf) { | |
emfTimeout += 1 ; | |
return "Timeout" ; | |
} | |
return "DeleteFiles" ; | |
} | |
}, | |
// Simple timeout state | |
Timeout: { | |
timeout: emfTimeout, | |
actions: { | |
'timeout': "DeleteFiles" | |
} | |
}, | |
// If the reference count is non-zero, then we wait here for the | |
// sub-directory machines to finish | |
SubDirWait: { | |
guard: function () { | |
if (subDirCount === 0) { | |
return "DeleteDir" ; | |
} | |
}, | |
actions: { | |
'.subdir.exit': function () { | |
subDirCount -= 1 ; | |
return "@self" ; | |
}, | |
'.subdir.error': "@error" | |
} | |
}, | |
// At last, we try deleting the directory. | |
DeleteDir: { | |
async: { | |
fn: 'fs.rmdir', | |
fnArgs: [dir] | |
}, | |
actions: { | |
'.done': "@exit", | |
'.err': "@error" | |
} | |
} | |
}, | |
defaults: { | |
defer: ['.subdir.exit', '.subdir.error'], | |
ignore: ['.event', '.call', '.change', '.subdir.done', '.ext'] | |
} | |
} ; | |
} | |
rmrf.defaults = { | |
imports: { fs: require('fs') }, | |
options: { logLevel: 0 } | |
} ; | |
rmrfFactory = new ignite.Factory(rmrf) ; | |
module.exports = rmrfFactory.spawnWithCb.bind(rmrfFactory) ; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment