Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature metadata refactor #151

Merged
merged 8 commits into from
Feb 26, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 = {};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this wasn't a valid option for gulp-sourcemaps so I snuck it in

}

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