parse-community / Parse-SDK-Android

The Android SDK for Parse Platform
https://parseplatform.org/
Other
1.88k stars 739 forks source link

fix: users logged out after SDK upgrade due to different cache path #1168

Closed rommansabbir closed 2 years ago

rommansabbir commented 2 years ago

ParsePlugins ::

parse-github-assistant[bot] commented 2 years ago

Thanks for opening this pull request!

codecov[bot] commented 2 years ago

Codecov Report

Merging #1168 (4e2c22c) into master (4842329) will increase coverage by 0.50%. The diff coverage is 90.00%.

@@             Coverage Diff              @@
##             master    #1168      +/-   ##
============================================
+ Coverage     66.81%   67.31%   +0.50%     
- Complexity     2249     2285      +36     
============================================
  Files           121      122       +1     
  Lines          9892     9962      +70     
  Branches       1332     1343      +11     
============================================
+ Hits           6609     6706      +97     
+ Misses         2771     2735      -36     
- Partials        512      521       +9     
Impacted Files Coverage Δ
parse/src/main/java/com/parse/ParseFileUtils.java 45.78% <75.00%> (+9.07%) :arrow_up:
...in/java/com/parse/ParseCacheDirMigrationUtils.java 91.80% <91.80%> (ø)
parse/src/main/java/com/parse/Parse.java 65.58% <100.00%> (+3.89%) :arrow_up:
...rc/main/java/com/parse/ParseRESTObjectCommand.java 73.07% <0.00%> (-11.54%) :arrow_down:
parse/src/main/java/com/parse/ParseRequest.java 82.00% <0.00%> (+2.00%) :arrow_up:
parse/src/main/java/com/parse/ParsePlugins.java 50.00% <0.00%> (+18.75%) :arrow_up:

Continue to review full report at Codecov.

Legend - Click here to learn more Δ = absolute <relative> (impact), ø = not affected, ? = missing data Powered by Codecov. Last update 4842329...4e2c22c. Read the comment docs.

azlekov commented 2 years ago

Here you can see an migration logic about the push notifications, I have expected to see something similar but much more simple: https://github.com/parse-community/Parse-SDK-Android/commit/7d0faa38bf3ccc85b715dc61965d5b82103c19a4#diff-6cb14853da236c30db3f14e4005ea40c5c45cb8fcb9fc49541693186620de1dcR60

mman commented 2 years ago

@rommansabbir Thanks for the PR, but looking at the proposed fix I think it is not gonna work at all.

There are potentially multiple cached files stored in both, the cache dir and the files dir. Your code will probably allow to read them from the original location, and then you delete them when the app exists (assuming the delete on exit works, as it might not as @L3K0V explains).

The actually files we care about are never moved anywhere, and even if they will have been be re-saved by the app at some point, they would end up in the old location and then then get deleted.

Needs more work IMHO...

mtrezza commented 2 years ago

Hi everyone, just to emphasize it, this is a particularly important PR, because any release without this migration logic is for most people likely a useless release. So this PR currently blocks the release pipeline. The sooner we get this merged the better, considering we have another issue with Facebook Login that is waiting to get merged. Any helping hand here is greatly appreciated and we have a bounty on this issue as well.

rommansabbir commented 2 years ago

@mtrezza @mman @L3K0V First of all, sorry for messed up with the PR. This is my first contribution to a such big community of Parse.

