Bonjour/MYBonjourBrowser.m
author Jens Alfke <jens@mooseyard.com>
Mon Apr 27 09:03:56 2009 -0700 (2009-04-27)
changeset 28 732576fa8a0d
parent 26 cb9cdf247239
child 31 1d6924779df7
permissions -rw-r--r--
Rewrote the Bonjour classes, using the low-level <dns_sd.h> API. They're now subclasses of MYDNSService.
jens@26
     1
//
jens@26
     2
//  MYBonjourBrowser.m
jens@26
     3
//  MYNetwork
jens@26
     4
//
jens@26
     5
//  Created by Jens Alfke on 1/22/08.
jens@26
     6
//  Copyright 2008 Jens Alfke. All rights reserved.
jens@26
     7
//
jens@26
     8
jens@26
     9
#import "MYBonjourBrowser.h"
jens@26
    10
#import "MYBonjourService.h"
jens@28
    11
#import "ExceptionUtils.h"
jens@26
    12
#import "Test.h"
jens@26
    13
#import "Logging.h"
jens@28
    14
#import <dns_sd.h>
jens@26
    15
jens@26
    16
jens@28
    17
static void browseCallback (DNSServiceRef                       sdRef,
jens@28
    18
                            DNSServiceFlags                     flags,
jens@28
    19
                            uint32_t                            interfaceIndex,
jens@28
    20
                            DNSServiceErrorType                 errorCode,
jens@28
    21
                            const char                          *serviceName,
jens@28
    22
                            const char                          *regtype,
jens@28
    23
                            const char                          *replyDomain,
jens@28
    24
                            void                                *context);
jens@28
    25
jens@26
    26
@interface MYBonjourBrowser ()
jens@26
    27
@property BOOL browsing;
jens@28
    28
- (void) _updateServiceList;
jens@26
    29
@end
jens@26
    30
jens@26
    31
jens@26
    32
@implementation MYBonjourBrowser
jens@26
    33
jens@26
    34
jens@26
    35
- (id) initWithServiceType: (NSString*)serviceType
jens@26
    36
{
jens@26
    37
    Assert(serviceType);
jens@26
    38
    self = [super init];
jens@26
    39
    if (self != nil) {
jens@28
    40
        self.continuous = YES;
jens@26
    41
        _serviceType = [serviceType copy];
jens@26
    42
        _services = [[NSMutableSet alloc] init];
jens@26
    43
        _addServices = [[NSMutableSet alloc] init];
jens@26
    44
        _rmvServices = [[NSMutableSet alloc] init];
jens@26
    45
        _serviceClass = [MYBonjourService class];
jens@26
    46
    }
jens@26
    47
    return self;
jens@26
    48
}
jens@26
    49
jens@26
    50
jens@26
    51
- (void) dealloc
jens@26
    52
{
jens@26
    53
    LogTo(Bonjour,@"DEALLOC BonjourBrowser");
jens@26
    54
    [_serviceType release];
jens@26
    55
    [_services release];
jens@26
    56
    [_addServices release];
jens@26
    57
    [_rmvServices release];
jens@26
    58
    [super dealloc];
jens@26
    59
}
jens@26
    60
jens@26
    61
jens@28
    62
@synthesize browsing=_browsing, services=_services, serviceClass=_serviceClass;
jens@26
    63
jens@26
    64
jens@28
    65
- (NSString*) description
jens@26
    66
{
jens@28
    67
    return $sprintf(@"%@[%@]", self.class,_serviceType);
jens@26
    68
}
jens@26
    69
jens@28
    70
jens@28
    71
- (DNSServiceRef) createServiceRef {
jens@28
    72
    DNSServiceRef serviceRef = NULL;
jens@28
    73
    self.error = DNSServiceBrowse(&serviceRef, 0, 0,
jens@28
    74
                                  _serviceType.UTF8String, NULL,
jens@28
    75
                                  &browseCallback, self);
jens@28
    76
    return serviceRef;
jens@26
    77
}
jens@26
    78
jens@26
    79
jens@28
    80
- (void) priv_gotError: (DNSServiceErrorType)errorCode {
jens@28
    81
    LogTo(Bonjour,@"%@ got error %i", self,errorCode);
jens@28
    82
    self.error = errorCode;
jens@26
    83
}
jens@26
    84
jens@28
    85
- (void) priv_gotServiceName: (NSString*)serviceName
jens@28
    86
                        type: (NSString*)regtype
jens@28
    87
                      domain: (NSString*)domain
