When a client acquires a lock, it must refresh it every X seconds. A lock timeout after Y seconds (where X <Y).AlockwithatimestampgreaterthanYisconsideredexpiredandcanbeignoredbyotherclients.Aclientthattriestorefreshalockthathasexpiredshouldfail.
For example, if a client is currently syncing, it must stop doing so if it couldn't refresh the lock with Y seconds.
For example, if a client is upgrading a target, it must stop doing so if it couldn't refresh the lock within Y seconds.
If the previous lock has expired, we shouldn't try to acquire a new one. This is because other clients, seeing no active lock, might have performed in the meantime operations that invalidates the current operation. For example, another client might have upgraded the sync target, so any active sync with an expired lock should be cancelled. Or if the current client was upgrading the sync target, another client might have synced since then, making any cached data invalid.
In some cases it should be safe to re-acquire a lock but adding support for this would make the algorithm more complex without much benefits.
The lock files are in format `<lockType>_<clientType>_<clientId>.json` with lockType being "exclusive" or "sync", clientType being "desktop", "mobile" or "cli" and clientId is the globally unique ID assigned to a client profile when it is created.
The have the following content:
```json
{
"type": "exclusive",
"clientType": <string>,
"clientId": <string>,
"updatedTime": <timestampinmilliseconds>,
}
```
(Note that the lock file content is for information purpose only. Its content is not used in the lock algorithm since all data can be derived from the filename and file timestamp)
Although only one client can acquire an exclusive lock, there can be multiple `exclusive_*.json` lock files in the lock folder (for example if a client crashed before releasing a lock or if two clients try to acquire a lock at the exact same time). In this case, only the oldest lock amongst the active ones is the valid one. If there are two locks with the same timestamp, the one with lowest client ID is the valid one.
First the app checks the sync target version - if it's new (no version), it set it up by upgrading to the latest sync version.
If it's the same as the client supported version, it syncs as normal.
If it's lower than the client supported version, the client does not allow sync and instead displays a message asking the user to upgrade the sync target (upgradeState = SHOULD_UPGRADE).
If the user click on the link to upgrade, upgradeState becomes MUST_UPGRADE, and the app restarts.
On startup, the app check the upgradeState setting. If it is MUST_UPGRADE it displays the upgrade screen and starts upgrading. Once done it sets upgradeState back to IDLE, and restart the app.