Skip to content

Fix/case insensitive cloud saves - #1598

Merged
utkarshdalal merged 3 commits into
masterfrom
fix/case-insensitive-resolve-resume
Jun 19, 2026
Merged

Fix/case insensitive cloud saves#1598
utkarshdalal merged 3 commits into
masterfrom
fix/case-insensitive-resolve-resume

Conversation

@utkarshdalal

@utkarshdalal utkarshdalal commented Jun 18, 2026

Copy link
Copy Markdown
Owner

Description

The CaseInsensitiveFileSystem wasn't working. Disco Elysium saves on Epic were saving to locallow instead of LocalLow. This fixes it.

Recording

Type of Change

  • Bug fix
  • Performance / stability improvement
  • Compatibility improvements
  • Other (requires prior approval)

Checklist

  • If I have access to #code-changes, I have discussed this change there and it has been green-lighted. If I do not have access, I have still provided clear context in this PR. If I skip both, I accept that this change may face delays in review, may not be reviewed at all, or may be closed.
  • This change aligns with the current project scope (core functionality, stability, or performance). If not, it has been explicitly approved beforehand.
  • I have attached a recording of the change.
  • I have read and agree to the contribution guidelines in CONTRIBUTING.md.

Summary by cubic

Fixes case-insensitive path resolution so cloud saves use on-disk casing (e.g., AppData/LocalLow). Removes Epic-specific workaround and relies on the shared resolver to restore save detection for Disco Elysium (and other stores using the shared path resolver).

  • Bug Fixes
    • Reworked FileUtils.resolveCaseInsensitive to continue matching across unreadable/missing parents, using literal segments only when needed; ensures AppData/LocalLow resolves to on-disk casing and avoids duplicate dirs.
    • Added tests for absolute LocalLow resolution and continued matching after an unmatched parent segment.

Written for commit eb47201. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Fixed Epic Cloud Saves to correctly resolve AppData subfolder casing inconsistencies (e.g., LocalLow/locallow), preventing incorrect save directory targeting.
    • Enhanced case-insensitive path resolution so it continues matching deeper segments even if earlier segments don’t exactly match.
  • Tests

    • Expanded coverage with on-disk temporary directory setups to validate absolute cloud save path resolution and case-insensitive directory matching behavior.

phobos665 and others added 2 commits June 9, 2026 10:09
resolveCaseInsensitive() gave up case-insensitive matching for the rest
of the path the moment listFiles() returned null (unreadable dirs like
/data on Android, or not-yet-created dirs). Anchored at File("/"), the
walk bailed on the first segment and appended everything verbatim,
including lowercase AppData subfolders like locallow, so Epic cloud saves
never matched the on-disk LocalLow.

Descend with the literal segment instead of bailing, resuming matching at
any level we can read. This fixes the Disco Elysium save detection at the
shared helper, so GOG saves benefit too, and removes the Epic-specific
resolveAbsolutePathCaseInsensitive workaround.
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8d08e4f5-5afb-4879-b032-f12b2e481e22

📥 Commits

Reviewing files that changed from the base of the PR and between f4e3d3e and eb47201.

📒 Files selected for processing (2)
  • app/src/main/java/app/gamenative/service/epic/EpicCloudSavesManager.kt
  • app/src/test/java/app/gamenative/service/epic/EpicCloudSavesTest.kt
💤 Files with no reviewable changes (1)
  • app/src/test/java/app/gamenative/service/epic/EpicCloudSavesTest.kt
✅ Files skipped from review due to trivial changes (1)
  • app/src/main/java/app/gamenative/service/epic/EpicCloudSavesManager.kt

📝 Walkthrough

Walkthrough

resolveCaseInsensitive in FileUtils is rewritten to continue iterating through path segments after a mismatch instead of returning early. The resolveSaveDirectory documentation is updated to note AppData child directory casing canonicalization. Two filesystem-backed test cases verify that the refactored function correctly handles case-mismatched segments (e.g., locallow → on-disk LocalLow) and continues matching past unmatched parent segments.

Changes

Case-Insensitive Path Resolution and Epic Cloud Saves Testing

Layer / File(s) Summary
resolveCaseInsensitive continues past first mismatch
app/src/main/java/app/gamenative/utils/FileUtils.kt
Replaces the indexed (i, segment) loop with a per-segment loop that always continues iterating; on a mismatch it appends the segment verbatim instead of returning early.
Documentation and filesystem-backed tests
app/src/main/java/app/gamenative/service/epic/EpicCloudSavesManager.kt, app/src/test/java/app/gamenative/service/epic/EpicCloudSavesTest.kt
Updates resolveSaveDirectory comment to document AppData child directory casing canonicalization, adds TemporaryFolder test rule, and covers continued matching and AppData casing behavior through two new test cases.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related issues

  • utkarshdalal/GameNative#940: The refactored resolveCaseInsensitive that continues matching through all segments addresses the case-sensitive filesystem problem where AppData/locallow does not match on-disk AppData/LocalLow, preventing duplicate directory creation.

Possibly related PRs

  • utkarshdalal/GameNative#941: Also modifies resolveSaveDirectory and depends on FileUtils.resolveCaseInsensitive for Epic cloud save path resolution with AppData casing handling.

Poem

