[ios] Base implementation of iCloud Synchronization #7641
Labels
No labels
Accessibility
Accessibility
Address
Address
Android
Android
Android Auto
Android Auto
Android Automotive (AAOS)
Android Automotive (AAOS)
API
API
AppGallery
AppGallery
AppStore
AppStore
Battery and Performance
Battery and Performance
Blocker
Blocker
Bookmarks and Tracks
Bookmarks and Tracks
Borders
Borders
Bug
Bug
Build
Build
CarPlay
CarPlay
Classificator
Classificator
Community
Community
Core
Core
CrashReports
CrashReports
Cycling
Cycling
Desktop
Desktop
DevEx
DevEx
DevOps
DevOps
dev_sandbox
dev_sandbox
Directions
Directions
Documentation
Documentation
Downloader
Downloader
Drape
Drape
Driving
Driving
Duplicate
Duplicate
Editor
Editor
Elevation
Elevation
Enhancement
Enhancement
Epic
Epic
External Map Datasets
External Map Datasets
F-Droid
F-Droid
Fonts
Fonts
Frequently User Reported
Frequently User Reported
Fund
Fund
Generator
Generator
Good first issue
Good first issue
Google Play
Google Play
GPS
GPS
GSoC
GSoC
iCloud
iCloud
Icons
Icons
iOS
iOS
Legal
Legal
Linux Desktop
Linux Desktop
Linux packaging
Linux packaging
Linux Phone
Linux Phone
Mac OS
Mac OS
Map Data
Map Data
Metro
Metro
Navigation
Navigation
Need Feedback
Need Feedback
Night Mode
Night Mode
NLnet 2024-06-281
NLnet 2024-06-281
No Feature Parity
No Feature Parity
Opening Hours
Opening Hours
Outdoors
Outdoors
POI Info
POI Info
Privacy
Privacy
Public Transport
Public Transport
Raw Idea
Raw Idea
Refactoring
Refactoring
Regional
Regional
Regression
Regression
Releases
Releases
RoboTest
RoboTest
Route Planning
Route Planning
Routing
Routing
Ruler
Ruler
Search
Search
Security
Security
Styles
Styles
Tests
Tests
Track Recording
Track Recording
Translations
Translations
TTS
TTS
UI
UI
UX
UX
Walk Navigation
Walk Navigation
Watches
Watches
Web
Web
Wikipedia
Wikipedia
Windows
Windows
Won't fix
Won't fix
World Map
World Map
No milestone
No project
No assignees
3 participants
Due date
No due date set.
Dependencies
No dependencies set.
Reference: organicmaps/organicmaps-tmp#7641
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "ios/ab-icloud-continuous-sync"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
This PR adds multi-device synchronization of bookmarks to iCloud on iOS:
Files App
(iOS) or viaFinder
(MacOS).Recently deleted
(iOS) or inBin
(MacOS).Closes #2678
Demos
Screen recordings with 2 devices and overall workflow you can see by the link (because they are even compressed bigger than 10mb):
https://drive.google.com/drive/folders/1K9aqaElsh6YOFps8dg1pJ_dK2XoBVlgB?usp=drive_link
Create bookmarks
Create a new list
Delete list from the file system
Drag and drop file into the iCloud container
Restore recentlky deleted file from Bin (onMac)
Merge conflict when on the both devices lists was changed but the one device was offline
Add bookmarks whe device is offline
Exportl kml file from GuruMaps directly into the Files app iCloud container
To Do
Things that are still in progress and should be discussed. Please share your thoughts on how to implement them!
There are many places in this implementation that may raise errors (starting from metadata items initialization, read/write operations, and continuing with the quota error: NSUbiquitousFileNotUploadedDueToQuotaError).
It is an open question about how to handle errors.
UI For handling the Errors and uploading process in the Bookmarks Table View Cell (as in the Files app)

UI for enable/disable Sync. I'll make an additional switcher in the settings which will be Off by default. And I want to add an Alert that informs users that the feature is in Beta and that it's better to back up all bookmarks for safety reasons.
Handle
disabled iCloud on the Device
in Settings.Now the reloading of bookmarks when all files were updated/saved starts by calling the
bookmarksManager.loadBookmarks()
which reloads All files. But it would be better to reload only provided groups. It needs to implement a method that will process only the current file and notify when all work is finished.When the app is installed for the first time and there are no bookmarks at all it means that we shouldn't process a file called
My Places.kml
and start downloading the cloud content (if needed) but there are no methods in the core that can return someBookmarksManager.userContentIsEmpy
bool because there already exists one file. So for the initial state, I use the hard coding to determine that there is really NO content. It needs to add some more convenient ways to handle situations when there isno content
.Add the C617.1 API Declaration "Declare this reason to access the timestamps, size, or other metadata of files inside the app container, app group container, or the app’s CloudKit container." to the PrivacyInfo
Unit tests
SynchronizationStateManager
andLocalDirectoryMonitor
are partly coveredBug:
Sometimes, lists are duplicated in the table (not the files themselves, but the lists) on the Bookmarks and Tracks screen. This is somehow related to the core and the generation of bookmarks, as the table draws what is generated by the core. Apparently, if loadBookmarks is called frequently or several times in succession, the cleaning is not performed correctly.
Found the reason: If the loadBookmarks method is called twice in a row, the lists will duplicate even without the cloud (in the master).
Bug: Fix the infinity loop on deletion of different bookmarks in the same group on different devices in parallel.
Bug: Fix UI crashes when the same file is changed on two devices in parallel
Nice to Have
Further Improvements (out of scope of this PR)
NSUbiquitousKeyValueStore
kml
s files. But in the future, there will be user-generated content such as images. So it is important to move from usingfielName
s as a uuid to the related paths.Toast message
when thePlacePage
or theEdit screen
were dismissed during theMarkID
invalidation.This should go to
AccessedAPICategoryFileTimestamp
, right?Thanks! This code is set incorrectly here. I'll fix it. This is for the timestamp
@ -0,0 +192,4 @@
private extension FileManager {
func source(for directory: URL) throws -> DispatchSourceFileSystemObject {
let directoryFileDescriptor = open(directory.path, O_EVTONLY)
This code monitor only directory changes, not file changes. E.g. if you edit/overwrite some file in-place, it won't fire. If you add a new file, delete, change file name, then it fires.
Our bookmarks saving code should create a new temporary file, and move it to overwrite the old file atomically, that's why it works.
However, there could be some other cases when files are just overwritten/appended, without modifying the directory.
Another case to test: what if user uploads some good/bad kml files (and other types too) using Finder/File Sharing into OM's monitored folder?
@ -0,0 +192,4 @@
private extension FileManager {
func source(for directory: URL) throws -> DispatchSourceFileSystemObject {
let directoryFileDescriptor = open(directory.path, O_EVTONLY)
This code is triggered on every write operation into the directory, not only the creation/renaming/removal because I've set up the source with the
write
eventMask:https://developer.apple.com/documentation/dispatch/dispatchsource/filesystemevent/1780646-write
I wrote the test case to check the file contents updating without replacing isolated from the bookmark manager:
We can only add files to the iCloud local container.
@ -0,0 +192,4 @@
private extension FileManager {
func source(for directory: URL) throws -> DispatchSourceFileSystemObject {
let directoryFileDescriptor = open(directory.path, O_EVTONLY)
Is any appending write to the existing file also catched? Can a test for append write be added?
Is it possible to avoid detection of appending data?
My Files app shows non-kml files. Is it a bug that they are synced to iCloud?
@ -0,0 +192,4 @@
private extension FileManager {
func source(for directory: URL) throws -> DispatchSourceFileSystemObject {
let directoryFileDescriptor = open(directory.path, O_EVTONLY)
It seems that these are temporary kmls created by the core...
I'll add filtering to these files.
@ -0,0 +192,4 @@
private extension FileManager {
func source(for directory: URL) throws -> DispatchSourceFileSystemObject {
let directoryFileDescriptor = open(directory.path, O_EVTONLY)
Another question is why these temporary files are not deleted...
It works like a charm in the latest version (32d9e014bc99202759f4b1d026745e50b38d2b01). Tested on two devices. No crashes. No data lost. Very interactive. Great work!