Bonjour/MYBonjourBrowser.m
changeset 26 cb9cdf247239
child 28 732576fa8a0d
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/Bonjour/MYBonjourBrowser.m	Wed Apr 22 16:45:39 2009 -0700
     1.3 @@ -0,0 +1,239 @@
     1.4 +//
     1.5 +//  MYBonjourBrowser.m
     1.6 +//  MYNetwork
     1.7 +//
     1.8 +//  Created by Jens Alfke on 1/22/08.
     1.9 +//  Copyright 2008 Jens Alfke. All rights reserved.
    1.10 +//
    1.11 +
    1.12 +#import "MYBonjourBrowser.h"
    1.13 +#import "MYBonjourService.h"
    1.14 +#import "Test.h"
    1.15 +#import "Logging.h"
    1.16 +
    1.17 +
    1.18 +@interface MYBonjourBrowser ()
    1.19 +@property BOOL browsing;
    1.20 +@property (retain) NSError* error;
    1.21 +@end
    1.22 +
    1.23 +
    1.24 +@implementation MYBonjourBrowser
    1.25 +
    1.26 +
    1.27 +- (id) initWithServiceType: (NSString*)serviceType
    1.28 +{
    1.29 +    Assert(serviceType);
    1.30 +    self = [super init];
    1.31 +    if (self != nil) {
    1.32 +        _serviceType = [serviceType copy];
    1.33 +        _browser = [[NSNetServiceBrowser alloc] init];
    1.34 +        _browser.delegate = self;
    1.35 +        _services = [[NSMutableSet alloc] init];
    1.36 +        _addServices = [[NSMutableSet alloc] init];
    1.37 +        _rmvServices = [[NSMutableSet alloc] init];
    1.38 +        _serviceClass = [MYBonjourService class];
    1.39 +    }
    1.40 +    return self;
    1.41 +}
    1.42 +
    1.43 +
    1.44 +- (void) dealloc
    1.45 +{
    1.46 +    LogTo(Bonjour,@"DEALLOC BonjourBrowser");
    1.47 +    [_browser stop];
    1.48 +    _browser.delegate = nil;
    1.49 +    [_browser release];
    1.50 +    [_serviceType release];
    1.51 +    [_error release];
    1.52 +    [_services release];
    1.53 +    [_addServices release];
    1.54 +    [_rmvServices release];
    1.55 +    [super dealloc];
    1.56 +}
    1.57 +
    1.58 +
    1.59 +@synthesize browsing=_browsing, error=_error, services=_services, serviceClass=_serviceClass;
    1.60 +
    1.61 +
    1.62 +- (void) start
    1.63 +{
    1.64 +    [_browser searchForServicesOfType: _serviceType inDomain: @"local."];
    1.65 +}
    1.66 +
    1.67 +- (void) stop
    1.68 +{
    1.69 +    [_browser stop];
    1.70 +}
    1.71 +
    1.72 +
    1.73 +- (void)netServiceBrowserWillSearch:(NSNetServiceBrowser *)netServiceBrowser
    1.74 +{
    1.75 +    LogTo(Bonjour,@"%@ started browsing",self);
    1.76 +    self.browsing = YES;
    1.77 +}
    1.78 +
    1.79 +- (void)netServiceBrowserDidStopSearch:(NSNetServiceBrowser *)netServiceBrowser
    1.80 +{
    1.81 +    LogTo(Bonjour,@"%@ stopped browsing",self);
    1.82 +    self.browsing = NO;
    1.83 +}
    1.84 +
    1.85 +- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser 
    1.86 +             didNotSearch:(NSDictionary *)errorDict
    1.87 +{
    1.88 +    NSString *domain = [errorDict objectForKey: NSNetServicesErrorDomain];
    1.89 +    int err = [[errorDict objectForKey: NSNetServicesErrorCode] intValue];
    1.90 +    self.error = [NSError errorWithDomain: domain code: err userInfo: nil];
    1.91 +    LogTo(Bonjour,@"%@ got error: ",self,self.error);
    1.92 +    self.browsing = NO;
    1.93 +}
    1.94 +
    1.95 +
    1.96 +- (void) _updateServiceList
    1.97 +{
    1.98 +    if( _rmvServices.count ) {
    1.99 +        [self willChangeValueForKey: @"services" 
   1.100 +                    withSetMutation: NSKeyValueMinusSetMutation
   1.101 +                       usingObjects: _rmvServices];
   1.102 +        [_services minusSet: _rmvServices];
   1.103 +        [self didChangeValueForKey: @"services" 
   1.104 +                   withSetMutation: NSKeyValueMinusSetMutation
   1.105 +                      usingObjects: _rmvServices];
   1.106 +        [_rmvServices makeObjectsPerformSelector: @selector(removed)];
   1.107 +        [_rmvServices removeAllObjects];
   1.108 +    }
   1.109 +    if( _addServices.count ) {
   1.110 +        [_addServices makeObjectsPerformSelector: @selector(added)];
   1.111 +        [self willChangeValueForKey: @"services" 
   1.112 +                    withSetMutation: NSKeyValueUnionSetMutation
   1.113 +                       usingObjects: _addServices];
   1.114 +        [_services unionSet: _addServices];
   1.115 +        [self didChangeValueForKey: @"services" 
   1.116 +                   withSetMutation: NSKeyValueUnionSetMutation
   1.117 +                      usingObjects: _addServices];
   1.118 +        [_addServices removeAllObjects];
   1.119 +    }
   1.120 +}
   1.121 +
   1.122 +
   1.123 +- (void) _handleService: (NSNetService*)netService 
   1.124 +                  addTo: (NSMutableSet*)addTo
   1.125 +             removeFrom: (NSMutableSet*)removeFrom
   1.126 +             moreComing: (BOOL)moreComing
   1.127 +{
   1.128 +    // Wrap the NSNetService in a BonjourService, using an existing instance if possible:
   1.129 +    MYBonjourService *service = [[_serviceClass alloc] initWithNetService: netService];
   1.130 +    MYBonjourService *existingService = [_services member: service];
   1.131 +    if( existingService ) {
   1.132 +        [service release];
   1.133 +        service = [existingService retain];
   1.134 +    }
   1.135 +    
   1.136 +    if( [removeFrom containsObject: service] )
   1.137 +        [removeFrom removeObject: service];
   1.138 +    else
   1.139 +        [addTo addObject: service];
   1.140 +    [service release];
   1.141 +    if( ! moreComing )
   1.142 +        [self _updateServiceList];
   1.143 +}
   1.144 +
   1.145 +- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser 
   1.146 +           didFindService:(NSNetService *)netService
   1.147 +               moreComing:(BOOL)moreComing 
   1.148 +{
   1.149 +    //LogTo(Bonjour,@"Add service %@",netService);
   1.150 +    [self _handleService: netService addTo: _addServices removeFrom: _rmvServices moreComing: moreComing];
   1.151 +}
   1.152 +
   1.153 +- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser 
   1.154 +         didRemoveService:(NSNetService *)netService 
   1.155 +               moreComing:(BOOL)moreComing 
   1.156 +{
   1.157 +    //LogTo(Bonjour,@"Remove service %@",netService);
   1.158 +    [self _handleService: netService addTo: _rmvServices removeFrom: _addServices moreComing: moreComing];
   1.159 +}
   1.160 +
   1.161 +
   1.162 +@end
   1.163 +
   1.164 +
   1.165 +
   1.166 +#pragma mark -
   1.167 +#pragma mark TESTING:
   1.168 +
   1.169 +@interface BonjourTester : NSObject
   1.170 +{
   1.171 +    MYBonjourBrowser *_browser;
   1.172 +}
   1.173 +@end
   1.174 +
   1.175 +@implementation BonjourTester
   1.176 +
   1.177 +- (id) init
   1.178 +{
   1.179 +    self = [super init];
   1.180 +    if (self != nil) {
   1.181 +        _browser = [[MYBonjourBrowser alloc] initWithServiceType: @"_http._tcp"];
   1.182 +        [_browser addObserver: self forKeyPath: @"services" options: NSKeyValueObservingOptionNew context: NULL];
   1.183 +        [_browser addObserver: self forKeyPath: @"browsing" options: NSKeyValueObservingOptionNew context: NULL];
   1.184 +        [_browser start];
   1.185 +    }
   1.186 +    return self;
   1.187 +}
   1.188 +
   1.189 +- (void) dealloc
   1.190 +{
   1.191 +    [_browser stop];
   1.192 +    [_browser removeObserver: self forKeyPath: @"services"];
   1.193 +    [_browser removeObserver: self forKeyPath: @"browsing"];
   1.194 +    [_browser release];
   1.195 +    [super dealloc];
   1.196 +}
   1.197 +
   1.198 +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
   1.199 +{
   1.200 +    LogTo(Bonjour,@"Observed change in %@: %@",keyPath,change);
   1.201 +    if( $equal(keyPath,@"services") ) {
   1.202 +        if( [[change objectForKey: NSKeyValueChangeKindKey] intValue]==NSKeyValueChangeInsertion ) {
   1.203 +            NSSet *newServices = [change objectForKey: NSKeyValueChangeNewKey];
   1.204 +            for( MYBonjourService *service in newServices ) {
   1.205 +                LogTo(Bonjour,@"    --> %@ : TXT=%@", service,service.txtRecord);
   1.206 +            }
   1.207 +        }
   1.208 +    }
   1.209 +}
   1.210 +
   1.211 +@end
   1.212 +
   1.213 +TestCase(Bonjour) {
   1.214 +    [NSRunLoop currentRunLoop]; // create runloop
   1.215 +    BonjourTester *tester = [[BonjourTester alloc] init];
   1.216 +    [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 15]];
   1.217 +    [tester release];
   1.218 +}
   1.219 +
   1.220 +
   1.221 +
   1.222 +/*
   1.223 + Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   1.224 + 
   1.225 + Redistribution and use in source and binary forms, with or without modification, are permitted
   1.226 + provided that the following conditions are met:
   1.227 + 
   1.228 + * Redistributions of source code must retain the above copyright notice, this list of conditions
   1.229 + and the following disclaimer.
   1.230 + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   1.231 + and the following disclaimer in the documentation and/or other materials provided with the
   1.232 + distribution.
   1.233 + 
   1.234 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   1.235 + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   1.236 + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   1.237 + BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   1.238 + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   1.239 +  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   1.240 + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   1.241 + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   1.242 + */