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

Add progress reporting #9

Merged
merged 11 commits into from
Jul 22, 2016
Merged

Conversation

YurySolovyov
Copy link
Contributor

API is likely to change, submitting PR to get some feedback.
Related to sindresorhus/cpy#33

@YurySolovyov
Copy link
Contributor Author

Tests pass locally without xo, and there are some errors even in clean repo.

var remains = progress.stat.size - write.bytesWritten;
var percent = ((written / progress.stat.size) * 100).toPrecision(2);
opts.onProgress({
file: src,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

maybe add both files here like src + dest ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

👍 src and dest instead of file

return new Promise(function startRead(resolve, reject) {
var read = fs.createReadStream(src);
function createReadPromise(src) {
var read = fs.createReadStream(src);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Move this back into the Promise constructor argument (line 26), because fs.createReadStream might throw some error (maybe in the future), e.g.
fs.createReadStream('/dev/null', {flags: 'unicorns'})

@schnittstabil
Copy link
Collaborator

Tests pass locally without xo, and there are some errors even in clean repo.

xo is undergoing continual improvements, some style violations are introduced by your PR though

@YurySolovyov
Copy link
Contributor Author

I think I can fix all of them, since I'm here anyway.

@schnittstabil
Copy link
Collaborator

remains and percent, are a nice ideas. However, I think remains isn't necessary in most use cases, and percent isn't very useful: some require values between 0 and 1, some need a different precision and some don't care for percent at all. – Personally, I would drop both.

@YurySolovyov
Copy link
Contributor Author

@schnittstabil I'd kept percent, but made it between 0 and 1.
Also what do you think of #9 (comment)

file: src,
written: written,
remains: remains,
percent: percent
Copy link
Collaborator

Choose a reason for hiding this comment

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

size would be nice, to calculate the total e.g. in cpy – or eion of course

Copy link
Contributor Author

Choose a reason for hiding this comment

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

that would also allow to drop remains, since that would be very easy to calc it yourself.

@YurySolovyov
Copy link
Contributor Author

Updated. Will take a look at 0.10 failure a bit later.

@YurySolovyov
Copy link
Contributor Author

I have no idea what happens with 0.10 to be honest. Seems like something with encodings?

@YurySolovyov
Copy link
Contributor Author

@sindresorhus @schnittstabil fixed 0.10 by using readable-stream module to wrap old-style streams.
I think this is due to how old streams treat adding data events.

return fsP.stat(src).then(function (stat) {
progress.stat = stat;
}).catch(function (err) {
Promise.reject(new CpFileError('NO_ENTRY', err));
Copy link
Owner

Choose a reason for hiding this comment

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

This isn't doing anything as it's not returned. Needs a regression test for that.

The correct solution is to just throw the error.

@YurySolovyov
Copy link
Contributor Author

@sindresorhus now that I tried this, I would say that maybe EventEmitter idea was better. Do you have any preference here?

@sindresorhus
Copy link
Owner

I would personally prefer EventEmitter as it's more Node'y.

@YurySolovyov
Copy link
Contributor Author

YurySolovyov commented Jul 17, 2016

@sindresorhus Re-implemented with EventEmitter.Though it is not really pretty either.

I wonder if we can somehow make return values of .on and .then to return object that has both EventEmitter and Promise methods so we can nicely chain it.

Edit: something like carrack?

@@ -80,6 +119,10 @@ module.exports = function (src, dest, opts) {
});
}
});

promise.events = progress.emitter;
Copy link
Collaborator

Choose a reason for hiding this comment

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

As far as I understand, @sindresorhus suggested sth. like (sindresorhus/cpy#33 (comment)):

promise.on = progress.emitter.on.bind(progress.emitter);

So, one can use it that way:

const p = cpFile();
p.on();
p.then()

Copy link
Collaborator

Choose a reason for hiding this comment

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

We may also do this:

promise.on = function () {
  progress.emitter.on.apply(progress.emitter, arguments);

  return promise;
};

So, one can use it this way:

cpFile()
  .on()
  .then();

// but not this way (!)
cpFile()
  .then()
  .on();

Copy link
Owner

Choose a reason for hiding this comment

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

👍

@YurySolovyov
Copy link
Contributor Author

Implemented @schnittstabil suggestions.

var progress = {
stat: null,
emitter: new EventEmitter()
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

Hence we have cpFile.on(…), stat and emitter aren't intended only for progress stuff anymore.
We should drop progress.… by:

var stat = null;
var eventEmitter = new EventEmitter();

Copy link
Contributor Author

Choose a reason for hiding this comment

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

but if I drop stat event, it would be the only for progress, right?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, but IMO it is easier to read and to maintain, e.g. we may emit other events in the future...

Copy link
Collaborator

Choose a reason for hiding this comment

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

As we shouldn't reuse stat for updating the access-times etc., we may use only the size:

- var stat = null;
+ var size = null;
// …
var promise = fsP.stat(src).then(function (stat) {
+       size = stat.size;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

maybe init it with 0 then? :)

Copy link
Collaborator

Choose a reason for hiding this comment

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

I should have thought first – the right thing would be simply:

var size;
  • 0 means the file is empty
  • null means we don't care
  • undefined means the size is not yet known

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@schnittstabil while this is nice, I have question if we want to optimize for this at all, is 1 stat call worth additional branching and checking? I'm ok with either answer, just wanted to raise a question. Even if it is, we might be able to avoid it in some other way.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I've thought about that, but we have:

cpFile() // returns the promise
  .on()  // but the listeners are added afterwards

Therefore, a workaround to avoid the stat call would be rather awkward IMO, but if you have an idea…

Copy link
Contributor Author

Choose a reason for hiding this comment

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

that way, if we have to do stat anyway, why check?

Copy link
Collaborator

Choose a reason for hiding this comment

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

, why check?

I do not know if I have understood you correctly.

I think we should always fs.lstat to determine the size. The alternative would be to postpone the call until a listener is added, which I think is too cumbersome.

Checking emitter.listenerCount('progress') > 0 before emitting is double workload, emitter.emit does a similar check.

Checking size !== null before emitting was based on a misconception (mentioned below), which have worked well in case of opts.onProgress 😒

Hence, I don't think we should check before emitting progress at all.

@YurySolovyov
Copy link
Contributor Author

@schnittstabil Updated.

  • Use write.on('close'). Actually works, thanks.
  • Report progress for empty files only on end.
  • Drop naive EE optimization

@schnittstabil
Copy link
Collaborator

@YurySolovyov Sorry for the delay. LGTM, only documentation of .on(…) in readme.md remains.

@YurySolovyov
Copy link
Contributor Author

@schnittstabil Added docs

@schnittstabil
Copy link
Collaborator

@sindresorhus Do you have further suggestions?

dest: dest,
size: size,
written: written,
percent: percent
Copy link
Owner

Choose a reason for hiding this comment

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

I think we should round this to 2 decimals. E.g. 0.45.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What if user wants a better precision?
Say if you have a progress bar with value in pixels, and you calc done value like

const done = progress * fullWidth;

Given that file can be big, you can have multiple progress events that rounds for same 0.45 value, making it stay at same position during multiple progress events.

Copy link
Owner

Choose a reason for hiding this comment

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

Yeah, sure, not a big deal.

@YurySolovyov
Copy link
Contributor Author

@sindresorhus Updated.

@sindresorhus sindresorhus merged commit 25a281c into sindresorhus:master Jul 22, 2016
@sindresorhus
Copy link
Owner

Awesome! Thanks for working on this @YurySolovyov. It turned out great :)

@YurySolovyov
Copy link
Contributor Author

@sindresorhus and @schnittstabil, thanks for your patience!

@sindresorhus
Copy link
Owner

3.2.0 published. Now on to cpy

@YurySolovyov YurySolovyov deleted the progress branch July 22, 2016 20:51
@schnittstabil
Copy link
Collaborator

Thanks a lot for your thorough work @YurySolovyov!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants