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

[file system][ios] Add download and upload in background #7380

Merged

Conversation

lukmccall
Copy link
Contributor

@lukmccall lukmccall commented Mar 16, 2020

Why

Part of #5841.

How

  • makes background session
  • creates task delegate
  • uses NSURLSessionUploadTask and NSURLSessionDownloadTask

ToDo

  • pod install in bare expo

Test Plan

@lukmccall lukmccall force-pushed the @lukmccall/expo-file-system-background-download-upload branch from f237602 to dfcb8a0 Compare March 16, 2020 23:15
@lukmccall lukmccall marked this pull request as ready for review March 17, 2020 14:28
@lukmccall lukmccall requested a review from tsapeta March 17, 2020 14:28
@lukmccall lukmccall linked an issue Mar 19, 2020 that may be closed by this pull request
Copy link
Member

@tsapeta tsapeta left a comment

Choose a reason for hiding this comment

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

Generally looks good 👍

  • Changelog entry and docs are missing 😞
  • Would it be possible to add examples in NCL and add some tests?

Also some comments below 👇

@lukmccall
Copy link
Contributor Author

@tsapeta, I'll add an entry to the changelog and update docs in separate PR.

It'll be difficult to add tests - we need a server to test upload functionality properly. However, I can add a session type switch to NCL.

@lukmccall lukmccall requested a review from tsapeta March 23, 2020 11:24
Copy link
Contributor

@bbarthec bbarthec left a comment

Choose a reason for hiding this comment

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

Great job! 💯
Is it possible for you to create some python or node script that would allow us to test it? 😅
It would be nice to be sure it always works 🤔

@tsapeta
Copy link
Member

tsapeta commented Mar 23, 2020

@tsapeta, I'll add an entry to the changelog and update docs in separate PR.

Sorry but I'm against adding changelog entries in different PRs - changelogs must always be updated together with a change that changed anything in module's behavior. We're going to use Danger.js to enforce changelog entries on all PRs so why should we make any exceptions before the actual Danger.js action is done? Let's say that before this happens, I'm the one who manually checks all these PRs 😃

@lukmccall lukmccall force-pushed the @lukmccall/expo-file-system-background-download-upload branch from 1f65208 to d6707e1 Compare March 24, 2020 13:34
@lukmccall lukmccall requested a review from bbarthec March 24, 2020 13:40
packages/expo-file-system/CHANGELOG.md Outdated Show resolved Hide resolved
packages/expo-file-system/CHANGELOG.md Outdated Show resolved Hide resolved
packages/expo-file-system/ios/EXFileSystem/EXFileSystem.m Outdated Show resolved Hide resolved
packages/expo-file-system/src/FileSystem.types.ts Outdated Show resolved Hide resolved
packages/expo-file-system/src/FileSystem.types.ts Outdated Show resolved Hide resolved
Copy link
Contributor

@sjchmiela sjchmiela left a comment

Choose a reason for hiding this comment

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

Nice work! 👍

@lukmccall lukmccall force-pushed the @lukmccall/expo-file-system-background-download-upload branch from 647bbd1 to 4596415 Compare April 2, 2020 15:31
Copy link
Contributor

@sjchmiela sjchmiela left a comment

Choose a reason for hiding this comment

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

Sorry it took me so long! Thank you for making the structure clear! Looking forward to reviewing it again!

packages/expo-file-system/CHANGELOG.md Outdated Show resolved Hide resolved
packages/expo-file-system/CHANGELOG.md Outdated Show resolved Hide resolved
@@ -48,6 +86,12 @@ export enum EncodingType {
Base64 = 'base64',
}

export enum FileSystemHttpMethods {
Copy link
Contributor

Choose a reason for hiding this comment

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

The conversation at #7380 (comment) has been resolved — was it by accident, or is it a strong decision to keep using enums for HTTP methods, or have I missed some discussion?

Copy link
Contributor

Choose a reason for hiding this comment

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

Nonetheless, I think this enum should be named with a singular form, i.e. FileSystemHttpMethod.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, it was resolved by accident.
However, I think we should give the user the ability to change the HTTP method. And we can do it by using strings or enum. I prefer using enum - it is safer. If you strongly disagree with this, I can change it ;)

packages/expo-file-system/src/FileSystem.types.ts Outdated Show resolved Hide resolved
packages/expo-file-system/ios/EXFileSystem/EXFileSystem.m Outdated Show resolved Hide resolved
packages/expo-file-system/ios/EXFileSystem/EXFileSystem.m Outdated Show resolved Hide resolved
packages/expo-file-system/ios/EXFileSystem/EXFileSystem.m Outdated Show resolved Hide resolved
packages/expo-file-system/ios/EXFileSystem/EXFileSystem.m Outdated Show resolved Hide resolved
@lukmccall lukmccall requested a review from sjchmiela April 8, 2020 20:14
Copy link
Contributor

@bbarthec bbarthec left a comment

Choose a reason for hiding this comment

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

LGTM 💯 I think we are ready to ship it once all concerns are resolved 😄

Copy link
Contributor

@sjchmiela sjchmiela left a comment

Choose a reason for hiding this comment

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

🏅

@lukmccall lukmccall requested a review from sjchmiela April 9, 2020 15:39
Copy link
Contributor

@sjchmiela sjchmiela left a comment

Choose a reason for hiding this comment

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

🙇

packages/expo-file-system/ios/EXFileSystem/EXFileSystem.m Outdated Show resolved Hide resolved
packages/expo-file-system/ios/EXFileSystem/EXFileSystem.h Outdated Show resolved Hide resolved
packages/expo-file-system/ios/EXFileSystem/EXFileSystem.h Outdated Show resolved Hide resolved
return [[NSFileManager defaultManager] fileExistsAtPath:path];
}

- (NSString * _Nullable)_importHttpMethod:(NSNumber *)httpMethod
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is a fourth time I mention this concern, it somehow always gets discarded on push 😃 — how about accepting a string httpMethod, like fetch does?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've commented on that here: #7380 (comment) ;)

Moreover, I don't like strings here, cause someone might pass get - that will crash the app. So, it would be nice to have some kind of validations. And what if someone misspelled it.

Copy link
Contributor

Choose a reason for hiding this comment

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

IMHO it would be best to:

  • try-catch not to crash the app if a user passed a get
  • throw an error with a helpful message if an invalid method has been passed in.

This would make sure we don't limit the developers if they wanted to use some other HTTP method that is supported we don't know of. But I don't have a very strong opinion, so I will defer to @bbarthec's opinion on that.

Copy link
Contributor

Choose a reason for hiding this comment

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

@lukmccall, I've discussed the thing with @sjchmiela and I think that going with fetch-like approach is desirable: https://fetch.spec.whatwg.org/#methods
I suggest enforcing TS type: type AcceptedHttpMethod = 'POST' | 'PUT' | 'PATCH' is sufficient guarding point, but later on let's go with String.toUpperCase() anyway while passing arguments to native side.
Are there any consequences of passing method that is not expected, eg. CHICKEN (I mean: are we semantically base on this value?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Are there any consequences of passing method that is not expected, eg. CHICKEN (I mean: are we semantically base on this value?)

The task will be canceled by the system later on. Then we will reject the promise. So, I think that everything will work fine ;)

packages/expo-file-system/ios/EXFileSystem/EXFileSystem.m Outdated Show resolved Hide resolved

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
if (_isActive) {
Copy link
Contributor

Choose a reason for hiding this comment

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

So… since first we deactivate the dispatcher and only then invalidateAndCancel the sessions in EXFileSystem's dealloc, doesn't it mean that here we wouldn't act on tasks cancellation?

Is it the way we want to handle this? Don't we want to reject the Promise, or inform of the termination?

This _isActive logic seems too easy 😃.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, we deactivate the dispatcher before invalidateAndCancel because we don't want to act on the task events.
We are in the middle of the deallocating of the react context. So, I believe we shouldn't reject the Promise or inform of the termination. Tasks will be canceled, but we don't know when.

Copy link
Contributor

Choose a reason for hiding this comment

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

Do you think that adding a separate invalidate call to native modules would make it easier to handle this situation? If we invalidatedAndCancelled sessions while invalidating modules maybe the task:didCompleteWithError: would be called in time before deallocating the bridge and a Promise rejection would come through? If this would be the case I think it could be worth to implement it… 🤔

@lukmccall lukmccall force-pushed the @lukmccall/expo-file-system-background-download-upload branch from 35e2394 to 937d247 Compare April 15, 2020 16:19
return [[NSFileManager defaultManager] fileExistsAtPath:path];
}

- (NSString * _Nullable)_importHttpMethod:(NSNumber *)httpMethod
Copy link
Contributor

Choose a reason for hiding this comment

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

IMHO it would be best to:

  • try-catch not to crash the app if a user passed a get
  • throw an error with a helpful message if an invalid method has been passed in.

This would make sure we don't limit the developers if they wanted to use some other HTTP method that is supported we don't know of. But I don't have a very strong opinion, so I will defer to @bbarthec's opinion on that.

packages/expo-file-system/ios/EXFileSystem/EXFileSystem.m Outdated Show resolved Hide resolved
packages/expo-file-system/ios/EXFileSystem/EXFileSystem.m Outdated Show resolved Hide resolved

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
if (_isActive) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you think that adding a separate invalidate call to native modules would make it easier to handle this situation? If we invalidatedAndCancelled sessions while invalidating modules maybe the task:didCompleteWithError: would be called in time before deallocating the bridge and a Promise rejection would come through? If this would be the case I think it could be worth to implement it… 🤔

@lukmccall
Copy link
Contributor Author

Do you think that adding a separate invalidate call to native modules would make it easier to handle this situation? If we invalidatedAndCancelled sessions while invalidating modules maybe the task:didCompleteWithError: would be called in time before deallocating the bridge and a Promise rejection would come through? If this would be the case I think it could be worth to implement it… 🤔

I think, this a good idea in general. However, I'm not sure if this helps here. Maybe we can reject all promises before the react context dies, but I don't think we need this in that particular case. Moreover, the js code will not know what causes rejection. So, the user could write a code that retries the download and this will cause bugs - the file-system module will no longer exist.

@sjchmiela
Copy link
Contributor

I think, this a good idea in general. However, I'm not sure if this helps here. Maybe we can reject all promises before the react context dies, but I don't think we need this in that particular case. Moreover, the js code will not know what causes rejection. So, the user could write a code that retries the download and this will cause bugs - the file-system module will no longer exist.

So, to sum up, the only updates we drop are download/upload progress updates which happened after deallocing EXFileSystem? So having no update on such request is like communicating "this request didn't complete", so the user won't be tempted to mark the request as either done or failed?

What I'm worried about is the scenario when a developer manages the downloads depending on the callbacks, so eg.:

  • the user requests to download eg. a map of Poland
  • developer saves information about a download-in-progress to AsyncStorage (that it is being downloaded)
  • the app is closed (refreshed, whatever, EXFileSystem is dealloced)
  • the app is opened
  • the information that the developer saved (that the download is processing) is invalid anymore

Is this how one would use the FileSystem module? Or is it obvious to always check if the download is processing when the app starts? How? 🤔

@lukmccall lukmccall force-pushed the @lukmccall/expo-file-system-background-download-upload branch from d3ebdc9 to 9af4920 Compare April 27, 2020 14:10
@lukmccall lukmccall requested a review from bbarthec April 27, 2020 14:10
@lukmccall lukmccall merged commit da65a76 into master Apr 27, 2020
lukmccall added a commit that referenced this pull request Apr 29, 2020
* [file-system] make downloadAsync work in backgroud

* [file-system] Upload method

* [file-system] Add body to response

* [file-system] Refactor

* [file system] Handle resumable downloads

* [file system] Add background/foreground option

* [file system] Make background session default

* [file system] Apply requested changes

* [file system] Fix typo

* [file system] Apply requested changes

* [file system] Add changelog

* [expo-file-system] Apply requested changes

* [expo-file-system] Fix session lifetime

* [expo-file-system] Refactor

* [expo-file-system] Fix headers

* [expo-file-system] Apply requested changes

* [expo-file-system] Fix lifetime of EXFileSystem

* [expo-file-system] Extract resumables manger

* [expo-file-system] Pass body safely

* [expo-file-system] Pod install

* [expo-file-system] Apply requested changes

* [expo-file-system] Apply requested changes

* [expo-file-system] Apply requested changes

* [expo-file-system] Pod install in bare-expo

* [expo-file-system] EXResumableManager -> EXResumablesManager

* [expo-file-system] Fix error codes
@tsapeta tsapeta deleted the @lukmccall/expo-file-system-background-download-upload branch February 13, 2021 19:07
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.

[iOS] Look into implementing Background Upload
4 participants