Bonjour/MYBonjourBrowser.m
author Jens Alfke <jens@mooseyard.com>
Sun May 10 19:00:50 2009 -0700 (2009-05-10)
changeset 45 8efb48eabd08
parent 31 1d6924779df7
child 50 63baa74c903f
permissions -rw-r--r--
Fixed MYAddressLookup to allocate an NSSet, and to send correct KV notifications. (Based on Jim Roepke's patch, but outsourcing the KV grunge to CollectionUtils.)
     1 //
     2 //  MYBonjourBrowser.m
     3 //  MYNetwork
     4 //
     5 //  Created by Jens Alfke on 1/22/08.
     6 //  Copyright 2008 Jens Alfke. All rights reserved.
     7 //
     8 
     9 #import "MYBonjourBrowser.h"
    10 #import "MYBonjourService.h"
    11 #import "MYBonjourRegistration.h"
    12 #import "ExceptionUtils.h"
    13 #import "Test.h"
    14 #import "Logging.h"
    15 #import <dns_sd.h>
    16 
    17 
    18 static void browseCallback (DNSServiceRef                       sdRef,
    19                             DNSServiceFlags                     flags,
    20                             uint32_t                            interfaceIndex,
    21                             DNSServiceErrorType                 errorCode,
    22                             const char                          *serviceName,
    23                             const char                          *regtype,
    24                             const char                          *replyDomain,
    25                             void                                *context);
    26 
    27 @interface MYBonjourBrowser ()
    28 @property BOOL browsing;
    29 - (void) _updateServiceList;
    30 @end
    31 
    32 
    33 @implementation MYBonjourBrowser
    34 
    35 
    36 - (id) initWithServiceType: (NSString*)serviceType
    37 {
    38     Assert(serviceType);
    39     self = [super init];
    40     if (self != nil) {
    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];
    47     }
    48     return self;
    49 }
    50 
    51 
    52 - (void) dealloc
    53 {
    54     LogTo(Bonjour,@"DEALLOC BonjourBrowser");
    55     [_myRegistration cancel];
    56     [_myRegistration release];
    57     [_serviceType release];
    58     [_services release];
    59     [_addServices release];
    60     [_rmvServices release];
    61     [super dealloc];
    62 }
    63 
    64 
    65 @synthesize browsing=_browsing, services=_services, serviceClass=_serviceClass;
    66 
    67 
    68 - (NSString*) description
    69 {
    70     return $sprintf(@"%@[%@]", self.class,_serviceType);
    71 }
    72 
    73 
    74 - (DNSServiceErrorType) createServiceRef: (DNSServiceRef*)sdRefPtr {
    75     return DNSServiceBrowse(sdRefPtr,
    76                             kDNSServiceFlagsShareConnection, 
    77                             0,
    78                             _serviceType.UTF8String, NULL,
    79                             &browseCallback, self);
    80 }
    81 
    82 
    83 - (void) priv_gotError: (DNSServiceErrorType)errorCode {
    84     LogTo(Bonjour,@"%@ got error %i", self,errorCode);
    85     self.error = errorCode;
    86 }
    87 
    88 - (void) priv_gotServiceName: (NSString*)serviceName
    89                         type: (NSString*)regtype
    90                       domain: (NSString*)domain
    91                    interface: (uint32_t)interfaceIndex
    92                        flags: (DNSServiceFlags)flags
    93 {
    94     // Create (or reuse existing) MYBonjourService object:
    95     MYBonjourService *service = [[_serviceClass alloc] initWithName: serviceName
    96                                                                type: regtype
    97                                                              domain: domain
    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);
   102         [service release];
   103         return;
   104     }
   105     MYBonjourService *existingService = [_services member: service];
   106     if( existingService ) {
   107         // Use existing service object instead of creating a new one
   108         [service release];
   109         service = [existingService retain];
   110     }
   111     
   112     // Add it to the add/remove sets:
   113     NSMutableSet *addTo, *removeFrom;
   114     if (flags & kDNSServiceFlagsAdd) {
   115         addTo = _addServices;
   116         removeFrom = _rmvServices;
   117     } else {
   118         addTo = _rmvServices;
   119         removeFrom = _addServices;
   120     }
   121     if( [removeFrom containsObject: service] )
   122         [removeFrom removeObject: service];
   123     else
   124         [addTo addObject: service];
   125     [service release];
   126     
   127     // Schedule a (single) call to _updateServiceList:
   128     if (!_pendingUpdate) {
   129         [self performSelector: @selector(_updateServiceList) withObject: nil afterDelay: 0];
   130         _pendingUpdate = YES;
   131     }
   132 }
   133 
   134 
   135 - (void) _updateServiceList
   136 {
   137     _pendingUpdate = NO;
   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];
   148     }
   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];
   159     }
   160 }
   161 
   162 
   163 static void browseCallback (DNSServiceRef                       sdRef,
   164                             DNSServiceFlags                     flags,
   165                             uint32_t                            interfaceIndex,
   166                             DNSServiceErrorType                 errorCode,
   167                             const char                          *serviceName,
   168                             const char                          *regtype,
   169                             const char                          *replyDomain,
   170                             void                                *context)
   171 {
   172     MYBonjourBrowser *browser = context;
   173     @try{
   174         //LogTo(Bonjour,@"browseCallback (error=%i, name='%s')", errorCode,serviceName);
   175         if (!errorCode)
   176             [browser priv_gotServiceName: [NSString stringWithUTF8String: serviceName]
   177                                     type: [NSString stringWithUTF8String: regtype]
   178                                   domain: [NSString stringWithUTF8String: replyDomain]
   179                                interface: interfaceIndex
   180                                    flags: flags];
   181     }catchAndReport(@"Bonjour");
   182     [browser gotResponse: errorCode];
   183 }
   184 
   185 
   186 - (void) cancel {
   187     [_myRegistration stop];
   188     [super cancel];
   189 }
   190 
   191 
   192 - (MYBonjourRegistration *) myRegistration {
   193     if (!_myRegistration)
   194         _myRegistration = [[MYBonjourRegistration alloc] initWithServiceType: _serviceType port: 0];
   195     return _myRegistration;
   196 }
   197 
   198 
   199 @end
   200 
   201 
   202 
   203 
   204 #pragma mark -
   205 #pragma mark TESTING:
   206 
   207 #if DEBUG
   208 
   209 #import "MYBonjourQuery.h"
   210 #import "MYAddressLookup.h"
   211 
   212 @interface BonjourTester : NSObject
   213 {
   214     MYBonjourBrowser *_browser;
   215 }
   216 @end
   217 
   218 @implementation BonjourTester
   219 
   220 - (id) init
   221 {
   222     self = [super init];
   223     if (self != nil) {
   224         _browser = [[MYBonjourBrowser alloc] initWithServiceType: @"_presence._tcp"];
   225         [_browser addObserver: self forKeyPath: @"services" 
   226                       options: NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew 
   227                       context: NULL];
   228         [_browser addObserver: self forKeyPath: @"browsing" 
   229                       options: NSKeyValueObservingOptionNew
   230                       context: NULL];
   231         [_browser start];
   232         
   233         MYBonjourRegistration *myReg = _browser.myRegistration;
   234         myReg.port = 12346;
   235         Assert([myReg start]);
   236     }
   237     return self;
   238 }
   239 
   240 - (void) dealloc
   241 {
   242     [_browser stop];
   243     [_browser removeObserver: self forKeyPath: @"services"];
   244     [_browser removeObserver: self forKeyPath: @"browsing"];
   245     [_browser release];
   246     [super dealloc];
   247 }
   248 
   249 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
   250 {
   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
   263                                            context: NULL];
   264                 [service queryForRecord: kDNSServiceType_NULL];
   265             }
   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"];
   271             }
   272         }
   273     }
   274 }
   275 
   276 @end
   277 
   278 TestCase(Bonjour) {
   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]];
   284     [tester release];
   285 }
   286 
   287 #endif
   288 
   289 
   290 /*
   291  Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   292  
   293  Redistribution and use in source and binary forms, with or without modification, are permitted
   294  provided that the following conditions are met:
   295  
   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
   300  distribution.
   301  
   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.
   310  */