diff --git a/lib/copy-sync/__tests__/copy-sync-preserve-time.test.js b/lib/copy-sync/__tests__/copy-sync-preserve-time.test.js index 6a4ad9f2..3f9f60ce 100644 --- a/lib/copy-sync/__tests__/copy-sync-preserve-time.test.js +++ b/lib/copy-sync/__tests__/copy-sync-preserve-time.test.js @@ -21,11 +21,11 @@ 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}) @@ -33,7 +33,7 @@ describeIf64('copySync', () => { }) }) - 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}) diff --git a/lib/copy-sync/copy-file-sync.js b/lib/copy-sync/copy-file-sync.js index 102a6be6..452ca6cc 100644 --- a/lib/copy-sync/copy-file-sync.js +++ b/lib/copy-sync/copy-file-sync.js @@ -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) @@ -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) @@ -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) } diff --git a/lib/copy/copy.js b/lib/copy/copy.js index 1aa53228..5288dd1c 100644 --- a/lib/copy/copy.js +++ b/lib/copy/copy.js @@ -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 { @@ -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() }) } @@ -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) } }) } @@ -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) }) } @@ -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) }) } @@ -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) diff --git a/lib/util/utimes.js b/lib/util/utimes.js index 4c320993..8916a1b8 100644 --- a/lib/util/utimes.js +++ b/lib/util/utimes.js @@ -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 }