root/tags/2D9/JVBuddy.m

Revision 2564, 18.2 kB (checked in by timothy, 3 years ago)

The buddy list has returned! Fully functional for IRC servers.

Line 
1 #import <AddressBook/AddressBook.h>
2 #import <ChatCore/MVChatConnection.h>
3 #import <ChatCore/NSStringAdditions.h>
4 #import <ChatCore/MVChatUser.h>
5
6 #import "JVBuddy.h"
7 #import "MVConnectionsController.h"
8
9 NSString *JVBuddyCameOnlineNotification = @"JVBuddyCameOnlineNotification";
10 NSString *JVBuddyWentOfflineNotification = @"JVBuddyWentOfflineNotification";
11
12 NSString *JVBuddyUserCameOnlineNotification = @"JVBuddyUserCameOnlineNotification";
13 NSString *JVBuddyUserWentOfflineNotification = @"JVBuddyUserWentOfflineNotification";
14 NSString *JVBuddyUserStatusChangedNotification = @"JVBuddyUserStatusChangedNotification";
15 NSString *JVBuddyUserIdleTimeUpdatedNotification = @"JVBuddyUserIdleTimeUpdatedNotification";
16
17 NSString *JVBuddyActiveUserChangedNotification = @"JVBuddyActiveUserChangedNotification";
18
19 static JVBuddyName _mainPreferredName = JVBuddyFullName;
20
21 @implementation JVBuddy
22 + (JVBuddyName) preferredName {
23         extern JVBuddyName _mainPreferredName;
24         return _mainPreferredName;
25 }
26
27 + (void) setPreferredName:(JVBuddyName) preferred {
28         extern JVBuddyName _mainPreferredName;
29         _mainPreferredName = preferred;
30 }
31
32 + (id) buddyWithPerson:(ABPerson *) person {
33         return [[[[self class] alloc] initWithPerson:person] autorelease];
34 }
35
36 + (id) buddyWithUniqueIdentifier:(NSString *) identifier {
37         ABRecord *person = [[ABAddressBook sharedAddressBook] recordForUniqueId:identifier];
38         if( [person isKindOfClass:[ABPerson class]] )
39                 return [[[[self class] alloc] initWithPerson:(ABPerson *)person] autorelease];
40         return nil;
41 }
42
43 #pragma mark -
44
45 - (id) initWithPerson:(ABPerson *) person {
46         if( ( self = [super init] ) ) {
47                 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _registerWithConnection: ) name:MVChatConnectionDidConnectNotification object:nil];
48                 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _disconnected: ) name:MVChatConnectionDidDisconnectNotification object:nil];
49                 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _buddyOnline: ) name:MVChatConnectionWatchedUserOnlineNotification object:nil];
50 //              [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _buddyAwayStatusChange: ) name:MVChatConnectionBuddyIsAwayNotification object:nil];
51 //              [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _buddyAwayStatusChange: ) name:MVChatConnectionBuddyIsUnawayNotification object:nil];
52
53                 _person = [person retain];
54                 _users = [[NSMutableArray array] retain];
55                 _onlineUsers = [[NSMutableArray array] retain];
56                 _activeUser = nil;
57
58                 ABMultiValue *value = [person valueForProperty:@"IRCNickname"];
59                 unsigned int i = 0, count = [value count];
60                 MVChatUser *user = nil;
61
62                 for( i = 0; i < count; i++ ) {
63                         user = [MVChatUser wildcardUserWithNicknameMask:[NSString stringWithFormat:@"%@@%@", [value valueAtIndex:i], [value labelAtIndex:i]] andHostMask:nil];
64                         [_users addObject:user];
65                         if( ! [self activeUser] ) [self setActiveUser:user];
66                 }
67
68                 [self registerWithApplicableConnections];
69         }
70         return self;
71 }
72
73 - (void) dealloc {
74         [self unregisterWithApplicableConnections];
75
76         [[NSNotificationCenter defaultCenter] removeObserver:self];
77
78         [_person release];
79         [_users release];
80         [_onlineUsers release];
81         [_activeUser release];
82
83         _person = nil;
84         _users = nil;
85         _onlineUsers = nil;
86         _activeUser = nil;
87
88         [super dealloc];
89 }
90
91 #pragma mark -
92
93 - (void) registerWithApplicableConnections {
94         NSEnumerator *enumerator = [_users objectEnumerator];
95         NSEnumerator *connectionEnumerator = nil;
96         MVChatConnection *connection = nil;
97         MVChatUser *user = nil;
98
99         while( ( user = [enumerator nextObject] ) ) {
100                 connectionEnumerator = [[[MVConnectionsController defaultManager] connectionsForServerAddress:[user serverAddress]] objectEnumerator];
101                 while( ( connection = [connectionEnumerator nextObject] ) )
102                         [connection startWatchingUser:user];
103         }
104 }
105
106 - (void) unregisterWithApplicableConnections {
107         NSEnumerator *enumerator = [_users objectEnumerator];
108         NSEnumerator *connectionEnumerator = nil;
109         MVChatConnection *connection = nil;
110         MVChatUser *user = nil;
111
112         while( ( user = [enumerator nextObject] ) ) {
113                 connectionEnumerator = [[[MVConnectionsController defaultManager] connectionsForServerAddress:[user serverAddress]] objectEnumerator];
114                 while( ( connection = [connectionEnumerator nextObject] ) )
115                         [connection stopWatchingUser:user];
116         }
117 }
118
119 #pragma mark -
120
121 - (MVChatUser *) activeUser {
122         return _activeUser;
123 }
124
125 - (void) setActiveUser:(MVChatUser *) user {
126         [_activeUser autorelease];
127         _activeUser = [user retain];
128 }
129
130 #pragma mark -
131
132 - (MVChatUserStatus) status {
133         return [[self activeUser] status];     
134 }
135
136 - (NSAttributedString *) awayStatusMessage {
137         return [[self activeUser] awayStatusMessage];
138 }
139
140 #pragma mark -
141
142 - (BOOL) isOnline {
143         return ( [_onlineUsers count] > 0 ? YES : NO );
144 }
145
146 - (NSDate *) dateConnected {
147         return [[self activeUser] dateConnected];       
148 }
149
150 - (NSDate *) dateDisconnected {
151         return [[self activeUser] dateDisconnected];   
152 }
153
154 #pragma mark -
155
156 - (NSTimeInterval) idleTime {
157         return [[self activeUser] idleTime];   
158 }
159
160 #pragma mark -
161
162 - (NSString *) displayName {
163         switch( [[self class] preferredName] ) {
164                 default:
165                 case JVBuddyFullName:
166                         return [self compositeName];
167                 case JVBuddyGivenNickname:
168                         if( [[self givenNickname] length] )
169                                 return [self givenNickname];
170                 case JVBuddyActiveNickname:
171                         return [self nickname];
172         }
173
174         return [self nickname];
175 }
176
177 - (NSString *) nickname {
178         return [[self activeUser] nickname];
179 }
180
181 #pragma mark -
182
183 - (NSArray *) users {
184         return [[_users retain] autorelease];
185 }
186
187 - (NSArray *) onlineUsers {
188         return [[_onlineUsers retain] autorelease];
189 }
190
191 #pragma mark -
192
193 - (void) addUser:(MVChatUser *) user {
194         if( [_users containsObject:user] ) return;
195
196         ABMutableMultiValue *value = [[[_person valueForProperty:@"IRCNickname"] mutableCopy] autorelease];
197         [value addValue:[user nickname] withLabel:[user serverAddress]];
198         [_person setValue:value forProperty:@"IRCNickname"];
199
200         if( ! [_users count] || ! [self activeUser] )
201                 [self setActiveUser:user];
202
203         [_users addObject:user];
204
205         [[ABAddressBook sharedAddressBook] save];
206
207         [self registerWithApplicableConnections];
208 }
209
210 - (void) removeUser:(MVChatUser *) user {
211         if( ! [_users containsObject:user] ) return;
212
213         ABMutableMultiValue *value = [[[_person valueForProperty:@"IRCNickname"] mutableCopy] autorelease];
214         int i = 0, count = [value count];
215
216         for( i = count - 1; i >= 0; i-- )
217                 if( [[user nickname] caseInsensitiveCompare:[value valueAtIndex:i]] == NSOrderedSame && [[user serverAddress] caseInsensitiveCompare:[value labelAtIndex:i]] == NSOrderedSame )
218                         [value removeValueAndLabelAtIndex:i];
219
220         [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:user];
221
222         [_users removeObject:user];
223         [_onlineUsers removeObject:user];
224         [_person setValue:value forProperty:@"IRCNickname"];
225
226         if( [[self activeUser] isEqual:user] )
227                 [self setActiveUser:( [_onlineUsers count] ? [_onlineUsers lastObject] : [_users lastObject] )];
228
229         [[ABAddressBook sharedAddressBook] save];
230 }
231
232 - (void) replaceUser:(MVChatUser *) oldUser withUser:(MVChatUser *) newUser {
233         [self removeUser:oldUser];
234         [self addUser:newUser];
235 }
236
237 #pragma mark -
238
239 - (NSImage *) picture {
240         return [[[NSImage alloc] initWithData:[_person imageData]] autorelease];
241 }
242
243 - (void) setPicture:(NSImage *) picture {
244         [_person setImageData:[picture TIFFRepresentation]];
245 }
246
247 #pragma mark -
248
249 - (NSString *) compositeName {
250         NSString *firstName = [self firstName];
251         NSString *lastName = [self lastName];
252
253         if( ! firstName && lastName ) return lastName;
254         else if( firstName && ! lastName ) return firstName;
255         else if( firstName && lastName ) {
256                 return [NSString stringWithFormat:@"%@ %@", firstName, lastName];
257         }
258
259         firstName = [self givenNickname];
260         if( [firstName length] ) return firstName;
261
262         return [[self activeUser] nickname];
263 }
264
265 - (NSString *) firstName {
266         return [_person valueForProperty:kABFirstNameProperty];
267 }
268
269 - (NSString *) lastName {
270         return [_person valueForProperty:kABLastNameProperty];
271 }
272
273 - (NSString *) primaryEmail {
274         ABMultiValue *value = [_person valueForProperty:kABEmailProperty];
275         return [value valueAtIndex:[value indexForIdentifier:[value primaryIdentifier]]];
276 }
277
278 - (NSString *) givenNickname {
279         return [_person valueForProperty:kABNicknameProperty];
280 }
281
282 #pragma mark -
283
284 - (void) setFirstName:(NSString *) name {
285         [_person setValue:name forProperty:kABFirstNameProperty];
286         [[ABAddressBook sharedAddressBook] save];
287 }
288
289 - (void) setLastName:(NSString *) name {
290         [_person setValue:name forProperty:kABLastNameProperty];
291         [[ABAddressBook sharedAddressBook] save];
292 }
293
294 - (void) setPrimaryEmail:(NSString *) email {
295         ABMutableMultiValue *value = [[[_person valueForProperty:kABEmailProperty] mutableCopy] autorelease];
296
297         if( ! value ) {
298                 value = [[[ABMutableMultiValue alloc] init] autorelease];
299                 [value addValue:email withLabel:kABOtherLabel];
300         } else [value replaceValueAtIndex:[value indexForIdentifier:[value primaryIdentifier]] withValue:email];
301
302         [_person setValue:value forProperty:kABEmailProperty];
303         [[ABAddressBook sharedAddressBook] save];
304 }
305
306 - (void) setGivenNickname:(NSString *) name {
307         [_person setValue:name forProperty:kABNicknameProperty];
308         [[ABAddressBook sharedAddressBook] save];
309 }
310
311 #pragma mark -
312
313 - (NSString *) uniqueIdentifier {
314         return [_person uniqueId];
315 }
316
317 - (ABPerson *) person {
318         return [[_person retain] autorelease];
319 }
320
321 - (void) editInAddressBook {
322         NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"addressbook://%@?edit", [_person uniqueId]]];
323         [[NSWorkspace sharedWorkspace] openURL:url];
324 }
325
326 - (void) viewInAddressBook {
327         NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"addressbook://%@", [_person uniqueId]]];
328         [[NSWorkspace sharedWorkspace] openURL:url];
329 }
330
331 #pragma mark -
332 #pragma mark Comparisons
333
334 - (NSComparisonResult) availabilityCompare:(JVBuddy *) buddy {
335         unsigned int b1 = 0, b2 = 0;
336
337         if( [self status] == MVChatUserAwayStatus ) b1 = 2;
338         else if( [self status] == MVChatUserAvailableStatus ) {
339                 if( [self idleTime] >= 600. ) b1 = 1;
340                 else b1 = 0;
341         } else b1 = 3;
342
343         if( [buddy status] == MVChatUserAwayStatus ) b2 = 2;
344         else if( [buddy status] == MVChatUserAvailableStatus ) {
345                 if( [buddy idleTime] >= 600. ) b2 = 1;
346                 else b2 = 0;
347         } else b2 = 3;
348
349         if( b1 > b2 ) return NSOrderedDescending;
350         else if( b1 < b2 ) return NSOrderedAscending;
351         return [self lastNameCompare:buddy];
352 }
353
354 - (NSComparisonResult) firstNameCompare:(JVBuddy *) buddy {
355         NSComparisonResult ret = NSOrderedSame;
356         NSString *name1 = [self firstName];
357         NSString *name2 = [buddy firstName];
358
359         if( ! [name1 length] ) name1 = [self lastName];
360         if( ! [name2 length] ) name2 = [buddy lastName];
361
362         if( ! [name1 length] && [name2 length] ) return NSOrderedAscending;
363         else if( ! [name2 length] && [name1 length]  ) return NSOrderedDescending;
364
365         ret = [name1 localizedCaseInsensitiveCompare:name2];
366         if( ret != NSOrderedSame ) return ret;
367
368         name1 = [self lastName];
369         name2 = [buddy lastName];
370
371         if( ! [name1 length] && [name2 length] ) return NSOrderedAscending;
372         else if( ! [name2 length] && [name1 length]  ) return NSOrderedDescending;
373
374         return [name1 localizedCaseInsensitiveCompare:name2];
375 }
376
377 - (NSComparisonResult) lastNameCompare:(JVBuddy *) buddy {
378         NSComparisonResult ret = NSOrderedSame;
379         NSString *name1 = [self lastName];
380         NSString *name2 = [buddy lastName];
381
382         if( ! [name1 length] ) name1 = [self firstName];
383         if( ! [name2 length] ) name2 = [buddy firstName];
384
385         if( ! [name1 length] && [name2 length] ) return NSOrderedAscending;
386         else if( ! [name2 length] && [name1 length]  ) return NSOrderedDescending;
387
388         ret = [name1 localizedCaseInsensitiveCompare:name2];
389         if( ret != NSOrderedSame ) return ret;
390
391         name1 = [self firstName];
392         name2 = [buddy firstName];
393
394         if( ! [name1 length] && [name2 length] ) return NSOrderedAscending;
395         else if( ! [name2 length] && [name1 length]  ) return NSOrderedDescending;
396
397         return [name1 localizedCaseInsensitiveCompare:name2];
398 }
399
400 - (NSComparisonResult) serverCompare:(JVBuddy *) buddy {
401         NSString *name1 = [[self activeUser] serverAddress];
402         NSString *name2 = [[buddy activeUser] serverAddress];
403         NSComparisonResult ret = [name1 caseInsensitiveCompare:name2];
404         return ( ret != NSOrderedSame ? ret : [self availabilityCompare:buddy] );
405 }
406
407 - (NSComparisonResult) nicknameCompare:(JVBuddy *) buddy {
408         NSString *name1 = [[self activeUser] nickname];
409         NSString *name2 = [[buddy activeUser] nickname];
410         return [name1 caseInsensitiveCompare:name2];
411 }
412 @end
413
414 #pragma mark -
415
416 @implementation JVBuddy (JVBuddyPrivate)
417 - (void) _buddyOnline:(NSNotification *) notification {
418         MVChatUser *user = [notification object];
419         if( [_users containsObject:user] ) {
420                 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _buddyOffline: ) name:MVChatConnectionWatchedUserOfflineNotification object:user];
421                 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _buddyIdleUpdate: ) name:MVChatUserIdleTimeUpdatedNotification object:user];
422                 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector( _buddyStatusChanged: ) name:MVChatUserStatusChangedNotification object:user];
423
424                 BOOL cameOnline = ( ! [_onlineUsers count] ? YES : NO );
425                 if( [[self activeUser] isEqual:user] ) [self setActiveUser:user]; // will remove the placeholder (wildcard user)
426                 [_users removeObject:user]; // will remove the placeholder (wildcard user)
427                 [_users addObject:user];
428                 [_onlineUsers addObject:user];
429
430                 if( [self status] != MVChatUserAvailableStatus || [self status] != MVChatUserAwayStatus ) [self setActiveUser:user];
431                 if( cameOnline ) [[NSNotificationCenter defaultCenter] postNotificationName:JVBuddyCameOnlineNotification object:self userInfo:nil];
432                 [[NSNotificationCenter defaultCenter] postNotificationName:JVBuddyUserCameOnlineNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:user, @"user", nil]];
433         }
434 }
435
436 - (void) _buddyOffline:(NSNotification *) notification {
437         MVChatUser *user = [notification object];
438         [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:user];
439
440         [_onlineUsers removeObject:user];
441
442         if( [_onlineUsers count] ) [self setActiveUser:[_onlineUsers lastObject]];
443         if( ! [_onlineUsers count] ) [[NSNotificationCenter defaultCenter] postNotificationName:JVBuddyWentOfflineNotification object:self userInfo:nil];
444
445         [[NSNotificationCenter defaultCenter] postNotificationName:JVBuddyUserWentOfflineNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:user, @"user", nil]];
446 }
447
448 - (void) _buddyIdleUpdate:(NSNotification *) notification {
449         MVChatUser *user = [notification object];
450         NSNotification *note = [NSNotification notificationWithName:JVBuddyUserIdleTimeUpdatedNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:user, @"user", nil]];
451         [[NSNotificationQueue defaultQueue] enqueueNotification:note postingStyle:NSPostASAP coalesceMask:( NSNotificationCoalescingOnName | NSNotificationCoalescingOnSender ) forModes:nil];
452 }
453
454 - (void) _buddyStatusChanged:(NSNotification *) notification {
455         MVChatUser *user = [notification object];
456         NSNotification *note = [NSNotification notificationWithName:JVBuddyUserStatusChangedNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:user, @"user", nil]];
457         [[NSNotificationQueue defaultQueue] enqueueNotification:note postingStyle:NSPostASAP coalesceMask:( NSNotificationCoalescingOnName | NSNotificationCoalescingOnSender ) forModes:nil];
458 }
459
460 - (void) _registerWithConnection:(NSNotification *) notification {
461         MVChatConnection *connection = [notification object];
462         NSEnumerator *enumerator = [_users objectEnumerator];
463         MVChatUser *user = nil;
464
465         while( ( user = [enumerator nextObject] ) ) {
466                 if( [[user connection] isEqual:connection] || [[user serverAddress] caseInsensitiveCompare:[connection server]] == NSOrderedSame ) {
467                         [connection startWatchingUser:user];
468                 }
469         }
470 }
471
472 - (void) _disconnected:(NSNotification *) notification {
473         NSEnumerator *enumerator = [[[MVConnectionsController defaultManager] connections] objectEnumerator];
474         MVChatConnection *connection = nil;
475         unsigned int count = 0;
476
477         while( ( connection = [enumerator nextObject] ) )
478                 if( [[connection server] caseInsensitiveCompare:[connection server]] == NSOrderedSame && [connection isConnected] )
479                         count++;
480
481         if( count >= 1 ) return;
482
483         connection = [notification object];
484         enumerator = [[[_onlineUsers copy] autorelease] objectEnumerator];
485         MVChatUser *user = nil;
486
487         while( ( user = [enumerator nextObject] ) ) {
488                 if( [[user connection] isEqual:connection] || [[user serverAddress] caseInsensitiveCompare:[connection server]] == NSOrderedSame ) {
489                         [_onlineUsers removeObject:user];
490 //                      [[NSNotificationCenter defaultCenter] postNotificationName:JVBuddyNicknameWentOfflineNotification object:self userInfo:[NSDictionary dictionaryWithObjectsAndKeys:nick, @"nickname", nil]];
491                         if( ! [_onlineUsers count] ) [[NSNotificationCenter defaultCenter] postNotificationName:JVBuddyWentOfflineNotification object:self userInfo:nil];
492                 }
493         }
494 }
495 @end
496 /*
497 #pragma mark -
498
499 @implementation JVBuddy (JVBuddyScripting)
500 - (NSDictionary *) activeNicknameDictionary {
501         MVChatConnection *connection = [[MVConnectionsController defaultManager] connectionForServerAddress:[[self activeNickname] host]];
502         return [NSDictionary dictionaryWithObjectsAndKeys:connection, @"connection", [[self activeNickname] user], @"nickname", nil];
503 }
504
505 - (NSArray *) nicknamesArray {
506         NSMutableArray *ret = [NSMutableArray arrayWithCapacity:[_users count]];
507         NSEnumerator *enumerator = [_users objectEnumerator];
508         NSURL *nick = nil;
509
510         while( ( nick = [enumerator nextObject] ) ) {
511                 MVChatConnection *connection = [[MVConnectionsController defaultManager] connectionForServerAddress:[nick host]];
512                 if( ! connection ) continue;
513                 NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:connection, @"connection", [nick user], @"nickname", nil];
514                 [ret addObject:info];
515         }
516
517         return ret;
518 }
519
520 - (NSArray *) onlineNicknamesArray {
521         NSMutableArray *ret = [NSMutableArray arrayWithCapacity:[_users count]];
522         NSEnumerator *enumerator = [_onlineUsers objectEnumerator];
523         NSURL *nick = nil;
524
525         while( ( nick = [enumerator nextObject] ) ) {
526                 MVChatConnection *connection = [[MVConnectionsController defaultManager] connectionForServerAddress:[nick host]];
527                 if( ! connection ) continue;
528                 NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:connection, @"connection", [nick user], @"nickname", nil];
529                 [ret addObject:info];
530         }
531
532         return ret;
533 }
534
535 - (void) editInAddressBookScriptCommand:(NSScriptCommand *) command {
536         [self editInAddressBook];
537 }
538
539 - (void) viewInAddressBookScriptCommand:(NSScriptCommand *) command {
540         [self viewInAddressBook];
541 }
542 @end */
Note: See TracBrowser for help on using the browser.