Skip to content

Feat: Add Google Drive cloud sync for multi-device sync#17

Open
kmhmubin wants to merge 21 commits into
1Finn2me:V1.0.2from
kmhmubin:feat/google-drive-sync
Open

Feat: Add Google Drive cloud sync for multi-device sync#17
kmhmubin wants to merge 21 commits into
1Finn2me:V1.0.2from
kmhmubin:feat/google-drive-sync

Conversation

@kmhmubin

Copy link
Copy Markdown
Contributor

Users can now sign in with Google Drive and sync their library, reading progress, bookmarks, history, stats, and settings across devices. The sync data is stored in Google Drive’s hidden app data folder, so it does not appear in the user’s normal Drive files.

What’s Included

  • Added Cloud Sync section in Storage & Backup.
  • Added Google Drive sign-in, manual sync, disconnect, and delete remote sync data actions.
  • Added automatic sync intervals: 30 minutes, hourly, 3h, 6h, 12h, daily, 2 days, weekly, and off.
  • Added optional sync triggers for app start, app resume, chapter open, and chapter read.
  • Added selectable sync categories: library, bookmarks, history, stats, and settings.
  • Added merge logic so multi-device sync keeps newer reading progress and avoids duplicate bookmarks/stats.
  • Added private sync notifications with cancel support for manual sync.
  • Improved background sync behavior to avoid excessive requests and battery drain.

Safety / Privacy

  • Uses Google Drive appDataFolder, not user-visible Drive storage.
  • Uses the limited drive.appdata scope only.
  • Stores Google Drive tokens in encrypted preferences.
  • Excludes secure sync token storage from Android backup.
  • Validates OAuth callback state before saving tokens.
  • Avoids foreground-service crashes for automatic background sync.

Release Setup Needed

For release builds, the maintainer needs to configure Google Cloud OAuth:

  • Go to Google Cloud Console.
  • Create a new project or select the project used for Novery releases.
  • Go to API & Services -> Library -> Google Drive API and click Enable.
  • Go to API & Services -> OAuth consent screen.
  • Create the consent screen and fill in the app name, user support email, and developer contact information.
  • In the scopes screen, click Add or remove scopes.
  • Add this scope: .../auth/drive.appdata and .../auth/drive.file
  • Publish the OAuth consent screen.
  • Go to API & Services -> Credentials.
  • Click Create credentials -> OAuth client ID.
  • Select Android.
  • Give it a name, for example Novery Android Release.
  • Set the package name to: com.emptycastle.novery
  • To get the release SHA-1 key, run: keytool -printcert -jarfile app-standard-universal-release.apk
  • Copy the listed SHA-1 and add it to the OAuth client.
  • Expand advanced settings and enable Custom URL scheme.
  • Use this redirect scheme: com.emptycastle.novery.google.oauth
  • Download the OAuth client JSON.
  • Rename it to: client_secrets.json
  • Put it in: app/src/main/assets/client_secrets.json
  • Do not commit private release credentials unless the project intentionally keeps OAuth client config public.
  • For CI/release builds, prefer storing the JSON in CI secrets and writing it to app/src/main/assets/client_secrets.json during the build.
  • Build and test Google Drive sign-in on a release-signed APK before publishing.

For release builds, this file should be provided by the maintainer or CI build process. It should not include personal testing credentials.

Testing

Tested on physical Android devices:

  • Google Drive sign-in works.
  • Manual sync works immediately after sign-in.
  • Automatic sync works without foreground-service crashes.
  • Multi-device sync updates library and reading progress.
  • Storage & Backup screen opens without crashes.
  • Last sync status updates correctly.

kmhmubin added 20 commits April 24, 2026 17:41
…cting library privacy visibility settings on the notificiation

- only show the updated info if the spicy shelf is visible or hide it from update notificaiton screen
- Added SyncManager for managing sync operations with Google Drive.
- Created SyncModels to define data structures for sync settings and payloads.
- Introduced SyncNotifier for handling sync progress notifications.
- Developed SyncStatusTracker to maintain sync execution state.
- Implemented SyncWorker for background sync tasks using WorkManager.
- Added StorageSyncSection UI component for managing sync settings and actions.
- Created GoogleDriveLoginActivity to handle OAuth redirects and token storage.
What was wrong:

  - Novery skipped applying remote data when the remote payload had the same deviceId. That can break multi-device sync if device ids are duplicated/restored or stale Drive metadata is returned.
  - Sync restore was using backup-style replacement behavior instead of sync-style merge behavior.
  - Library merge could keep stale unread/progress state.
  - Bookmark sync could duplicate bookmarks because bookmark ids are local auto-generated ids.
  - Google Drive file lookup did not explicitly pull the newest modified sync file if duplicate sync files existed.

  Changes made:

  - Always merge and apply remote payload when a remote payload exists.
  - Sync restore now uses mergeWithExisting = true.
  - Library entries are merged field-by-field: remote-only books are inserted, newer reading position wins, chapter counts/read indexes are preserved correctly.
  - Read chapters are unioned through existing REPLACE insert behavior, so Device B should mark Device A’s read chapters as read after sync.
  - Bookmark restore now de-dupes by stable bookmark content key.
  - Google Drive now orders remote sync files by modifiedTime desc.
 - Storage sync card now refreshes sync settings on screen resume, so after Google sign-in it should immediately switch from Sign in to the signed-in sync actions.
  - Added refreshSyncSettings() so UI can reload persisted sync state, including lastSyncTimestamp and Google Drive token state.
  - Reader sync now stores a stable character offset in the library record for cloud sync, instead of only a pixel offset.
  - Reader restore now falls back to synced library position when local per-chapter scroll cache does not exist, so another device can resume inside the synced chapter instead of starting at 0%.
  - Removed the manual thread/pipe Drive upload path and replaced it with buffered gzip upload to avoid hangs/leaks on low-end devices.
  - Prevented concurrent sync jobs with a process-level mutex.
  - Changed manual sync enqueue policy from REPLACE to KEEP so active sync work is not interrupted.
  - Sanitized sync/sign-in error messages so raw OAuth/network exception details are not exposed.
  - Reduced Google Drive OAuth scope to DRIVE_APPDATA only.
  - Made sync notifications private/low-priority.
  - Remote purge now deletes duplicate sync files too.
  - Upload now prunes duplicate remote sync payload files.
  - Fixed reading stats merge restore so auto-generated Room IDs do not create duplicate stats rows.
 - Automatic sync now runs as normal WorkManager background work.
  - Manual sync still uses foreground progress notification and cancel action.
  - Sync notifications are now non-fatal, so notification permission/state cannot crash the worker.
  - Use a shared PreferencesManager instance for consistent sync state
  - Refresh sync settings after Google Drive token changes
  - Ensure manual sync reads fresh settings before enqueueing work
  - Avoid redundant token refresh during a normal Drive sync
  - Require battery-not-low for automatic sync jobs
  - Harden OAuth callback state validation
@kmhmubin kmhmubin changed the base branch from main to V1.0.2 May 1, 2026 02:08
@kmhmubin kmhmubin changed the base branch from V1.0.2 to main May 1, 2026 02:09
@kmhmubin kmhmubin changed the base branch from main to V1.0.2 May 1, 2026 05:07
…-sync

# Conflicts:
#	app/src/main/java/com/emptycastle/novery/data/local/dao/HistoryDao.kt
#	app/src/main/java/com/emptycastle/novery/data/repository/HistoryRepository.kt
#	app/src/main/java/com/emptycastle/novery/data/repository/LibraryRepository.kt
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.

1 participant