Conversation
Implements Phase 1 of WebDAV synchronization feature: - Add dependencies: WorkManager, OkHttp, security-crypto - Add network permissions (INTERNET, ACCESS_NETWORK_STATE) - Create SyncSettings data class with sync configuration - Implement CredentialManager for encrypted credential storage - Implement WebDAVClient with full WebDAV operations - Basic authentication support - PROPFIND, PUT, GET, DELETE, MKCOL methods - Directory creation and file streaming support
Implements Phase 2 of WebDAV synchronization: - FolderSerializer: Convert folder hierarchy to/from folders.json - NotebookSerializer: Convert notebooks/pages/strokes/images to/from JSON - Handles manifest.json for notebook metadata - Handles per-page JSON with all strokes and images - Converts absolute URIs to relative paths for WebDAV storage - Supports ISO 8601 timestamps for conflict resolution Phase 2 complete. Next: SyncEngine for orchestrating sync operations.
Creates skeleton implementations for remaining sync components: Core Sync Components: - SyncEngine: Core orchestrator with stub methods for sync operations - ConnectivityChecker: Network state monitoring (complete) - SyncWorker: Background periodic sync via WorkManager - SyncScheduler: Helper to enable/disable periodic sync UI Integration: - Add "Sync" tab to Settings UI - Stub SyncSettings composable with basic toggle All components compile and have proper structure. Ready to fill in implementation details incrementally. TODOs mark where logic needs to be added.
- Fix KvProxy import path (com.ethran.notable.data.db.KvProxy) - Replace HTTP_METHOD_NOT_ALLOWED with constant 405 - Correct package imports in SyncEngine
Add full-featured sync settings interface with: - Server URL, username, password input fields - Test Connection button with success/failure feedback - Enable/disable sync toggle - Auto-sync toggle (enables/disables WorkManager) - Sync on note close toggle - Manual "Sync Now" button - Last sync timestamp display - Encrypted credential storage via CredentialManager - Proper styling matching app's design patterns All settings are functional and persist correctly. UI is ready for actual sync implementation.
showHint takes (text, scope) not (context, text)
Log URL and credentials being used, response codes, and errors to help diagnose connection issues
Use Dispatchers.IO for network calls (Test Connection, Sync Now). Switch back to Dispatchers.Main for UI updates using withContext. Fixes: NetworkOnMainThreadException when testing WebDAV connection
Core sync implementation: - syncAllNotebooks(): Orchestrates full sync of folders + notebooks - syncFolders(): Bidirectional folder hierarchy sync with merge - syncNotebook(): Per-notebook sync with last-write-wins conflict resolution - uploadNotebook/uploadPage(): Upload notebook data and files to WebDAV - downloadNotebook/downloadPage(): Download notebook data and files from WebDAV - Image and background file handling (upload/download) Database enhancements: - Add getAll() to FolderDao/FolderRepository - Add getAll() to NotebookDao/BookRepository Sync features: - Timestamp-based conflict resolution (last-write-wins) - Full page overwrite on conflict (no partial merge) - Image file sync with local path resolution - Custom background sync (skips native templates) - Comprehensive error handling and logging - Resilient to partial failures (continues if one notebook fails) Quick Pages sync still TODO (marked in code).
- Remove context parameter from ensureBackgroundsFolder/ensureImagesFolder - Fix image URI updating (create new Image objects instead of reassigning val) - Use updatedImages when saving to database - Handle nullable URI checks properly
Safety Features for Initial Sync Setup: - forceUploadAll(): Delete server data, upload all local notebooks/folders - forceDownloadAll(): Delete local data, download all from server UI: - "Replace Server with Local Data" button (orange warning) - "Replace Local with Server Data" button (red warning) - Confirmation dialogs with clear warnings - Prevents accidental data loss on fresh device sync Use cases: - Setting up sync for first time - Adding new device to existing sync - Recovering from sync conflicts - Resetting sync environment
Log notebook discovery, download attempts, and directory listings to diagnose sync issues
Features: - SyncLogger: Maintains last 50 sync log entries in memory - Live log display in Settings UI (last 20 entries) - Color-coded: green (info), orange (warning), red (error) - Auto-scrolls to bottom as new logs arrive - Clear button to reset logs - Monospace font for readability Makes debugging sync issues much easier for end users without needing to check Logcat.
Fixes: - forceUploadAll: Delete only notebook directories, not entire /Notable folder - Add detailed SyncLogger calls throughout force operations - Add logging to upload/download operations with notebook titles Log viewer now shows: - Exactly which notebooks are being uploaded/downloaded - Success/failure for each notebook - Number of pages per notebook - Any errors encountered This makes debugging sync issues much easier and prevents accidentally wiping the entire sync directory.
Add auto-sync trigger when switching pages in editor: - Hook into EditorControlTower.switchPage() - Pass context to EditorControlTower constructor - Trigger SyncEngine.syncNotebook() when leaving a page - Only syncs if enabled in settings - Runs in background on IO dispatcher - Logs to SyncLogger for visibility Now sync-on-close setting is functional.
Show clearly in sync log: - ↑ Uploading (local newer or doesn't exist on server) - ↓ Downloading (remote newer) - Timestamp comparison for each decision - Which path is taken for each notebook This will help diagnose why sync only goes up and never down.
Create AppRepository instance to properly access PageRepository in triggerSyncForPage method.
Previous logic: equal timestamps → upload New logic: equal timestamps → skip (no changes needed) Now properly handles three cases: - Remote newer → download - Local newer → upload - Equal → skip This prevents unnecessary re-uploads when nothing has changed.
Move sync trigger to EditorView's DisposableEffect.onDispose which fires when navigating away from the editor. Now sync-on-close actually works when you: - Navigate back to library - Switch to a different notebook - Exit the app Will show "Auto-syncing on editor close" in sync log.
Use new CoroutineScope instead of composition scope in onDispose. The composition scope gets cancelled during disposal, causing "rememberCoroutineScope left the composition" error. Now sync-on-close will actually complete.
Log when credentials are loaded or missing to help diagnose AUTH_ERROR issues.
Show full Date.time millisecond values and difference to diagnose why timestamps that appear equal are being treated as different. This should reveal if there are sub-second differences causing unnecessary uploads.
Add null-safe access to remoteUpdatedAt.time
Problem: ISO 8601 serialization loses milliseconds, causing local timestamps to always be slightly newer (100-500ms). Solution: Ignore differences < 1 second (1000ms) - Difference < -1000ms: remote newer → download - Difference > 1000ms: local newer → upload - Within ±1 second: no significant change → skip This prevents unnecessary re-uploads of unchanged notebooks while still detecting real changes.
After syncing local notebooks, now scans server for notebooks that don't exist locally and downloads them. Flow: 1. Sync folders 2. Sync all local notebooks (upload/download/skip) 3. Discover new notebooks on server 4. Download any that don't exist locally This enables proper bidirectional sync - devices can now discover and download notebooks created on other devices.
Ethran
left a comment
There was a problem hiding this comment.
Initial review, I only glanced at the code. Letter I'll actually look at code in folder sync.
Please clean up the stuff left behind by AI.
In setting syncing setting should be implemented in separate file. Probably other setting also should be done this way, but don't change it in this PR.
|
Also, as I understand it should work with nextcloud? If so, please give a short instruction how to setup it, etc. I will test it on nextcloud probably (if I have still access to university instance). |
Removing username from logger as suggested. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Do you mean how to set up Nextcloud? Or something else? And, do you want me to just create a "how to use sync" .md in /docs? That's probably easiest. |
|
It would be best to have two files:
|
Ethran
left a comment
There was a problem hiding this comment.
Also, how do you handle conflicts in stroke data?
I feel like there should be some function that decide how to resolve conflicts, and it should be reusable for imports, if possible.
https://github.com/Ethran/notable/blob/main/app/src/main/java/com/ethran/notable/io/ImportEngine.kt
Whenever possible reuse functions, it makes code easier to maintain.
…nstead of 3 so those calls would compile.
Ethran
left a comment
There was a problem hiding this comment.
I would say that first we should settle on the format for notable pages. Make proper documentation explaining how its handle etc. Add ability for app to import export it, handle marge conflicts. Then syncing should reuse this format.
It should be internal to notable, but it has to be well thought out. It also should be easily modifiable as db schema changes. Now you putted all the data in json, its really bad approach for space, speed, and possibly future modifications. The strokes points should be encoded in SB1 -- see docs.
I think it would be best to implement the basic sync format in a separate PR, then update the WebDAV sync to use that format. The format doesn't need to be perfect initially, but it should be flexible and easy to modify. Labeling it "experimental" for the first few releases is a sensible approach: it lets us make breaking changes without requiring migrations. That said, it would be good to start from a solid foundation.
I will be going on vacation for couple of days soon, so I won't be answering probably.
…s.kt Changing syncInterval as suggested. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
OK just for clarification: what do you mean by conflicts in stroke data? Do you mean two different devices making changes? Because since this isn't a multiuser system, my inclination is just to go with "most recent page wins." |
|
Multiple devices making changes, changes done on server while device was offline, etc. A lot of things might get wrong, there might be a lot of edge cases: I was thinking that there should be some strategies for merging predefined: So in questionable situations we know what to do (ie always prioritise device, ask case by case, etc) I didn't implemented it yet, so you can change it a little bit, if you have better idea. But I would expect some functions that will take data from device and database, and merging settings, and return merged data. Try to make it as much generic as possible without too much hassle, so it can be latter reused. Also, for now one strategy will be enough, ie take newest changes, but leave space for other to be added later. And, I really think that debugging will be much easier if you add first possibility to import new format, then to merge changes from the linked file, and then to reuse those for syncing. Just testing one thing at the time. Also, I'm will have quite busy two weeks, so I might not look into code, but I will try to answer questions. |
OK, got you I think. I don't think we need a new format at all; I've gone ahead and switched to SB1 and it works quite well imo. For now at least I've defaulted to "last writer wins" including preventing changes to notes saved after deletion from being lost. I don't get the sense there's going to be huge demand for multiple conflict resolution strategies as long as it works consistently. |
|
I think that it would be best to get the some rough sketch on how the sync is working, I saw that you added documentation for users, that's great. When you add it I will review this, so you you will know if concept is alright. It will also be needed for latter development. The purpose for this file should be to get an idea how the sync works, how its structured, without looking at the code itself. Proper review of the code will have to wait a little bit, in February I should be able to do it. |
Ethran
left a comment
There was a problem hiding this comment.
Please make sure that all the information on different providers are correct, or mark [UNTESTED] if you cant.
Try to get more data from refused connection, give user more information what want wrong.
Give more troubleshooting information, I spend a lot of time trying to find what is wrong. URL: https://your-nextcloud.com/remote.php/dav/files/ it claimed that is correct, but on trying to create folder it failed, with 403.
For now I tested only with my phone, after reviewing code I will check on my main device.
| ### 2. Configure Notable | ||
|
|
||
| 1. Open Notable | ||
| 2. Go to **Settings** (three-line menu icon) |
| 1. **Nextcloud** (Recommended for self-hosting) | ||
| - Free and open source | ||
| - Full control over your data | ||
| - URL format: `https://your-nextcloud.com/remote.php/dav/files/username/` |
There was a problem hiding this comment.
there should not be a username in the link, at list in my case.
in my case this worked:
https://your-nextcloud.com/remote.php/webdav/
You should probably add instructions "how to know that my url is correct?" in troubleshooting section.
ie for next cloud going to the url should result in blank page or
"This is the WebDAV interface. It can only be accessed by WebDAV clients such as the Nextcloud desktop sync client."
|
|
||
| ### Connection Failed | ||
|
|
||
| **Problem**: Test connection fails with "✗ Connection failed" |
There was a problem hiding this comment.
The error message should be more descriptive, try showing:
something like:
"url does not exists"
"wrong password/username"
|
I'm not sure how you check which file was modified, I suppose that you aren't using ETag, which would require db changes, and hashes I think are not available over WebDav. I think later (NOT in this PR) we should save ETag to db. Either way, I think that it is very important to make sure that server time, and device time are the same. If there would be two devices with different times set, the merging base on modification time will break. I even think that we should not allow syncing if time is messed up (or at list require explicite action to sync with broken time). |
|
please add switch to explicitly allow syncing on mobile data. It shouldn't be syncing by default when it's not connected to wifi. |
|
Also, please provide instructions how to setup syncing if user have 2fa, ie: |

Outstanding Issues: