Skip to content

Commit

Permalink
Add support for MyListener call getting updates to service TXT record…
Browse files Browse the repository at this point in the history
…s (2nd attempt) (#166)

Add support for MyListener call getting updates to service TXT records

At the moment, the implementation supports notification to the  ServiceListener class for additions and removals of service, but for service updates to the TXT record, the client must poll the ServiceInfo class. This draft PR provides a mechanism to have a callback on the ServiceListener class be invoked when the TXT record changes.
  • Loading branch information
mattsaxon authored and jstasiak committed May 15, 2019
1 parent e1c2b00 commit d4e06bc
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 4 deletions.
34 changes: 32 additions & 2 deletions test_zeroconf.py
Expand Up @@ -737,12 +737,13 @@ def test_integration_with_listener_class(self):

service_added = Event()
service_removed = Event()
service_updated = Event()

subtype_name = "My special Subtype"
type_ = "_http._tcp.local."
subtype = subtype_name + "._sub." + type_
name = "xxxyyyæøå"
registration_name = "%s.%s" % (name, type_)
registration_name = "%s.%s" % (name, subtype)

class MyListener(r.ServiceListener):
def add_service(self, zeroconf, type, name):
Expand All @@ -752,6 +753,16 @@ def add_service(self, zeroconf, type, name):
def remove_service(self, zeroconf, type, name):
service_removed.set()

class MySubListener(r.ServiceListener):
def add_service(self, zeroconf, type, name):
pass

def remove_service(self, zeroconf, type, name):
pass

def update_service(self, zeroconf, type, name):
service_updated.set()

listener = MyListener()
zeroconf_browser = Zeroconf(interfaces=['127.0.0.1'])
zeroconf_browser.add_service_listener(subtype, listener)
Expand Down Expand Up @@ -779,7 +790,7 @@ def remove_service(self, zeroconf, type, name):
assert service_added.is_set()

# short pause to allow multicast timers to expire
time.sleep(2)
time.sleep(3)

# clear the answer cache to force query
for record in zeroconf_browser.cache.entries():
Expand All @@ -799,9 +810,28 @@ def remove_service(self, zeroconf, type, name):
assert info is not None
assert info.properties[b'prop_none'] is False

# Begin material test addition
sublistener = MySubListener()
zeroconf_browser.add_service_listener(registration_name, sublistener)
properties['prop_blank'] = b'an updated string'
desc.update(properties)
info_service = ServiceInfo(
subtype, registration_name,
socket.inet_aton("10.0.1.2"), 80, 0, 0,
desc, "ash-2.local.")
zeroconf_registrar.update_service(info_service)
service_updated.wait(1)
assert service_updated.is_set()

info = zeroconf_browser.get_service_info(type_, registration_name)
assert info is not None
assert info.properties[b'prop_blank'] == properties['prop_blank']
# End material test addition

zeroconf_registrar.unregister_service(info_service)
service_removed.wait(1)
assert service_removed.is_set()

finally:
zeroconf_registrar.close()
zeroconf_browser.remove_service_listener(listener)
Expand Down
37 changes: 35 additions & 2 deletions zeroconf.py
Expand Up @@ -169,7 +169,7 @@ class InterfaceChoice(enum.Enum):
class ServiceStateChange(enum.Enum):
Added = 1
Removed = 2

Updated = 3

# utility functions

Expand Down Expand Up @@ -1267,6 +1267,9 @@ def add_service(self, zc, type_, name) -> None:
def remove_service(self, zc, type_, name) -> None:
raise NotImplementedError()

def update_service(self, zc, type_, name) -> None:
raise NotImplementedError()


class ServiceBrowser(RecordUpdateListener, threading.Thread):

Expand Down Expand Up @@ -1312,6 +1315,9 @@ def on_change(zeroconf, service_type, name, state_change):
listener.add_service(*args)
elif state_change is ServiceStateChange.Removed:
listener.remove_service(*args)
elif state_change is ServiceStateChange.Updated:
if hasattr(listener, 'update_service'):
listener.update_service(*args)
else:
raise NotImplementedError(state_change)
handlers.append(on_change)
Expand Down Expand Up @@ -1361,6 +1367,12 @@ def enqueue_callback(state_change: ServiceStateChange, name: str) -> None:
if expires < self.next_time:
self.next_time = expires

elif record.type == _TYPE_TXT and record.name == self.type:
assert isinstance(record, DNSText)
expired = record.is_expired(now)
if not expired:
enqueue_callback(ServiceStateChange.Updated, record.name)

def cancel(self):
self.done = True
self.zc.remove_listener(self)
Expand Down Expand Up @@ -1855,6 +1867,24 @@ def register_service(
self.servicetypes[info.type] += 1
else:
self.servicetypes[info.type] = 1

self._broadcast_service(info)

def update_service(
self, info: ServiceInfo
) -> None:
"""Registers service information to the network with a default TTL.
Zeroconf will then respond to requests for information for that
service."""

assert self.services[info.name.lower()] is not None

self.services[info.name.lower()] = info

self._broadcast_service(info)

def _broadcast_service(self, info: ServiceInfo) -> None:

now = current_time_millis()
next_time = now
i = 0
Expand Down Expand Up @@ -2031,9 +2061,12 @@ def handle_response(self, msg: DNSIncoming) -> None:
entry.reset_ttl(record)
else:
self.cache.add(record)
if record.type == _TYPE_TXT:
self.update_record(now, record)

for record in msg.answers:
self.update_record(now, record)
if record.type != _TYPE_TXT:
self.update_record(now, record)

def handle_query(self, msg: DNSIncoming, addr: str, port: int) -> None:
"""Deal with incoming query packets. Provides a response if
Expand Down

0 comments on commit d4e06bc

Please sign in to comment.