Skip to content

Harden blueman/main/Sendto.py: reliability, UX, portability, tests - #3319

Open
geraldo-netto wants to merge 9 commits into
blueman-project:mainfrom
geraldo-netto:fix/sendto-hardening
Open

Harden blueman/main/Sendto.py: reliability, UX, portability, tests#3319
geraldo-netto wants to merge 9 commits into
blueman-project:mainfrom
geraldo-netto:fix/sendto-hardening

Conversation

@geraldo-netto

Copy link
Copy Markdown
Contributor

Works through the open blueman/main/Sendto.py findings. One commit per fix; adds the first test file for this module (test/main/test_sendto.py).

Reliability

  • time-2: throttle transfer-progress speed/ETA updates with time.monotonic() so a wall-clock step can't stall or spam the UI.
  • rob-7: replace the ZeroDivisionError flow in on_transfer_progress with an explicit spd > 0 guard, and only update the progress fraction when total_bytes > 0 (a transfer of only empty files previously raised an uncaught ZeroDivisionError).

UX

  • ux-1: stop blocking the UI thread with time.sleep(1) after stopping discovery — defer session creation via a one-shot GLib.timeout_add_seconds.
  • prodeng-1: show an error dialog (not just a console log) when no Bluetooth adapter is present.
  • prodeng-2: print a stderr notice when --source names an unknown adapter instead of silently falling back.

Cleanup / portability

  • vec-1: simplify _has_objpush to a single any().
  • dup-7: extract _setup_signal_handlers for the repeated connect_signal wiring.
  • obs-8: use str(e) instead of the deprecated GLib.Error.message.

Tests

  • New test/main/test_sendto.py (26 tests) using Sender/SendTo built via __new__ to exercise the logic methods without GTK/D-Bus init.
  • In-scope coverage of the touched/testable methods ~99%. The GTK __init__ and modal-dialog paths are not headless-reachable, so whole-file coverage is lower by nature.
  • mypy -p blueman --strict clean; flake8 (core codes) clean.

Deferred

  • leg-3 / ux-6 (deprecated dialog.run()/.destroy() → async response handlers): three of the four sites (select_files, select_device, the obex-start error dialog) are synchronous startup gates in __init__ whose return values drive control flow before the GTK main loop runs. Converting to async response-signals requires restructuring both __init__s into continuation-based flows — not headless-testable and high regression risk on the core send path. Left for a dedicated GTK4-era change.

🤖 Generated with Claude Code

geraldo-netto and others added 9 commits June 19, 2026 21:24
on_transfer_progress throttled speed/ETA updates with time.time(); a
backward wall-clock step (NTP or manual) stalled all updates until real
time caught up, and a forward step fired every call. Switch the throttle
to time.monotonic(), which never steps. Add test/main/test_sendto.py with
a Sender built via __new__ (no GTK/D-Bus init) covering the throttle
window and progress accumulation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The ETA computation relied on catching ZeroDivisionError to handle a zero
speed, and the progress-bar fraction divided by total_bytes with no guard
at all — a transfer of only empty files (total_bytes == 0) raised an
uncaught ZeroDivisionError. Replace the exception flow with an explicit
`if spd > 0` guard (logging when ETA can't be estimated) and only update
the fraction when total_bytes > 0. Add tests for zero speed, zero
total_bytes, and the normal ETA path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the explicit per-UUID loop with a single any() over the device's
UUIDs checking for the OBEX Object Push service class. Same behaviour,
less boilerplate. Add tests for present / absent / empty UUID lists.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SendTo.__init__ repeated the connect_signal boilerplate six times across
the manager and the any-adapter/any-device watchers. Add a
_setup_signal_handlers(source, handlers) helper that connects each signal
to its (callback, *args) and route all six wirings through it.
connect_signal is an alias of GObject.connect, so behaviour is unchanged.
Add tests for multi-handler wiring and the empty case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PyGObject deprecated GLib.Error.message. In the obex client-start error
path, switch both the StartServiceByName check and the debug log to
str(e), which yields the same message without the deprecated attribute.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A mistyped -s/--source previously only logged "Unknown adapter, trying
first available" to the console before silently falling back, so a CLI
user never learned their choice was ignored. Also print a clear notice
to stderr naming the unknown adapter and the fallback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Launched from a file manager's "Send To", sendto previously aborted with
only a console log when no Bluetooth adapter was present, leaving the
user with no on-screen feedback. Show an ErrorDialog explaining that an
adapter must be connected/enabled (mirroring check_bluetooth_status)
before exiting.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
After stopping discovery, Sender.__init__ called time.sleep(1) on the UI
thread to let the adapter settle, freezing the dialog for a second.
Replace it with GLib.timeout_add_seconds scheduling a one-shot
_create_session_timeout, so the UI stays responsive and the session is
created after the delay. Add a test that the timer callback creates the
session once and returns False.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add headless tests (Sender/SendTo built via __new__) for the manager and
property-changed signal dispatch, _start_discovery, the progress-bar text
formatter, and the send queue (process_queue / on_transfer_completed),
raising in-scope coverage of the testable logic to ~99%. The GTK/D-Bus
__init__ and modal dialog paths remain outside headless reach.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

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