Skip to content

Instantly share code, notes, and snippets.

@richardms
Created August 11, 2011 16:51
Show Gist options
  • Save richardms/1140156 to your computer and use it in GitHub Desktop.
Save richardms/1140156 to your computer and use it in GitHub Desktop.
ignite.js rimraf implementation
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