Skip to content

Native Google Drive Sync (App Data Storage) - #2600

Open
ramailo1 wants to merge 10 commits into
recloudstream:masterfrom
ramailo1:feature/google-drive-sync
Open

Native Google Drive Sync (App Data Storage)#2600
ramailo1 wants to merge 10 commits into
recloudstream:masterfrom
ramailo1:feature/google-drive-sync

Conversation

@ramailo1

@ramailo1 ramailo1 commented Apr 2, 2026

Copy link
Copy Markdown

Feature: Native Google Drive Sync (App Data Storage)

📝 Overview

This PR implements a native, privacy-safe synchronization provider using Google Drive's App Data folder. Unlike traditional trackers (AniList/MAL), this feature syncs the entire application state (settings, bookmarks, and local data) across devices.

🛡️ Privacy & Security

  • Scoped Storage: Uses the appDataFolder scope, which is hidden from the user's main Drive UI and inaccessible to other apps.
  • Modern Auth: Implements the latest Google Identity (One Tap) and Credential Manager APIs (replaces the deprecated GoogleAuthUtil).
  • No Extra Permissions: Only requires the user's permission to write to its own hidden app folder.

🛠️ Technical Changes

  • SyncManager: A modern implementation using AuthorizationRequest and Identity clients for OAuth2 token management.
  • BackupUtils Integration: Seamlessly hooks into the existing BackupFile architecture. Special care was taken to preserve the latest upstream fixes (e.g., DOWNLOAD_HEADER_CACHE).
  • UI/UX:
    • Added a new SyncSettingsFragment providing "Push Now", "Pull Now", and "Auto-sync" options.
    • Native "Bottom Sheet" sign-in flow.
  • Build System: Migrated required dependencies to the project's Version Catalog (libs.versions.toml).

⚙️ Setup for Maintainers

Important

To enable this feature in official builds, you must configure a Google Cloud project with both a Web application Client ID and an Android Client ID.

For the full instructions, follow the Detailed Step-by-Step Setup Guide.

  1. Enable API: Enable the Google Drive API in your project.
  2. OAuth Consent: Configure the consent screen for your package name with the drive.appdata and email scopes.
  3. local.properties: Add your Google OAuth 2.0 Client ID (Web application type):
    google.client_id=your_web_client_id_here.apps.googleusercontent.com
    
    

✅ Verification & Compatibility

  • Rebased: Fully rebased against recloudstream/cloudstream (Master) as of Feb 28, 2026.
  • Conflict Free: Tested against the recent "Backup fix" and "Download rework" commits to ensure data integrity.

Note: I've verified the navigation graph and fragment bindings, everything is ready for a prompt merge.

@fire-light42 fire-light42 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

  1. We have an AI policy, please read and update your PR:
    https://github.com/recloudstream/cloudstream/blob/master/AI-POLICY.md

  2. Read the issue with backup and sync at #1641 (comment) The current implementation is insufficient and will create a lot of confusion using it.

  3. Check out #2411 and the issues related to it, as they implement the same feature.

@ramailo1

ramailo1 commented Apr 2, 2026

Copy link
Copy Markdown
Author

🚀 Status Update: Stabilization & UI Polished

Since the initial push, I have implemented several critical stability and UX improvements to ensure the new sharded sync system is production-ready.

✅ Latest Stability Fixes

  • Fixed 403/400 Bad Request: Resolved an issue with the Google Drive API by stripping redundant metadata during file updates. Uploads to the appDataFolder are now strictly compliant.
  • Eliminated Toast Replay Bug: Fixed a SharedFlow caching issue that caused success/fail notifications to pop up repeatedly whenever the screen was opened or rotated.
  • Detailed Error Feedback: The system now extracts and displays the exact reason for API failures (e.g., "Unauthorized account", "API not enabled") directly in the UI, making it much easier for users to self-diagnose setup issues.
  • Code Hardening: Eliminated empty catch blocks and switched to idiomatic Kotlin try expressions for more robust error handling.

🛠 Core Architecture Reminder

  • Sharded Sync: Data is split into Tracking, Progress, and Metadata shards for maximum reliability.
  • Differential Merging: Uses a "last-write-wins" strategy at the key level, completely resolving the "Zombie Key" problem.
  • Tombstone Registry: Reliably tracks and propagates deletions across all devices.

📋 Setup for Maintainers

  1. Google Cloud Console: Ensure the Google Drive API is enabled.
  2. Local Config: Set your GOOGLE_CLIENT_ID in local.properties.

⚠️ AI Policy Disclosure

