Changeset 3190

Show
Ignore:
Timestamp:
04/02/06 05:32:29 (2 years ago)
Author:
timothy
Message:

More work on the new buddy list.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/Chat Core.exp

    r3175 r3190  
    22.objc_class_name_MVChatRoom 
    33.objc_class_name_MVChatUser 
     4.objc_class_name_MVChatUserWatchRule 
    45.objc_class_name_MVFileTransfer 
    56.objc_class_name_MVDownloadFileTransfer 
     
    6768_MVChatUserInformationUpdatedNotification 
    6869_MVChatUserAttributeUpdatedNotification 
     70_MVChatUserWatchRuleMatchedNotification 
    6971_MVDownloadFileTransferOfferNotification 
    7072_MVFileTransferStartedNotification 
  • trunk/Chat Core.xcodeproj/project.pbxproj

    r3179 r3190  
    3131                1C7C776907DBBA4500FB5F83 /* NSScriptCommandAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = 1C7C776707DBBA4500FB5F83 /* NSScriptCommandAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; 
    3232                1C7C776A07DBBA4500FB5F83 /* NSScriptCommandAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C7C776807DBBA4500FB5F83 /* NSScriptCommandAdditions.m */; }; 
    33                 1C8AAEFE09D69F2700CF29EB /* MVChatUserWatchRule.h in Headers */ = {isa = PBXBuildFile; fileRef = 1C8AAEFC09D69F2700CF29EB /* MVChatUserWatchRule.h */; }; 
     33                1C8AAEFE09D69F2700CF29EB /* MVChatUserWatchRule.h in Headers */ = {isa = PBXBuildFile; fileRef = 1C8AAEFC09D69F2700CF29EB /* MVChatUserWatchRule.h */; settings = {ATTRIBUTES = (Public, ); }; }; 
    3434                1C8AAEFF09D69F2700CF29EB /* MVChatUserWatchRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C8AAEFD09D69F2700CF29EB /* MVChatUserWatchRule.m */; }; 
    3535                1C8AAF2B09D6A0A100CF29EB /* AGRegex.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C8AAF2609D6A0A100CF29EB /* AGRegex.framework */; }; 
  • trunk/Chat Core/MVChatConnection.h

    r3186 r3190  
    252252#pragma mark - 
    253253 
    254 - (void) startWatchingUser:(MVChatUser *) user; 
    255 - (void) stopWatchingUser:(MVChatUser *) user; 
    256  
    257 #pragma mark - 
    258  
    259254- (void) addChatUserWatchRule:(MVChatUserWatchRule *) rule; 
    260255- (void) removeChatUserWatchRule:(MVChatUserWatchRule *) rule; 
  • trunk/Chat Core/MVChatConnection.m

    r3186 r3190  
    666666#pragma mark - 
    667667 
    668 - (void) startWatchingUser:(MVChatUser *) user { 
    669 // subclass this method, if needed 
    670 } 
    671  
    672 - (void) stopWatchingUser:(MVChatUser *) user { 
    673 // subclass this method, if needed 
    674 } 
    675  
    676668- (void) addChatUserWatchRule:(MVChatUserWatchRule *) rule { 
     669        NSParameterAssert( rule != nil ); 
    677670        if( ! _chatUserWatchRules ) 
    678671                _chatUserWatchRules = [[NSMutableSet allocWithZone:nil] initWithCapacity:10]; 
    679672        @synchronized( _chatUserWatchRules ) { 
    680                 [_chatUserWatchRules addObject:rule]; 
     673                if( ! [_chatUserWatchRules containsObject:rule] ) 
     674                        [_chatUserWatchRules addObject:rule]; 
    681675        } 
    682676} 
    683677 
    684678- (void) removeChatUserWatchRule:(MVChatUserWatchRule *) rule { 
     679        NSParameterAssert( rule != nil ); 
    685680        @synchronized( _chatUserWatchRules ) { 
    686681                [_chatUserWatchRules removeObject:rule]; 
  • trunk/Chat Core/MVChatUser.h

    r3186 r3190  
    125125- (unsigned long) modes; 
    126126 
    127 - (void) startWatching; 
    128 - (void) stopWatching; 
    129  
    130127- (void) refreshInformation; 
    131128 
  • trunk/Chat Core/MVChatUser.m

    r3186 r3190  
    363363#pragma mark - 
    364364 
    365 - (void) startWatching { 
    366         [[self connection] startWatchingUser:self]; 
    367 } 
    368  
    369 - (void) stopWatching { 
    370         [[self connection] stopWatchingUser:self]; 
    371 } 
    372  
    373 #pragma mark - 
    374  
    375365- (void) refreshInformation { 
    376366// subclass this method, if needed 
  • trunk/Chat Core/MVChatUserWatchRule.h

    r3187 r3190  
    1515        AGRegex *_addressRegex; 
    1616        NSData *_publicKey; 
     17        NSArray *_applicableServerDomains; 
    1718        BOOL _interim; 
    1819} 
     
    4748- (BOOL) isInterim; 
    4849- (void) setInterim:(BOOL) interim; 
     50 
     51- (NSArray *) applicableServerDomains; 
     52- (void) setApplicableServerDomains:(NSArray *) serverDomains; 
    4953@end 
  • trunk/Chat Core/MVChatUserWatchRule.m

    r3187 r3190  
    1717                [self setPublicKey:[dictionary objectForKey:@"publicKey"]]; 
    1818                [self setInterim:[[dictionary objectForKey:@"interim"] boolValue]]; 
     19                [self setApplicableServerDomains:[dictionary objectForKey:@"applicableServerDomains"]]; 
    1920        } 
    2021 
     
    3031        [self setPublicKey:[self publicKey]]; 
    3132        [self setInterim:[self isInterim]]; 
     33        [self setApplicableServerDomains:[self applicableServerDomains]]; 
    3234        return copy; 
    3335} 
     
    4143        if( _publicKey ) [dictionary setObject:_nickname forKey:@"publicKey"]; 
    4244        if( _interim ) [dictionary setObject:[NSNumber numberWithBool:_interim] forKey:@"interim"]; 
     45        if( _applicableServerDomains ) [dictionary setObject:_applicableServerDomains forKey:@"applicableServerDomains"]; 
    4346        return [dictionary autorelease]; 
    4447} 
     
    8184 
    8285        NSString *string = [user nickname]; 
    83         if( _nicknameRegex && string && ! [_nicknameRegex findInString:string] ) return NO; 
    84         if( ! _nicknameRegex && _nickname && string && [_nickname length] && ! [_nickname isEqualToString:string] ) return NO; 
     86        if( _nicknameRegex && ! string ) return NO; 
     87        if( _nicknameRegex && ! [_nicknameRegex findInString:string] ) return NO; 
     88        if( ! _nicknameRegex && _nickname && [_nickname length] && ! [_nickname isEqualToString:string] ) return NO; 
    8589 
    8690        string = [user username]; 
    87         if( _usernameRegex && string && ! [_usernameRegex findInString:string] ) return NO; 
    88         if( ! _usernameRegex && _username && string && [_username length] && ! [_username isEqualToString:string] ) return NO; 
     91        if( _usernameRegex && ! string ) return NO; 
     92        if( _usernameRegex && ! [_usernameRegex findInString:string] ) return NO; 
     93        if( ! _usernameRegex && _username && [_username length] && ! [_username isEqualToString:string] ) return NO; 
    8994 
    9095        string = [user address]; 
    91         if( _addressRegex && string && ! [_addressRegex findInString:string] ) return NO; 
    92         if( ! _addressRegex && _address && string && [_address length] && ! [_address isEqualToString:string] ) return NO; 
     96        if( _addressRegex && ! string ) return NO; 
     97        if( _addressRegex && ! [_addressRegex findInString:string] ) return NO; 
     98        if( ! _addressRegex && _address && [_address length] && ! [_address isEqualToString:string] ) return NO; 
    9399 
    94100        string = [user realName]; 
    95         if( _realNameRegex && string && ! [_realNameRegex findInString:string] ) return NO; 
    96         if( ! _realNameRegex && _realName && string && [_realName length] && ! [_realName isEqualToString:string] ) return NO; 
     101        if( _realNameRegex && ! string ) return NO; 
     102        if( _realNameRegex && ! [_realNameRegex findInString:string] ) return NO; 
     103        if( ! _realNameRegex && _realName && [_realName length] && ! [_realName isEqualToString:string] ) return NO; 
    97104 
    98105        NSData *data = [user publicKey]; 
    99         if( _publicKey && data && [_publicKey length] && ! [_publicKey isEqualToData:data] ) return NO; 
     106        if( _publicKey && [_publicKey length] && ! [_publicKey isEqualToData:data] ) return NO; 
    100107 
    101108        @synchronized( _matchedChatUsers ) { 
     
    213220        _interim = interim; 
    214221} 
     222 
     223- (NSArray *) applicableServerDomains { 
     224        return [[_applicableServerDomains retain] autorelease]; 
     225} 
     226 
     227- (void) setApplicableServerDomains:(NSArray *) serverDomains { 
     228        id old = _applicableServerDomains; 
     229        _applicableServerDomains = [serverDomains copyWithZone:nil]; 
     230        [old release]; 
     231} 
    215232@end 
  • trunk/Chat Core/MVIRCChatConnection.m

    r3189 r3190  
    424424#pragma mark - 
    425425 
    426 - (void) startWatchingUser:(MVChatUser *) user { 
    427         NSParameterAssert( user != nil ); 
    428         NSParameterAssert( [[user nickname] length] > 0 ); 
    429  
    430 } 
    431  
    432 - (void) stopWatchingUser:(MVChatUser *) user { 
    433         NSParameterAssert( user != nil ); 
    434         NSParameterAssert( [[user nickname] length] > 0 ); 
    435  
    436 } 
    437  
    438426- (void) addChatUserWatchRule:(MVChatUserWatchRule *) rule { 
     427        @synchronized( _chatUserWatchRules ) { 
     428                if( [_chatUserWatchRules containsObject:rule] ) return; 
     429        } 
     430 
    439431        [super addChatUserWatchRule:rule]; 
    440432 
    441         if( [self isConnected] && [rule nickname] && ! [rule nicknameIsRegularExpression] ) { 
    442                 if( _watchCommandSupported ) [self sendRawMessageWithFormat:@"WATCH +%@", [rule nickname]]; 
    443                 else [self sendRawMessageWithFormat:@"ISON %@", [rule nickname]]; 
     433        if( [rule nickname] && ! [rule nicknameIsRegularExpression] ) { 
     434                MVChatUser *user = [self chatUserWithUniqueIdentifier:[rule nickname]]; 
     435                [rule matchChatUser:user]; 
     436                if( [self isConnected] ) { 
     437                        if( _watchCommandSupported ) [self sendRawMessageWithFormat:@"WATCH +%@", [rule nickname]]; 
     438                        else [self sendRawMessageWithFormat:@"ISON %@", [rule nickname]]; 
     439                } 
    444440        } 
    445441} 
     
    628624 
    629625        _isonSentCount = 0; 
    630  
    631         @synchronized( _knownUsers ) { 
    632                 [_knownUsers removeAllObjects]; 
    633         } 
    634626 
    635627        [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector( _periodicCleanUp ) object:nil]; 
  • trunk/Colloquy.xcodeproj/project.pbxproj

    r3176 r3190  
    475475                1C0A4CF40799BEA70093B702 /* JVViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JVViewCell.h; path = Views/JVViewCell.h; sourceTree = "<group>"; }; 
    476476                1C0A4CF50799BEA70093B702 /* JVViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = JVViewCell.m; path = Views/JVViewCell.m; sourceTree = "<group>"; }; 
     477                1C0B2F0809DF8A8B009952E7 /* MVChatUserWatchRule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MVChatUserWatchRule.h; path = "Chat Core/MVChatUserWatchRule.h"; sourceTree = "<group>"; }; 
    477478                1C18CD100528DBBA000001C8 /* NSColorAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NSColorAdditions.h; path = Additions/NSColorAdditions.h; sourceTree = "<group>"; }; 
    478479                1C1ACE410533943900F71D4E /* JVInspectorController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JVInspectorController.h; path = Controllers/JVInspectorController.h; sourceTree = "<group>"; }; 
     
    16541655                                1CDDCF830716208300FE11C3 /* MVChatRoom.h */, 
    16551656                                1CDDCF840716208300FE11C3 /* MVChatUser.h */, 
     1657                                1C0B2F0809DF8A8B009952E7 /* MVChatUserWatchRule.h */, 
    16561658                                1C8CF8E1066530D60080A2F5 /* MVFileTransfer.h */, 
    16571659                                1C2EF7EB0427A40B00000102 /* MVChatPluginManager.h */, 
  • trunk/Controllers/MVBuddyListController.m

    r3072 r3190  
    233233                [self showNewPersonSheet:nil]; 
    234234        } else { 
    235               JVBuddy *buddy = [JVBuddy buddyWithPerson:person]; 
    236               [self _addBuddyToList:buddy]; 
     235//            JVBuddy *buddy = [JVBuddy buddyWithPerson:person]; 
     236//            [self _addBuddyToList:buddy]; 
    237237                [self _saveBuddyList]; 
    238238        } 
     
    338338 
    339339        if( person ) { 
    340               JVBuddy *buddy = [JVBuddy buddyWithPerson:person]; 
    341               [self _addBuddyToList:buddy]; 
     340//            JVBuddy *buddy = [JVBuddy buddyWithPerson:person]; 
     341//            [self _addBuddyToList:buddy]; 
    342342                [self _saveBuddyList]; 
    343343        } 
     
    974974 
    975975- (void) _saveBuddyList { 
    976         NSMutableArray *list = [NSMutableArray arrayWithCapacity:[_buddyList count]]; 
     976        NSMutableArray *list = [[NSMutableArray allocWithZone:nil] initWithCapacity:[_buddyList count]]; 
    977977        NSEnumerator *enumerator = [_buddyList objectEnumerator]; 
    978978        JVBuddy *buddy = nil; 
    979979 
    980980        while( ( buddy = [enumerator nextObject] ) ) 
    981                 [list addObject:[buddy uniqueIdentifier]]; 
    982  
    983         [[NSUserDefaults standardUserDefaults] setObject:list forKey:@"JVChatBuddies"]; 
    984         [[NSUserDefaults standardUserDefaults] synchronize]; 
     981                [list addObject:[buddy dictionaryRepresentation]]; 
     982 
     983        [list writeToFile:[@"~/Library/Application Support/Colloquy/Buddy List.plist" stringByExpandingTildeInPath] atomically:YES]; 
     984        [list release]; 
    985985} 
    986986 
    987987- (void) _loadBuddyList { 
    988         NSArray *list = [[NSUserDefaults standardUserDefaults] objectForKey:@"JVChatBuddies"]; 
     988        NSArray *list = [[NSArray allocWithZone:nil] initWithContentsOfFile:[@"~/Library/Application Support/Colloquy/Buddy List.plist" stringByExpandingTildeInPath]]; 
    989989        NSEnumerator *enumerator = [list objectEnumerator]; 
    990         NSString *identifier = nil; 
    991  
    992         while( ( identifier = [enumerator nextObject] ) ) { 
    993                 JVBuddy *buddy = [JVBuddy buddyWithUniqueIdentifier:identifier]; 
    994                 if( [[buddy users] count] ) [self _addBuddyToList:buddy]; 
    995         } 
     990        NSDictionary *buddyDictionary = nil; 
     991 
     992        while( ( buddyDictionary = [enumerator nextObject] ) ) { 
     993                JVBuddy *buddy = [[JVBuddy allocWithZone:[self zone]] initWithDictionaryRepresentation:buddyDictionary]; 
     994                if( buddy ) [self _addBuddyToList:buddy]; 
     995                [buddy release]; 
     996        } 
     997 
     998        [list release]; 
    996999} 
    9971000@end 
  • trunk/Controllers/MVConnectionsController.m

    r3147 r3190  
    559559 
    560560        while( ( info = [enumerator nextObject] ) ) { 
    561                 if( [[(MVChatConnection *)[info objectForKey:@"connection"] server] caseInsensitiveCompare:address] == NSOrderedSame ) { 
     561                if( [[(MVChatConnection *)[info objectForKey:@"connection"] server] compare:address options:( NSCaseInsensitiveSearch | NSLiteralSearch | NSBackwardsSearch | NSAnchoredSearch )] == NSOrderedSame ) { 
    562562                        ret = [info objectForKey:@"connection"]; 
    563563                        if( [ret isConnected] ) return ret; 
     
    574574 
    575575        while( ( info = [enumerator nextObject] ) ) 
    576                 if( [[(MVChatConnection *)[info objectForKey:@"connection"] server] caseInsensitiveCompare:address] == NSOrderedSame ) 
     576                if( [[(MVChatConnection *)[info objectForKey:@"connection"] server] compare:address options:( NSCaseInsensitiveSearch | NSLiteralSearch | NSBackwardsSearch | NSAnchoredSearch )] == NSOrderedSame ) 
    577577                        [ret addObject:[info objectForKey:@"connection"]]; 
    578578 
  • trunk/Models/JVBuddy.h

    r3072 r3190  
    1010 
    1111@class ABPerson; 
     12@class MVChatUserWatchRule; 
    1213 
    1314extern NSString * const JVBuddyAddressBookIRCNicknameProperty; 
     
    2223@interface JVBuddy : NSObject { 
    2324        ABPerson *_person; 
     25        NSMutableArray *_rules; 
    2426        NSMutableArray *_users; 
    2527        NSMutableSet *_onlineUsers; 
     
    2931+ (void) setPreferredName:(JVBuddyName) preferred; 
    3032 
    31 + (id) buddyWithPerson:(ABPerson *) person; 
    32 + (id) buddyWithUniqueIdentifier:(NSString *) identifier; 
    33  
    34 - (id) initWithPerson:(ABPerson *) person; 
     33- (id) initWithDictionaryRepresentation:(NSDictionary *) dictionary; 
     34- (NSDictionary *) dictionaryRepresentation; 
    3535 
    3636- (void) registerWithApplicableConnections; 
     
    5555- (NSSet *) onlineUsers; 
    5656 
    57 - (void) addUser:(MVChatUser *) user; 
    58 - (void) removeUser:(MVChatUser *) user; 
    59 - (void) replaceUser:(MVChatUser *) oldUser withUser:(MVChatUser *) newUser; 
     57- (void) addWatchRule:(MVChatUserWatchRule *) rule; 
     58- (void) removeWatchRule:(MVChatUserWatchRule *) rule; 
    6059 
    6160- (NSImage *) picture; 
  • trunk/Models/JVBuddy.m

    r3072 r3190  
    11#import "JVBuddy.h" 
     2 
     3#import <ChatCore/MVChatUserWatchRule.h> 
    24#import "MVConnectionsController.h" 
    35 
     
    2830} 
    2931 
    30 + (id) buddyWithPerson:(ABPerson *) person { 
    31         return [[[[self class] alloc] initWithPerson:person] autorelease]; 
    32 
    33  
    34 + (id) buddyWithUniqueIdentifier:(NSString *) identifier { 
    35         ABRecord *person = [[ABAddressBook sharedAddressBook] recordForUniqueId:identifier]; 
    36         if( [person isKindOfClass:[ABPerson class]] ) 
    37                 return [[[[self class] alloc] initWithPerson:(ABPerson *)person] autorelease]; 
    38         return nil; 
    39 
    40  
    41 #pragma mark - 
    42  
    43 - (id) initWithPerson:(ABPerson *) person { 
     32#pragma mark - 
     33 
     34- (id) initWithDictionaryRepresentation:(NSDictionary *) dictionary { 
    4435        if( ( self = [super init] ) ) { 
    45                 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _registerWithConnection: ) name:MVChatConnectionDidConnectNotification object:nil]; 
    46                 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _disconnected: ) name:MVChatConnectionDidDisconnectNotification object:nil]; 
    47                 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _buddyOnline: ) name:MVChatConnectionWatchedUserOnlineNotification object:nil]; 
    48 //              [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _buddyAwayStatusChange: ) name:MVChatConnectionBuddyIsAwayNotification object:nil]; 
    49 //              [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _buddyAwayStatusChange: ) name:MVChatConnectionBuddyIsUnawayNotification object:nil]; 
    50  
    51                 _person = [person retain]; 
     36                _rules = [[NSMutableArray allocWithZone:nil] initWithCapacity:10]; 
    5237                _users = [[NSMutableArray allocWithZone:nil] initWithCapacity:5]; 
    5338                _onlineUsers = [[NSMutableSet allocWithZone:nil] initWithCapacity:5]; 
    5439                _activeUser = nil; 
    5540 
    56                 ABMultiValue *value = [person valueForProperty:JVBuddyAddressBookIRCNicknameProperty]; 
    57                 unsigned int i = 0, count = [value count]; 
    58                 MVChatUser *user = nil; 
    59  
    60                 for( i = 0; i < count; i++ ) { 
    61                         user = [MVChatUser wildcardUserWithNicknameMask:[NSString stringWithFormat:@"%@@%@", [value valueAtIndex:i], [value labelAtIndex:i]] andHostMask:nil]; 
    62                         [_users addObject:user]; 
    63                         if( ! [self activeUser] ) [self setActiveUser:user]; 
     41                NSEnumerator *enumerator = [[dictionary objectForKey:@"rules"] objectEnumerator]; 
     42                NSDictionary *ruleDictionary = nil; 
     43                while( ( ruleDictionary = [enumerator nextObject] ) ) { 
     44                        MVChatUserWatchRule *rule = [[MVChatUserWatchRule allocWithZone:[self zone]] initWithDictionaryRepresentation:ruleDictionary]; 
     45                        if( rule ) [self addWatchRule:rule]; 
     46                        [rule release]; 
    6447                } 
     48 
     49                [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _registerWithConnection: ) name:MVChatConnectionDidConnectNotification object:nil]; 
     50                [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _disconnected: ) name:MVChatConnectionDidDisconnectNotification object:nil]; 
    6551 
    6652                [self registerWithApplicableConnections]; 
     
    7662 
    7763        [_person release]; 
     64        [_rules release]; 
    7865        [_users release]; 
    7966        [_onlineUsers release]; 
     
    8269        _person = nil; 
    8370        _users = nil; 
     71        _rules = nil; 
    8472        _onlineUsers = nil; 
    8573        _activeUser = nil; 
     
    9078#pragma mark - 
    9179 
     80- (NSDictionary *) dictionaryRepresentation { 
     81        NSMutableDictionary *dictionary = [[NSMutableDictionary allocWithZone:nil] initWithCapacity:5]; 
     82        [dictionary setObject:_rules forKey:@"rules"]; 
     83        return dictionary; 
     84} 
     85 
     86#pragma mark - 
     87 
    9288- (void) registerWithApplicableConnections { 
    93         NSEnumerator *enumerator = [_users objectEnumerator]; 
    94         NSEnumerator *connectionEnumerator = nil; 
    95         MVChatConnection *connection = nil; 
    96         MVChatUser *user = nil; 
    97  
    98         while( ( user = [enumerator nextObject] ) ) { 
    99                 connectionEnumerator = [[[MVConnectionsController defaultController] connectionsForServerAddress:[user serverAddress]] objectEnumerator]; 
    100                 while( ( connection = [connectionEnumerator nextObject] ) ) 
    101                         [connection startWatchingUser:user]; 
     89        NSEnumerator *enumerator = [_rules objectEnumerator]; 
     90        MVChatUserWatchRule *rule = nil; 
     91 
     92        while( ( rule = [enumerator nextObject] ) ) { 
     93                if( [[rule applicableServerDomains] count] ) { 
     94                        NSEnumerator *domainEnumerator = [[rule applicableServerDomains] objectEnumerator]; 
     95                        NSString *domain = nil; 
     96 
     97                        while( ( domain = [domainEnumerator nextObject] ) ) { 
     98                                NSEnumerator *connectionEnumerator = [[[MVConnectionsController defaultController] connectionsForServerAddress:domain] objectEnumerator]; 
     99                                MVChatConnection *connection = nil; 
     100 
     101                                while( ( connection = [connectionEnumerator nextObject] ) ) 
     102                                        [connection addChatUserWatchRule:rule]; 
     103                        } 
     104                } else { 
     105                        NSEnumerator *connectionEnumerator = [[[MVConnectionsController defaultController] connections] objectEnumerator]; 
     106                        MVChatConnection *connection = nil; 
     107 
     108                        while( ( connection = [connectionEnumerator nextObject] ) ) 
     109                                [connection addChatUserWatchRule:rule]; 
     110                } 
    102111        } 
    103112} 
    104113 
    105114- (void) unregisterWithApplicableConnections { 
    106         NSEnumerator *enumerator = [_users objectEnumerator]; 
    107         NSEnumerator *connectionEnumerator = nil; 
    108         MVChatConnection *connection = nil; 
    109         MVChatUser *user = nil; 
    110  
    111         while( ( user = [enumerator nextObject] ) ) { 
    112                 connectionEnumerator = [[[MVConnectionsController defaultController] connectionsForServerAddress:[user serverAddress]] objectEnumerator]; 
    113                 while( ( connection = [connectionEnumerator nextObject] ) ) 
    114                         [connection stopWatchingUser:user]; 
     115        NSEnumerator *enumerator = [_rules objectEnumerator]; 
     116        MVChatUserWatchRule *rule = nil; 
     117 
     118        while( ( rule = [enumerator nextObject] ) ) { 
     119                if( [[rule applicableServerDomains] count] ) { 
     120                        NSEnumerator *domainEnumerator = [[rule applicableServerDomains] objectEnumerator]; 
     121                        NSString *domain = nil; 
     122 
     123                        while( ( domain = [domainEnumerator nextObject] ) ) { 
     124                                NSEnumerator *connectionEnumerator = [[[MVConnectionsController defaultController] connectionsForServerAddress:domain] objectEnumerator]; 
     125                                MVChatConnection *connection = nil; 
     126 
     127                                while( ( connection = [connectionEnumerator nextObject] ) ) 
     128                                        [connection removeChatUserWatchRule:rule]; 
     129                        } 
     130                } else { 
     131                        NSEnumerator *connectionEnumerator = [[[MVConnectionsController defaultController] connections] objectEnumerator]; 
     132                        MVChatConnection *connection = nil; 
     133 
     134                        while( ( connection = [connectionEnumerator nextObject] ) ) 
     135                                [connection removeChatUserWatchRule:rule]; 
     136                } 
    115137        } 
    116138} 
     
    123145 
    124146- (void) setActiveUser:(MVChatUser *) user { 
    125         if( [user isWildcardUser] ) { 
    126                 NSEnumerator *enumerator = [_onlineUsers objectEnumerator]; 
    127                 MVChatUser *match = nil; 
    128  
    129                 while( ( match = [enumerator nextObject] ) ) { 
    130                         if( [match isEqualToChatUser:user] ) { 
    131                                 user = match; 
    132                                 break; 
    133                         } 
    134                 } 
    135         } 
    136  
    137147        [_activeUser autorelease]; 
    138148        _activeUser = [user retain]; 
     
    202212#pragma mark - 
    203213 
    204 - (void) addUser:(MVChatUser *) user { 
    205         if( [_users containsObject:user] ) return; 
    206  
    207         ABMutableMultiValue *value = [[[_person valueForProperty:JVBuddyAddressBookIRCNicknameProperty] mutableCopy] autorelease]; 
    208         [value addValue:[user nickname] withLabel:[user serverAddress]]; 
    209         [_person setValue:value forProperty:JVBuddyAddressBookIRCNicknameProperty]; 
    210  
    211         if( ! [_users count] || ! [self activeUser] ) 
    212                 [self setActiveUser:user]; 
    213  
    214         [_users addObject:user]; 
    215  
    216         [[ABAddressBook sharedAddressBook] save]; 
    217  
    218         [self registerWithApplicableConnections]; 
    219 
    220  
    221 - (void) removeUser:(MVChatUser *) user { 
    222         if( ! [_users containsObject:user] ) return; 
    223  
    224         ABMutableMultiValue *value = [[[_person valueForProperty:JVBuddyAddressBookIRCNicknameProperty] mutableCopy] autorelease]; 
    225         int i = 0, count = [value count]; 
    226  
    227         for( i = count - 1; i >= 0; i-- ) 
    228                 if( [[user nickname] caseInsensitiveCompare:[value valueAtIndex:i]] == NSOrderedSame && [[user serverAddress] caseInsensitiveCompare:[value labelAtIndex:i]] == NSOrderedSame ) 
    229                         [value removeValueAndLabelAtIndex:i]; 
    230  
    231         [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:user]; 
    232  
    233         [_users removeObject:user]; 
    234  
    235         NSArray *online = [_onlineUsers allObjects]; 
    236         unsigned int index = [online indexOfObject:user]; 
    237         if( index != NSNotFound ) [_onlineUsers removeObject:[online objectAtIndex:index]]; 
    238  
    239         [_person setValue:value forProperty:JVBuddyAddressBookIRCNicknameProperty]; 
    240  
    241         if( [[self activeUser] isEqualToChatUser:user] ) 
    242                 [self setActiveUser:( [_onlineUsers count] ? [_onlineUsers anyObject] : [_users lastObject] )]; 
    243  
    244         [[ABAddressBook sharedAddressBook] save]; 
    245 
    246  
    247 - (void) replaceUser:(MVChatUser *) oldUser withUser:(MVChatUser *) newUser { 
    248         [self removeUser:oldUser]; 
    249         [self addUser:newUser]; 
     214- (void) addWatchRule:(MVChatUserWatchRule *) rule { 
     215        if( [_rules containsObject:rule] ) return; 
     216        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _ruleMatched: ) name:MVChatUserWatchRuleMatchedNotification object:rule]; 
     217        [_rules addObject:rule]; 
     218
     219 
     220- (void) removeWatchRule:(MVChatUserWatchRule *) rule { 
     221        if( ! [_rules containsObject:rule] ) return; 
     222        [[NSNotificationCenter defaultCenter] removeObserver:self name:MVChatUserWatchRuleMatchedNotification object:rule]; 
     223        [_rules removeObject:rule]; 
    250224} 
    251225 
     
    345319 
    346320- (void) editInAddressBook { 
     321        if( ! _person ) return; 
    347322        NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"addressbook://%@?edit", [_person uniqueId]]]; 
    348323        [[NSWorkspace sharedWorkspace] openURL:url]; 
     
    350325 
    351326- (void) viewInAddressBook { 
     327        if( ! _person ) return; 
    352328        NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"addressbook://%@", [_person uniqueId]]]; 
    353329        [[NSWorkspace sharedWorkspace] openURL:url]; 
     
    442418- (void) _buddyOnline:(NSNotification *) notification { 
    443419        MVChatUser *user = [notification object]; 
    444         if( [_users containsObject:user] ) { 
    445                 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _buddyOffline: ) name:MVChatConnectionWatchedUserOfflineNotification object:user]; 
    446                 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _buddyIdleUpdate: ) name:MVChatUserIdleTimeUpdatedNotification object:user]; 
    447                 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _buddyStatusChanged: ) name:MVChatUserStatusChangedNotification object:user]; 
    448  
    449                 BOOL cameOnline = ( ! [_onlineUsers count] ? YES : NO ); 
    450                 [_onlineUsers addObject:user]; 
    451  
    452                 if( [self status] != MVChatUserAvailableStatus || [self status] != MVChatUserAwayStatus ) [self setActiveUser:user]; 
    453                 if( cameOnline ) [[NSNotificationCenter defaultCenter] postNotificationName:JVBuddyCameOnlineNotification object:self userInfo:nil]; 
    454                 [[NSNotificationCenter defaultCenter] postNotificationName:JVBuddyUserCameOnlineNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:user, @"user", nil]]; 
    455         } 
     420        BOOL cameOnline = ( ! [_onlineUsers count] ? YES : NO ); 
     421        [_onlineUsers addObject:user]; 
     422 
     423        if( [self status] != MVChatUserAvailableStatus || [self status] != MVChatUserAwayStatus ) [self setActiveUser:user]; 
     424        if( cameOnline ) [[NSNotificationCenter defaultCenter] postNotificationName:JVBuddyCameOnlineNotification object:self userInfo:nil]; 
     425        [[NSNotificationCenter defaultCenter] postNotificationName:JVBuddyUserCameOnlineNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:user, @"user", nil]]; 
    456426} 
    457427 
    458428- (void) _buddyOffline:(NSNotification *) notification { 
    459429        MVChatUser *user = [notification object]; 
    460         [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:user]; 
    461430 
    462431        [_onlineUsers removeObject:user]; 
     
    486455- (void) _registerWithConnection:(NSNotification *) notification { 
    487456        MVChatConnection *connection = [notification object]; 
    488         NSEnumerator *enumerator = [_users objectEnumerator]; 
     457        NSEnumerator *enumerator = [_rules objectEnumerator]; 
     458        MVChatUserWatchRule *rule = nil; 
     459 
     460        while( ( rule = [enumerator nextObject] ) ) { 
     461                if( [[rule applicableServerDomains] count] ) { 
     462                        NSEnumerator *domainEnumerator = [[rule applicableServerDomains] objectEnumerator]; 
     463                        NSString *domain = nil; 
     464 
     465                        while( ( domain = [domainEnumerator nextObject] ) ) { 
     466                                if( [[connection server] compare:domain options:( NSCaseInsensitiveSearch | NSLiteralSearch | NSBackwardsSearch | NSAnchoredSearch )] == NSOrderedSame ) 
     467                                        [connection addChatUserWatchRule:rule]; 
     468                        } 
     469                } else [connection addChatUserWatchRule:rule]; 
     470        } 
     471
     472 
     473- (void) _disconnected:(NSNotification *) notification { 
     474        MVChatConnection *connection = [notification object]; 
     475        NSEnumerator *enumerator = [[[_onlineUsers copy] autorelease] objectEnumerator]; 
    489476        MVChatUser *user = nil; 
    490477 
    491478        while( ( user = [enumerator nextObject] ) ) { 
    492                 if( [[user connection] isEqual:connection] || [[user serverAddress] caseInsensitiveCompare:[connection server]] == NSOrderedSame ) { 
    493                         [connection startWatchingUser:user]; 
    494                 } 
    495         } 
    496 
    497  
    498 - (void) _disconnected:(NSNotification *) notification { 
    499         NSEnumerator *enumerator = [[[MVConnectionsController defaultController] connections] objectEnumerator]; 
    500         MVChatConnection *connection = nil; 
    501         unsigned int count = 0; 
    502  
    503         while( ( connection = [enumerator nextObject] ) ) 
    504                 if( [[connection server] caseInsensitiveCompare:[connection server]] == NSOrderedSame && [connection isConnected] ) 
    505                         count++; 
    506  
    507         if( count >= 1 ) return; 
    508  
    509         connection = [notification object]; 
    510         enumerator = [[[_onlineUsers copy] autorelease] objectEnumerator]; 
    511         MVChatUser *user = nil; 
    512  
    513         while( ( user = [enumerator nextObject] ) ) { 
    514                 if( [[user connection] isEqual:connection] || [[user serverAddress] caseInsensitiveCompare:[connection server]] == NSOrderedSame ) { 
     479                if( [[user connection] isEqual:connection] ) { 
    515480                        [_onlineUsers removeObject:user]; 
    516481                        if( ! [_onlineUsers count] ) [[NSNotificationCenter defaultCenter] postNotificationName:JVBuddyWentOfflineNotification object:self userInfo:nil]; 
    517482                } 
    518483        } 
     484} 
     485 
     486- (void) _ruleMatched:(NSNotification *) notification { 
     487        MVChatUser *user = [[notification userInfo] objectForKey:@"user"]; 
     488 
     489        [_users addObject:user]; 
     490        if( ! [self activeUser] ) 
     491                [self setActiveUser:user]; 
     492 
     493        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _buddyOnline: ) name:MVChatConnectionWatchedUserOfflineNotification object:user]; 
     494        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _buddyOffline: ) name:MVChatConnectionWatchedUserOfflineNotification object:user]; 
     495        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _buddyIdleUpdate: ) name:MVChatUserIdleTimeUpdatedNotification object:user]; 
     496        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _buddyStatusChanged: ) name:MVChatUserStatusChangedNotification object:user]; 
    519497} 
    520498@end