BROKEN COMMIT. Majority of code to handle closing has been added. Listeners do not close correctly.
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 BonjourBrowser");
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')", errorCode,serviceName);
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 NSString *hostname = service.hostname; // block till it's resolved
257 Log(@"##### %@ : at %@:%hu, TXT=%@",
258 service, hostname, service.port, service.txtRecord);
259 service.addressLookup.continuous = YES;
260 [service.addressLookup addObserver: self
261 forKeyPath: @"addresses"
262 options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
264 [service queryForRecord: kDNSServiceType_NULL];
266 } else if( [[change objectForKey: NSKeyValueChangeKindKey] intValue]==NSKeyValueChangeRemoval ) {
267 NSSet *oldServices = [change objectForKey: NSKeyValueChangeOldKey];
268 for( MYBonjourService *service in oldServices ) {
269 Log(@"##### REMOVED: %@", service);
270 [service.addressLookup removeObserver: self forKeyPath: @"addresses"];
279 EnableLogTo(Bonjour,YES);
280 EnableLogTo(DNS,YES);
281 [NSRunLoop currentRunLoop]; // create runloop
282 BonjourTester *tester = [[BonjourTester alloc] init];
283 [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 1500]];
291 Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
293 Redistribution and use in source and binary forms, with or without modification, are permitted
294 provided that the following conditions are met:
296 * Redistributions of source code must retain the above copyright notice, this list of conditions
297 and the following disclaimer.
298 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
299 and the following disclaimer in the documentation and/or other materials provided with the
302 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
303 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
304 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
305 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
306 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
307 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
308 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
309 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.