Skip to content

Enhance GUI sync feedback and conflict handling for large files#18

Merged
guydols merged 6 commits into
mainfrom
dev
Jul 2, 2026
Merged

Enhance GUI sync feedback and conflict handling for large files#18
guydols merged 6 commits into
mainfrom
dev

Conversation

@guydols

@guydols guydols commented Jul 2, 2026

Copy link
Copy Markdown
Owner

This pull request enhances the synchronization client to better track and display file conflict information in the GUI, and improves the accuracy of GUI sync activity statistics. It does so by ensuring that all conflicts detected during sync operations are pushed to the GUI state, and by updating GUI statistics (like files/bytes sent or received) at every relevant sync event. Additionally, it introduces utilities for formatting modification times and includes comprehensive tests for the new conflict reporting logic.

Conflict reporting and GUI integration:

  • Added a new helper function push_gui_conflicts to route all detected file conflicts (from bundle application and large file handling) into the GUI state, so that users are promptly informed of sync conflicts. This function is invoked in all relevant sync paths, including bundle application and large file end events. [1] [2] [3] [4] [5]
  • Updated the LargeFileEndOutcome enum to include a CommittedWithConflict variant, and ensured this new outcome is handled both in the core logic and when updating the GUI. [1] [2] [3]

GUI sync activity statistics:

  • Ensured that the GUI's sync activity counters (files/bytes sent/received) are updated at every relevant point in both the send and receive loops, including for renames, deletes, and large file chunk events. [1] [2] [3] [4] [5] [6] [7]

Code structure and utility improvements:

  • Refactored the send/receive loops and related functions to accept and utilize the optional SharedState for GUI integration, and added new utility functions for formatting file modification times for display. [1] [2] [3] [4]

Testing:

  • Added comprehensive tests for the new push_gui_conflicts logic, including edge cases (no GUI state, empty conflicts), and for the date formatting utility.

Imports and minor refactoring:

  • Updated imports to support new features and utilities, and made minor refactorings for clarity and consistency. [1] [2]

These changes collectively ensure that users are reliably notified of all file conflicts during sync, and that the GUI accurately reflects ongoing sync activity, improving both usability and reliability.

guydols added 6 commits July 2, 2026 16:17
…rding it

`handle_recv_large_file_end` collapsed `FinishResult::CommittedWithConflict`
into a plain `LargeFileEndOutcome::Committed`, silently discarding the
`ConflictInfo` for any large file that conflicted during transfer. This made
it impossible for callers (the filesync client's GUI state) to ever learn
about large-file conflicts.

Add `LargeFileEndOutcome::CommittedWithConflict(ConflictInfo)` so the
information survives, and update the server's `client_recv_loop` match (which
has no GUI to update) to treat it the same as `Committed` for broadcast
purposes.
`SyncSnapshot` had no way to represent "a live incremental sync is
happening" (only `InitialSync`) and no API for pushing conflicts into
the snapshot, which is why the GUI's Conflicts tab and progress bar
never reflected live-sync activity.

Add to `gui/state.rs`:
- `ConnectionStatus::Syncing`, a distinct state from `InitialSync` for
  post-initial live sync batches, with its own label/colour.
- `SyncSnapshot::begin_sync_activity()` — transitions `Idle` -> `Syncing`
  (resetting transfer counters only on that transition) and always bumps
  `last_activity`.
- `SyncSnapshot::end_sync_activity_if_quiet(idle_after)` — drops back to
  `Idle` once no activity has been observed for `idle_after` (intended to
  be driven from the GUI's tick loop).
- `SyncSnapshot::push_conflict(...)` plus a module-level `next_conflict_id()`
  to append `Conflict` entries with unique ids from any code path.

Includes unit tests for all new behavior.
…st initial sync

The header badge, status dot, and progress bar in the status panel only
activated for `ConnectionStatus::InitialSync`, so once the initial sync
finished the UI gave no indication that anything was happening during
later incremental syncs — from the user's perspective, a working live sync
looked identical to a stuck/frozen client.

- `header.rs` / `status_panel.rs`: treat `ConnectionStatus::Syncing` the
  same as `InitialSync` for badge colour, status dot colour, and progress
  bar visibility.
- `status_panel.rs`: the transfer detail text now also shows the file
  count (`"{files} file(s) · {bytes} transferred"`), not just bytes, so
  users can see concrete evidence of activity instead of only steps.

Includes tests for the new `Syncing` render paths.
The `Message::Tick` handler ran on every tick (once per second) and forced
`active_tab` based on the *current* conflict count: switching to Conflicts
whenever conflicts were non-empty and the user was on Stats, and switching
back to Stats whenever conflicts were empty and the user was on Conflicts.
The second branch fired continuously and overrode any manual tab selection,
so clicking into the Conflicts tab would immediately get forced back to
Stats.

Make the auto-switch edge-triggered instead: track whether conflicts were
present on the previous tick (`DashboardState::had_conflicts`) and only
jump to the Conflicts tab the moment conflicts newly appear. The tab is
never forcibly switched away again, so the user can freely stay on
Conflicts (or navigate elsewhere) regardless of tick-to-tick conflict
count changes.

Also drive `SyncSnapshot::end_sync_activity_if_quiet` from the tick handler
so the `Syncing` status correctly falls back to `Idle` once a live-sync
batch goes quiet.
Two related bugs shared the same root cause: the client's send/recv loops
never touched `gui_state` outside of the initial-sync phase, and even
during initial sync, detected conflicts were computed but thrown away.

- "GUI can't show conflicts": `ApplyResult.conflicts` / `ConflictInfo` from
  both bundle and large-file transfers was discarded (only `.written` was
  used). Add `push_gui_conflicts()`, which converts each `ConflictInfo`
  into a `Conflict` (resolving real display filename/folder and mtimes via
  a new dependency-free `format_modified`/`format_unix_secs` UTC
  formatter), and call it from every path that can produce a conflict:
  the initial-sync bundle/large-file loop and the live-sync `recv_loop`
  (bundle and large-file-end arms).

- "Sync looks frozen" / no live-sync feedback: thread `gui_state` through
  `recv_loop`, `send_loop`, `flush_to_server`, and `send_paths_to_server`
  so every incoming/outgoing bundle, large-file chunk, delete, and rename
  calls `SyncSnapshot::begin_sync_activity()` and updates
  `files_sent`/`files_received`/`bytes_sent`/`bytes_received`. Combined
  with the new `ConnectionStatus::Syncing` state and status-panel changes,
  this makes ongoing background sync activity visible instead of the UI
  going quiet right after the initial sync completes.

Adds `push_gui_conflicts_tests` (whitebox unit tests) covering the
conflict-routing behavior directly, since reproducing a genuine two-sided
conflict race over a real network connection is inherently flaky.
Every existing integration test constructs its `Client` with
`gui_state: None`; the `Some(gui_state)` path used exclusively by the real
`filesync-gui` binary was completely untested, which is exactly the
scenario in the reported "initial sync freezes" bug report.

Add `test_client_gui_state.rs`, which drives a real client/server pair
over TCP+TLS with a live `SharedState` attached and asserts that initial
sync actually completes (does not hang) and leaves the GUI snapshot in a
correct, non-stuck state (status Idle/Syncing, correct file/byte counts,
no spurious conflicts).
@guydols guydols merged commit fb20c91 into main Jul 2, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant