/* * Grouch.app Copyright (C) 2006 Andy Sveikauskas * ------------------------------------------------------------------------ * This program is free software under the GNU General Public License * -- * Keeps a local copy of Server-Stored Information */ #import #import #import #import #import #import #import #import #import #import #import #import #ifdef __APPLE__ #import #import #endif @implementation OscarSsiList - init { [super init]; records = [NSMutableArray new]; groupsByNumber = [NSMutableDictionary new]; buddiesByGroupAndNumber = [NSMutableDictionary new]; buddies = [NSMutableArray new]; return self; } - (void)dealloc { [records release]; [groupsByNumber release]; [buddiesByGroupAndNumber release]; [buddies release]; [super dealloc]; } - (void)setClient:c { cli = c; } - (OscarSsiRecord*)_getVirtual:(OscarSsiType)type { OscarSsiRecord *r; int i; for( i=0; i<[records count]; ++i ) if( [r=[records objectAtIndex:i] type] == type ) return r; r = [[OscarSsiRecord new] autorelease]; [r setType:type]; [r setVirtual:YES]; [self addChild:r]; return r; } - (void)processUser:(id)u withRecord:(OscarSsiRecord*)rec { OscarTlvListIn *tlv = [rec tlv]; OscarIncomingSnac *buf; // Buddy alias if( (buf=[tlv getTLV:0x0131]) ) [u setAlias:[buf readRawString]]; } - (void)addRecord:(NSString*)string item:(int)iid group:(int)gid type:(int)type tlv:t { OscarSsiRecord *rec = [[OscarSsiRecord new] autorelease], *rec2; id user; [rec setParent:self]; [rec setDescription:string]; [rec setType:type]; [rec setItem:iid]; [rec setGid:gid]; [rec setTlv:t]; [self setUp:rec]; switch( type ) { case OscarSsiUser: user = [cli getUser:string]; [rec setDescription:user]; [self processUser:user withRecord:rec]; break; case OscarSsiPermit: case OscarSsiDeny: rec2 = [self _getVirtual:type]; [rec2 addChild:rec]; break; default: [self addChild:rec]; } } - (NSArray*)findRecordsOfType:(OscarSsiType)type { NSMutableArray *r = [NSMutableArray array]; int i; OscarSsiRecord *obj; for( i=0; i<[records count]; ++i ) if( [obj=[records objectAtIndex:i] type] == type ) [r addObject:obj]; return r; } - (NSArray*)getContactList:(BOOL)showOfflineUsers { NSMutableArray *r = [NSMutableArray array]; NSEnumerator *groupEnumerator = [groupsByNumber objectEnumerator]; OscarSsiRecord *rec; while( (rec=[groupEnumerator nextObject]) ) { NSMutableArray *currentGroup; NSArray *child; int j; if( ![rec gid] ) continue; currentGroup = [NSMutableArray array]; [currentGroup addObject:[rec description]]; child = [rec children]; for( j=0; j<[child count]; ++j ) { id u; rec = [child objectAtIndex:j]; u = [rec description]; if( showOfflineUsers || [u online] ) [currentGroup addObject:u]; } if( [currentGroup count] > 1 ) [r addObject:currentGroup]; } return r; } - (void)addChild:(OscarSsiRecord*)child { [child setParent:self]; [records addObject:child]; } - (void)removeChild:child { int i; for( i=0; i<[records count]; ++i ) if( [records objectAtIndex:i] == child ) { [records removeObjectAtIndex:i]; --i; } } - (void)setUp:(OscarSsiRecord*)obj { NSMutableDictionary *dict = nil; int key = 0; switch( [obj type] ) { case OscarSsiUser: { NSNumber *key2 = [NSNumber numberWithInt:[obj gid]]; dict = [buddiesByGroupAndNumber objectForKey:key2]; if( !dict ) [buddiesByGroupAndNumber setObject:dict=[NSMutableDictionary dictionary] forKey:key2]; key = [obj item]; [buddies addObject:obj]; break; } case OscarSsiGroup: dict = groupsByNumber; key = [obj gid]; break; } if(dict) [dict setObject:obj forKey:[NSNumber numberWithInt:key]]; } - (OscarSsiRecord*)groupById:(int)gid { return [groupsByNumber objectForKey:[NSNumber numberWithInt:gid]]; } - (OscarSsiRecord*)userById:(int)user andGroup:(int)gid { NSNumber *key1 = [NSNumber numberWithInt:user], *key2 = [NSNumber numberWithInt:gid]; NSDictionary *dict = [buddiesByGroupAndNumber objectForKey:key2]; if( dict ) return [dict objectForKey:key1]; else return nil; } - (BOOL)containsUser:(id)user { int i; for( i=0; i<[buddies count]; ++i ) if( (id)[[buddies objectAtIndex:i] description] == user ) return YES; return NO; } #define TLV_USER_LIST 0xc8 // XXX extra 16 bits? /* * This extraodinarily complicated method looks at the buddy list and makes * sure it is relatively sane. If it is not, it updates. */ - (void)reparentChildrenForBos:(OscarBos*)bos { NSMutableDictionary *recordsToUpdate = [NSMutableDictionary dictionary]; NSMutableArray *orphans = [NSMutableArray array]; NSEnumerator *e; OscarSsiRecord *group, *user; int i; /* * Scan through the groups and attach their children. * If children are invalid or inserted twice, then we'll correct the * problem server-side. */ e = [groupsByNumber objectEnumerator]; while( (group = [e nextObject]) ) { OscarTlvListIn *tlv = [group tlv]; OscarIncomingSnac *buf = [tlv getTLV:TLV_USER_LIST]; OscarBuffer *newTlv = [OscarBuffer new]; BOOL tlvChanged = NO; /* * Look at the TLV's children. If they are invalid or dupes, * remove them. */ while( buf && [buf bytesRemaining] ) { i = [buf readInt16]; /* * Group 0's children are the other groups. * Other groups list buddies in the TLV. */ if( [group gid] ) user = [self userById:i andGroup:[group gid]]; else { NSNumber *key = [NSNumber numberWithInt:i]; user = [groupsByNumber objectForKey:key]; } if( user && ![user marked] ) // Attach to this group { if( [group gid] ) [group addChild:user]; [user mark]; [newTlv addInt16:i]; } else // User is already in list or may not exist. tlvChanged = YES; } /* * If this is GID 0, look for orphan groups */ if( ![group gid] ) { NSEnumerator *e2 = [groupsByNumber objectEnumerator]; while( (user=[e2 nextObject]) ) { if( ![user marked] && [user gid] ) { [newTlv addInt16:[user gid]]; tlvChanged = YES; } } } /* * If we changed the list, set the actual TLV and make note of * the fact. */ if( tlvChanged ) { OscarBuffer *output = [[group tlvRaw] bufferByRemovingTlv:TLV_USER_LIST]; [output addTLV:TLV_USER_LIST with:newTlv]; [group setTlv:output]; [recordsToUpdate setObject:group forKey:[NSString stringWithFormat:@"%p", group]]; } [newTlv release]; } /* * Now scan through the users to see who we didn't insert * based on this list. If the group node exists, link the * buddy in. Otherwise add it to the array of orphans. */ for( i=0; i<[buddies count]; ++i ) { user = [buddies objectAtIndex:i]; if( ![user marked] ) { group = [groupsByNumber objectForKey: [NSNumber numberWithInt:[user gid]]]; if( group ) // Attach to this group { OscarBuffer *output = [[group tlvRaw] bufferByRemovingTlv:TLV_USER_LIST]; OscarIncomingSnac *input = [[group tlv] getTLV:TLV_USER_LIST]; OscarBuffer *newTlv = [OscarBuffer new]; [newTlv addBuffer:[input buffer] withLength: [input bytesRemaining]]; [newTlv addInt16:[user item]]; [output addTLV:TLV_USER_LIST with:newTlv]; [group setTlv:output]; [newTlv release]; [recordsToUpdate setObject:group forKey: [NSString stringWithFormat:@"%p", group]]; [group addChild:user]; } else [orphans addObject:user]; } } /* * Now we need to figure out what to do with the damned orphans. * These are nodes whose group IDs do not exist. If they are out * there it is probably due to a buggy client. * * For simplicity's sake I say remove them. */ if( [orphans count] ) [bos removeSsiRecords:orphans]; /* * If you were paying attention earlier you will notice that we * kept a list of nodes which we updated TLVs to. Now let's make * that happen. */ orphans = [NSMutableArray array]; e = [recordsToUpdate objectEnumerator]; while( (group = [e nextObject]) ) [orphans addObject:group]; if( [orphans count] ) [bos updateSsiRecords:orphans]; } - (void)confirmAdd:(OscarSsiRecord*)obj bos:(OscarBos*)bos { NSNumber *key = nil; int set = 0; switch( [obj type] ) { case OscarSsiUser: key = [NSNumber numberWithInt:[obj gid]]; set = [obj item]; break; case OscarSsiGroup: key = [NSNumber numberWithInt:0]; set = [obj gid]; } if( key ) { OscarSsiRecord *group = [groupsByNumber objectForKey:key]; if( group ) { OscarBuffer *buf = [[group tlvRaw] bufferByRemovingTlv: TLV_USER_LIST]; OscarBuffer *newTlv = [OscarBuffer new]; OscarIncomingSnac *old = [[group tlv] getTLV: TLV_USER_LIST]; if( old ) [newTlv addBuffer:[old buffer] withLength:[old bytesRemaining]]; [newTlv addInt16:set]; [buf addTLV:TLV_USER_LIST with:newTlv]; [newTlv release]; [obj setTlv:buf]; [bos updateSsiRecords:[NSArray arrayWithObject:obj]]; } } } - (void)_remove:(OscarSsiRecord*)obj { NSMutableDictionary *dict = nil; int key = 0; switch( [obj type] ) { case OscarSsiUser: { NSNumber *key2 = [NSNumber numberWithInt:[obj gid]]; dict = [buddiesByGroupAndNumber objectForKey:key2]; key = [obj item]; [buddies removeObject:obj]; break; } case OscarSsiGroup: dict = groupsByNumber; key = [obj gid]; break; } if( dict ) [dict removeObjectForKey:[NSNumber numberWithInt:key]]; } - (int)_nextKeyForDict:(NSDictionary*)dict { int i = 1; while( i < 65535 ) { NSNumber *value = [NSNumber numberWithInt:i]; if( ![dict objectForKey:value] ) return i; else ++i; } return -1; } - (int)nextGroup { return [self _nextKeyForDict:groupsByNumber]; } - (int)nextBuddyForGroup:(int)gid { NSNumber *key = [NSNumber numberWithInt:gid]; NSMutableDictionary *dict = [buddiesByGroupAndNumber objectForKey:key]; if( !dict ) [buddiesByGroupAndNumber setObject:dict=[NSMutableDictionary dictionary] forKey:key]; return [self _nextKeyForDict:dict]; } - (void)setAlias:(NSString*)alias forUser:(id)user withBos:(OscarBos*)bos { int i; for( i=0; i<[buddies count]; ++i ) { OscarSsiRecord *p = [buddies objectAtIndex:i]; if( [p description] == user ) { OscarBuffer *tlv = [p tlvRaw]; int type = 0x0131; if( p ) tlv = [tlv bufferByRemovingTlv:type]; else tlv = [[OscarBuffer new] autorelease]; [tlv addTLV:type withString:alias]; [p setTlv:(id)tlv]; [bos updateSsiRecords:[NSArray arrayWithObject:p]]; [user setAlias:alias]; break; } } } @end @implementation OscarSsiRecord - (void)dealloc { if( desc ) [desc release]; if( children ) [children release]; if( tlv ) [tlv release]; [super dealloc]; } - description { return desc; } - (void)setDescription:p { if( desc ) [desc release]; [desc=p retain]; } - (int)type { return type; } - (void)setType:(int)t { type = t; } - (int)item { return iid; } - (void)setItem:(int)i { iid = i; } - (int)gid { return gid; } - (void)setGid:(int)i; { gid = i; } - (BOOL)isVirtual { return virtual; } - (void)setVirtual:(BOOL)b { virtual = b; } - (OscarBuffer*)tlvRaw { return tlv; } - (OscarTlvListIn*)tlv { if( tlv ) { void *ptr; size_t len; NSData *d; [tlv createHeapBuffer:&ptr withLength:&len]; d = [NSData dataWithBytesNoCopy:ptr length:len]; return [[OscarTlvListIn listFromBuffer:[d bytes] andLength:[d length] andTLVs:NULL] autorelease]; } else return nil; } - (void)setTlv:t { if( tlv ) [tlv release]; tlv = nil; if( [(NSObject*)t isKindOfClass:[OscarBuffer class]] ) [tlv=t retain]; else if( [(NSObject*)t isKindOfClass:[OscarIncomingSnac class]] ) { OscarIncomingSnac *snac = t; tlv = [OscarBuffer new]; [tlv addBuffer:[snac buffer] withLength:[snac bytesRemaining]]; } else if( [(NSObject*)t isKindOfClass:[OscarTlvListIn class]] ) { OscarTlvListIn *list = t; tlv = [OscarBuffer new]; [tlv addBuffer:[list buffer] withLength:[list length]]; } else if(t) // programmer error. NSLog(@"%s does not belong here", [t class]->name); } - (void)output:(OscarBuffer*)buf children:(BOOL)childrenToo; { BOOL v = [self isVirtual]; NSArray *child; int i; if( !v ) { NSObject *obj = [self description]; OscarTlvListIn *t; NSData *data; size_t len; if( [obj conformsToProtocol:@protocol(GrouchUser)] ) obj = [(id)obj name]; if( [obj isKindOfClass:[NSString class]] ) obj = [(NSString*)obj dataUsingEncoding: [NSString defaultCStringEncoding]]; data = (NSData*)obj; len = [data length]; [buf addInt16:len]; [buf addBuffer:[data bytes] withLength:len]; [buf addInt16:[self gid]]; [buf addInt16:[self item]]; [buf addInt16:[self type]]; if( (t=[self tlv]) ) { len = [t length]; [buf addInt16:len]; [buf addBuffer:[t buffer] withLength:len]; } else [buf addInt16:0]; } if(childrenToo) for( i=0, child=[self children]; i<[child count]; ++i ) { OscarSsiRecord *rec = [child objectAtIndex:i]; [rec output:buf]; } } - (void)output:(OscarBuffer*)buf { [self output:buf children:NO]; } - (NSArray*)children { return children ? children : [NSArray array]; } - (void)addChild:(OscarSsiRecord*)rec { if( !children ) children = [NSMutableArray new]; [children addObject:rec]; [rec setParent:self]; } - (void)remove { id obj = self; while( [obj respondsToSelector:@selector(parent)] && [obj parent] ) { obj = [obj parent]; if( obj && [obj isKindOfClass:[OscarSsiList class]] ) { [obj _remove:self]; break; } } [parent removeChild:self]; } - (void)removeChild:child { int i; for( i=0; i<[children count]; ++i ) if( [children objectAtIndex:i] == child ) { [children removeObjectAtIndex:i]; --i; } } - parent { return parent; } - (void)setParent:p { parent = p; } - (BOOL)marked { return marked; } - (void)mark { marked = YES; } @end