jens@26: // jens@26: // MYBonjourBrowser.m jens@26: // MYNetwork jens@26: // jens@26: // Created by Jens Alfke on 1/22/08. jens@26: // Copyright 2008 Jens Alfke. All rights reserved. jens@26: // jens@26: jens@26: #import "MYBonjourBrowser.h" jens@26: #import "MYBonjourService.h" jens@26: #import "Test.h" jens@26: #import "Logging.h" jens@26: jens@26: jens@26: @interface MYBonjourBrowser () jens@26: @property BOOL browsing; jens@26: @property (retain) NSError* error; jens@26: @end jens@26: jens@26: jens@26: @implementation MYBonjourBrowser jens@26: jens@26: jens@26: - (id) initWithServiceType: (NSString*)serviceType jens@26: { jens@26: Assert(serviceType); jens@26: self = [super init]; jens@26: if (self != nil) { jens@26: _serviceType = [serviceType copy]; jens@26: _browser = [[NSNetServiceBrowser alloc] init]; jens@26: _browser.delegate = self; jens@26: _services = [[NSMutableSet alloc] init]; jens@26: _addServices = [[NSMutableSet alloc] init]; jens@26: _rmvServices = [[NSMutableSet alloc] init]; jens@26: _serviceClass = [MYBonjourService class]; jens@26: } jens@26: return self; jens@26: } jens@26: jens@26: jens@26: - (void) dealloc jens@26: { jens@26: LogTo(Bonjour,@"DEALLOC BonjourBrowser"); jens@26: [_browser stop]; jens@26: _browser.delegate = nil; jens@26: [_browser release]; jens@26: [_serviceType release]; jens@26: [_error release]; jens@26: [_services release]; jens@26: [_addServices release]; jens@26: [_rmvServices release]; jens@26: [super dealloc]; jens@26: } jens@26: jens@26: jens@26: @synthesize browsing=_browsing, error=_error, services=_services, serviceClass=_serviceClass; jens@26: jens@26: jens@26: - (void) start jens@26: { jens@26: [_browser searchForServicesOfType: _serviceType inDomain: @"local."]; jens@26: } jens@26: jens@26: - (void) stop jens@26: { jens@26: [_browser stop]; jens@26: } jens@26: jens@26: jens@26: - (void)netServiceBrowserWillSearch:(NSNetServiceBrowser *)netServiceBrowser jens@26: { jens@26: LogTo(Bonjour,@"%@ started browsing",self); jens@26: self.browsing = YES; jens@26: } jens@26: jens@26: - (void)netServiceBrowserDidStopSearch:(NSNetServiceBrowser *)netServiceBrowser jens@26: { jens@26: LogTo(Bonjour,@"%@ stopped browsing",self); jens@26: self.browsing = NO; jens@26: } jens@26: jens@26: - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser jens@26: didNotSearch:(NSDictionary *)errorDict jens@26: { jens@26: NSString *domain = [errorDict objectForKey: NSNetServicesErrorDomain]; jens@26: int err = [[errorDict objectForKey: NSNetServicesErrorCode] intValue]; jens@26: self.error = [NSError errorWithDomain: domain code: err userInfo: nil]; jens@26: LogTo(Bonjour,@"%@ got error: ",self,self.error); jens@26: self.browsing = NO; jens@26: } jens@26: jens@26: jens@26: - (void) _updateServiceList jens@26: { jens@26: if( _rmvServices.count ) { jens@26: [self willChangeValueForKey: @"services" jens@26: withSetMutation: NSKeyValueMinusSetMutation jens@26: usingObjects: _rmvServices]; jens@26: [_services minusSet: _rmvServices]; jens@26: [self didChangeValueForKey: @"services" jens@26: withSetMutation: NSKeyValueMinusSetMutation jens@26: usingObjects: _rmvServices]; jens@26: [_rmvServices makeObjectsPerformSelector: @selector(removed)]; jens@26: [_rmvServices removeAllObjects]; jens@26: } jens@26: if( _addServices.count ) { jens@26: [_addServices makeObjectsPerformSelector: @selector(added)]; jens@26: [self willChangeValueForKey: @"services" jens@26: withSetMutation: NSKeyValueUnionSetMutation jens@26: usingObjects: _addServices]; jens@26: [_services unionSet: _addServices]; jens@26: [self didChangeValueForKey: @"services" jens@26: withSetMutation: NSKeyValueUnionSetMutation jens@26: usingObjects: _addServices]; jens@26: [_addServices removeAllObjects]; jens@26: } jens@26: } jens@26: jens@26: jens@26: - (void) _handleService: (NSNetService*)netService jens@26: addTo: (NSMutableSet*)addTo jens@26: removeFrom: (NSMutableSet*)removeFrom jens@26: moreComing: (BOOL)moreComing jens@26: { jens@26: // Wrap the NSNetService in a BonjourService, using an existing instance if possible: jens@26: MYBonjourService *service = [[_serviceClass alloc] initWithNetService: netService]; jens@26: MYBonjourService *existingService = [_services member: service]; jens@26: if( existingService ) { jens@26: [service release]; jens@26: service = [existingService retain]; jens@26: } jens@26: jens@26: if( [removeFrom containsObject: service] ) jens@26: [removeFrom removeObject: service]; jens@26: else jens@26: [addTo addObject: service]; jens@26: [service release]; jens@26: if( ! moreComing ) jens@26: [self _updateServiceList]; jens@26: } jens@26: jens@26: - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser jens@26: didFindService:(NSNetService *)netService jens@26: moreComing:(BOOL)moreComing jens@26: { jens@26: //LogTo(Bonjour,@"Add service %@",netService); jens@26: [self _handleService: netService addTo: _addServices removeFrom: _rmvServices moreComing: moreComing]; jens@26: } jens@26: jens@26: - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser jens@26: didRemoveService:(NSNetService *)netService jens@26: moreComing:(BOOL)moreComing jens@26: { jens@26: //LogTo(Bonjour,@"Remove service %@",netService); jens@26: [self _handleService: netService addTo: _rmvServices removeFrom: _addServices moreComing: moreComing]; jens@26: } jens@26: jens@26: jens@26: @end jens@26: jens@26: jens@26: jens@26: #pragma mark - jens@26: #pragma mark TESTING: jens@26: jens@26: @interface BonjourTester : NSObject jens@26: { jens@26: MYBonjourBrowser *_browser; jens@26: } jens@26: @end jens@26: jens@26: @implementation BonjourTester jens@26: jens@26: - (id) init jens@26: { jens@26: self = [super init]; jens@26: if (self != nil) { jens@26: _browser = [[MYBonjourBrowser alloc] initWithServiceType: @"_http._tcp"]; jens@26: [_browser addObserver: self forKeyPath: @"services" options: NSKeyValueObservingOptionNew context: NULL]; jens@26: [_browser addObserver: self forKeyPath: @"browsing" options: NSKeyValueObservingOptionNew context: NULL]; jens@26: [_browser start]; jens@26: } jens@26: return self; jens@26: } jens@26: jens@26: - (void) dealloc jens@26: { jens@26: [_browser stop]; jens@26: [_browser removeObserver: self forKeyPath: @"services"]; jens@26: [_browser removeObserver: self forKeyPath: @"browsing"]; jens@26: [_browser release]; jens@26: [super dealloc]; jens@26: } jens@26: jens@26: - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context jens@26: { jens@26: LogTo(Bonjour,@"Observed change in %@: %@",keyPath,change); jens@26: if( $equal(keyPath,@"services") ) { jens@26: if( [[change objectForKey: NSKeyValueChangeKindKey] intValue]==NSKeyValueChangeInsertion ) { jens@26: NSSet *newServices = [change objectForKey: NSKeyValueChangeNewKey]; jens@26: for( MYBonjourService *service in newServices ) { jens@26: LogTo(Bonjour,@" --> %@ : TXT=%@", service,service.txtRecord); jens@26: } jens@26: } jens@26: } jens@26: } jens@26: jens@26: @end jens@26: jens@26: TestCase(Bonjour) { jens@26: [NSRunLoop currentRunLoop]; // create runloop jens@26: BonjourTester *tester = [[BonjourTester alloc] init]; jens@26: [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 15]]; jens@26: [tester release]; jens@26: } jens@26: jens@26: jens@26: jens@26: /* jens@26: Copyright (c) 2008-2009, Jens Alfke . All rights reserved. jens@26: jens@26: Redistribution and use in source and binary forms, with or without modification, are permitted jens@26: provided that the following conditions are met: jens@26: jens@26: * Redistributions of source code must retain the above copyright notice, this list of conditions jens@26: and the following disclaimer. jens@26: * Redistributions in binary form must reproduce the above copyright notice, this list of conditions jens@26: and the following disclaimer in the documentation and/or other materials provided with the jens@26: distribution. jens@26: jens@26: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR jens@26: IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND jens@26: FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- jens@26: BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES jens@26: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR jens@26: PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN jens@26: CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF jens@26: THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. jens@26: */