Fixed bug which caused PyBLIP to stop sending responses while the connection was closing.
5 // Created by Jens Alfke on 1/22/08.
6 // Copyright 2008 Jens Alfke. All rights reserved.
9 #import "MYBonjourBrowser.h"
10 #import "MYBonjourService.h"
11 #import "MYBonjourRegistration.h"
12 #import "ExceptionUtils.h"
18 static void browseCallback (DNSServiceRef sdRef,
19 DNSServiceFlags flags,
20 uint32_t interfaceIndex,
21 DNSServiceErrorType errorCode,
22 const char *serviceName,
24 const char *replyDomain,
27 @interface MYBonjourBrowser ()
28 @property BOOL browsing;
29 - (void) _updateServiceList;
33 @implementation MYBonjourBrowser
36 - (id) initWithServiceType: (NSString*)serviceType
41 self.continuous = YES;
42 _serviceType = [serviceType copy];
43 _services = [[NSMutableSet alloc] init];
44 _addServices = [[NSMutableSet alloc] init];
45 _rmvServices = [[NSMutableSet alloc] init];
46 _serviceClass = [MYBonjourService class];
54 LogTo(Bonjour,@"DEALLOC MYBonjourBrowser");
55 [_myRegistration cancel];
56 [_myRegistration release];
57 [_serviceType release];
59 [_addServices release];
60 [_rmvServices release];
65 @synthesize browsing=_browsing, services=_services, serviceClass=_serviceClass;
68 - (NSString*) description
70 return $sprintf(@"%@[%@]", self.class,_serviceType);
74 - (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr {
75 return DNSServiceBrowse(sdRefPtr,
76 kDNSServiceFlagsShareConnection,
78 _serviceType.UTF8String, NULL,
79 &browseCallback, self);
83 - (void) priv_gotError: (DNSServiceErrorType)errorCode {
84 LogTo(Bonjour,@"%@ got error %i", self,errorCode);
85 self.error = errorCode;
88 - (void) priv_gotServiceName: (NSString*)serviceName
89 type: (NSString*)regtype
90 domain: (NSString*)domain
91 interface: (uint32_t)interfaceIndex
92 flags: (DNSServiceFlags)flags
94 // Create (or reuse existing) MYBonjourService object:
95 MYBonjourService *service = [[_serviceClass alloc] initWithName: serviceName
98 interface: interfaceIndex];
99 if ([_myRegistration isSameAsService: service]) {
100 // This is an echo of my own registration, so ignore it
101 LogTo(Bonjour,@"%@ ignoring echo %@", self,service);
105 MYBonjourService *existingService = [_services member: service];
106 if( existingService ) {
107 // Use existing service object instead of creating a new one
109 service = [existingService retain];
112 // Add it to the add/remove sets:
113 NSMutableSet *addTo, *removeFrom;
114 if (flags & kDNSServiceFlagsAdd) {
115 addTo = _addServices;
116 removeFrom = _rmvServices;
118 addTo = _rmvServices;
119 removeFrom = _addServices;
121 if( [removeFrom containsObject: service] )
122 [removeFrom removeObject: service];
124 [addTo addObject: service];
127 // Schedule a (single) call to _updateServiceList:
128 if (!_pendingUpdate) {
129 [self performSelector: @selector(_updateServiceList) withObject: nil afterDelay: 0];
130 _pendingUpdate = YES;
135 - (void) _updateServiceList
138 if( _rmvServices.count ) {
139 [self willChangeValueForKey: @"services"
140 withSetMutation: NSKeyValueMinusSetMutation
141 usingObjects: _rmvServices];
142 [_services minusSet: _rmvServices];
143 [self didChangeValueForKey: @"services"
144 withSetMutation: NSKeyValueMinusSetMutation
145 usingObjects: _rmvServices];
146 [_rmvServices makeObjectsPerformSelector: @selector(removed)];
147 [_rmvServices removeAllObjects];
149 if( _addServices.count ) {
150 [_addServices makeObjectsPerformSelector: @selector(added)];
151 [self willChangeValueForKey: @"services"
152 withSetMutation: NSKeyValueUnionSetMutation
153 usingObjects: _addServices];
154 [_services unionSet: _addServices];
155 [self didChangeValueForKey: @"services"
156 withSetMutation: NSKeyValueUnionSetMutation
157 usingObjects: _addServices];
158 [_addServices removeAllObjects];
163 static void browseCallback (DNSServiceRef sdRef,
164 DNSServiceFlags flags,
165 uint32_t interfaceIndex,
166 DNSServiceErrorType errorCode,
167 const char *serviceName,
169 const char *replyDomain,
172 MYBonjourBrowser *browser = context;
174 LogTo(Bonjour,@"browseCallback (error=%i, name='%s', intf=%u)", errorCode,serviceName,interfaceIndex);
176 [browser priv_gotServiceName: [NSString stringWithUTF8String: serviceName]
177 type: [NSString stringWithUTF8String: regtype]
178 domain: [NSString stringWithUTF8String: replyDomain]
179 interface: interfaceIndex
181 }catchAndReport(@"Bonjour");
182 [browser gotResponse: errorCode];
187 [_myRegistration stop];
192 - (MYBonjourRegistration *) myRegistration {
193 if (!_myRegistration)
194 _myRegistration = [[MYBonjourRegistration alloc] initWithServiceType: _serviceType port: 0];
195 return _myRegistration;
205 #pragma mark TESTING:
209 #import "MYBonjourQuery.h"
210 #import "MYAddressLookup.h"
212 @interface BonjourTester : NSObject
214 MYBonjourBrowser *_browser;
218 @implementation BonjourTester
224 _browser = [[MYBonjourBrowser alloc] initWithServiceType: @"_presence._tcp"];
225 [_browser addObserver: self forKeyPath: @"services"
226 options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
228 [_browser addObserver: self forKeyPath: @"browsing"
229 options: NSKeyValueObservingOptionNew
233 MYBonjourRegistration *myReg = _browser.myRegistration;
235 Assert([myReg start]);
243 [_browser removeObserver: self forKeyPath: @"services"];
244 [_browser removeObserver: self forKeyPath: @"browsing"];
249 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
251 Log(@"Observed change in %@: %@",keyPath,change);
252 if( $equal(keyPath,@"services") ) {
253 if( [[change objectForKey: NSKeyValueChangeKindKey] intValue]==NSKeyValueChangeInsertion ) {
254 NSSet *newServices = [change objectForKey: NSKeyValueChangeNewKey];
255 for( MYBonjourService *service in newServices ) {
256 Log(@"##### %@ : at %@:%hu, TXT=%@",
257 service, service.hostname, service.port, service.txtRecord);
258 service.addressLookup.continuous = YES;
259 [service.addressLookup addObserver: self
260 forKeyPath: @"addresses"
261 options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
263 [service queryForRecord: kDNSServiceType_NULL];
265 } else if( [[change objectForKey: NSKeyValueChangeKindKey] intValue]==NSKeyValueChangeRemoval ) {
266 NSSet *oldServices = [change objectForKey: NSKeyValueChangeOldKey];
267 for( MYBonjourService *service in oldServices ) {
268 Log(@"##### REMOVED: %@", service);
269 [service.addressLookup removeObserver: self forKeyPath: @"addresses"];
278 EnableLogTo(Bonjour,YES);
279 EnableLogTo(DNS,YES);
280 [NSRunLoop currentRunLoop]; // create runloop
281 BonjourTester *tester = [[BonjourTester alloc] init];
282 [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 1500]];
290 Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
292 Redistribution and use in source and binary forms, with or without modification, are permitted
293 provided that the following conditions are met:
295 * Redistributions of source code must retain the above copyright notice, this list of conditions
296 and the following disclaimer.
297 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
298 and the following disclaimer in the documentation and/or other materials provided with the
301 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
302 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
303 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
304 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
305 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
306 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
307 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
308 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.