Bonjour/MYBonjourBrowser.m
author Jens Alfke <jens@mooseyard.com>
Fri Apr 24 10:10:32 2009 -0700 (2009-04-24)
changeset 27 92581f26073e
child 28 732576fa8a0d
permissions -rw-r--r--
* Refactored MYPortMapper to use a new abstract base class MYDNSService; that way I can re-use it later for implementing Bonjour.
* Fixed issue #1: a memory leak in BLIPProperties, reported by codechemist.
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@26
    11
#import "Test.h"
jens@26
    12
#import "Logging.h"
jens@26
    13
jens@26
    14
jens@26
    15
@interface MYBonjourBrowser ()
jens@26
    16
@property BOOL browsing;
jens@26
    17
@property (retain) NSError* error;
jens@26
    18
@end
jens@26
    19
jens@26
    20
jens@26
    21
@implementation MYBonjourBrowser
jens@26
    22
jens@26
    23
jens@26
    24
- (id) initWithServiceType: (NSString*)serviceType
jens@26
    25
{
jens@26
    26
    Assert(serviceType);
jens@26
    27
    self = [super init];
jens@26
    28
    if (self != nil) {
jens@26
    29
        _serviceType = [serviceType copy];
jens@26
    30
        _browser = [[NSNetServiceBrowser alloc] init];
jens@26
    31
        _browser.delegate = self;
jens@26
    32
        _services = [[NSMutableSet alloc] init];
jens@26
    33
        _addServices = [[NSMutableSet alloc] init];
jens@26
    34
        _rmvServices = [[NSMutableSet alloc] init];
jens@26
    35
        _serviceClass = [MYBonjourService class];
jens@26
    36
    }
jens@26
    37
    return self;
jens@26
    38
}
jens@26
    39
jens@26
    40
jens@26
    41
- (void) dealloc
jens@26
    42
{
jens@26
    43
    LogTo(Bonjour,@"DEALLOC BonjourBrowser");
jens@26
    44
    [_browser stop];
jens@26
    45
    _browser.delegate = nil;
jens@26
    46
    [_browser release];
jens@26
    47
    [_serviceType release];
jens@26
    48
    [_error release];
jens@26
    49
    [_services release];
jens@26
    50
    [_addServices release];
jens@26
    51
    [_rmvServices release];
jens@26
    52
    [super dealloc];
jens@26
    53
}
jens@26
    54
jens@26
    55
jens@26
    56
@synthesize browsing=_browsing, error=_error, services=_services, serviceClass=_serviceClass;
jens@26
    57
jens@26
    58
jens@26
    59
- (void) start
jens@26
    60
{
jens@26
    61
    [_browser searchForServicesOfType: _serviceType inDomain: @"local."];
jens@26
    62
}
jens@26
    63
jens@26
    64
- (void) stop
jens@26
    65
{
jens@26
    66
    [_browser stop];
jens@26
    67
}
jens@26
    68
jens@26
    69
jens@26
    70
- (void)netServiceBrowserWillSearch:(NSNetServiceBrowser *)netServiceBrowser
jens@26
    71
{
jens@26
    72
    LogTo(Bonjour,@"%@ started browsing",self);
jens@26
    73
    self.browsing = YES;
jens@26
    74
}
jens@26
    75
jens@26
    76
- (void)netServiceBrowserDidStopSearch:(NSNetServiceBrowser *)netServiceBrowser
jens@26
    77
{
jens@26
    78
    LogTo(Bonjour,@"%@ stopped browsing",self);
jens@26
    79
    self.browsing = NO;
jens@26
    80
}
jens@26
    81
jens@26
    82
- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser 
jens@26
    83
             didNotSearch:(NSDictionary *)errorDict
jens@26
    84
{
jens@26
    85
    NSString *domain = [errorDict objectForKey: NSNetServicesErrorDomain];
jens@26
    86
    int err = [[errorDict objectForKey: NSNetServicesErrorCode] intValue];
jens@26
    87
    self.error = [NSError errorWithDomain: domain code: err userInfo: nil];
jens@26
    88
    LogTo(Bonjour,@"%@ got error: ",self,self.error);
jens@26
    89
    self.browsing = NO;
jens@26
    90
}
jens@26
    91
jens@26
    92
jens@26
    93
- (void) _updateServiceList
jens@26
    94
{
jens@26
    95
    if( _rmvServices.count ) {
jens@26
    96
        [self willChangeValueForKey: @"services" 
jens@26
    97
                    withSetMutation: NSKeyValueMinusSetMutation
jens@26
    98
                       usingObjects: _rmvServices];
jens@26
    99
        [_services minusSet: _rmvServices];
jens@26
   100
        [self didChangeValueForKey: @"services" 
jens@26
   101
                   withSetMutation: NSKeyValueMinusSetMutation
jens@26
   102
                      usingObjects: _rmvServices];
jens@26
   103
        [_rmvServices makeObjectsPerformSelector: @selector(removed)];
jens@26
   104
        [_rmvServices removeAllObjects];
jens@26
   105
    }
jens@26
   106
    if( _addServices.count ) {
jens@26
   107
        [_addServices makeObjectsPerformSelector: @selector(added)];
jens@26
   108
        [self willChangeValueForKey: @"services" 
jens@26
   109
                    withSetMutation: NSKeyValueUnionSetMutation
jens@26
   110
                       usingObjects: _addServices];
jens@26
   111
        [_services unionSet: _addServices];
jens@26
   112
        [self didChangeValueForKey: @"services" 
jens@26
   113
                   withSetMutation: NSKeyValueUnionSetMutation
jens@26
   114
                      usingObjects: _addServices];
jens@26
   115
        [_addServices removeAllObjects];
jens@26
   116
    }
jens@26
   117
}
jens@26
   118
