Lynavo Drive Data Model And Statistics Semantics

July 15, 2026 ยท View on GitHub

This document records the core persistence structures, identity semantics, and statistics semantics on mobile and sidecar.

1. Identity Semantics

1.1 Device Identity

  • Mobile identity: clientId
  • Generated by mobile and persisted in platform secure storage.
  • Desktop uses it to identify the same phone, not device name or IP.

1.2 Device Display Name

  • System device names are not reliable on iOS 16+.
  • The current implementation generates a stable unique name for generic names, for example Phone 9C2A.
  • User-edited display names are stored in Keychain.

1.3 deviceId On Each Side

The shared HistoryLedgerCardDTO.deviceId points in different directions on each side:

  • desktop: deviceId = mobile clientId
  • mobile: deviceId = desktop serverId

Keep this directional difference in mind when reading code.

2. Sidecar Database

The sidecar SQLite initial migration is defined at:

  • services/sidecar-go/internal/store/migrations/001_initial.sql

2.1 paired_devices

Purpose:

  1. Store paired devices.
  2. Store client_name / device_alias / last_ip / pairing_id / pairing_token_hash.
  3. Serve as the primary device index for dashboard and detail views.

2.2 sessions

Purpose:

  1. Current sync sessions from the sidecar perspective.
  2. Record state / active_file_key / active_offset / started_at / updated_at.

2.3 uploads

Purpose:

  1. One final upload record per file_key.
  2. Record final path, hash, completion time, transfer duration, and committed bytes.

Key fields:

  • status
  • part_path
  • final_path
  • committed_bytes
  • active_transmission_ms
  • completed_at
  • updated_at

2.4 device_daily_stats

Purpose:

  1. Aggregate completed records by device plus date.
  2. Provide fast statistics for desktop dashboard/history.

Key fields:

  • stat_date
  • client_id
  • client_name_snapshot
  • client_ip_snapshot
  • file_count
  • total_bytes
  • active_transmission_ms

2.5 settings / share_config

Purpose:

  1. Basic sidecar settings.
  2. Shared directory detection and SMB URL status.

3. Mobile Database

Mobile SQLite is managed by UploadStore.swift.

3.1 binding

Purpose:

  1. Current bound desktop information.
  2. Includes device_id / host / port / pairing_id / share_name / last_bound_at.

3.2 upload_items

Purpose:

  1. Local upload queue and file-level state machine.
  2. Real data source for the current sync main loop.

Key fields:

  • asset_local_id
  • modified_at
  • media_type
  • original_filename
  • file_key
  • file_size
  • status
  • acked_offset
  • last_error_code
  • updated_at

Important constraints:

  • The pending queue comes from status in ('queued','discovered','preparing','ready','cloud_downloading','uploading').
  • The real upload set must be read from this table.
  • Do not use only assets newly scanned in the current round as the upload set.

3.3 sync_sessions

Purpose:

  1. Sync session snapshots from the mobile perspective.
  2. Store queue_total_count / queue_total_bytes / completed_count / completed_bytes / active_file_key.

3.4 daily_ledgers

Purpose:

  1. Mobile history page and home page statistics.
  2. Store which desktop, which day, and how much was transferred.

Key fields:

  • ledger_date
  • device_id
  • device_name_snapshot
  • device_ip_snapshot
  • file_count
  • total_bytes
  • active_transmission_ms

4. Statistics Semantics

4.1 Which Day A Transfer Belongs To

Current unified rule:

  • Use the sidecar / desktop completion day.

Reasons:

  1. The real write happens in the desktop sidecar receive directory.
  2. Desktop statistics already depend on the sidecar.
  3. Mobile previously bucketed by its own UTC date, which caused drift from desktop buckets.

Current implementation:

  1. The sidecar returns ledgerDate in FILE_END_RES.
  2. Mobile prefers that ledgerDate.
  3. Local date is used only as an exceptional fallback.

4.2 Completion Time

Desktop detail completion time comes from:

  • Prefer uploads.completed_at.
  • Fall back to updated_at or filesystem modTime.

Notes:

  • The UI currently defaults to minute precision, HH:mm.
  • Therefore files completed within the same minute can appear to have the same time.

4.3 Queue Count

  • The mobile home queue count comes from the local upload_items pending set.
  • The sidecar queueCount comes from the current sync session's SYNC_BEGIN_REQ.

These values should match in theory.

If this happens:

  • The UI shows many queued items.
  • Sidecar queueCount=1 or 0.

It usually means the app main loop is using the wrong data source, not that the sidecar lost data.

5. Filesystem Layout

Default receive root:

  • macOS: ~/Library/Application Support/Lynavo Drive/received
  • Windows: %AppData%\\Lynavo Drive\\received

These defaults come from the sidecar's os.UserConfigDir()/Lynavo Drive/received.

Actual layout:

received/
  <devicePath>/
    <YYYY-MM-DD>/
      <original file>

Notes:

  1. storagePath is the receive root.
  2. devicePath is the device-specific directory.
  3. Desktop detail should prefer opening <devicePath>/<selectedDate>.

6. Device Naming And Receive Directory

6.1 Device Identity

Device identity depends only on clientId. It does not depend on clientName, deviceAlias, receiveDirName, or IP. Device name changes do not create a new device identity.

6.2 Display Name

The display name is only for UI and diagnostics. It does not participate in receive-path calculation.

Derivation:

displayName = deviceAlias ?? clientName ?? clientId
  • deviceAlias: user-set alias in the product, preferred for display.
  • clientName: system device name reported by mobile on each connection.
  • clientId: final fallback, used only when data is abnormal or initialization is incomplete.

When clientName or deviceAlias changes, the UI display name updates immediately, but no disk operation is triggered.

6.3 Receive Directory Name (receiveDirName)

receiveDirName is a storage-layer field mapped to paired_devices.receive_dir_name. It is a stable storage key and does not change automatically once set for a device.

When it is generated:

  • New device: generated and written atomically by PairDeviceWithDirName during pairing. Directory-name generation and paired-device persistence happen in the same critical section.
  • Old device with empty receive_dir_name: lazily backfilled by EnsureReceiveDirName, preferring to claim an existing legacy directory and generating a new name only if none is found.

Immutability constraints:

  • clientName changes do not trigger directory rename.
  • deviceAlias changes do not trigger directory rename.
  • The sidecar does not perform full rename or full directory rebuild at startup.

6.4 displayName And receiveDirName Can Differ

The UI display name may differ from the folder name shown in Finder / Explorer. This is a design decision, not a bug. The presentation layer and storage layer are intentionally decoupled to keep external backups, indexing, and script paths stable.

When troubleshooting, use device detail or diagnostics to confirm the mapping between displayName, receiveDirName, and devicePath.

7. iCloud Assets

iCloud assets have special handling, but they do not change data-model primary keys:

  1. They are queued normally during scanning.
  2. During export, the system may download them from iCloud to a local temporary file.
  3. Queue items can carry the isCloudAsset marker.
  4. Export enters the cloud_downloading state.

This means:

  • iCloud assets affect preparation time.
  • They should not change queueCount statistics semantics.