_copy.js 5.51 KB
// imported from ncp (this is temporary, will rewrite)

var fs = require('graceful-fs')
var path = require('path')

function ncp (source, dest, options, callback) {
  var cback = callback

  if (!callback) {
    cback = options
    options = {}
  }

  var basePath = process.cwd()
  var currentPath = path.resolve(basePath, source)
  var targetPath = path.resolve(basePath, dest)

  var filter = options.filter
  var transform = options.transform
  var clobber = options.clobber !== false
  var dereference = options.dereference

  var errs = null
  
  var started = 0
  var finished = 0
  var running = 0
  // this is pretty useless now that we're using graceful-fs
  // consider removing
  var limit = options.limit || 512

  startCopy(currentPath)
  
  function startCopy(source) {
    started++
    if (filter) {
      if (filter instanceof RegExp) {
        if (!filter.test(source)) {
          return cb(true)
        }
      }
      else if (typeof filter === 'function') {
        if (!filter(source)) {
          return cb(true)
        }
      }
    }
    return getStats(source)
  }

  function getStats(source) {
    var defer = global.setImmediate || process.nextTick
    var stat = dereference ? fs.stat : fs.lstat
    if (running >= limit) {
      return defer(function () {
        getStats(source)
      })
    }
    running++
    stat(source, function (err, stats) {
      var item = {}
      if (err) {
        return onError(err)
      }

      // We need to get the mode from the stats object and preserve it.
      item.name = source
      item.mode = stats.mode
      item.mtime = stats.mtime //modified time
      item.atime = stats.atime //access time

      if (stats.isDirectory()) {
        return onDir(item)
      }
      else if (stats.isFile()) {
        return onFile(item)
      }
      else if (stats.isSymbolicLink()) {
        // Symlinks don't really need to know about the mode.
        return onLink(source)
      }
    })
  }

  function onFile(file) {
    var target = file.name.replace(currentPath, targetPath)
    isWritable(target, function (writable) {
      if (writable) {
        copyFile(file, target)
      } else {
        if(clobber) {
          rmFile(target, function () {
            copyFile(file, target)
          })
        } else {
          cb()
        }
      }
    })
  }

  function copyFile(file, target) {
    var readStream = fs.createReadStream(file.name),
        writeStream = fs.createWriteStream(target, { mode: file.mode })
    
    readStream.on('error', onError)
    writeStream.on('error', onError)
    
    if(transform) {
      transform(readStream, writeStream, file)
    } else {
      writeStream.on('open', function() {
        readStream.pipe(writeStream)
      })
    }

    //presumably old node then
    var eventName = global.setImmediate ? 'finish' : 'close'
    writeStream.once(eventName, function() {
      cb()
    })
  }

  function rmFile(file, done) {
    fs.unlink(file, function (err) {
      if (err) {
        return onError(err)
      }
      return done()
    })
  }

  function onDir(dir) {
    var target = dir.name.replace(currentPath, targetPath)
    isWritable(target, function (writable) {
      if (writable) {
        return mkDir(dir, target)
      }
      copyDir(dir.name)
    })
  }

  function mkDir(dir, target) {
    fs.mkdir(target, dir.mode, function (err) {
      if (err) {
        return onError(err)
      }
      copyDir(dir.name)
    })
  }

  function copyDir(dir) {
    fs.readdir(dir, function (err, items) {
      if (err) {
        return onError(err)
      }
      items.forEach(function (item) {
        startCopy(path.join(dir, item))
      })
      return cb()
    })
  }

  function onLink(link) {
    var target = link.replace(currentPath, targetPath)
    fs.readlink(link, function (err, resolvedPath) {
      if (err) {
        return onError(err)
      }
      checkLink(resolvedPath, target)
    })
  }

  function checkLink(resolvedPath, target) {
    if (dereference) {
      resolvedPath = path.resolve(basePath, resolvedPath)
    }
    isWritable(target, function (writable) {
      if (writable) {
        return makeLink(resolvedPath, target)
      }
      fs.readlink(target, function (err, targetDest) {
        if (err) {
          return onError(err)
        }
        if (dereference) {
          targetDest = path.resolve(basePath, targetDest)
        }
        if (targetDest === resolvedPath) {
          return cb()
        }
        return rmFile(target, function () {
          makeLink(resolvedPath, target)
        })
      })
    })
  }

  function makeLink(linkPath, target) {
    fs.symlink(linkPath, target, function (err) {
      if (err) {
        return onError(err)
      }
      return cb()
    })
  }

  function isWritable(path, done) {
    fs.lstat(path, function (err) {
      if (err) {
        if (err.code === 'ENOENT') return done(true)
        return done(false)
      }
      return done(false)
    })
  }

  function onError(err) {
    if (options.stopOnError) {
      return cback(err)
    }
    else if (!errs && options.errs) {
      errs = fs.createWriteStream(options.errs)
    }
    else if (!errs) {
      errs = []
    }
    if (typeof errs.write === 'undefined') {
      errs.push(err)
    }
    else { 
      errs.write(err.stack + '\n\n')
    }
    return cb()
  }

  function cb(skipped) {
    if (!skipped) running--
    finished++
    if ((started === finished) && (running === 0)) {
      if (cback !== undefined ) {
        return errs ? cback(errs) : cback(null)
      }
    }
  }
}

// todo, make this just export ncp
module.exports.ncp = ncp