jens@28
    88
                   interface: (uint32_t)interfaceIndex
jens@28
    89
                       flags: (DNSServiceFlags)flags
jens@26
    90
{
jens@28
    91
    // Create (or reuse existing) MYBonjourService object:
jens@28
    92
    MYBonjourService *service = [[_serviceClass alloc] initWithName: serviceName
jens@28
    93
                                                               type: regtype
jens@28
    94
                                                             domain: domain
jens@28
    95
                                                          interface: interfaceIndex];
jens@28
    96
    MYBonjourService *existingService = [_services member: service];
jens@28
    97
    if( existingService ) {
jens@28
    98
        [service release];
jens@28
    99
        service = [existingService retain];
jens@28
   100
    }
jens@28
   101
    
jens@28
   102
    // Add it to the add/remove sets:
jens@28
   103
    NSMutableSet *addTo, *removeFrom;
jens@28
   104
    if (flags & kDNSServiceFlagsAdd) {
jens@28
   105
        addTo = _addServices;
jens@28
   106
        removeFrom = _rmvServices;
jens@28
   107
    } else {
jens@28
   108
        addTo = _rmvServices;
jens@28
   109
        removeFrom = _addServices;
jens@28
   110
    }
jens@28
   111
    if( [removeFrom containsObject: service] )
jens@28
   112
        [removeFrom removeObject: service];
jens@28
   113
    else
jens@28
   114
        [addTo addObject: service];
jens@28
   115
    [service release];
jens@28
   116
    
jens@28
   117
    // After a round of updates is done, do the update:
jens@28
   118
    if( ! (flags & kDNSServiceFlagsMoreComing) )
jens@28
   119
        [self _updateServiceList];
jens@26
   120
}
jens@26
   121
jens@26
   122
jens@26
   123
- (void) _updateServiceList
jens@26
   124
{
jens@26
   125
    if( _rmvServices.count ) {
jens@26
   126
        [self willChangeValueForKey: @"services" 
jens@26
   127
                    withSetMutation: NSKeyValueMinusSetMutation
jens@26
   128
                       usingObjects: _rmvServices];
jens@26
   129
        [_services minusSet: _rmvServices];
jens@26
   130
        [self didChangeValueForKey: @"services" 
jens@26
   131
                   withSetMutation: NSKeyValueMinusSetMutation
jens@26
   132
                      usingObjects: _rmvServices];
jens@26
   133
        [_rmvServices makeObjectsPerformSelector: @selector(removed)];
jens@26
   134
        [_rmvServices removeAllObjects];
jens@26
   135
    }
jens@26
   136
    if( _addServices.count ) {
jens@26
   137
        [_addServices makeObjectsPerformSelector: @selector(added)];
jens@26
   138
        [self willChangeValueForKey: @"services" 
jens@26
   139
                    withSetMutation: NSKeyValueUnionSetMutation
jens@26
   140
                       usingObjects: _addServices];
jens@26
   141
        [_services unionSet: _addServices];
jens@26
   142
        [self didChangeValueForKey: @"services" 
jens@26
   143
                   withSetMutation: NSKeyValueUnionSetMutation
jens@26
   144
                      usingObjects: _addServices];
jens@26
   145
        [_addServices removeAllObjects];
jens@26
   146
    }
jens@26
   147
}
jens@26
   148
jens@26
   149
jens@28
   150
static void browseCallback (DNSServiceRef                       sdRef,
jens@28
   151
                            DNSServiceFlags                     flags,
jens@28
   152
                            uint32_t                            interfaceIndex,
jens@28
   153
                            DNSServiceErrorType                 errorCode,
jens@28
   154
                            const char                          *serviceName,
jens@28
   155
                            const char                          *regtype,
jens@28
   156
                            const char                          *replyDomain,
jens@28
   157
                            void                                *context)
jens@26
   158
{
jens@28
   159
    @try{
jens@28
   160
        //LogTo(Bonjour,@"browseCallback (error=%i, name='%s')", errorCode,serviceName);
jens@28
   161
        if (errorCode)
jens@28
   162
            [(MYBonjourBrowser*)context priv_gotError: errorCode];
jens@28
   163
        else
jens@28
   164
            [(MYBonjourBrowser*)context priv_gotServiceName: [NSString stringWithUTF8String: serviceName]
jens@28
   165
                                                       type: [NSString stringWithUTF8String: regtype]
jens@28
   166
                                                     domain: [NSString stringWithUTF8String: replyDomain]
jens@28
   167
                                                  interface: interfaceIndex
jens@28
   168
                                                      flags: flags];
jens@28
   169
    }catchAndReport(@"Bonjour");
jens@26
   170
}
jens@26
   171