jens@26
   119
jens@26
   120
- (void) _handleService: (NSNetService*)netService 
jens@26
   121
                  addTo: (NSMutableSet*)addTo
jens@26
   122
             removeFrom: (NSMutableSet*)removeFrom
jens@26
   123
             moreComing: (BOOL)moreComing
jens@26
   124
{
jens@26
   125
    // Wrap the NSNetService in a BonjourService, using an existing instance if possible:
jens@26
   126
    MYBonjourService *service = [[_serviceClass alloc] initWithNetService: netService];
jens@26
   127
    MYBonjourService *existingService = [_services member: service];
jens@26
   128
    if( existingService ) {
jens@26
   129
        [service release];
jens@26
   130
        service = [existingService retain];
jens@26
   131
    }
jens@26
   132
    
jens@26
   133
    if( [removeFrom containsObject: service] )
jens@26
   134
        [removeFrom removeObject: service];
jens@26
   135
    else
jens@26
   136
        [addTo addObject: service];
jens@26
   137
    [service release];
jens@26
   138
    if( ! moreComing )
jens@26
   139
        [self _updateServiceList];
jens@26
   140
}
jens@26
   141
jens@26
   142
- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser 
jens@26
   143
           didFindService:(NSNetService *)netService
jens@26
   144
               moreComing:(BOOL)moreComing 
jens@26
   145
{
jens@26
   146
    //LogTo(Bonjour,@"Add service %@",netService);
jens@26
   147
    [self _handleService: netService addTo: _addServices removeFrom: _rmvServices moreComing: moreComing];
jens@26
   148
}
jens@26
   149
jens@26
   150
- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser 
jens@26
   151
         didRemoveService:(NSNetService *)netService 
jens@26
   152
               moreComing:(BOOL)moreComing 
jens@26
   153
{
jens@26
   154
    //LogTo(Bonjour,@"Remove service %@",netService);
jens@26
   155
    [self _handleService: netService addTo: _rmvServices removeFrom: _addServices moreComing: moreComing];
jens@26
   156
}
jens@26
   157
jens@26
   158
jens@26
   159
@end
jens@26
   160
jens@26
   161
jens@26
   162
jens@26
   163
#pragma mark -
jens@26
   164
#pragma mark TESTING:
jens@26
   165
jens@26
   166
@interface BonjourTester : NSObject
jens@26
   167
{
jens@26
   168
    MYBonjourBrowser *_browser;
jens@26
   169
}
jens@26
   170
@end
jens@26
   171
jens@26
   172
@implementation BonjourTester
jens@26
   173
jens@26
   174
- (id) init
jens@26
   175
{
jens@26
   176
    self = [super init];
jens@26
   177
    if (self != nil) {
jens@26
   178
        _browser = [[MYBonjourBrowser alloc] initWithServiceType: @"_http._tcp"];
jens@26
   179
        [_browser addObserver: self forKeyPath: @"services" options: NSKeyValueObservingOptionNew context: NULL];
jens@26
   180
        [_browser addObserver: self forKeyPath: @"browsing" options: NSKeyValueObservingOptionNew context: NULL];
jens@26
   181
        [_browser start];
jens@26
   182
    }
jens@26
   183
    return self;
jens@26
   184
}
jens@26
   185
jens@26
   186
- (void) dealloc
jens@26
   187
{
jens@26
   188
    [_browser stop];
jens@26
   189
    [_browser removeObserver: self forKeyPath: @"services"];
jens@26
   190
    [_browser removeObserver: self forKeyPath: @"browsing"];
jens@26
   191
    [_browser release];
jens@26
   192
    [super dealloc];
jens@26
   193
}
jens@26
   194
jens@26
   195
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
jens@26
   196
{
jens@26
   197
    LogTo(Bonjour,@"Observed change in %@: %@",keyPath,change);
jens@26
   198
    if( $equal(keyPath,@"services") ) {
jens@26
   199
        if( [[change objectForKey: NSKeyValueChangeKindKey] intValue]==NSKeyValueChangeInsertion ) {
jens@26
   200
            NSSet *newServices = [change objectForKey: NSKeyValueChangeNewKey];
jens@26
   201
            for( MYBonjourService *service in newServices ) {
jens@26
   202
                LogTo(Bonjour,@"    --> %@ : TXT=%@", service,service.txtRecord);
jens@26
   203
            }
jens@26
   204
        }
jens@26
   205
    }
jens@26
   206
}
jens@26
   207
jens@26
   208
@end
jens@26
   209
jens@26
   210
TestCase(Bonjour) {
jens@26
   211
    [NSRunLoop currentRunLoop]; // create runloop
jens@26
   212
    BonjourTester *tester = [[BonjourTester alloc] init];
jens@26
   213
    [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 15]];
jens@26
   214
    [tester release];
jens@26
   215
}
jens@26
   216
jens@26
   217
jens@26
   218
jens@26
   219
/*
jens@26
   220
 Copyright (c) 2008-2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
jens@26
   221
 
jens@26
   222
 Redistribution and use in source and binary forms, with or without modification, are permitted
jens@26
   223
 provided that the following conditions are met:
jens@26
   224
 
jens@26
   225
 * Redistributions of source code must retain the above copyright notice, this list of conditions
jens@26
   226
 and the following disclaimer.
jens@26
   227
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
jens@26
   228
 and the following disclaimer in the documentation and/or other materials provided with the
jens@26
   229
 distribution.
jens@26
   230
 
jens@26
   231
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
jens@26
   232
 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
jens@26
   233
 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
jens@26
   234
 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
jens@26
   235
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
jens@26
   236
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
jens@26
   237
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
jens@26
   238
 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
jens@26
   239
 */