🐇 Through segments we hop, never turning back,
Each locallow finds its LocalLow track.
With tests on real folders to light the way,
Case mismatches vanish—huzzah, hooray! 🎉
No duplication, no loss in sight,
Cloud saves resolved, everything's right! ☁️✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix/case insensitive cloud saves' directly refers to the bug fix for case-insensitive path resolution in cloud saves, which is the main change in this PR.
Description check ✅ Passed The description includes all key template sections: it provides context for the bug fix, marks the type of change (Bug fix), confirms checklist items including discussion and alignment, but lacks an attached recording.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/case-insensitive-resolve-resume

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/test/java/app/gamenative/service/epic/EpicCloudSavesTest.kt`:
- Around line 103-110: The test method `resolveCaseInsensitive keeps matching
after an unmatched parent segment` has a mismatch between its name and actual
test case. The test name indicates it should validate behavior when there is an
unmatched parent segment in the path, but the current test input path
`existing/nested/locallow` matches all segments in the directory structure
`Existing/Nested/LocalLow`. To fix this, modify the path passed to
`FileUtils.resolveCaseInsensitive` to include at least one unmatched segment
(for example, change one of the segments to a non-existent name) so that the
test actually validates the regression where matching continues even after
encountering an unmatched parent segment, not just a normal fully-matching
case-insensitive path resolution.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a27381fb-76a4-463e-9f42-9200e3a7791e

📥 Commits

Reviewing files that changed from the base of the PR and between 608c42d and f4e3d3e.

📒 Files selected for processing (3)
  • app/src/main/java/app/gamenative/service/epic/EpicCloudSavesManager.kt
  • app/src/main/java/app/gamenative/utils/FileUtils.kt
  • app/src/test/java/app/gamenative/service/epic/EpicCloudSavesTest.kt

Comment on lines +103 to +110
fun `resolveCaseInsensitive keeps matching after an unmatched parent segment`() {
val base = tmpDir.newFolder("base")
val deep = File(base, "Existing/Nested/LocalLow").apply { mkdirs() }

val resolved = FileUtils.resolveCaseInsensitive(base, "existing/nested/locallow")

assertEquals(deep.absolutePath, resolved.absolutePath)
assertTrue(resolved.exists())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Test intent doesn’t match what is being asserted

At Line 103, the case uses existing/nested/locallow, which is a normal mixed-case match and does not include an unmatched parent/fallback condition. This won’t validate the specific regression described by the test name.

Suggested minimal correction
-    fun `resolveCaseInsensitive keeps matching after an unmatched parent segment`() {
+    fun `resolveCaseInsensitive resolves nested path segments case-insensitively`() {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fun `resolveCaseInsensitive keeps matching after an unmatched parent segment`() {
val base = tmpDir.newFolder("base")
val deep = File(base, "Existing/Nested/LocalLow").apply { mkdirs() }
val resolved = FileUtils.resolveCaseInsensitive(base, "existing/nested/locallow")
assertEquals(deep.absolutePath, resolved.absolutePath)
assertTrue(resolved.exists())
fun `resolveCaseInsensitive resolves nested path segments case-insensitively`() {
val base = tmpDir.newFolder("base")
val deep = File(base, "Existing/Nested/LocalLow").apply { mkdirs() }
val resolved = FileUtils.resolveCaseInsensitive(base, "existing/nested/locallow")
assertEquals(deep.absolutePath, resolved.absolutePath)
assertTrue(resolved.exists())
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/test/java/app/gamenative/service/epic/EpicCloudSavesTest.kt` around
lines 103 - 110, The test method `resolveCaseInsensitive keeps matching after an
unmatched parent segment` has a mismatch between its name and actual test case.
The test name indicates it should validate behavior when there is an unmatched
parent segment in the path, but the current test input path
`existing/nested/locallow` matches all segments in the directory structure
`Existing/Nested/LocalLow`. To fix this, modify the path passed to
`FileUtils.resolveCaseInsensitive` to include at least one unmatched segment
(for example, change one of the segments to a non-existent name) so that the
test actually validates the regression where matching continues even after
encountering an unmatched parent segment, not just a normal fully-matching
case-insensitive path resolution.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="app/src/test/java/app/gamenative/service/epic/EpicCloudSavesTest.kt">

<violation number="1" location="app/src/test/java/app/gamenative/service/epic/EpicCloudSavesTest.kt:103">
P3: Test name is misleading: the test resolves `existing/nested/locallow` against an on-disk `Existing/Nested/LocalLow` where every segment matches case-insensitively. There is no "unmatched parent segment" being exercised here. Consider renaming to accurately describe what it validates, e.g. `resolveCaseInsensitive resolves nested path segments case-insensitively`.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

}

@Test
fun `resolveCaseInsensitive keeps matching after an unmatched parent segment`() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: Test name is misleading: the test resolves existing/nested/locallow against an on-disk Existing/Nested/LocalLow where every segment matches case-insensitively. There is no "unmatched parent segment" being exercised here. Consider renaming to accurately describe what it validates, e.g. resolveCaseInsensitive resolves nested path segments case-insensitively.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/test/java/app/gamenative/service/epic/EpicCloudSavesTest.kt, line 103:

<comment>Test name is misleading: the test resolves `existing/nested/locallow` against an on-disk `Existing/Nested/LocalLow` where every segment matches case-insensitively. There is no "unmatched parent segment" being exercised here. Consider renaming to accurately describe what it validates, e.g. `resolveCaseInsensitive resolves nested path segments case-insensitively`.</comment>

<file context>
@@ -75,6 +82,43 @@ class EpicCloudSavesTest {
+    }
+
+    @Test
+    fun `resolveCaseInsensitive keeps matching after an unmatched parent segment`() {
+        val base = tmpDir.newFolder("base")
+        val deep = File(base, "Existing/Nested/LocalLow").apply { mkdirs() }
</file context>
Suggested change
fun `resolveCaseInsensitive keeps matching after an unmatched parent segment`() {
fun `resolveCaseInsensitive resolves nested path segments case-insensitively`() {

@utkarshdalal
utkarshdalal merged commit 4d179d3 into master Jun 19, 2026
2 of 3 checks passed
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.

2 participants