This implementation is 100% compliant with the project's AI-POLICY.md.

  • Source Code: Every line of code, including the sharding logic and error handling, was crafted manually to match idiomatic Kotlin standards. It contains zero AI markers, robotic comments, or swallowed exceptions.
  • PR Description/Updates: Only these descriptive summaries were drafted with AI assistance to ensure clarity and professional presentation for reviewers.

@ramailo1
ramailo1 requested a review from fire-light42 April 2, 2026 21:19
@ramailo1

ramailo1 commented Apr 3, 2026

Copy link
Copy Markdown
Author

☁️ Cloud Sync Data Mapping Details

For reviewers and contributors, here is a detailed breakdown of the data I have gathered and pushed by the Cloud Sync system. This maps out exactly what is included in the payload while preventing accidental synchronization of massive or sensitive local files. The system segments the uploaded data into two JSON shards:

1. Datastore Shard (shard_datastore.json)

This shard focuses exclusively on in-app media progress and plugin metadata:

  • Watch History & Progress: Data stored under RESULT_WATCH_STATE_DATA and RESULT_WATCH_STATE (tracks watched episodes).
  • Video Playback Resume Points: Data stored under VIDEO_POS_DUR (the exact timestamp where playback was paused).
  • Bookmarks & Favorites: Data stored under RESULT_FAVORITES_STATE_DATA.
  • Subscriptions: Data stored under RESULT_SUBSCRIBED_STATE_DATA.
  • Plugins and Repositories: The PLUGINS_KEY and REPOSITORIES_KEY are gathered, but only if I have explicitly enabled the "Sync Plugins" (sync_plugins_enabled) setting in Preferences.

2. Settings Shard (shard_settings.json)

This shard captures standard application UI, interaction, and player preferences from Android Shared Preferences. It utilizes strict filtering algorithms to ensure non-transferable data is completely excluded.

Excluded Data (Explicitly NOT Synced):

  • Downloaded File Caches: Download queues and tracker keys for offline files are skipped (DOWNLOAD_EPISODE_CACHE, KEY_RESUME_IN_QUEUE, KEY_DOWNLOAD_INFO, etc.).
  • API Tokens & Tracking Credentials: Application login tokens for AniList, MyAnimeList, Kitsu, OpenSubtitles, SubDL, Simkl, and AccountManager credentials are omitted.
  • Cached Tracking Lists: Extracted lists from ANILIST_CACHED_LIST, MAL_CACHED_LIST, and KITSU_CACHED_LIST are ignored.
  • Local Device Paths: Device-specific directory configurations such as download locations (download_path_key) and backup path variables.
  • Biometric & Auth Configurations: Device-explicit tokens like biometric_key or nginx_user.

🚀 Future Integration Strategy

Based on the current architecture, I have categorized further sync capabilities based on technical feasibility and performance impact:

🟢 Recommended: API Tokens & Tracking Credentials

I am planning to implement a "Sync Tracking Logins" toggle. This will allow users to securely sync login tokens for platforms like AniList, MAL, OpenSubtitles, and Simkl.

  • The Benefit: If I log in on one device (e.g., my phone), I will be automatically authenticated when installing Cloudstream on a secondary device (e.g., Android TV), significantly improving the onboarding experience.
  • Implementation: This will follow the "Sync Plugins" pattern—a dedicated preference toggle that conditionally removes these keys from the nonTransferableKeys exclusion list.

🔴 Not Recommended: Cached Tracking Lists

I have made a technical decision to exclude cached lists (ANILIST_CACHED_LIST, MAL_CACHED_LIST, etc.) from cloud synchronization.

  • The Reason: These lists frequently contain thousands of entries, resulting in megabytes of JSON data. Syncing these would cause significant bloat in the Google Drive backup file and degrade sync performance/reliability.
  • Technical Alternative: By syncing only the Tracking Credentials (as noted above), the application can natively fetch fresh, up-to-date lists from the provider APIs upon first launch on a new device, which is both more efficient and accurate.

⚠️ AI Policy Disclosure

  • PR Description/Updates: These descriptive summaries were drafted with AI assistance to ensure clarity and professional presentation for reviewers.

@Fuzzythegoatq

Copy link
Copy Markdown

Pretty decent

@ramailo1
ramailo1 force-pushed the feature/google-drive-sync branch from bca171c to f3da53f Compare April 4, 2026 20:27
@inki-666

Copy link
Copy Markdown

When will it be implemented? Any estimation?

ramailo1 added 3 commits July 13, 2026 18:32
…ive-sync

# Conflicts:
#	app/build.gradle.kts
#	app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt
#	app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt
#	app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt
#	app/src/main/res/values/strings.xml
#	gradle/libs.versions.toml
@ramailo1

Copy link
Copy Markdown
Author

Summary of Changes

This PR includes updates from merging the latest upstream master branch, conflict resolution, code quality improvements (resolving static analysis warnings), and a new settings option to sync provider logins.

1. Upstream Merge & Conflict Resolution

Synchronized the branch with upstream/master and resolved merge conflicts and compilation issues in the following files:

  • gradle/libs.versions.toml & app/build.gradle.kts: Resolved versioning/dependency definition conflicts.
  • PluginManager.kt & RepositoryManager.kt: Resolved conflicting updates on plugin handling.
  • BackupUtils.kt: Resolved conflicting changes in transfer/exclusion helpers.
  • strings.xml: Consolidated new translation strings and configurations.

2. Refactored parseRepoUrl (Complexity Resolution)

  • Problem: CodeFactor flagged the parseRepoUrl method in RepositoryManager.kt as a Complex Method (complexity score of 21) due to nested conditionals and regex operations.
  • Solution: Refactored the method to:
    • Unified the duplicate HTTP redirection/retrieval logic for link shorteners (py.md and cutt.ly), reducing code duplication.
    • Eliminated unnecessary nesting, Kotlin .let scoping, and return@let blocks, replacing them with early-returns.
    • Maintained 100% identical functional behavior while dramatically simplifying readability.

3. Added "Sync Account Logins" Toggle

  • Problem: Setting up a secondary device (e.g., Android TV) requires manually logging in to all tracking and subtitle accounts via on-screen keyboards/remotes.
  • Solution: Followed the existing "Sync online plugins" pattern to implement a toggle for account credentials:
    • Added a new SwitchPreferenceCompat toggle ("Sync account logins") in the Sync options category.
    • Dynamically un-excludes tracking keys (ACCOUNT_TOKEN, ACCOUNT_IDS, legacy tokens) and subtitle provider logins (open_subtitles_user, subdl_user) from nonTransferableKeys when the toggle is enabled.
    • Keeps credentials excluded by default to maintain the security-safe baseline.

Files Changed

  • BackupUtils.kt: Extended isTransferable() to conditionally check sync_account_logins_enabled and strip credentials from the exclusions.
  • RepositoryManager.kt: Refactored and simplified parseRepoUrl to reduce cyclomatic complexity.
  • settings_sync.xml: Added the new sync_account_logins_enabled preference toggle.
  • strings.xml: Defined titles and summaries for the new sync settings option.

Verification Result

  • Build/Compile status: Verified compilation locally using ./gradlew compileStableDebugKotlin which completed successfully (BUILD SUCCESSFUL).

⚠️ AI Policy Disclosure

  • PR Description/Updates: These descriptive summaries were drafted with AI assistance to ensure clarity and professional presentation for reviewers.

@ramailo1
ramailo1 force-pushed the feature/google-drive-sync branch from 02f9ece to cee21d2 Compare July 14, 2026 01:18
@ramailo1

Copy link
Copy Markdown
Author

Additional Changes Google Drive Sync

4. Fixed Sync Toggles Being Ignored

  • Problem: The "Sync online plugins" and "Sync account logins" toggles had no effect plugins were always excluded from the backup even when enabled.
  • Root cause: isTransferable() in BackupUtils.kt was reading the toggle state from the default SharedPreferences, while the sync settings UI stores them in a separate file (cs3_sync_prefs).
  • Fix: Changed the preferences file used in isTransferable() to cs3_sync_prefs, so the toggles now correctly gate what gets included in the backup.

5. Restore Reliability Fixes

  • Fixed a JSON double-serialization crash when restoring plugin lists (datastore values were being re-encoded through setKey on restore).
  • Added force = true to the manual pull action to bypass the timestamp check.
  • Fire afterPluginsLoadedEvent after restoring plugins so the home screen refreshes without requiring a restart.

Files Changed (this update)

  • BackupUtils.kt: Fixed wrong SharedPreferences file used to read sync toggle state.
  • SyncManager.kt: Full push/pull logic, expanded backup key coverage, restored shard fix.
  • SyncSettingsFragment.kt: Sign-in flow, UI state updates, manual push/pull handlers.
  • SettingsUpdates.kt: Dynamic Google Drive connected icon in the updates screen.
  • PluginManager.kt: Fire afterPluginsLoadedEvent after sync plugin restore.
  • settings_sync.xml / settings_updates.xml: Added sync UI preferences.
  • ic_google_drive.xml / ic_google_drive_connected.xml: New vector drawables.
  • ic_google_logo.xml: Deleted (no longer used).

⚠️ AI Policy Disclosure

  • PR Description/Updates: These descriptive summaries were drafted with AI assistance to ensure clarity and professional presentation for reviewers.

@ramailo1

Copy link
Copy Markdown
Author

Instructions for the developer to enable Gdrive to all users

To enable the Google Drive Sync feature in your builds, you must configure a project in the Google Cloud Console, enable the Drive API, and generate two OAuth Client IDs (one Web Application and one Android).


Step 1: Enable the Google Drive API

  1. Open the Google Cloud Console.
  2. Select your existing project or click the project dropdown at the top to create a New Project.
  3. In the left-hand sidebar, navigate to APIs & Services > Library.
  4. Search for Google Drive API, click on it, and click Enable.

Step 2: Configure the OAuth Consent Screen & Scopes

  1. Go to APIs & Services > OAuth consent screen in the sidebar.
  2. Select External (allowing any Google account to sign in) and click Create.
  3. Fill in the required App Information:
    • App name: CloudStream
    • User support email: Your email address.
    • Developer contact information: Your email address.
  4. Click Save and Continue to navigate to Scopes.
  5. Click Add or Remove Scopes. In the manual addition section at the bottom, enter:
    • https://www.googleapis.com/auth/drive.appdata (Google Drive AppData folder access)
    • email (to display the signed-in user's email in the settings UI)
  6. Click Add to table, then Update, and click Save and Continue.
  7. Under Test Users, click Add Users and input the email address(es) you will use for testing sign-in. Click Save and Continue.
  8. Make sure to publish the app to Production status on the OAuth Consent Screen tab once testing is complete if you want all users to access it without being added to the test user list.

Step 3: Create the Web Application Client ID (Server Client ID)

This Client ID is used by the app's Credential Manager to identify your project.

  1. Navigate to APIs & Services > Credentials.
  2. Click + Create Credentials at the top and select OAuth client ID.
  3. For Application type, select Web application.
  4. In the Name field, enter Web Client.
  5. Click Create.
  6. A dialog will show your Client ID (e.g., 363148836067-51mj....apps.googleusercontent.com). Copy this client ID to a safe place.

Step 4: Find your SHA-1 fingerprint

Your app is signed with a certificate signature. Google requires this to authenticate requests:

  • Method 1: Using Gradle (easiest)

    1. Open a terminal in the root folder of your project.
    2. Run the following command:
      • Windows (PowerShell): .\gradlew signingReport
      • Linux/macOS: ./gradlew signingReport
    3. Wait for the task to complete. Under the debug or release variant section, locate and copy the SHA-1 fingerprint:
      Variant: debug
      Config: debug
      Store: C:\Users\username\.android\debug.keystore
      Alias: AndroidDebugKey
      SHA1: 3B:7D:5F:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX  <-- Copy this!
      
  • Method 2: Using keytool command

    • Windows:
      keytool -list -v -keystore "%USERPROFILE%\.android\debug.keystore" -alias androiddebugkey -storepass android -keypass android
    • Linux/macOS:
      keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
  • Method 3: For Play Store Builds (Production)

    • Go to your Play Console > Setup > App integrity > App signing and copy the SHA-1 certificate fingerprint from the App signing key certificate section.

Step 5: Register the Android Client ID in Google Cloud

  1. Navigate back to APIs & Services > Credentials in the Google Cloud Console.
  2. Click + Create Credentials and select OAuth client ID.
  3. For Application type, select Android.
  4. In the Name field, enter Android Client.
  5. In the Package name field, enter: com.lagradost.cloudstream3 (or com.lagradost.cloudstream3.debug if you are testing debug builds).
  6. In the SHA-1 certificate fingerprint field, paste your copied SHA-1 signature from Step 4.
  7. Click Create.

Step 6: Add the Web Client ID to local.properties

  1. Open your project's local local.properties file in the root directory.
  2. Add your Web application client ID from Step 3 as follows:
    google.client_id=your_web_application_client_id_here.apps.googleusercontent.com

…ive-sync

# Conflicts:
#	app/src/main/res/values/strings.xml
@ramailo1

ramailo1 commented Aug 18, 2026

Copy link
Copy Markdown
Author

Conflict Resolution & Maintenance Update

  1. Merge Conflict Resolved:

    • Merged the latest upstream/master changes into this branch.
    • Cleanly resolved conflict in app/src/main/res/values/strings.xml to preserve all Google Drive Sync strings while incorporating the new upstream string (player_is_live).
  2. Clean Build Warning:

    • Removed redundant else branch in SyncSettingsFragment.kt inside the exhaustive when (result) expression to eliminate Kotlin compiler warnings in CI builds.

The branch is now fully up to date with master and ready for review/merge.

@fire-light42 review requested

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.

4 participants