Skip to content

Commit

Permalink
Merge pull request #151 from gulpjs/blaine-feature-metadata-refactor
Browse files Browse the repository at this point in the history
Feature metadata refactor
  • Loading branch information
phated committed Feb 26, 2016
2 parents 231294d + f8ed1db commit 822908d
Show file tree
Hide file tree
Showing 26 changed files with 2,251 additions and 702 deletions.
7 changes: 4 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
sudo: false
sudo: required
language: node_js
node_js:
- 'stable'
- '4.1'
- '4.0'
- '4'
- '0.12'
- '0.10'
before_script:
- find test -type d -exec chmod g+s {} \;
- sudo chown root test/not-owned/
- sudo chown root test/not-owned/not-owned.txt
after_script:
- npm run coveralls
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,16 @@ Any through2-related options are documented in [through2].
Takes a folder path string or a function as the first argument and an options object as the second. If given a function, it will be called with each [vinyl] `File` object and must return a folder path.
Returns a stream that accepts [vinyl] `File` objects, writes them to disk at the folder/cwd specified, and passes them downstream so you can keep piping these around.

Once the file is written to disk, an attempt is made to determine if the `stat.mode`, `stat.mtime` and `stat.atime` of the [vinyl] `File` object differ from the file on the filesystem.
If they differ and the running process owns the file, the corresponding filesystem metadata is updated.
If they don't differ or the process doesn't own the file, the attempt is skipped silently.
__This functionality is disabled on Windows operating systems or any other OS that doesn't support `process.getuid` or `process.geteuid` in node. This is due to Windows having very unexpected results through usage of `fs.fchmod` and `fs.futimes`.__

If the file has a `symlink` attribute specifying a target path, then a symlink will be created.

__Note: The file will be modified after being written to this stream.__
- `cwd`, `base`, and `path` will be overwritten to match the folder.
- `stat.mode` will be overwritten if you used a mode parameter.
- `stat` will be updated to match the file on the filesystem.
- `contents` will have it's position reset to the beginning if it is a stream.

#### Options
Expand Down
24 changes: 24 additions & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# http://www.appveyor.com/docs/appveyor-yml
# http://www.appveyor.com/docs/lang/nodejs-iojs

environment:
matrix:
# node.js
- nodejs_version: "0.10"
- nodejs_version: "0.12"
- nodejs_version: "4"
- nodejs_version: "5"

install:
- ps: Install-Product node $env:nodejs_version
- npm install

test_script:
- node --version
- npm --version
- cmd: npm test

build: off

# build version format
version: "{build}"
2 changes: 1 addition & 1 deletion lib/dest/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function dest(outFolder, opt) {

var sourcemapOpt = opt.sourcemaps;
if (typeof sourcemapOpt === 'boolean') {
sourcemapOpt = { sourcemaps: sourcemapOpt };
sourcemapOpt = {};
}

var mapStream = sourcemaps.write(sourcemapOpt.path, sourcemapOpt);
Expand Down
30 changes: 6 additions & 24 deletions lib/dest/writeContents/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
'use strict';

var fs = require('fs');
var writeDir = require('./writeDir');
var writeStream = require('./writeStream');
var writeBuffer = require('./writeBuffer');
var writeSymbolicLink = require('./writeSymbolicLink');

function writeContents(writePath, file, cb) {
function writeContents(writePath, file, callback) {
// If directory then mkdirp it
if (file.isDirectory()) {
return writeDir(writePath, file, written);
Expand All @@ -29,34 +28,17 @@ function writeContents(writePath, file, cb) {

// If no contents then do nothing
if (file.isNull()) {
return complete();
}

function complete(err) {
cb(err, file);
return written();
}

// This is invoked by the various writeXxx modules when they've finished
// writing the contents.
function written(err) {

if (isErrorFatal(err)) {
return complete(err);
}

if (!file.stat || typeof file.stat.mode !== 'number' || file.symlink) {
return complete();
return callback(err);
}

fs.stat(writePath, function(err, st) {
if (err) {
return complete(err);
}
var currentMode = (st.mode & parseInt('0777', 8));
var expectedMode = (file.stat.mode & parseInt('0777', 8));
if (currentMode === expectedMode) {
return complete();
}
fs.chmod(writePath, expectedMode, complete);
});
callback(null, file);
}

function isErrorFatal(err) {
Expand Down
35 changes: 15 additions & 20 deletions lib/dest/writeContents/writeBuffer.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,26 @@
'use strict';

var fs = require('graceful-fs');
var fo = require('../../fileOperations');

var futimes = require('../../futimes');
function writeBuffer(writePath, file, written) {
var opt = {
mode: file.stat.mode,
flag: file.flag,
};

function writeBuffer(writePath, file, cb) {
var stat = file.stat;
fo.writeFile(writePath, file.contents, opt, onWriteFile);

fs.open(writePath, file.flag, stat.mode, function(err, fd) {
if (err) {
return cb(err);
function onWriteFile(writeErr, fd) {
if (writeErr) {
return fo.closeFd(writeErr, fd, written);
}

fs.write(fd, file.contents, 0, file.contents.length, 0, function(error) {
if (error) {
return complete(error);
}
futimes(fd, stat, complete);
});
fo.updateMetadata(fd, file, onUpdate);
}

// Cleanup
function complete(err1) {
fs.close(fd, function(err2) {
cb(err1 || err2);
});
}
});
function onUpdate(statErr, fd) {
fo.closeFd(statErr, fd, written);
}
}

module.exports = writeBuffer;
48 changes: 46 additions & 2 deletions lib/dest/writeContents/writeDir.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,53 @@
'use strict';

var fs = require('graceful-fs');
var mkdirp = require('mkdirp');

function writeDir(writePath, file, cb) {
mkdirp(writePath, file.stat.mode, cb);
var fo = require('../../fileOperations');

function writeDir(writePath, file, written) {
var mkdirpOpts = {
mode: file.stat.mode,
fs: fs,
};
mkdirp(writePath, mkdirpOpts, onMkdirp);

function onMkdirp(mkdirpErr) {
if (mkdirpErr) {
return written(mkdirpErr);
}

fs.open(writePath, 'r', onOpen);
}

function onOpen(openErr, fd) {
// If we don't have access, just move along
if (isInaccessible(openErr)) {
return fo.closeFd(null, fd, written);
}

if (openErr) {
return fo.closeFd(openErr, fd, written);
}

fo.updateMetadata(fd, file, onUpdate);
}

function onUpdate(statErr, fd) {
fo.closeFd(statErr, fd, written);
}
}

function isInaccessible(err) {
if (!err) {
return false;
}

if (err.code === 'EACCES') {
return true;
}

return false;
}

module.exports = writeDir;
76 changes: 36 additions & 40 deletions lib/dest/writeContents/writeStream.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,59 +2,55 @@

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

var fo = require('../../fileOperations');
var streamFile = require('../../src/getContents/streamFile');
var futimes = require('../../futimes');

function writeStream(writePath, file, cb) {
var stat = file.stat;
var outStream;
var outFD;
function writeStream(writePath, file, written) {
var opt = {
mode: file.stat.mode,
flag: file.flag,
};

fs.open(writePath, 'w', file.stat.mode, function(err, fd) {
if (err) {
cb(err);
}

outFD = fd;
outStream = fs.createWriteStream(null, { fd: fd });
var outStream = fs.createWriteStream(writePath, opt);

file.contents.once('error', complete);
file.contents.once('end', readStreamEnd);
outStream.once('error', complete);
outStream.once('finish', complete);
file.contents.once('error', complete);
file.contents.once('end', readStreamEnd);
outStream.once('error', complete);
outStream.once('finish', complete);

// Streams are piped with end disabled, this prevents the
// WriteStream from closing the file descriptor after all
// data is written.
file.contents.pipe(outStream, { end: false });
});
// Streams are piped with end disabled, this prevents the
// WriteStream from closing the file descriptor after all
// data is written.
file.contents.pipe(outStream, { end: false });

function readStreamEnd() {
streamFile(file, {}, function(error) {
if (error) {
return complete(error);
}

futimes(outFD, stat, function(error) {
if (error) {
return complete(error);
}

// All finished with WriteStream, close and clean up
outStream.end();
});
});
streamFile(file, complete);
}

function end(propagatedErr) {
outStream.end(onEnd);

function onEnd(endErr) {
written(propagatedErr || endErr);
}
}

// Cleanup
function complete(err) {
function complete(streamErr) {
file.contents.removeListener('error', complete);
file.contents.removeListener('end', readStreamEnd);
if (outStream) {
outStream.removeListener('error', complete);
outStream.removeListener('finish', complete);
outStream.removeListener('error', complete);
outStream.removeListener('finish', complete);

if (streamErr) {
return end(streamErr);
}
cb(err);

if (typeof outStream.fd !== 'number') {
return end();
}

fo.updateMetadata(outStream.fd, file, end);
}
}

Expand Down
13 changes: 9 additions & 4 deletions lib/dest/writeContents/writeSymbolicLink.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@

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

function writeSymbolicLink(writePath, file, cb) {
function writeSymbolicLink(writePath, file, written) {
// TODO handle symlinks properly
fs.symlink(file.symlink, writePath, function(err) {
if (err && err.code !== 'EEXIST') {
return cb(err);
if (isFatalError(err)) {
return written(err);
}

cb(null, file);
written();
});
}

function isFatalError(err) {
return (err && err.code !== 'EEXIST');
}

module.exports = writeSymbolicLink;
Loading

0 comments on commit 822908d

Please sign in to comment.