Skip to content

Commit

Permalink
Merge pull request #505 from jprichardson/native-copyFile
Browse files Browse the repository at this point in the history
Add native fs.copyFile and fs.copyFileSync to copy() and copySync()
  • Loading branch information
jprichardson authored Nov 7, 2017
2 parents 166f4a7 + 1abc2a3 commit d8adc47
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 32 deletions.
6 changes: 3 additions & 3 deletions lib/copy-sync/__tests__/copy-sync-preserve-time.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,19 @@ describeIf64('copySync', () => {
require(process.cwd()).emptyDir(TEST_DIR, done)
})

describe('> modification option', () => {
describe('> preserveTimestamps option', () => {
const SRC_FIXTURES_DIR = path.join(__dirname, './fixtures')
const FILES = ['a-file', path.join('a-folder', 'another-file'), path.join('a-folder', 'another-folder', 'file3')]

describe('> when modified option is turned off', () => {
describe('> when preserveTimestamps option is false', () => {
it('should have different timestamps on copy', () => {
const from = path.join(SRC_FIXTURES_DIR)
copySync(from, TEST_DIR, {preserveTimestamps: false})
FILES.forEach(testFile({preserveTimestamps: false}))
})
})

describe('> when modified option is turned on', () => {
describe('> when preserveTimestamps option is true', () => {
it('should have the same timestamps on copy', () => {
const from = path.join(SRC_FIXTURES_DIR)
copySync(from, TEST_DIR, {preserveTimestamps: true})
Expand Down
17 changes: 13 additions & 4 deletions lib/copy-sync/copy-file-sync.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict'

const fs = require('graceful-fs')
const utimesSync = require('../util/utimes.js').utimesMillisSync

const BUF_LENGTH = 64 * 1024
const _buff = require('../util/buffer')(BUF_LENGTH)
Expand All @@ -18,6 +19,17 @@ function copyFileSync (srcFile, destFile, options) {
} else return
}

if (typeof fs.copyFileSync === 'function') {
fs.copyFileSync(srcFile, destFile)
const st = fs.lstatSync(srcFile)
fs.chmodSync(destFile, st.mode)
if (preserveTimestamps) utimesSync(destFile, st.atime, st.mtime)
return undefined
}
return copyFileSyncFallback(srcFile, destFile, preserveTimestamps)
}

function copyFileSyncFallback (srcFile, destFile, preserveTimestamps) {
const fdr = fs.openSync(srcFile, 'r')
const stat = fs.fstatSync(fdr)
const fdw = fs.openSync(destFile, 'w', stat.mode)
Expand All @@ -30,10 +42,7 @@ function copyFileSync (srcFile, destFile, options) {
pos += bytesRead
}

if (preserveTimestamps) {
fs.futimesSync(fdw, stat.atime, stat.mtime)
}

if (preserveTimestamps) fs.futimesSync(fdw, stat.atime, stat.mtime)
fs.closeSync(fdr)
fs.closeSync(fdw)
}
Expand Down
59 changes: 35 additions & 24 deletions lib/copy/copy.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ function onFile (srcStat, src, dest, opts, cb) {
checkDest(dest, (err, resolvedPath) => {
if (err) return cb(err)
if (resolvedPath === notExist) {
return cpFile(srcStat, src, dest, opts, cb)
return copyFile(srcStat, src, dest, opts, cb)
} else if (resolvedPath === existsReg) {
return mayCopyFile(srcStat, src, dest, opts, cb)
} else {
Expand All @@ -89,30 +89,41 @@ function mayCopyFile (srcStat, src, dest, opts, cb) {
if (opts.overwrite) {
fs.unlink(dest, err => {
if (err) return cb(err)
return cpFile(srcStat, src, dest, opts, cb)
return copyFile(srcStat, src, dest, opts, cb)
})
} else if (opts.errorOnExist) {
return cb(new Error(`'${dest}' already exists`))
} else return cb()
}

function cpFile (srcStat, src, dest, opts, cb) {
function copyFile (srcStat, src, dest, opts, cb) {
if (typeof fs.copyFile === 'function') {
return fs.copyFile(src, dest, err => {
if (err) return cb(err)
return setDestModeAndTimestamps(srcStat, dest, opts, cb)
})
}
return copyFileFallback(srcStat, src, dest, opts, cb)
}

function copyFileFallback (srcStat, src, dest, opts, cb) {
const rs = fs.createReadStream(src)
const ws = fs.createWriteStream(dest, { mode: srcStat.mode })

rs.on('error', err => cb(err))
ws.on('error', err => cb(err))

ws.on('open', () => {
rs.pipe(ws)
}).once('close', () => {
fs.chmod(dest, srcStat.mode, err => {
if (err) return cb(err)
if (opts.preserveTimestamps) {
return utimes(dest, srcStat.atime, srcStat.mtime, cb)
}
return cb()
})
ws.on('open', () => rs.pipe(ws))
.once('close', () => setDestModeAndTimestamps(srcStat, dest, opts, cb))
}

function setDestModeAndTimestamps (srcStat, dest, opts, cb) {
fs.chmod(dest, srcStat.mode, err => {
if (err) return cb(err)
if (opts.preserveTimestamps) {
return utimes(dest, srcStat.atime, srcStat.mtime, cb)
}
return cb()
})
}

Expand All @@ -131,7 +142,7 @@ function onDir (srcStat, src, dest, opts, cb) {
return mayCopyDir(src, dest, opts, cb)
} else {
if (src === resolvedPath) return cb()
return cpDir(src, dest, opts, cb)
return copyDir(src, dest, opts, cb)
}
})
}
Expand All @@ -142,7 +153,7 @@ function mayCopyDir (src, dest, opts, cb) {
if (!st.isDirectory()) {
return cb(new Error(`Cannot overwrite non-directory '${dest}' with directory '${src}'.`))
}
return cpDir(src, dest, opts, cb)
return copyDir(src, dest, opts, cb)
})
}

Expand All @@ -151,24 +162,24 @@ function mkDirAndCopy (srcStat, src, dest, opts, cb) {
if (err) return cb(err)
fs.chmod(dest, srcStat.mode, err => {
if (err) return cb(err)
return cpDir(src, dest, opts, cb)
return copyDir(src, dest, opts, cb)
})
})
}

function cpDir (src, dest, opts, cb) {
function copyDir (src, dest, opts, cb) {
fs.readdir(src, (err, items) => {
if (err) return cb(err)
return cpDirItems(items, src, dest, opts, cb)
return copyDirItems(items, src, dest, opts, cb)
})
}

function cpDirItems (items, src, dest, opts, cb) {
function copyDirItems (items, src, dest, opts, cb) {
const item = items.pop()
if (!item) return cb()
startCopy(path.join(src, item), path.join(dest, item), opts, err => {
if (err) return cb(err)
return cpDirItems(items, src, dest, opts, cb)
return copyDirItems(items, src, dest, opts, cb)
})
}

Expand Down Expand Up @@ -201,27 +212,27 @@ function onLink (src, dest, opts, cb) {
if (st.isDirectory() && isSrcSubdir(resolvedDestPath, resolvedSrcPath)) {
return cb(new Error(`Cannot overwrite '${resolvedDestPath}' with '${resolvedSrcPath}'.`))
}
return cpLink(resolvedSrcPath, dest, cb)
return copyLink(resolvedSrcPath, dest, cb)
})
}
})
})
}

function cpLink (resolvedSrcPath, dest, cb) {
function copyLink (resolvedSrcPath, dest, cb) {
fs.unlink(dest, err => {
if (err) return cb(err)
return fs.symlink(resolvedSrcPath, dest, cb)
})
}

// check dest to see if it exists and/or is a symlink
// check if dest exists and/or is a symlink
function checkDest (dest, cb) {
fs.readlink(dest, (err, resolvedPath) => {
if (err) {
if (err.code === 'ENOENT') return cb(null, notExist)

// dest exists and is a regular file or directory, Windows throws UNKNOWN error.
// dest exists and is a regular file or directory, Windows may throw UNKNOWN error.
if (err.code === 'EINVAL' || err.code === 'UNKNOWN') return cb(null, existsReg)

return cb(err)
Expand Down
9 changes: 8 additions & 1 deletion lib/util/utimes.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,16 @@ function utimesMillis (path, atime, mtime, callback) {
})
}

function utimesMillisSync (path, atime, mtime) {
const fd = fs.openSync(path, 'r+')
fs.futimesSync(fd, atime, mtime)
return fs.closeSync(fd)
}

module.exports = {
hasMillisRes,
hasMillisResSync,
timeRemoveMillis,
utimesMillis
utimesMillis,
utimesMillisSync
}

0 comments on commit d8adc47

Please sign in to comment.