From 2e56e16114808e56dfc9cdccf3da707f33120bf9 Mon Sep 17 00:00:00 2001
From: Peter Jankuliak
Date: Fri, 17 Jan 2025 19:10:19 +0100
Subject: [PATCH 1/6] Listen to to service removal events in ServiceBrowser
(WIP: Avahi only)
---
zeroconf/src/avahi/browser.rs | 16 +++++++++++-----
zeroconf/src/browser.rs | 16 +++++++++++-----
zeroconf/src/lib.rs | 2 +-
3 files changed, 23 insertions(+), 11 deletions(-)
diff --git a/zeroconf/src/avahi/browser.rs b/zeroconf/src/avahi/browser.rs
index f041178..952e39e 100644
--- a/zeroconf/src/avahi/browser.rs
+++ b/zeroconf/src/avahi/browser.rs
@@ -14,8 +14,8 @@ use crate::ffi::{c_str, AsRaw, FromRaw};
use crate::prelude::*;
use crate::Result;
use crate::{
- EventLoop, NetworkInterface, ServiceDiscoveredCallback, ServiceDiscovery, ServiceType,
- TxtRecord,
+ BrowserEvent, EventLoop, NetworkInterface, ServiceDiscoveredCallback, ServiceDiscovery,
+ ServiceType, TxtRecord,
};
use avahi_sys::{
AvahiAddress, AvahiBrowserEvent, AvahiClient, AvahiClientFlags, AvahiClientState, AvahiIfIndex,
@@ -56,7 +56,7 @@ impl TMdnsBrowser for AvahiMdnsBrowser {
avahi_util::interface_from_index(self.context.interface_index)
}
- fn set_service_discovered_callback(
+ fn set_service_callback(
&mut self,
service_discovered_callback: Box,
) {
@@ -132,7 +132,7 @@ impl AvahiBrowserContext {
}
}
- fn invoke_callback(&self, result: Result) {
+ fn invoke_callback(&self, result: Result) {
if let Some(f) = &self.service_discovered_callback {
f(result, self.user_context.clone());
} else {
@@ -205,6 +205,12 @@ unsafe extern "C" fn browse_callback(
avahi_sys::AvahiBrowserEvent_AVAHI_BROWSER_FAILURE => {
context.invoke_callback(Err("browser failure".into()))
}
+ avahi_sys::AvahiBrowserEvent_AVAHI_BROWSER_REMOVE => {
+ let name = c_str::raw_to_str(name).to_string();
+ let kind = c_str::raw_to_str(kind).to_string();
+ let domain = c_str::raw_to_str(domain).to_string();
+ context.invoke_callback(Ok(BrowserEvent::Remove { name, kind, domain }));
+ }
_ => {}
};
}
@@ -324,7 +330,7 @@ unsafe fn handle_resolver_found(
debug!("Service resolved: {:?}", result);
- context.invoke_callback(Ok(result));
+ context.invoke_callback(Ok(BrowserEvent::New(result)));
Ok(())
}
diff --git a/zeroconf/src/browser.rs b/zeroconf/src/browser.rs
index 4561a31..770e53b 100644
--- a/zeroconf/src/browser.rs
+++ b/zeroconf/src/browser.rs
@@ -4,6 +4,15 @@ use crate::{EventLoop, NetworkInterface, Result, ServiceType, TxtRecord};
use std::any::Any;
use std::sync::Arc;
+pub enum BrowserEvent {
+ New(ServiceDiscovery),
+ Remove {
+ name: String, // The "abc" part in "abc._http._udp.local"
+ kind: String, // The "_http._udp" part in "abc._http._udp.local"
+ domain: String, // The "local" part in "abc._http._udp.local"
+ },
+}
+
/// Interface for interacting with underlying mDNS implementation service browsing capabilities.
pub trait TMdnsBrowser {
/// Creates a new `MdnsBrowser` that browses for the specified `kind` (e.g. `_http._tcp`)
@@ -22,10 +31,7 @@ pub trait TMdnsBrowser {
/// resolved a service.
///
/// [`ServiceDiscoveredCallback`]: ../type.ServiceDiscoveredCallback.html
- fn set_service_discovered_callback(
- &mut self,
- service_discovered_callback: Box,
- );
+ fn set_service_callback(&mut self, service_discovered_callback: Box);
/// Sets the optional user context to pass through to the callback. This is useful if you need
/// to share state between pre and post-callback. The context type must implement `Any`.
@@ -45,7 +51,7 @@ pub trait TMdnsBrowser {
/// * `context` - The optional user context passed through
///
/// [`MdnsBrowser`]: type.MdnsBrowser.html
-pub type ServiceDiscoveredCallback = dyn Fn(Result, Option>);
+pub type ServiceDiscoveredCallback = dyn Fn(Result, Option>);
/// Represents a service that has been discovered by a [`MdnsBrowser`].
///
diff --git a/zeroconf/src/lib.rs b/zeroconf/src/lib.rs
index ad88d21..06461dd 100644
--- a/zeroconf/src/lib.rs
+++ b/zeroconf/src/lib.rs
@@ -228,7 +228,7 @@ pub mod avahi;
#[cfg(any(target_vendor = "apple", target_vendor = "pc"))]
pub mod bonjour;
-pub use browser::{ServiceDiscoveredCallback, ServiceDiscovery};
+pub use browser::{BrowserEvent, ServiceDiscoveredCallback, ServiceDiscovery};
pub use interface::*;
pub use service::{ServiceRegisteredCallback, ServiceRegistration};
pub use service_type::*;
From ccd6a668e78bc75b83a38a40069491733459a36d Mon Sep 17 00:00:00 2001
From: Peter Jankuliak
Date: Tue, 21 Jan 2025 08:35:57 +0100
Subject: [PATCH 2/6] Listen to service removal events (Bonjour)
---
zeroconf/src/bonjour/browser.rs | 52 ++++++++++++++++++++++++---------
1 file changed, 39 insertions(+), 13 deletions(-)
diff --git a/zeroconf/src/bonjour/browser.rs b/zeroconf/src/bonjour/browser.rs
index d410b3f..3056f49 100644
--- a/zeroconf/src/bonjour/browser.rs
+++ b/zeroconf/src/bonjour/browser.rs
@@ -8,7 +8,7 @@ use super::{bonjour_util, constants};
use crate::ffi::{c_str, AsRaw, FromRaw};
use crate::prelude::*;
use crate::{EventLoop, NetworkInterface, Result, ServiceType, TxtRecord};
-use crate::{ServiceDiscoveredCallback, ServiceDiscovery};
+use crate::{ServiceDiscoveredCallback, ServiceDiscovery, BrowserEvent};
#[cfg(target_vendor = "pc")]
use bonjour_sys::sockaddr_in;
use bonjour_sys::{DNSServiceErrorType, DNSServiceFlags, DNSServiceRef};
@@ -48,7 +48,7 @@ impl TMdnsBrowser for BonjourMdnsBrowser {
bonjour_util::interface_from_index(self.interface_index)
}
- fn set_service_discovered_callback(
+ fn set_service_callback(
&mut self,
service_discovered_callback: Box,
) {
@@ -98,7 +98,7 @@ struct BonjourBrowserContext {
}
impl BonjourBrowserContext {
- fn invoke_callback(&self, result: Result) {
+ fn invoke_callback(&self, result: Result) {
if let Some(f) = &self.service_discovered_callback {
f(result, self.user_context.clone());
} else {
@@ -120,7 +120,7 @@ impl fmt::Debug for BonjourBrowserContext {
unsafe extern "system" fn browse_callback(
_sd_ref: DNSServiceRef,
- _flags: DNSServiceFlags,
+ flags: DNSServiceFlags,
interface_index: u32,
error: DNSServiceErrorType,
name: *const c_char,
@@ -129,23 +129,28 @@ unsafe extern "system" fn browse_callback(
context: *mut c_void,
) {
let ctx = BonjourBrowserContext::from_raw(context);
- if let Err(e) = handle_browse(ctx, error, name, regtype, domain, interface_index) {
- ctx.invoke_callback(Err(e));
+
+ if error != 0 {
+ ctx.invoke_callback(Err(format!("browse_callback() reported error (code: {})", error).into()));
+ return;
+ }
+
+ if flags & bonjour_sys::kDNSServiceFlagsAdd != 0 {
+ if let Err(e) = handle_browse_add(ctx, name, regtype, domain, interface_index) {
+ ctx.invoke_callback(Err(e));
+ }
+ } else {
+ handle_browse_remove(ctx, name, regtype, domain);
}
}
-unsafe fn handle_browse(
+unsafe fn handle_browse_add(
ctx: &mut BonjourBrowserContext,
- error: DNSServiceErrorType,
name: *const c_char,
regtype: *const c_char,
domain: *const c_char,
interface_index: u32,
) -> Result<()> {
- if error != 0 {
- return Err(format!("browse_callback() reported error (code: {})", error).into());
- }
-
ctx.resolved_name = Some(c_str::copy_raw(name));
ctx.resolved_kind = Some(c_str::copy_raw(regtype));
ctx.resolved_domain = Some(c_str::copy_raw(domain));
@@ -163,6 +168,27 @@ unsafe fn handle_browse(
)
}
+unsafe fn handle_browse_remove(
+ ctx: &mut BonjourBrowserContext,
+ name: *const c_char,
+ regtype: *const c_char,
+ domain: *const c_char,
+) {
+ let name = c_str::raw_to_str(name);
+ let regtype = c_str::raw_to_str(regtype);
+ let domain = c_str::raw_to_str(domain);
+
+ // Remove the "." suffix to be consistent with the Avahi implementation.
+ let regtype = regtype.strip_suffix(".").unwrap_or(domain);
+ let domain = domain.strip_suffix(".").unwrap_or(domain);
+
+ ctx.invoke_callback(Ok(BrowserEvent::Remove {
+ name: name.to_string(),
+ kind: regtype.to_string(),
+ domain: domain.to_string(),
+ }));
+}
+
unsafe extern "system" fn resolve_callback(
_sd_ref: DNSServiceRef,
_flags: DNSServiceFlags,
@@ -313,7 +339,7 @@ unsafe fn handle_get_address_info(
.build()
.expect("could not build ServiceResolution");
- ctx.invoke_callback(Ok(result));
+ ctx.invoke_callback(Ok(BrowserEvent::New(result)));
Ok(())
}
From ed090d0756fd948fc3571327296d963b41195ce6 Mon Sep 17 00:00:00 2001
From: Peter Jankuliak
Date: Tue, 21 Jan 2025 09:40:13 +0100
Subject: [PATCH 3/6] Fix tests
---
examples/browser/src/main.rs | 8 +++---
zeroconf/src/browser.rs | 1 +
zeroconf/src/lib.rs | 10 ++++----
zeroconf/src/tests/service_test.rs | 39 +++++++++++++++++-------------
4 files changed, 32 insertions(+), 26 deletions(-)
diff --git a/examples/browser/src/main.rs b/examples/browser/src/main.rs
index 0797c83..2ad3800 100644
--- a/examples/browser/src/main.rs
+++ b/examples/browser/src/main.rs
@@ -7,7 +7,7 @@ use std::any::Any;
use std::sync::Arc;
use std::time::Duration;
use zeroconf::prelude::*;
-use zeroconf::{MdnsBrowser, ServiceDiscovery, ServiceType};
+use zeroconf::{BrowserEvent, MdnsBrowser, ServiceType};
/// Example of a simple mDNS browser
#[derive(Parser, Debug)]
@@ -45,7 +45,7 @@ fn main() -> zeroconf::Result<()> {
let mut browser = MdnsBrowser::new(service_type);
- browser.set_service_discovered_callback(Box::new(on_service_discovered));
+ browser.set_service_callback(Box::new(on_service_discovered));
let event_loop = browser.browse_services()?;
@@ -56,11 +56,11 @@ fn main() -> zeroconf::Result<()> {
}
fn on_service_discovered(
- result: zeroconf::Result,
+ result: zeroconf::Result,
_context: Option>,
) {
info!(
- "Service discovered: {:?}",
+ "Service event: {:?}",
result.expect("service discovery failed")
);
diff --git a/zeroconf/src/browser.rs b/zeroconf/src/browser.rs
index 770e53b..5798f9a 100644
--- a/zeroconf/src/browser.rs
+++ b/zeroconf/src/browser.rs
@@ -4,6 +4,7 @@ use crate::{EventLoop, NetworkInterface, Result, ServiceType, TxtRecord};
use std::any::Any;
use std::sync::Arc;
+#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BrowserEvent {
New(ServiceDiscovery),
Remove {
diff --git a/zeroconf/src/lib.rs b/zeroconf/src/lib.rs
index 06461dd..7bf6529 100644
--- a/zeroconf/src/lib.rs
+++ b/zeroconf/src/lib.rs
@@ -116,7 +116,7 @@
//! use std::sync::Arc;
//! use std::time::Duration;
//! use zeroconf::prelude::*;
-//! use zeroconf::{MdnsBrowser, ServiceDiscovery, ServiceType};
+//! use zeroconf::{BrowserEvent, MdnsBrowser, ServiceDiscovery, ServiceType};
//!
//! /// Example of a simple mDNS browser
//! #[derive(Parser, Debug)]
@@ -154,7 +154,7 @@
//!
//! let mut browser = MdnsBrowser::new(service_type);
//!
-//! browser.set_service_discovered_callback(Box::new(on_service_discovered));
+//! browser.set_service_callback(Box::new(on_service_event));
//!
//! let event_loop = browser.browse_services()?;
//!
@@ -164,12 +164,12 @@
//! }
//! }
//!
-//! fn on_service_discovered(
-//! result: zeroconf::Result,
+//! fn on_service_event(
+//! result: zeroconf::Result,
//! _context: Option>,
//! ) {
//! info!(
-//! "Service discovered: {:?}",
+//! "Service event: {:?}",
//! result.expect("service discovery failed")
//! );
//!
diff --git a/zeroconf/src/tests/service_test.rs b/zeroconf/src/tests/service_test.rs
index 916d0d9..4cc684d 100644
--- a/zeroconf/src/tests/service_test.rs
+++ b/zeroconf/src/tests/service_test.rs
@@ -1,5 +1,5 @@
use crate::prelude::*;
-use crate::{MdnsBrowser, MdnsService, ServiceType, TxtRecord};
+use crate::{BrowserEvent, MdnsBrowser, MdnsService, ServiceType, TxtRecord};
use std::sync::{Arc, Mutex};
use std::time::Duration;
@@ -46,22 +46,27 @@ fn service_register_is_browsable() {
browser.set_context(Box::new(context.clone()));
- browser.set_service_discovered_callback(Box::new(|service, context| {
- let service = service.unwrap();
-
- if service.name() == SERVICE_NAME {
- let mut mtx = context
- .as_ref()
- .unwrap()
- .downcast_ref::>>()
- .unwrap()
- .lock()
- .unwrap();
-
- mtx.txt.clone_from(service.txt());
- mtx.is_discovered = true;
-
- debug!("Service discovered");
+ browser.set_service_callback(Box::new(|event, context| {
+ match event.unwrap() {
+ BrowserEvent::New(service) => {
+ if service.name() == SERVICE_NAME {
+ let mut mtx = context
+ .as_ref()
+ .unwrap()
+ .downcast_ref::>>()
+ .unwrap()
+ .lock()
+ .unwrap();
+
+ mtx.txt.clone_from(service.txt());
+ mtx.is_discovered = true;
+
+ debug!("Service discovered");
+ }
+ }
+ BrowserEvent::Remove { name, kind, domain } => {
+ debug!("Service removed: {name}.{kind}.{domain}");
+ }
}
}));
From 43b81707f17801d009cd1d1c40f95f44d97b3420 Mon Sep 17 00:00:00 2001
From: Peter Jankuliak
Date: Tue, 21 Jan 2025 10:01:53 +0100
Subject: [PATCH 4/6] Refactor for consistency and documentation
---
examples/browser/src/main.rs | 7 ++---
zeroconf/src/avahi/browser.rs | 44 ++++++++++++++++++++----------
zeroconf/src/bonjour/browser.rs | 30 +++++++++++---------
zeroconf/src/browser.rs | 39 +++++++++++++++++---------
zeroconf/src/lib.rs | 4 +--
zeroconf/src/tests/service_test.rs | 43 +++++++++++++++--------------
6 files changed, 99 insertions(+), 68 deletions(-)
diff --git a/examples/browser/src/main.rs b/examples/browser/src/main.rs
index 2ad3800..a4e8772 100644
--- a/examples/browser/src/main.rs
+++ b/examples/browser/src/main.rs
@@ -45,7 +45,7 @@ fn main() -> zeroconf::Result<()> {
let mut browser = MdnsBrowser::new(service_type);
- browser.set_service_callback(Box::new(on_service_discovered));
+ browser.set_service_callback(Box::new(on_service_event));
let event_loop = browser.browse_services()?;
@@ -55,10 +55,7 @@ fn main() -> zeroconf::Result<()> {
}
}
-fn on_service_discovered(
- result: zeroconf::Result,
- _context: Option>,
-) {
+fn on_service_event(result: zeroconf::Result, _context: Option>) {
info!(
"Service event: {:?}",
result.expect("service discovery failed")
diff --git a/zeroconf/src/avahi/browser.rs b/zeroconf/src/avahi/browser.rs
index 952e39e..6adab5e 100644
--- a/zeroconf/src/avahi/browser.rs
+++ b/zeroconf/src/avahi/browser.rs
@@ -14,8 +14,8 @@ use crate::ffi::{c_str, AsRaw, FromRaw};
use crate::prelude::*;
use crate::Result;
use crate::{
- BrowserEvent, EventLoop, NetworkInterface, ServiceDiscoveredCallback, ServiceDiscovery,
- ServiceType, TxtRecord,
+ BrowserEvent, EventLoop, NetworkInterface, ServiceBrowserCallback, ServiceDiscovery,
+ ServiceRemoval, ServiceType, TxtRecord,
};
use avahi_sys::{
AvahiAddress, AvahiBrowserEvent, AvahiClient, AvahiClientFlags, AvahiClientState, AvahiIfIndex,
@@ -56,11 +56,8 @@ impl TMdnsBrowser for AvahiMdnsBrowser {
avahi_util::interface_from_index(self.context.interface_index)
}
- fn set_service_callback(
- &mut self,
- service_discovered_callback: Box,
- ) {
- self.context.service_discovered_callback = Some(service_discovered_callback);
+ fn set_service_callback(&mut self, service_callback: Box) {
+ self.context.service_callback = Some(service_callback);
}
fn set_context(&mut self, context: Box) {
@@ -112,7 +109,7 @@ impl TMdnsBrowser for AvahiMdnsBrowser {
struct AvahiBrowserContext {
client: Option>,
resolvers: ServiceResolverSet,
- service_discovered_callback: Option>,
+ service_callback: Option>,
user_context: Option>,
interface_index: AvahiIfIndex,
kind: CString,
@@ -124,7 +121,7 @@ impl AvahiBrowserContext {
Self {
client: None,
resolvers: ServiceResolverSet::default(),
- service_discovered_callback: None,
+ service_callback: None,
user_context: None,
interface_index,
kind,
@@ -133,7 +130,7 @@ impl AvahiBrowserContext {
}
fn invoke_callback(&self, result: Result) {
- if let Some(f) = &self.service_discovered_callback {
+ if let Some(f) = &self.service_callback {
f(result, self.user_context.clone());
} else {
warn!("attempted to invoke browser callback but none was set");
@@ -206,10 +203,7 @@ unsafe extern "C" fn browse_callback(
context.invoke_callback(Err("browser failure".into()))
}
avahi_sys::AvahiBrowserEvent_AVAHI_BROWSER_REMOVE => {
- let name = c_str::raw_to_str(name).to_string();
- let kind = c_str::raw_to_str(kind).to_string();
- let domain = c_str::raw_to_str(domain).to_string();
- context.invoke_callback(Ok(BrowserEvent::Remove { name, kind, domain }));
+ handle_browser_remove(context, name, kind, domain);
}
_ => {}
};
@@ -248,6 +242,26 @@ unsafe fn handle_browser_new(
Ok(())
}
+unsafe fn handle_browser_remove(
+ ctx: &mut AvahiBrowserContext,
+ name: *const c_char,
+ regtype: *const c_char,
+ domain: *const c_char,
+) {
+ let name = c_str::raw_to_str(name);
+ let regtype = c_str::raw_to_str(regtype);
+ let domain = c_str::raw_to_str(domain);
+
+ ctx.invoke_callback(Ok(BrowserEvent::Remove(
+ ServiceRemoval::builder()
+ .name(name.to_string())
+ .kind(regtype.to_string())
+ .domain(domain.to_string())
+ .build()
+ .expect("could not build ServiceRemoval"),
+ )));
+}
+
unsafe extern "C" fn resolve_callback(
resolver: *mut AvahiServiceResolver,
_interface: AvahiIfIndex,
@@ -330,7 +344,7 @@ unsafe fn handle_resolver_found(
debug!("Service resolved: {:?}", result);
- context.invoke_callback(Ok(BrowserEvent::New(result)));
+ context.invoke_callback(Ok(BrowserEvent::Add(result)));
Ok(())
}
diff --git a/zeroconf/src/bonjour/browser.rs b/zeroconf/src/bonjour/browser.rs
index 3056f49..20ea4b5 100644
--- a/zeroconf/src/bonjour/browser.rs
+++ b/zeroconf/src/bonjour/browser.rs
@@ -7,8 +7,8 @@ use super::txt_record_ref::ManagedTXTRecordRef;
use super::{bonjour_util, constants};
use crate::ffi::{c_str, AsRaw, FromRaw};
use crate::prelude::*;
+use crate::{BrowserEvent, ServiceBrowserCallback, ServiceDiscovery, ServiceRemoval};
use crate::{EventLoop, NetworkInterface, Result, ServiceType, TxtRecord};
-use crate::{ServiceDiscoveredCallback, ServiceDiscovery, BrowserEvent};
#[cfg(target_vendor = "pc")]
use bonjour_sys::sockaddr_in;
use bonjour_sys::{DNSServiceErrorType, DNSServiceFlags, DNSServiceRef};
@@ -48,10 +48,7 @@ impl TMdnsBrowser for BonjourMdnsBrowser {
bonjour_util::interface_from_index(self.interface_index)
}
- fn set_service_callback(
- &mut self,
- service_discovered_callback: Box,
- ) {
+ fn set_service_callback(&mut self, service_discovered_callback: Box) {
self.context.service_discovered_callback = Some(service_discovered_callback);
}
@@ -88,7 +85,7 @@ impl TMdnsBrowser for BonjourMdnsBrowser {
#[derive(Default, FromRaw, AsRaw)]
struct BonjourBrowserContext {
- service_discovered_callback: Option>,
+ service_discovered_callback: Option>,
resolved_name: Option,
resolved_kind: Option,
resolved_domain: Option,
@@ -131,7 +128,11 @@ unsafe extern "system" fn browse_callback(
let ctx = BonjourBrowserContext::from_raw(context);
if error != 0 {
- ctx.invoke_callback(Err(format!("browse_callback() reported error (code: {})", error).into()));
+ ctx.invoke_callback(Err(format!(
+ "browse_callback() reported error (code: {})",
+ error
+ )
+ .into()));
return;
}
@@ -182,11 +183,14 @@ unsafe fn handle_browse_remove(
let regtype = regtype.strip_suffix(".").unwrap_or(domain);
let domain = domain.strip_suffix(".").unwrap_or(domain);
- ctx.invoke_callback(Ok(BrowserEvent::Remove {
- name: name.to_string(),
- kind: regtype.to_string(),
- domain: domain.to_string(),
- }));
+ ctx.invoke_callback(Ok(BrowserEvent::Remove(
+ ServiceRemoval::builder()
+ .name(name.to_string())
+ .kind(regtype.to_string())
+ .domain(domain.to_string())
+ .build()
+ .expect("could not build ServiceRemoval"),
+ )));
}
unsafe extern "system" fn resolve_callback(
@@ -339,7 +343,7 @@ unsafe fn handle_get_address_info(
.build()
.expect("could not build ServiceResolution");
- ctx.invoke_callback(Ok(BrowserEvent::New(result)));
+ ctx.invoke_callback(Ok(BrowserEvent::Add(result)));
Ok(())
}
diff --git a/zeroconf/src/browser.rs b/zeroconf/src/browser.rs
index 5798f9a..5817e7d 100644
--- a/zeroconf/src/browser.rs
+++ b/zeroconf/src/browser.rs
@@ -4,14 +4,13 @@ use crate::{EventLoop, NetworkInterface, Result, ServiceType, TxtRecord};
use std::any::Any;
use std::sync::Arc;
+/// Event from [`MdnsBrowser`] received by the `ServiceBrowserCallback`.
+///
+/// [`MdnsBrowser`]: type.MdnsBrowser.html
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BrowserEvent {
- New(ServiceDiscovery),
- Remove {
- name: String, // The "abc" part in "abc._http._udp.local"
- kind: String, // The "_http._udp" part in "abc._http._udp.local"
- domain: String, // The "local" part in "abc._http._udp.local"
- },
+ Add(ServiceDiscovery),
+ Remove(ServiceRemoval),
}
/// Interface for interacting with underlying mDNS implementation service browsing capabilities.
@@ -28,11 +27,11 @@ pub trait TMdnsBrowser {
/// Returns the network interface on which to browse for services on.
fn network_interface(&self) -> NetworkInterface;
- /// Sets the [`ServiceDiscoveredCallback`] that is invoked when the browser has discovered and
- /// resolved a service.
+ /// Sets the [`ServiceBrowserCallback`] that is invoked when the browser has discovered and
+ /// resolved or removed a service.
///
- /// [`ServiceDiscoveredCallback`]: ../type.ServiceDiscoveredCallback.html
- fn set_service_callback(&mut self, service_discovered_callback: Box);
+ /// [`ServiceBrowserCallback`]: ../type.ServiceBrowserCallback.html
+ fn set_service_callback(&mut self, service_callback: Box);
/// Sets the optional user context to pass through to the callback. This is useful if you need
/// to share state between pre and post-callback. The context type must implement `Any`.
@@ -45,14 +44,15 @@ pub trait TMdnsBrowser {
fn browse_services(&mut self) -> Result;
}
-/// Callback invoked from [`MdnsBrowser`] once a service has been discovered and resolved.
+/// Callback invoked from [`MdnsBrowser`] once a service has been discovered and resolved or
+/// removed.
///
/// # Arguments
-/// * `discovered_service` - The service that was disovered
+/// * `browser_event` - The event received from Zeroconf
/// * `context` - The optional user context passed through
///
/// [`MdnsBrowser`]: type.MdnsBrowser.html
-pub type ServiceDiscoveredCallback = dyn Fn(Result, Option>);
+pub type ServiceBrowserCallback = dyn Fn(Result, Option>);
/// Represents a service that has been discovered by a [`MdnsBrowser`].
///
@@ -68,3 +68,16 @@ pub struct ServiceDiscovery {
port: u16,
txt: Option,
}
+
+/// Represents a service that has been removed by a [`MdnsBrowser`].
+///
+/// [`MdnsBrowser`]: type.MdnsBrowser.html
+#[derive(Debug, Getters, Builder, BuilderDelegate, Clone, PartialEq, Eq)]
+pub struct ServiceRemoval {
+ /// The "abc" part in "abc._http._udp.local"
+ name: String,
+ /// The "_http._udp" part in "abc._http._udp.local"
+ kind: String,
+ /// The "local" part in "abc._http._udp.local"
+ domain: String,
+}
diff --git a/zeroconf/src/lib.rs b/zeroconf/src/lib.rs
index 7bf6529..6263f21 100644
--- a/zeroconf/src/lib.rs
+++ b/zeroconf/src/lib.rs
@@ -116,7 +116,7 @@
//! use std::sync::Arc;
//! use std::time::Duration;
//! use zeroconf::prelude::*;
-//! use zeroconf::{BrowserEvent, MdnsBrowser, ServiceDiscovery, ServiceType};
+//! use zeroconf::{BrowserEvent, MdnsBrowser, ServiceDiscovery, ServiceRemoval, ServiceType};
//!
//! /// Example of a simple mDNS browser
//! #[derive(Parser, Debug)]
@@ -228,7 +228,7 @@ pub mod avahi;
#[cfg(any(target_vendor = "apple", target_vendor = "pc"))]
pub mod bonjour;
-pub use browser::{BrowserEvent, ServiceDiscoveredCallback, ServiceDiscovery};
+pub use browser::{BrowserEvent, ServiceBrowserCallback, ServiceDiscovery, ServiceRemoval};
pub use interface::*;
pub use service::{ServiceRegisteredCallback, ServiceRegistration};
pub use service_type::*;
diff --git a/zeroconf/src/tests/service_test.rs b/zeroconf/src/tests/service_test.rs
index 4cc684d..055f044 100644
--- a/zeroconf/src/tests/service_test.rs
+++ b/zeroconf/src/tests/service_test.rs
@@ -46,28 +46,31 @@ fn service_register_is_browsable() {
browser.set_context(Box::new(context.clone()));
- browser.set_service_callback(Box::new(|event, context| {
- match event.unwrap() {
- BrowserEvent::New(service) => {
- if service.name() == SERVICE_NAME {
- let mut mtx = context
- .as_ref()
- .unwrap()
- .downcast_ref::>>()
- .unwrap()
- .lock()
- .unwrap();
-
- mtx.txt.clone_from(service.txt());
- mtx.is_discovered = true;
-
- debug!("Service discovered");
- }
- }
- BrowserEvent::Remove { name, kind, domain } => {
- debug!("Service removed: {name}.{kind}.{domain}");
+ browser.set_service_callback(Box::new(|event, context| match event.unwrap() {
+ BrowserEvent::Add(service) => {
+ if service.name() == SERVICE_NAME {
+ let mut mtx = context
+ .as_ref()
+ .unwrap()
+ .downcast_ref::>>()
+ .unwrap()
+ .lock()
+ .unwrap();
+
+ mtx.txt.clone_from(service.txt());
+ mtx.is_discovered = true;
+
+ debug!("Service discovered");
}
}
+ BrowserEvent::Remove(service) => {
+ debug!(
+ "Service removed: {}.{}.{}",
+ service.name(),
+ service.kind(),
+ service.domain()
+ );
+ }
}));
let event_loop = browser.browse_services().unwrap();
From f22b53092b58c08dcdc55114fc7092e7abd40651 Mon Sep 17 00:00:00 2001
From: Peter Jankuliak
Date: Thu, 23 Jan 2025 14:10:57 +0100
Subject: [PATCH 5/6] Also rename the callback in the example
---
examples/browser/src/main.rs | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/examples/browser/src/main.rs b/examples/browser/src/main.rs
index a4e8772..58bb460 100644
--- a/examples/browser/src/main.rs
+++ b/examples/browser/src/main.rs
@@ -45,7 +45,7 @@ fn main() -> zeroconf::Result<()> {
let mut browser = MdnsBrowser::new(service_type);
- browser.set_service_callback(Box::new(on_service_event));
+ browser.set_service_callback(Box::new(on_service_discovery_event));
let event_loop = browser.browse_services()?;
@@ -55,7 +55,10 @@ fn main() -> zeroconf::Result<()> {
}
}
-fn on_service_event(result: zeroconf::Result, _context: Option>) {
+fn on_service_discovery_event(
+ result: zeroconf::Result,
+ _context: Option>,
+) {
info!(
"Service event: {:?}",
result.expect("service discovery failed")
From 9e66dc1292b29548b12b3588aa89923502ab3f03 Mon Sep 17 00:00:00 2001
From: Peter Jankuliak
Date: Thu, 23 Jan 2025 14:11:27 +0100
Subject: [PATCH 6/6] Set the default example logging to "info"
Otherwise the user wouldn't see any events unless they also set RUST_LOG
---
examples/browser/src/main.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/examples/browser/src/main.rs b/examples/browser/src/main.rs
index 58bb460..5f768ce 100644
--- a/examples/browser/src/main.rs
+++ b/examples/browser/src/main.rs
@@ -27,7 +27,7 @@ struct Args {
}
fn main() -> zeroconf::Result<()> {
- env_logger::init();
+ env_logger::Builder::from_env(env_logger::Env::new().filter_or("RUST_LOG", "info")).init();
let Args {
name,