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

progress callback draft #433

Closed

Conversation

ITotalJustice
Copy link
Contributor

Hi! I added an issue here #432 a few minutes ago, but then i realised that there was an already existing issue that's been open for quite a while sadly #111.

This is a quick draft / example of adding a progress callback option, and it works 😄 . This allows us to create progress bars and also cancel any request early by returning a non zero value from the callback!

If there's any chance of adding this and you want me to clean up anything, let me know! Thanks

@ITotalJustice
Copy link
Contributor Author

Made some changes to the initial pr. I simple class that the user passes a function callback and a data that they want to be passed into callback (this was missing from my initial pr). I think this looks much better that the initial pr.

@ITotalJustice
Copy link
Contributor Author

ITotalJustice commented Aug 8, 2020

I was thinking, would it be a good idea to create 2 more classes for write and read callbacks? I could rename progresscb.h to callback.h and then have the 3 classes there.

Iirc there's a few more libcurl callbacks also, such as headers. Maybe it might be a good option for me to add those also?

EDIT: Not sure why one of the tests fails. It says it's the callback test. I checked if maybe i named something the same as a func / var in the test, but i haven't. I did change 2 const vars to constexpr, but surely it can't be that 🤔

@ITotalJustice
Copy link
Contributor Author

ITotalJustice commented Aug 9, 2020

I noticed that a lot of the time, people call curl_easy_getinfo in the progress callback. A way to still support this would be give curl a callback created cpr, which when called, calls the user callback also passing in a const *curl.

Or I think a better way would to pass a const struct / class to the user callback. This would simplify the callback that the user has to create as it will only have one pram in the function, then access only what they want from the struct / class.

One immediate problem is that let's say the user returns != 0 from the user callback (meaning that they want to exit), this value would actually be returned back to the cpr function. I mean, the cpr class can just save that value and then return that value on the next progress call, but it wouldn't be immediate. However it should be fast enough, and i think it is 100% worth that trade-off of a max 1 second delay (usually only like 0.3 second) to instead allow the user full access to curl_easy_getinfo.

EDIT: After reading the easy curl header, i found this in regards to the curl_easy_getinfo This function is intended to get used AFTER a performed transfer, all results from this function are undefined until the transfer is completed.. I am not sure why a lot of liburls examples go completely against this statement...
If the results are undefined then this makes this whole idea (in this message) pretty irrelevant.

@COM8 COM8 added this to the CPR 1.6.0 milestone Aug 9, 2020
include/cpr/progresscb.h Outdated Show resolved Hide resolved
include/cpr/progresscb.h Outdated Show resolved Hide resolved
include/cpr/progresscb.h Outdated Show resolved Hide resolved
@COM8
Copy link
Member

COM8 commented Aug 9, 2020

Hi @ITotalJustice, thank you for taking the time to add support for this this feature to CPR!

@COM8
Copy link
Member

COM8 commented Aug 9, 2020

I was thinking, would it be a good idea to create 2 more classes for write and read callbacks? I could rename progresscb.h to callback.h and then have the 3 classes there.

Iirc there's a few more libcurl callbacks also, such as headers. Maybe it might be a good option for me to add those also?

I agree. This would be a great idea. I'd say go for it.

EDIT: Not sure why one of the tests fails. It says it's the callback test. I checked if maybe i named something the same as a func / var in the test, but i haven't. I did change 2 const vars to constexpr, but surely it can't be that thinking

Don't worry about macOS. It fails for every second commit and we currently do not know why.

@COM8
Copy link
Member

COM8 commented Aug 9, 2020

[...]
EDIT: After reading the easy curl header, i found this in regards to the curl_easy_getinfo This function is intended to get used AFTER a performed transfer, all results from this function are undefined until the transfer is completed.. I am not sure why a lot of liburls examples go completely against this statement...
If the results are undefined then this makes this whole idea (in this message) pretty irrelevant.

I also do not think, we should use curl_easy_getinfo for progress callbacks.
To me it looks like a good idea to give the user access to the curl_easy_getinfo results, but those results should then be made available after the request in the Response class of the request.
I will create a PR for this.

@COM8
Copy link
Member

COM8 commented Aug 9, 2020

Thinks that have to be done before a merge:

  • Please create a PR against the gh-pages branche to add documentation for this great feature.
  • Update the README.md features list.
  • Add unit tests

@ITotalJustice
Copy link
Contributor Author

I've rebased against master, however it's now showing the master commit history in this PR. Is this correct? It's my first time rebasing haha, I hope that I didn't mess anything up.

I'll work on the documentation now anyway.

@COM8
Copy link
Member

COM8 commented Aug 9, 2020

I've rebased against master, however it's now showing the master commit history in this PR. Is this correct? It's my first time rebasing haha, I hope that I didn't mess anything up.

I'll work on the documentation now anyway.

Interesting, but should be fine :)

ITotalJustice added a commit to ITotalJustice/cpr that referenced this pull request Aug 9, 2020
@ITotalJustice
Copy link
Contributor Author

ITotalJustice commented Aug 9, 2020

So i've changed how the progress callback works. no longer is the user function directly passed to libcurl, instead, we wrap the function and pass everything in a nice tidy struct like this:

int ProgressCallbackFunction(ProgressCallbackData* data, cpr_off_t dlnow, cpr_off_t dltotal, cpr_off_t ulnow, cpr_off_t ultotal) {
    data->data.downloadNow = dlnow;
    data->data.downloadTotal = dltotal;
    data->data.uploadNow = ulnow;
    data->data.uploadTotal = ultotal;
    return data->cb(&data->data);
  }

which then for the user, all they have to do is this:

int myCallback(cpr::ProgressCallbackUser *data) {
    MyData *myData = (MyData*)data->userData;
    std::cout << myData->message << std::endl;
    return 0;
}

I think this is ideal because it makes the callback struct for the user much simpler (mirrors how the response class works) and also because we can add more info to the struct if libcurl api ever changes without the user having to change anything.

The names i've given everything are just temporary, so let me know what you would like to rename them as. Also let me know where I should put the structs / function, or are you happy with where they are?

EDIT: I tried to see if there's a way to use a template for the userdata, that way the user wouldn't have to cast the void*, but I couldn't get it to work. Do you think it's possible / worth looking into?

@ITotalJustice
Copy link
Contributor Author

Sorry for making another big change 😄

I've added read and write callback support. This will allow for users to write data to disk in chunks, rather than storing it all in memory. This closes a few more issues such as #255 #208 #111 maybe #25 #271

include/cpr/callback.h Outdated Show resolved Hide resolved
@COM8
Copy link
Member

COM8 commented Aug 9, 2020

I think something went wrong during the rebase earlier.
I'd suggest you try it again, since there are a lot of conflicts, that should not be conflicts.
For reference: https://gist.github.com/ravibhure/a7e0918ff4937c9ea1c456698dcd58aa

include/cpr/callback.h Outdated Show resolved Hide resolved
this will be needed for unit tests to make sure that cancelling the request works. Also useful for the user to know if curl finished the request *before* the request was cancelled, as maybe then the user might still cache some data (like images or auth login etc...)
the docs specify `char*` but actually use `void*` in every example... So I guess it doesn't matter, but probably better to use the generic void*.
also added space at the end of callback.h file
@ITotalJustice ITotalJustice force-pushed the add-progress-callback branch from 36938a6 to 9cea9c7 Compare August 9, 2020 16:45
@ITotalJustice
Copy link
Contributor Author

I rebased following the guide that you sent (thank you!). Did this fix the issues?

@xloem
Copy link
Contributor

xloem commented Sep 17, 2020

Hey, I found this PR and see it's been sitting for about a month. Do you guys need help with the tests and documentation situation, or do you just need some time?

@COM8
Copy link
Member

COM8 commented Sep 18, 2020

Only the documentation for the return values is still pending.
Besides that, this PR is ready to be merged.

void Session::Impl::SetReadCallback(const ReadCallback& read) {
CURL* curl = curl_->handle;
if (curl) {
curl_easy_setopt(curl, CURLOPT_READFUNCTION , read.callback.cb);
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm looking at this patch a little and noticed that CURLOPT_READFUNCTION is assigned a value of a type of int(*read_cb_t)(ReadCallbackUser*); (types in callback.h), whereas https://curl.haxx.se/libcurl/c/CURLOPT_READFUNCTION.html specifies a type of size_t (*read_callback)(char *buffer, size_t size, size_t nitems, void *userdata);. It seems like that would cause a crash if used, due to the change: am I mistaken?

if (curl) {
curl_easy_setopt(curl, CURLOPT_READFUNCTION , read.callback.cb);
curl_easy_setopt(curl, CURLOPT_READDATA, &read.callback.data);
this->userRead_ = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

this member variable appears unused

@xloem
Copy link
Contributor

xloem commented Sep 18, 2020

I'm not sure this PR is complete; it kind of looks like a proof of concept waiting for confirmation around design choices, maybe even for somebody else to finish when needed. It looks like most of the work is in the progress callback. I might try to contribute a little.

@ITotalJustice
Copy link
Contributor Author

Just got an email notification for this, very sorry again for late to replying (can't believe it's been a month already!)

@xloem yeah it was mainly a quick draft. i put it together in like an hours because i needed to early cancel api requests. There's a few(?) unused variables as i changed the design of the pr a few times over. It would be great if you can contribute to the pr (or a new pr). I don't really have much free time sadly

@xloem
Copy link
Contributor

xloem commented Sep 19, 2020

I'm spending some time bringing this together locally. I expect to finish the tests, likely the docs.

@xloem
Copy link
Contributor

xloem commented Sep 19, 2020

Completed work for this PR at ITotalJustice#1

@COM8
Copy link
Member

COM8 commented Sep 22, 2020

Closing this one in favour of #446.

@COM8 COM8 closed this Sep 22, 2020
COM8 added a commit that referenced this pull request Sep 23, 2020
* add documentation for ProgressCallback For  #433

* fix my example code

* start adding info about read / write callbacks

* Update advanced-usage.md

requested changes #1

* Updated docs for my changes in other branch, including mentioning return values.

* forgot to mention DebugCallback in documentation; added it

* Update advanced-usage.md

* Update advanced-usage.md

* Update advanced-usage.md

* Update advanced-usage.md

* Update advanced-usage.md

Co-authored-by: ITotalJustice <47043333+ITotalJustice@users.noreply.github.com>
Co-authored-by: Fabian Sauter <sauter.fabian@mailbox.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants