⚡ Sangsakala Proxy 🏠 Home
Skip to content

Commit 35ac7a3

Browse files
authored
Breakout ServiceBrowser handler from listener creation (#736)
1 parent 9d31245 commit 35ac7a3

2 files changed

Lines changed: 149 additions & 23 deletions

File tree

‎tests/test_services.py‎

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,11 @@ def _mock_get_expiration_time(self, percent):
515515
assert service_added_count == 3
516516
assert service_removed_count == 0
517517

518+
_inject_response(
519+
zeroconf,
520+
mock_incoming_msg(r.ServiceStateChange.Updated, service_types[0], service_names[0], 0),
521+
)
522+
518523
# all three services removed
519524
_inject_response(
520525
zeroconf,
@@ -1265,6 +1270,124 @@ def mock_incoming_msg(records) -> r.DNSIncoming:
12651270
zc.close()
12661271

12671272

1273+
def test_service_browser_listeners_update_service():
1274+
"""Test that the ServiceBrowser ServiceListener that implements update_service."""
1275+
1276+
# instantiate a zeroconf instance
1277+
zc = Zeroconf(interfaces=['127.0.0.1'])
1278+
# start a browser
1279+
type_ = "_hap._tcp.local."
1280+
registration_name = "xxxyyy.%s" % type_
1281+
callbacks = []
1282+
1283+
class MyServiceListener(r.ServiceListener):
1284+
def add_service(self, zc, type_, name) -> None:
1285+
nonlocal callbacks
1286+
if name == registration_name:
1287+
callbacks.append(("add", type_, name))
1288+
1289+
def remove_service(self, zc, type_, name) -> None:
1290+
nonlocal callbacks
1291+
if name == registration_name:
1292+
callbacks.append(("remove", type_, name))
1293+
1294+
def update_service(self, zc, type_, name) -> None:
1295+
nonlocal callbacks
1296+
if name == registration_name:
1297+
callbacks.append(("update", type_, name))
1298+
1299+
listener = MyServiceListener()
1300+
1301+
browser = r.ServiceBrowser(zc, type_, None, listener)
1302+
1303+
desc = {'path': '/~paulsm/'}
1304+
address_parsed = "10.0.1.2"
1305+
address = socket.inet_aton(address_parsed)
1306+
info = ServiceInfo(type_, registration_name, 80, 0, 0, desc, "ash-2.local.", addresses=[address])
1307+
1308+
def mock_incoming_msg(records) -> r.DNSIncoming:
1309+
generated = r.DNSOutgoing(const._FLAGS_QR_RESPONSE)
1310+
for record in records:
1311+
generated.add_answer_at_time(record, 0)
1312+
return r.DNSIncoming(generated.packets()[0])
1313+
1314+
_inject_response(
1315+
zc,
1316+
mock_incoming_msg([info.dns_pointer(), info.dns_service(), info.dns_text(), *info.dns_addresses()]),
1317+
)
1318+
time.sleep(0.2)
1319+
info.port = 400
1320+
_inject_response(
1321+
zc,
1322+
mock_incoming_msg([info.dns_service()]),
1323+
)
1324+
time.sleep(0.2)
1325+
1326+
assert callbacks == [
1327+
('add', type_, registration_name),
1328+
('update', type_, registration_name),
1329+
]
1330+
browser.cancel()
1331+
1332+
zc.close()
1333+
1334+
1335+
def test_service_browser_listeners_no_update_service():
1336+
"""Test that the ServiceBrowser ServiceListener that does not implement update_service."""
1337+
1338+
# instantiate a zeroconf instance
1339+
zc = Zeroconf(interfaces=['127.0.0.1'])
1340+
# start a browser
1341+
type_ = "_hap._tcp.local."
1342+
registration_name = "xxxyyy.%s" % type_
1343+
callbacks = []
1344+
1345+
class MyServiceListener:
1346+
def add_service(self, zc, type_, name) -> None:
1347+
nonlocal callbacks
1348+
if name == registration_name:
1349+
callbacks.append(("add", type_, name))
1350+
1351+
def remove_service(self, zc, type_, name) -> None:
1352+
nonlocal callbacks
1353+
if name == registration_name:
1354+
callbacks.append(("remove", type_, name))
1355+
1356+
listener = MyServiceListener()
1357+
1358+
browser = r.ServiceBrowser(zc, type_, None, listener)
1359+
1360+
desc = {'path': '/~paulsm/'}
1361+
address_parsed = "10.0.1.2"
1362+
address = socket.inet_aton(address_parsed)
1363+
info = ServiceInfo(type_, registration_name, 80, 0, 0, desc, "ash-2.local.", addresses=[address])
1364+
1365+
def mock_incoming_msg(records) -> r.DNSIncoming:
1366+
generated = r.DNSOutgoing(const._FLAGS_QR_RESPONSE)
1367+
for record in records:
1368+
generated.add_answer_at_time(record, 0)
1369+
return r.DNSIncoming(generated.packets()[0])
1370+
1371+
_inject_response(
1372+
zc,
1373+
mock_incoming_msg([info.dns_pointer(), info.dns_service(), info.dns_text(), *info.dns_addresses()]),
1374+
)
1375+
time.sleep(0.2)
1376+
info.port = 400
1377+
_inject_response(
1378+
zc,
1379+
mock_incoming_msg([info.dns_service()]),
1380+
)
1381+
time.sleep(0.2)
1382+
1383+
assert callbacks == [
1384+
('add', type_, registration_name),
1385+
]
1386+
browser.cancel()
1387+
1388+
zc.close()
1389+
1390+
12681391
def test_changing_name_updates_serviceinfo_key():
12691392
"""Verify a name change will adjust the underlying key value."""
12701393
type_ = "_homeassistant._tcp.local."

‎zeroconf/_services/__init__.py‎

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,31 @@ def _group_ptr_queries_with_known_answers(
223223
return [query_bucket.out for query_bucket in query_buckets]
224224

225225

226+
def _service_state_changed_from_listener(listener: ServiceListener) -> Callable[..., None]:
227+
"""Generate a service_state_changed handlers from a listener."""
228+
229+
def on_change(
230+
zeroconf: 'Zeroconf', service_type: str, name: str, state_change: ServiceStateChange
231+
) -> None:
232+
assert listener is not None
233+
args = (zeroconf, service_type, name)
234+
if state_change is ServiceStateChange.Added:
235+
listener.add_service(*args)
236+
elif state_change is ServiceStateChange.Removed:
237+
listener.remove_service(*args)
238+
elif state_change is ServiceStateChange.Updated:
239+
if hasattr(listener, 'update_service'):
240+
listener.update_service(*args)
241+
else:
242+
warnings.warn(
243+
"%r has no update_service method. Provide one (it can be empty if you "
244+
"don't care about the updates), it'll become mandatory." % (listener,),
245+
FutureWarning,
246+
)
247+
248+
return on_change
249+
250+
226251
class _ServiceBrowserBase(RecordUpdateListener):
227252
"""Base class for ServiceBrowser."""
228253

@@ -263,29 +288,7 @@ def __init__(
263288
handlers = cast(List[Callable[..., None]], handlers or [])
264289

265290
if listener:
266-
267-
def on_change(
268-
zeroconf: 'Zeroconf', service_type: str, name: str, state_change: ServiceStateChange
269-
) -> None:
270-
assert listener is not None
271-
args = (zeroconf, service_type, name)
272-
if state_change is ServiceStateChange.Added:
273-
listener.add_service(*args)
274-
elif state_change is ServiceStateChange.Removed:
275-
listener.remove_service(*args)
276-
elif state_change is ServiceStateChange.Updated:
277-
if hasattr(listener, 'update_service'):
278-
listener.update_service(*args)
279-
else:
280-
warnings.warn(
281-
"%r has no update_service method. Provide one (it can be empty if you "
282-
"don't care about the updates), it'll become mandatory." % (listener,),
283-
FutureWarning,
284-
)
285-
else:
286-
raise NotImplementedError(state_change)
287-
288-
handlers.append(on_change)
291+
handlers.append(_service_state_changed_from_listener(listener))
289292

290293
for h in handlers:
291294
self.service_state_changed.register_handler(h)

0 commit comments

Comments
 (0)