jens@26
   172
jens@26
   173
@end
jens@26
   174
jens@26
   175
jens@26
   176
jens@26
   177
#pragma mark -
jens@26
   178
#pragma mark TESTING:
jens@26
   179
jens@28
   180
#if DEBUG
jens@28
   181
jens@28
   182
#import "MYBonjourQuery.h"
jens@28
   183
#import "MYAddressLookup.h"
jens@28
   184
jens@26
   185
@interface BonjourTester : NSObject
jens@26
   186
{
jens@26
   187
    MYBonjourBrowser *_browser;
jens@26
   188
}
jens@26
   189
@end
jens@26
   190
jens@26
   191
@implementation BonjourTester
jens@26
   192
jens@26
   193
- (id) init
jens@26
   194
{
jens@26
   195
    self = [super init];
jens@26
   196
    if (self != nil) {
jens@28
   197
        _browser = [[MYBonjourBrowser alloc] initWithServiceType: @"_presence._tcp"];
jens@26
   198
        [_browser addObserver: self forKeyPath: @"services" options: NSKeyValueObservingOptionNew context: NULL];
jens@26
   199
        [_browser addObserver: self forKeyPath: @"browsing" options: NSKeyValueObservingOptionNew context: NULL];
jens@26
   200
        [_browser start];
jens@26
   201
    }
jens@26
   202
    return self;
jens@26
   203
}
jens@26
   204
jens@26
   205
- (void) dealloc
jens@26
   206
{
jens@26
   207
    [_browser stop];
jens@26
   208
    [_browser removeObserver: self forKeyPath: @"services"];
jens@26
   209
    [_browser removeObserver: self forKeyPath: @"browsing"];
jens@26
   210
    [_browser release];
jens@26
   211
    [super dealloc];
jens@26
   212
}
jens@26
   213
jens@26
   214
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
jens@26
   215
{
jens@26
   216
    LogTo(Bonjour,@"Observed change in %@: %@",keyPath,change);
jens@26
   217
    if( $equal(keyPath,@"services") ) {
jens@26
   218
        if( [[change objectForKey: NSKeyValueChangeKindKey] intValue]==NSKeyValueChangeInsertion ) {
jens@26
   219
            NSSet *newServices = [change objectForKey: NSKeyValueChangeNewKey];
jens@26
   220
            for( MYBonjourService *service in newServices ) {
jens@28
   221
                NSString *hostname = service.hostname;  // block till it's resolved
jens@28
   222
                Log(@"##### %@ : at %@:%hu, TXT=%@", 
jens@28
   223
                      service, hostname, service.port, service.txtRecord);
jens@28
   224
                service.addressLookup.continuous = YES;
jens@28
   225
                [service queryForRecord: kDNSServiceType_NULL];
jens@26
   226
            }
jens@26
   227
        }
jens@26
   228
    }
jens@26
   229
}
jens@26
   230
jens@26
   231
@end
jens@26
   232
jens@26
   233
TestCase(Bonjour) {
jens@28
   234
    EnableLogTo(Bonjour,YES);
jens@28
   235
    EnableLogTo(DNS,YES);
jens@26
   236
    [NSRunLoop currentRunLoop]; // create runloop
jens@26
   237
    BonjourTester *tester = [[BonjourTester alloc] init];
jens@28
   238
    [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 1500]];
jens@26
   239
    [tester release];
jens@26
   240
}
jens@26
   241
jens@28
   242
#endif
jens@26
   243
jens@26
   244
jens@26
   245
/*
jens@26
   246
 Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
jens@26
   247
 
jens@26
   248
 Redistribution and use in source and binary forms, with or without modification, are permitted
jens@26
   249
 provided that the following conditions are met:
jens@26
   250
 
jens@26
   251
 * Redistributions of source code must retain the above copyright notice, this list of conditions
jens@26
   252
 and the following disclaimer.
jens@26
   253
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
jens@26
   254
 and the following disclaimer in the documentation and/or other materials provided with the
jens@26
   255
 distribution.
jens@26
   256
 
jens@26
   257
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
jens@26
   258
 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
jens@26
   259
 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
jens@26
   260
 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
jens@26
   261
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
jens@26
   262
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
jens@26
   263
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
jens@26
   264
 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
jens@26
   265
 */