zbus backend#6
Conversation
|
@TitikshaBansal |
Signed-off-by: Ayush <mail@ayuch.dev>
Signed-off-by: Ayush <mail@ayuch.dev>
Signed-off-by: Ayush <mail@ayuch.dev>
- added tests, documentation and examples for the zbus backend Signed-off-by: Ayush <mail@ayuch.dev>
Signed-off-by: Ayush <mail@ayuch.dev> diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2490a08..80dd49c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,15 +44,15 @@ jobs: uses: Swatinem/rust-cache@v2 - name: Build (all targets) - run: cargo build --all-targets --verbose + run: cargo build --all-targets --all-features --verbose - - name: Run tests + - name: Run unit tests env: LD_LIBRARY_PATH: /usr/lib:/usr/local/lib - run: cargo test --all --verbose -- --nocapture + run: cargo test --all-targets --all-features --verbose - name: Build examples - run: cargo build --examples --verbose + run: cargo build --examples --all-features --verbose docs: name: Documentation @@ -78,7 +78,7 @@ jobs: - name: Docs env: RUSTDOCFLAGS: -D warnings - run: cargo doc --no-deps -p cpdb-rs + run: cargo doc --no-deps -p cpdb-rs --all-features audit: name: Security Audit @@ -126,6 +126,6 @@ jobs: uses: Swatinem/rust-cache@v2 - name: Build Rust library (bindgen + compile, no link) - run: cargo build --lib --verbose + run: cargo build --lib --all-features --verbose
Signed-off-by: Ayush <mail@ayuch.dev>
Signed-off-by: Ayush <mail@ayuch.dev>
Signed-off-by: Ayush <mail@ayuch.dev>
- improve get_all_printers error handling Signed-off-by: Ayush <mail@ayuch.dev>
Signed-off-by: Ayush <mail@ayuch.dev>
Signed-off-by: Ayush <mail@ayuch.dev>
Signed-off-by: Ayush <mail@ayuch.dev>
|
@TitikshaBansal |
TitikshaBansal
left a comment
There was a problem hiding this comment.
Thanks for this, @bakayu — and sorry for the delayed review. This is a strong
direction: dropping GLib for native async zbus is a real upgrade, and the effort
shows (clean FFI safety docs, #[non_exhaustive] error enum, the docs.rs
stub-bindings handling in build.rs, the owned/borrowed split, and gating FFI
behind an optional feature — all good calls).
I've left inline comments. Grouping them by priority below so it's easy to
triage. A few blockers, then some things worth addressing before we call this an
industrial release, then minor nits.
Blockers (let's resolve before merge):
-
client.rs~L162 usesstd::thread::sleepinside an async fn. This blocks
the executor thread — on a current-thread runtime the whole app freezes for
200ms. Since the PR's goal is non-blocking async, this one's worth fixing
first:tokio::time::sleep(...).await. -
The README's "automatic keep-alive in the background" — from what I can tell
keep_alive_all()is fully manual (its own doc says to call it periodically),
with no spawned task. Could we either spawn a real stream-bound keep-alive
task, or adjust the README so it matches the current behavior? -
Similarly, "auto-retry handles activation races automatically" — the retry
looks one-shot and only inget_all_printers; the other methods
(get_filtered_printers,get_printer_details,print_fd, etc.) hit the
same race with no retry. Either generalize it or soften the wording.
Worth addressing before an industrial release:
-
links = "cpdb"is unconditional, but the default build is pure-Rust (no FFI).
linksreserves the name globally and can't be feature-gated, so a pure-Rust
default could conflict with a realcpdb-sys. Splitting FFI into a separate
cpdb-syscrate would be the clean fix. -
The library writes recoverable errors to stderr via
eprintln!in several
places. Sincelogis already a dependency,log::warn!/error!would let
consumers control output. -
proxy_for(~L447) matches withends_with, so"PS"resolves to
"...Backend.CUPS". Matching the last.-segment exactly would avoid
ambiguity once there are multiple backends. -
The printer list is decoded by hand from
OwnedValue(unpack_printer_variants
/field_as_str), which silently drops structs on a field mismatch. Since the
proxy already has typed structs (RawOption,RawMedia), a typedRawPrinter
would make this much more robust — this is the most fragile path right now. -
discovery_stream+ backend auto-exit (~30s) + manual keep-alive means a
consumer just awaiting the stream silently stops getting events. Worth coupling
keep-alive to the stream's lifetime, or at minimum a loud doc warning.
Minor / nits:
- A couple of
examples/are named like tests;test_print_jobactually submits
a real job, so acargo run --examplewould have a side effect. Maybe rename to
discover_printers/print_a_document? _connectionfield looks dead — zbus proxies hold their ownArc<Connection>.- This is a sweeping breaking change (default feature flip, FFI now behind
features = ["ffi"]) but still 0.1.0 — a short migration note for existing
cpdb_rs::Frontendusers would help. - Doc typo in
get_printer_details: "in a D-Bus call" → "in a single D-Bus call". - Magic numbers (200ms retry, ~30s auto-exit) could be named consts.
On tests: the count is great, but most of the zbus tests that exercise real logic
are #[ignore] (need a live bus), so the decode path, retry logic, and
proxy_for matching aren't covered in CI. If field_as_str / unpack_printer_variants
were made testable, the suffix-matching bug in (6) would be a nice unit test.
Really good work overall — happy to talk through any of these. Once the blockers
are in I think this is close.
| // If we get an `UnknownMethod` error on the very first call, wait and retry. | ||
| if let Err(zbus::Error::MethodError(ref name, _, _)) = result { | ||
| if name.as_str() == "org.freedesktop.DBus.Error.UnknownMethod" { | ||
| std::thread::sleep(std::time::Duration::from_millis(200)); |
There was a problem hiding this comment.
std::thread::sleep inside an async fn blocks the executor thread (200ms freeze on a current-thread runtime). Could we switch this to tokio::time::sleep(...).await?
| fn proxy_for(&self, backend: &str) -> Result<&PrintBackendProxy<'static>> { | ||
| self.backends | ||
| .iter() | ||
| .find(|b| b.service_name.ends_with(backend) || b.service_name == backend) |
There was a problem hiding this comment.
ends_with matches partial names — e.g. "PS" would resolve to ...Backend.CUPS.
Matching the final .-separated segment exactly would prevent ambiguity with multiple backends.
| } | ||
| } | ||
|
|
||
| /// Extracts a string from a zbus `Value`, returning an empty string on mismatch. |
There was a problem hiding this comment.
This hand-rolled decode silently drops a printer on any field/type mismatch. Since the proxy already has typed structs, deriving a RawPrinter for the (sssssbss) signature would give a real typed error instead of a vanished printer.
| /// ``` | ||
| #[derive(Clone)] | ||
| pub struct CpdbClient { | ||
| _connection: zbus::Connection, |
There was a problem hiding this comment.
This field looks dead — zbus proxies already hold their own Arc<Connection>, so storing _connection to "keep it alive" is redundant. Probably safe to remove (or a quick comment explaining why it's kept).
| ); | ||
| } | ||
|
|
||
| for bh in &self.backends { |
There was a problem hiding this comment.
The errors from do_listing(true) are swallowed with let _, and backends auto-exit after ~30s. Combined with keep-alive being manual, a consumer that just awaits this stream will silently stop receiving events after ~30s with no error. Could we tie a keep-alive task to the stream's lifetime, or at least document this loudly?
| fn print_fd( | ||
| &self, | ||
| printer_id: &str, | ||
| num_settings: i32, |
There was a problem hiding this comment.
num_settings mirrors the C API, but a D-Bus array already carries its own length, so the wrapper could compute this internally rather than asking the caller for it.
| repository = "https://github.com/OpenPrinting/cpdb-rs" | ||
| rust-version = "1.85" | ||
| build = "build.rs" | ||
| links = "cpdb" |
There was a problem hiding this comment.
links = "cpdb" is unconditional, but the default build is now pure-Rust with no C linking. links reserves the name globally for the whole dependency graph and can't be feature-gated, so a pure-Rust default crate would conflict with a real cpdb-sys. Splitting the FFI into a separate cpdb-sys crate would resolve this cleanly, can you please do it also?!
| glib-sys = "0.22" | ||
| glib-sys = { version = "0.22", optional = true } | ||
| serde = { version = "1.0", features = ["derive"] } | ||
| zbus = { version = "5.15.0", default-features = false, features = [ |
There was a problem hiding this comment.
zbus is pulled in with only the tokio feature, so the "native async" client is tokio-only (async-std users are out). Reasonable choice, but worth documenting as a limitation — ideally the runtime feature could be selectable.
|
|
||
| // Spawn a background task to keep the backends from automatically | ||
| // timing out and exiting after 30 seconds of inactivity. | ||
| let keep_alive_client = client.clone(); |
There was a problem hiding this comment.
This describes automatic background keep-alive, but keep_alive_all() looks fully manual (its own doc says to call it periodically) with no spawned task. Could we either spawn a real stream-bound keep-alive task, or reword this so it matches the current behavior?
| - **Pure Rust D-Bus Client:** No `libcpdb-dev` or C compiler needed! Everything runs natively over D-Bus using `zbus`. | ||
| - **Async First:** All methods are `async` and powered by Tokio. | ||
| - **Live Discovery:** Subscribe to a native Rust `Stream` for real-time printer additions, removals, and state changes. | ||
| - **Robust Auto-Retry:** Handles D-Bus activation race conditions automatically. |
There was a problem hiding this comment.
The retry here is one-shot, uses a blocking sleep, and only covers get_all_printers — but get_filtered_printers, get_printer_details, get_translations, print_fd, and print_socket hit the same activation race with no retry. Either generalizing it or softening "robust/automatic" would make the docs match the code.
Description
This PR introduces a pure native, async Rust client for CPDB using
zbus, completely eliminating the dependency on GLib.The new
zbus-backendprovides a modern async API that allows developers to seamlessly integrate print dialogs without blocking the UI thread. Thecpdb-libsC bindings have been moved behind an optionalffifeature flag.Key Features
CpdbClientwhich automatically discovers and connects to all active CPDB backends.discovery_stream()which yields real-timePrinterAdded,PrinterRemoved, andPrinterStateChangedsignals, making it easy to build reactive print dialog UIs.Note: The new
zbus-backendis now the default feature for the crate.