About my logic for cache path migrations. Based on @mman migration thoughts (https://github.com/parse-community/Parse-SDK-Android/issues/1158#issuecomment-1110097992)

I came up with this ::

If the solution seems suitable for migration process, I will create a new PR (which will include changes in ParsePlugins.java and ParseFileUtils.java).

Let me know, Thanks.

mman commented 2 years ago

@rommansabbir Yes, this is the approach I would take, but please note that since 3.0.0 is already in the wild, and since it forced people to re-login, there will be cases where there will be fresh files in new location com.parse as well as files in old location, the app_Parse directory. In that case, the new files should be used, and old just deleted...

rommansabbir commented 2 years ago

One things more, in the old version, I have seen that ParsePlugins.getParseDir() API return file for app_Parse and it's deprecated. But in new APIs ParsePlugins.getCacheDir() and ParsePlugins.getFilesDir() points to (eg.) */cache/com.parse and */files/com.parse, two different path.

So, when running migration, for a specific file, in which location I should point to move the spcific file, */cache/com.parse or */files/com.parse?.

mman commented 2 years ago

@rommansabbir That is exactly what I have not yet had a time to investigate properly and in detail (read: This PR is actually trickier then it may appear on a first look).

rommansabbir commented 2 years ago

@mman I think we can find out from where the old API is accessed and which FILE_NAME (File name) was used, eg. File(ParsePlugins.get().getParseDir(), FILENAME_CURRENT_CONFIG). We can look for others Constants like FILENAME_CURRENT_CONFIG. So, when migration is ongoing, we can check for item name matches with our Constants list or not, if match - then move item to */files/com.parse/ else */cache/com.parse/

mtrezza commented 2 years ago

So, when running migration, for a specific file, in which location I should point to move the spcific file, /cache/com.parse or /files/com.parse?.

The two storages have a different purpose and are managed differently. That determines which file should go where.

For example, the user session token would go into persistent storage.

I would just make a list of all the SDK's internal files from looking at the pre-3.x logic, post the list here and then we can determine whether it should be persistent or not. That wrapped into the logic I proposed earlier should do the trick. I don't think there is a lot more to it.

rommansabbir commented 2 years ago
ParsePlugins::installationId(){File(getParseDir(), INSTALLATION_ID_LOCATION)}
ParseCorePlugins::getCurrentUserController() {File(Parse.getParseDir(), FILENAME_CURRENT_USER)}
ParseCorePlugins::getConfigController() {File(ParsePlugins.get().getParseDir(), FILENAME_CURRENT_CONFIG)}
ParseCorePlugins::getCurrentInstallationController() {File(ParsePlugins.get().getParseDir(), FILENAME_CURRENT_INSTALLATION)}
ParseCommandCache::getCacheDir(){File(Parse.getParseDir(), "CommandCache")}
LocalIdManager(Parse.getParseDir()){File(root, "LocalId")}
PushRouter::getInstance(){File(ParsePlugins.get().getParseDir(), LEGACY_STATE_LOCATION)}

from where ParsePlugins.get().getParseDir() API has been accessed. All of those files managed by SDK itself, so when migration is ongoing, we can look for those listed files and move files to */files/com.parse/ directory, then delete the old files from app_Parse by covering known cases.

After that, all other files that left in app_Parse directory should move to */cache/com.parse/ directory by covering known cases.

  • The files dir is exclusively managed by the app. It is persistent and must be managed by the app. If the app doesn't delete outdated files there the files will stay and the app's footprint will swell up until the OS will report a storage issue to the user.

Version: 2.0.6

ParseCorePlugins::getFileController(){
             ParseFileController(
                    ParsePlugins.get().restClient(), 
                    Parse.getParseCacheDir("files")
            ))
}

calling Parse.getParseCacheDir("files") API which point to */cache/com.parse/. In this case, all files (eg. when storing an ParseObject to LocalDataStore that contain an File itself call something.mp3) that are stored in the cache directory should point to */files/com.parse/ as something.mp3 is managed by SDK itself.

mtrezza commented 2 years ago

All of those files managed by SDK itself, so when migration is ongoing, we can look for those listed files and move files to */files/com.parse/ directory, then delete the old files from app_Parse by covering known cases.

Sounds good; how about the other modules like facebook, fcm, it is possible that they store anything in there as well?

After that, all other files that left in app_Parse directory should move to */cache/com.parse/ directory by covering known cases.

What are these other files?

Reopening this PR, as the issue conversation is happening here instead of in the issue. I guess this PR can just be extended with the modifications discussed above, instead of opening a new one.

rommansabbir commented 2 years ago

@mtrezza I found those usages in the mentioned list. If any other module deal with File, at the end they all point to the same API ParsePlugins.get().getParseDir() to retreive File object.

What are these other files?

I'm not sure about how many files (File) is accessed (CRUD) to old dir (app_Parse) by the SDK itself. So,

mtrezza commented 2 years ago

In general I agree with your strategy, but I wonder what these other files could be?

I assume it's only the Parse SDK itself that writes to that dir, so any write logic, even for temporary files should be identifiable in code. Have you seen that there are also other files, or is that just an assumption? Or is it that the OS uses the cache dir of a module automatically to store cache files that are opaque to us?

rommansabbir commented 2 years ago

According to my exploration of the code, many controller and classes using new locations ( */files/com.parse/,*/cache/com.parse/), but some classes still accessing the old location app_Parse.

Regarding the migrations: I have checked the migration with mentioned files and some random data. Screenshot_1 Screenshot_2

I'm not sure about how many files (File) is accessed (CRUD) to old dir (app_Parse) by the SDK itself. So,

* Files that matches with our lists, will be moved to `*/files/com.parse/` but what about others files in the old dir? (If exists)

* We can assume those files should be moved to `*/cache/com.parse/` dir.
mtrezza commented 2 years ago

OK, so I guess let's go with your suggested migration concept and then just test it out.

but some classes still accessing the old location app_Parse.

You mean pre 3.x or even with the latest version of the SDK?

rommansabbir commented 2 years ago

You mean pre 3.x or even with the latest version of the SDK?

pre 3.x since, from 3.x API removed from codebase and using */files/com.parse/ and */cache/com.parse/.

Also, Can I write the test in Kotlin?

mtrezza commented 2 years ago

Got it. Not sure about Kotlin, we probably want to keep all tests in a consistent language, what do you think @L3K0V?

rommansabbir commented 2 years ago

Test Cases has been written in Java. I tried to push code to the remote branch patch-1 but return fatal: unable to access : 403. Please, help me with this.

azlekov commented 2 years ago

Tests should be in Java for now.

mtrezza commented 2 years ago

@rommansabbir you get a 403 error pushing to the branch in your own fork? If so, you need to authenticate yourself for GitHub to accept a push.

rommansabbir commented 2 years ago

you get a 403 error pushing to the branch in your own fork? If so, you need to authenticate yourself for GitHub to accept a push.

I think, Git was trying push to the remote branch as well as my forked branch. But yeah, code is psuhed to my fork's branch.

I have added test for the migration process. Still, one more thing left, calling new overloaded APIs ParsePlugins.get().getCacheDir(true) and ParsePlugins.get().getFilesDir(true) should be used instead of old APIs in the respective classes.

mtrezza commented 2 years ago

Amazing!

calling new overloaded APIs

Is this an obvious change or do you need any guidance?

rommansabbir commented 2 years ago

Thanks, I will definitely ask you for further guidance. I think, yes. Changes is obvious for silent migration (Although it will be called everytimes, when cache or files API is called). Or how do we do it?. Can review the changes? So that, we can close this ASAP.

mtrezza commented 2 years ago

it will be called everytimes, when cache or files API is called

I would call the migration routine only once on SDK initialization. I don't think we need to set a flag that the migration has happened - checking whether the old path exists should be enough. Maybe a flag is not even a safe way, because in a scenario where the app is restored from a backup that logic may get messed up. So I would always run the migration on SDK init.

I think we can keep the migration routine for ~1 or 2 years, then remove it as a breaking change and note in the changelog that an app may first have to migrate using a previous version where the routine is present.

Can review the changes?

I just ran the CI to see whether the tests pass. When you say it's ready maybe @L3K0V could do a review.

So that, we can close this ASAP.

That would be wonderful!

mtrezza commented 2 years ago

@PLR2388 Since you originally reported the issue, do you think you could test out this fix to see whether the migration happens successfully?

Would be nice to have a pre-release branch to test things out easier, to consider for the future.

rommansabbir commented 2 years ago

@mman @mtrezza Any update on this PR?

mtrezza commented 2 years ago

Given the severity of the issue, I would like to have someone test this out with an actual app before we merge it and recommend to developers that the release is safe to use. That means:

  1. use the 2.x SDK
  2. log in a user
  3. update the SDK using this PR, relaunch the app, confirm at least whether:

If @PLR2388, @qmetzler-luna or @mman could try that out, that would be fantastic.

rommansabbir commented 2 years ago

If @PLR2388, @qmetzler-luna or @mman could try that out, that would be fantastic.

@mtrezza @mman I have tested the "patch-1" version to check the migrations status & users logged out issue. Migrations was successful & users stayed logged in after SDK version updates from 2.x to patch-1.

Step 1:

Step 2:

The result is as we expected 😃

mtrezza commented 2 years ago

@rommansabbir Thanks for documenting it in such detail. I will try this out as well just to confirm before we merge.

Side note, since you already did a lot of investigation, do you know why the applicationId file is stored in cache and what the file is used for? The ID is set during SDK initialization and probably stored in memory as it's always required for SDK requests. So why is it written to a cache file that may be deleted by the OS anytime anyway.

rommansabbir commented 2 years ago

@mtrezza I will review the codes and will update you.

mman commented 2 years ago

@rommansabbir I will try to test until the end of this week...

rommansabbir commented 2 years ago

@mtrezza According to the code comments:

mtrezza commented 2 years ago

Thanks for investigating, so the applicationId there is essentially to validate that the cache belongs to the app. So if an app would allow to switch between different application IDs, the cache would get invalidated with every switch. That makes sense.

mtrezza commented 2 years ago

I can confirm that the migration works fine.

It moves from com.example.app/app_Parse/ to com.example.app/files/com.parse/:

It moves from com.example.app/app_Parse/ to com.example.app/cache/com.parse/:

The following file locations are unchanged:

Everything as expected, installation update and user requests server side also work fine.

Great job, @rommansabbir! Would you be interested in joining the Parse Android SDK team? No commitment in terms of contribution, but it would be great if you could at times help out with things like PR reviews for example.

mtrezza commented 2 years ago

Changelog entry:

Fix

  • users logged out after SDK upgrade due to different cache path; this fixes the bug that was introduced with release 3.0.0 which ignores SDK-internal data that is stored locally on the client side (#1168)
mtrezza commented 2 years ago

@mman If you want to test that would be great, then we'll wait until the end of the week with the release.

rommansabbir commented 2 years ago

Great job, @rommansabbir! Would you be interested in joining the Parse Android SDK team? No commitment in terms of contribution, but it would be great if you could at times help out with things like PR reviews for example.

Yes, I would love to 😃

mman commented 2 years ago

@rommansabbir @mtrezza I'd love to double check, the code looks good. What's the easiest way to build my app against this branch. I have tried naive approach to depend directly on git branch, but that apparently does not work. What steps are needed? Sorry for my lame question.

rommansabbir commented 2 years ago

What's the easiest way to build my app against this branch. I have tried naive approach to depend directly on git branch, but that apparently does not work. What steps are needed?

@mman

image

Add this branch dependency:

implementation 'com.github.rommansabbir:Parse-SDK-Android:patch-1-SNAPSHOT'

I was able to install by following this approach. Now, you can install the latest SDK from this branch.

mtrezza commented 2 years ago

You can also clone the repo, checkout the PR branch, build to local maven and reference the builds in your example app. I think that's a question many may have; maybe we should add a step-by-step instruction to the contribution guide.

mman commented 2 years ago

Thanks @rommansabbir, I was missing the -SNAPSHOT in the github reference.

implementation 'com.github.rommansabbir:Parse-SDK-Android:patch-1-SNAPSHOT'

I have now tried our apps and looks like this PR does correctly move all files around.

Thanks, LGTM 👍

mtrezza commented 2 years ago

Thanks confirming @mman. We now have 3 confirmations that this PR works, so I will go ahead and merge.

Great teamwork everyone and thanks especially to @rommansabbir for kicking it off with this PR!

parseplatformorg commented 2 years ago

🎉 This change has been released in version 3.0.1