377 lines
8.2 KiB
Markdown
377 lines
8.2 KiB
Markdown
# Synchronization exchange
|
|
|
|
## 1. Status
|
|
|
|
```text
|
|
DIRECT_MTP_TRANSPORT=PASS
|
|
ANDROID_TO_PC_IMPORT=PASS
|
|
PC_TO_ANDROID_CATALOG=PASS
|
|
COMMON_SYNC_ENGINE=PASS
|
|
TRAINLOG_SYNCD=PASS
|
|
ANDROID_TRIGGERED_SYNC=PASS
|
|
ANDROID_SYNC_RECEIPT=PASS
|
|
TUI_SYNC_LOG_SHOW=PASS
|
|
BIDIRECTIONAL_SYNC_V1=PASS
|
|
MULTI_OCCURRENCE_SESSION_V2=PASS
|
|
EQUIPMENT_ASSOCIATIONS_V2=PASS
|
|
|
|
TRAINLOG_FORMAT_V1=FROZEN_UNCHANGED
|
|
```
|
|
|
|
Synchronization artifacts are separate from the frozen Trainlog session JSON
|
|
v1 format.
|
|
|
|
## 2. Exchange directory
|
|
|
|
Canonical Android shared-storage directory:
|
|
|
|
```text
|
|
Download/Trainlog
|
|
```
|
|
|
|
Desktop accesses this directory through direct MTP.
|
|
|
|
Android accesses PC-created artifacts through a persistent Storage Access
|
|
Framework folder grant.
|
|
|
|
## 3. Artifact table
|
|
|
|
| Direction | File | Format |
|
|
| --- | --- | --- |
|
|
| Android -> PC | `trainlog-mobile-export-v1.json` | `trainlog-mobile-export` v1 |
|
|
| Android -> PC | `trainlog-mobile-export-v2.json` | `trainlog-mobile-export` v2 (active) |
|
|
| Android -> PC | `trainlog-equipment-associations-v2.json` | `trainlog-equipment-associations` v2 |
|
|
| PC -> Android | `trainlog-pc-catalog-v1.json` | `trainlog-pc-catalog` v1 |
|
|
| PC -> Android | `trainlog-pc-mobile-export-v2.json` | `trainlog-mobile-export` v2 |
|
|
| PC -> Android | `trainlog-equipment-associations-v2.json` | `trainlog-equipment-associations` v2 |
|
|
| Android -> PC agent | `trainlog-sync-request-v1.json` | `trainlog-sync-request` v1 |
|
|
| PC agent -> Android | `trainlog-sync-receipt-v1.json` | `trainlog-sync-receipt` v1 |
|
|
|
|
No SQLite file is transferred.
|
|
|
|
V2 is a separate format: every session entry has an `entry_id`, `position`,
|
|
metrics, actual loads and optional equipment identity. This permits two
|
|
occurrences of the same exercise without fusion. V1 remains readable with its
|
|
frozen contract. A V1 historical session is reconciled with V2 only when the
|
|
exercise/order correspondence is unambiguous; otherwise the importer reports
|
|
a conflict rather than silently overwriting data.
|
|
|
|
## 4. Android -> PC mobile snapshot
|
|
|
|
Header:
|
|
|
|
```json
|
|
{
|
|
"format": "trainlog-mobile-export",
|
|
"version": 2
|
|
}
|
|
```
|
|
|
|
The artifact is a complete idempotent mobile snapshot containing:
|
|
|
|
```text
|
|
exercises
|
|
sessions
|
|
body_observations
|
|
```
|
|
|
|
V2 session entries additionally carry:
|
|
|
|
```text
|
|
entry_id stable occurrence identity
|
|
position stable order within session
|
|
equipment_id optional canonical equipment identity
|
|
weight_kg optional actual value on each set
|
|
```
|
|
|
|
The desktop imports sessions first, preserving `entry_id`, then applies the
|
|
equipment companion only after all referenced entries exist. Reimporting either
|
|
artifact reconciles stable identities; it neither duplicates sessions nor
|
|
regenerates occurrence IDs.
|
|
|
|
Exercise profile fields:
|
|
|
|
```text
|
|
exercise_id
|
|
name
|
|
recording_mode
|
|
tracking_mode
|
|
data_fields
|
|
```
|
|
|
|
Set-based session exercise actuals use ordered:
|
|
|
|
```text
|
|
sets[]
|
|
```
|
|
|
|
with either:
|
|
|
|
```text
|
|
reps
|
|
```
|
|
|
|
or:
|
|
|
|
```text
|
|
duration_seconds
|
|
```
|
|
|
|
Heterogeneous repetition values are valid.
|
|
|
|
Continuous actuals use:
|
|
|
|
```text
|
|
continuous
|
|
duration_seconds
|
|
speed_kmh optional
|
|
distance_km optional
|
|
```
|
|
|
|
No synthetic set is created for continuous work.
|
|
|
|
Android-local active-session drafts are excluded from this snapshot and remain
|
|
local during synchronization. Only successful atomic finalization makes a draft
|
|
a completed exportable session. The v1 artifact has no draft fields or tables;
|
|
catalog reconciliation preserves active draft references.
|
|
Same-ID catalog entries may update display-name metadata in their existing
|
|
catalog row; a rename never creates a second exercise identity.
|
|
|
|
## 5. Desktop mobile importer
|
|
|
|
Reference importer:
|
|
|
|
```text
|
|
tools/import_mobile_export.py
|
|
```
|
|
|
|
Properties:
|
|
|
|
```text
|
|
strict full-snapshot validation
|
|
transactional import
|
|
stable-ID idempotence
|
|
catalog reconciliation
|
|
profile conflict rejection
|
|
heterogeneous performed-set preservation
|
|
targetless schema-v5 import when no true target exists
|
|
continuous activity kept separate
|
|
```
|
|
|
|
The importer never invents a uniform target merely to fit desktop persistence.
|
|
|
|
## 6. PC -> Android catalog
|
|
|
|
The desktop publishes:
|
|
|
|
```text
|
|
trainlog-pc-catalog-v1.json
|
|
```
|
|
|
|
It is a canonical exercise catalog snapshot containing stable profile metadata.
|
|
|
|
Android reconciles the received catalog into its local exercise catalog.
|
|
|
|
This direction does not overload frozen Trainlog session JSON v1.
|
|
|
|
## 7. Android sync request
|
|
|
|
Android writes:
|
|
|
|
```text
|
|
trainlog-sync-request-v1.json
|
|
```
|
|
|
|
Header:
|
|
|
|
```json
|
|
{
|
|
"format": "trainlog-sync-request",
|
|
"version": 1
|
|
}
|
|
```
|
|
|
|
Required synchronization identity:
|
|
|
|
```text
|
|
request_id = sr_<uuid-v4>
|
|
```
|
|
|
|
The artifact also carries the request timestamp.
|
|
|
|
A new `request_id` represents a new synchronization request.
|
|
|
|
## 8. PC sync receipt
|
|
|
|
After processing an Android request, the PC publishes:
|
|
|
|
```text
|
|
trainlog-sync-receipt-v1.json
|
|
```
|
|
|
|
Header:
|
|
|
|
```json
|
|
{
|
|
"format": "trainlog-sync-receipt",
|
|
"version": 1
|
|
}
|
|
```
|
|
|
|
The receipt contains:
|
|
|
|
```text
|
|
request_id
|
|
sync_id
|
|
status
|
|
summary
|
|
Android -> PC counts
|
|
PC -> Android catalog count
|
|
```
|
|
|
|
Android accepts a receipt only when its `request_id` matches the pending
|
|
request.
|
|
|
|
## 9. Shared desktop engine
|
|
|
|
Canonical implementation:
|
|
|
|
```text
|
|
trainlog_sync_run()
|
|
```
|
|
|
|
TUI path:
|
|
|
|
```text
|
|
TUI
|
|
-> shared engine
|
|
```
|
|
|
|
Android-triggered path:
|
|
|
|
```text
|
|
Android request
|
|
-> trainlog-syncd
|
|
-> shared engine
|
|
-> receipt
|
|
```
|
|
|
|
One synchronization transaction performs:
|
|
|
|
```text
|
|
mobile snapshot download
|
|
-> mobile import
|
|
-> equipment companion import by (session_id, entry_id)
|
|
-> PC catalog export
|
|
-> PC mobile V2 export
|
|
-> PC equipment companion V2 export
|
|
-> PC catalog MTP publication
|
|
-> optional receipt publication
|
|
-> structured run history
|
|
```
|
|
|
|
## 10. Concurrency and request consumption
|
|
|
|
Synchronization owns:
|
|
|
|
```text
|
|
$XDG_DATA_HOME/trainlog/sync.lock
|
|
```
|
|
|
|
The daemon uses non-blocking acquisition while polling.
|
|
|
|
The TUI manual action waits for the active synchronization lock.
|
|
|
|
After a request is completed and its receipt is published, the request ID is
|
|
recorded locally so the same request is not processed as a new request again.
|
|
|
|
## 11. Structured sync history
|
|
|
|
Every real run has:
|
|
|
|
```text
|
|
sy_<uuid-v4>
|
|
```
|
|
|
|
Artifacts:
|
|
|
|
```text
|
|
$XDG_DATA_HOME/trainlog/sync_runs/sy_*.json
|
|
$XDG_DATA_HOME/trainlog/sync_runs/sy_*.txt
|
|
$XDG_DATA_HOME/trainlog/sync_history.log
|
|
```
|
|
|
|
The TUI presents newest runs in a selectable list and opens the detail file with
|
|
`Enter`.
|
|
|
|
Legacy history rows without a `sync_id` remain readable as list entries but
|
|
cannot have structured detail.
|
|
|
|
## 12. PC user service
|
|
|
|
Install or refresh:
|
|
|
|
```bash
|
|
bash tools/install_syncd_user.sh
|
|
```
|
|
|
|
Check:
|
|
|
|
```bash
|
|
systemctl --user is-active trainlog-syncd.service
|
|
systemctl --user --no-pager --full status trainlog-syncd.service
|
|
```
|
|
|
|
Daemon log:
|
|
|
|
```bash
|
|
tail -f ~/.local/state/trainlog/syncd.log
|
|
```
|
|
|
|
No root privilege is required.
|
|
|
|
## 13. Transport invariants
|
|
|
|
Do not regress to:
|
|
|
|
```text
|
|
SQLite database copying
|
|
mandatory GVFS/FUSE mounts
|
|
exercise-name identity heuristics
|
|
fake sets for continuous activity
|
|
fake uniform targets for heterogeneous actual sets
|
|
overloading frozen Trainlog JSON v1
|
|
```
|
|
|
|
## 14. Hardware validation
|
|
|
|
## 15. Equipment associations V2 and legacy V1
|
|
|
|
`TRAINLOG_FORMAT_V1` remains frozen. The active companion is
|
|
`trainlog-equipment-associations-v2.json`, format
|
|
`trainlog-equipment-associations`, version `2`. Each row is identified by
|
|
`(session_id, entry_id)` and contains `exercise_id` as consistency metadata,
|
|
then either `state: set` with a canonical `equipment_id`, or `state: cleared`
|
|
for an intentional removal. A missing companion conveys no equipment
|
|
information and cannot clear a previously known choice. Unknown canonical IDs,
|
|
unknown entries and ambiguous identities reject the companion transaction
|
|
explicitly; an unknown equipment reference is never silently changed to null.
|
|
|
|
The historical V1 companion remains readable only where its
|
|
`(session_id, exercise_id)` targeting is unambiguous. It cannot represent two
|
|
occurrences of the same exercise in one session and is not redefined to do so.
|
|
|
|
Validated on the physical Android device:
|
|
|
|
```text
|
|
MTP device discovery PASS
|
|
storage access PASS
|
|
read/write/list/delete PASS
|
|
Android mobile snapshot download PASS
|
|
desktop idempotent import PASS
|
|
PC catalog publication PASS
|
|
Android request detection PASS
|
|
trainlog-syncd processing PASS
|
|
receipt publication/readback PASS
|
|
multiple distinct Android request IDs PASS
|
|
```
|