/* Copyright (C) 2002-2005 SKYRIX Software AG This file is part of OpenGroupware.org. OGo is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. OGo is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with OGo; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include /* This command fetches vCards for globalIds (Person or Enterprise). It first fetches the current ids and version of the objects (during that the access is checked) and looks for cached vCards ( ..vcf in LSAttachmentPath) and builds new if needed. The command supports grouping (groupBy parameter) and these attributes: vCardData companyId globalID objectVersion If no attribute is requested, an array of vCard strings is returned. @see: RFC 2426 */ @class NSString, NSArray; @interface LSGetVCardForGlobalIDsCommand : LSDBObjectBaseCommand { NSArray *gids; BOOL buildResponse; NSArray *attributes; NSString *groupBy; } @end #include "NSString+VCard.h" #include "LSVCardCompanyFormatter.h" #include "common.h" // TODO: do we really need to have a dependency on WOResponse? #include @implementation LSGetVCardForGlobalIDsCommand static NSString *LSAttachmentPath = nil; static NSString *skyrixId = nil; static NSDictionary *telephoneMapping = nil; static NSDictionary *addressMapping = nil; + (void)initialize { NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; skyrixId = [ud stringForKey:@"skyrix_id"]; skyrixId = [[NSString alloc] initWithFormat:@"skyrix://%@/%@/", [[NSHost currentHost] name], skyrixId]; addressMapping = [[ud dictionaryForKey:@"LSVCard_AddressMapping"] copy]; telephoneMapping = [[ud dictionaryForKey:@"LSVCard_TelephoneMapping"] copy]; LSAttachmentPath = [[ud stringForKey:@"LSAttachmentPath"] copy]; if ([LSAttachmentPath length] == 0) NSLog(@"ERROR: did not find 'LSAttachmentPath'!"); else NSLog(@"Note: storing cached vCards files in: '%@'", LSAttachmentPath); } - (void)dealloc { [self->attributes release]; [self->groupBy release]; [self->gids release]; [super dealloc]; } /* command methods */ - (id)_prepareResultForCount:(int)_cnt { /* Note: results are retained */ if (([self->attributes count] != 0) && ([self->groupBy length] > 0)) return [[NSMutableDictionary alloc] initWithCapacity:_cnt+1]; return [[NSMutableArray alloc] initWithCapacity:(_cnt + 1)]; } - (void)_addVCard:(NSString *)_vCard ofRecord:(id)_record toResult:(id)_result { NSMutableDictionary *entry; id tmp; id val; if (![_vCard isNotNull]) { [self logWithFormat:@"WARNING[%s]: got no vCard!", __PRETTY_FUNCTION__]; return; } if ([self->attributes count] == 0) { [_result addObject:_vCard]; return; } entry = [[NSMutableDictionary alloc] initWithCapacity:4]; [entry setObject:_vCard forKey:@"vCardData"]; if ([self->attributes containsObject:@"companyId"]) { if ((tmp = [_result valueForKey:@"companyId"])) [entry setObject:tmp forKey:@"companyId"]; } if ([self->attributes containsObject:@"globalID"]) { if ((tmp = [_result valueForKey:@"globalID"])) [entry setObject:tmp forKey:@"globalID"]; } if ([self->attributes containsObject:@"objectVersion"]) { if ((tmp = [_result valueForKey:@"objectVersion"])) [entry setObject:tmp forKey:@"objectVersion"]; } if ([self->groupBy length] == 0) { [_result addObject:entry]; return; } if ((tmp = [entry valueForKey:self->groupBy]) == nil) { NSLog(@"WARNING[%s]: cannot map entry %@ by key %@", __PRETTY_FUNCTION__, entry, self->groupBy); return; } if ((val = [_result valueForKey:tmp]) != nil) { NSLog(@"WARNING[%s]: map already contains an entry for key %@: %@", __PRETTY_FUNCTION__, tmp, val); return; } [(NSMutableDictionary *)_result setObject:entry forKey:tmp]; } /* fetching */ - (NSArray *)_fetchIdsAndVersionsInContext:(id)_context { static NSArray *attrs = nil; NSMutableArray *result; NSMutableArray *persons; NSMutableArray *enterprises; NSMutableArray *teams; EOKeyGlobalID *gid; int cnt; if (attrs == nil) { attrs = [[NSArray alloc] initWithObjects: @"companyId", @"globalID", @"objectVersion", nil]; } cnt = [self->gids count]; if (cnt == 0) return [NSArray array]; persons = [[NSMutableArray alloc] initWithCapacity:cnt]; enterprises = [[NSMutableArray alloc] initWithCapacity:cnt]; teams = [[NSMutableArray alloc] initWithCapacity:cnt]; while (cnt--) { gid = [self->gids objectAtIndex:cnt]; if ([[gid entityName] isEqualToString:@"Person"]) [persons addObject:gid]; else if ([[gid entityName] isEqualToString:@"Enterprise"]) [enterprises addObject:gid]; else if ([[gid entityName] isEqualToString:@"Team"]) [teams addObject:gid]; else { NSString *s; s = [NSString stringWithFormat: @"invalid entityName '%@' " @"(Person, Team and Enterprise accepted)", [gid entityName]]; [self assert:NO reason:s]; } } result = [NSMutableArray arrayWithCapacity:[persons count]+[enterprises count]+ [teams count]]; if ([persons count] > 0) { [result addObjectsFromArray: LSRunCommandV(_context, @"person", @"get-by-globalid", @"gids", persons, @"attributes", attrs, nil)]; } if ([enterprises count] > 0) { [result addObjectsFromArray: LSRunCommandV(_context, @"enterprise", @"get-by-globalid", @"gids", enterprises, @"attributes", attrs, nil)]; } if ([teams count] > 0) { [result addObjectsFromArray: LSRunCommandV(_context, @"team", @"get-by-globalid", @"gids", teams, @"attributes", attrs, nil)]; } [persons release]; [enterprises release]; [teams release]; return result; } /* caching */ - (id)_cachedVCardForRecord:(id)_record inContext:(id)_context { NSString *path; NSString *file; NSFileManager *manager; NSNumber *cId, *oV; [self assert:(_record != nil) reason:@"no record to fetch vCard for!"]; cId = [_record valueForKey:@"companyId"]; oV = [_record valueForKey:@"objectVersion"]; if (cId == nil || oV == nil) { NSLog(@"%s: missing companyId and/or objectVersion in record: %@", __PRETTY_FUNCTION__, _record); return nil; } path = LSAttachmentPath; file = [[NSString alloc] initWithFormat:@"%@.%@.vcf", cId, oV]; path = [path stringByAppendingPathComponent:file]; [file release]; file = nil; manager = [NSFileManager defaultManager]; if ([manager fileExistsAtPath:path]) return [NSString stringWithContentsOfFile:path]; return nil; } - (void)_cacheVCard:(NSString *)_vCard forContact:(id)_comp inContext:(id)_context { NSString *path; NSString *file; NSFileManager *manager; BOOL ok; id cId, oV; [self assert:(_vCard != nil) reason:@"no vCard to save!"]; [self assert:(_comp != nil) reason:@"no record to save vCard for!"]; cId = [_comp valueForKey:@"companyId"]; oV = [_comp valueForKey:@"objectVersion"]; if (cId == nil || oV == nil) { NSLog(@"%s: missing companyId and/or objectVersion in record: %@", __PRETTY_FUNCTION__, _comp); return; } path = LSAttachmentPath; file = [NSString stringWithFormat:@"%@.%@.vcf", cId, oV]; path = [path stringByAppendingPathComponent:file]; manager = [NSFileManager defaultManager]; if ([manager fileExistsAtPath:path]) [manager removeFileAtPath:path handler:nil]; ok = [_vCard writeToFile:path atomically:YES]; [self assert:ok reason:@"error during save of vCard cache file"]; } /* execution */ - (void)_buildAndCacheVCardsForContacts:(NSArray *)_uncachedContacts type:(NSString *)_type // person, enterprise result:(id)_result inContext:(id)_context { NSArray *globalIDs; NSArray *contacts; unsigned cnt, i; globalIDs = [_uncachedContacts valueForKey:@"globalID"]; contacts = LSRunCommandV(_context, _type, @"get-by-globalid", @"gids", globalIDs, nil); for (i = 0, cnt = [contacts count]; i < cnt; i++) { EOKeyGlobalID *gid; NSFormatter *formatter; NSString *vCard; id contact; contact = [contacts objectAtIndex:i]; gid = [contact valueForKey:@"globalID"]; /* select vCard generator */ if (([[gid entityName] isEqualToString:@"Person"])) formatter = [LSVCardPersonFormatter formatter]; else if (([[gid entityName] isEqualToString:@"Enterprise"])) formatter = [LSVCardEnterpriseFormatter formatter]; else if (([[gid entityName] isEqualToString:@"Team"])) formatter = [LSVCardTeamFormatter formatter]; else { [self logWithFormat:@"ERROR: cannot process record: %@", gid]; continue; } /* fetch addresses */ if (![[gid entityName] isEqualToString:@"Team"]) { NSArray *addrs; addrs = LSRunCommandV(_context, @"address", @"get", @"companyId", [contact valueForKey:@"companyId"], @"returnType", intObj(LSDBReturnType_ManyObjects), nil); [contact takeValue:addrs forKey:@"addresses"]; } /* build vCard */ if ((vCard = [formatter stringForObjectValue:contact]) == nil) { [self logWithFormat: @"ERROR(%s): failed building vCard for contact (%@): %@", __PRETTY_FUNCTION__, formatter, contact]; continue; } [self _cacheVCard:vCard forContact:contact inContext:_context]; [self _addVCard:vCard ofRecord:contact toResult:_result]; } } - (id)_buildResponseForVCards:(id)_vCards inContext:(id)_context { // TODO: this does not belong here, the command should only provide the // NSData or NSString objects NSString *s; NSData *data; id response; if ([_vCards isKindOfClass:[NSDictionary class]]) _vCards = [_vCards allValues]; if ([self->attributes count] > 0) _vCards = [_vCards valueForKey:@"vCardData"]; s = [_vCards componentsJoinedByString:@""]; data = [s dataUsingEncoding:NSUTF8StringEncoding]; response = [[[NSClassFromString(@"WOResponse") alloc] init] autorelease]; [response setStatus:200]; [response setHeader:@"text/x-vcard; charset=utf-8" forKey:@"content-type"]; [response setHeader:@"identity" forKey:@"content-encoding"]; [response setContent:data]; return response; } - (void)_executeInContext:(id)_context { NSArray *records; id result; int cnt; /* fetch data */ records = [self _fetchIdsAndVersionsInContext:_context]; /* process data */ if ((cnt = [records count])) { NSMutableArray *uncachedPersons; NSMutableArray *uncachedEnterprises; NSMutableArray *uncachedTeams; id cached, record; result = [self _prepareResultForCount:cnt]; // retained uncachedPersons = [[NSMutableArray alloc] initWithCapacity:8]; uncachedEnterprises = [[NSMutableArray alloc] initWithCapacity:8]; uncachedTeams = [[NSMutableArray alloc] initWithCapacity:8]; while (cnt--) { record = [records objectAtIndex:cnt]; cached = [self _cachedVCardForRecord:record inContext:_context]; if (cached) { [self _addVCard:cached ofRecord:record toResult:result]; } else { EOKeyGlobalID *gid; gid = [record valueForKey:@"globalID"]; if ([[gid entityName] isEqualToString:@"Person"]) [uncachedPersons addObject:record]; else if ([[gid entityName] isEqualToString:@"Enterprise"]) [uncachedEnterprises addObject:record]; else if ([[gid entityName] isEqualToString:@"Team"]) [uncachedTeams addObject:record]; else { NSString *error; error = [NSString stringWithFormat: @"invalid entityName '%@' " @"(Person, Enterprise and Team accepted)", [gid entityName]]; [self assert:NO reason:error]; } } } if ([uncachedPersons count] > 0) { [self _buildAndCacheVCardsForContacts:uncachedPersons type:@"person" result:result inContext:_context]; } if ([uncachedEnterprises count] > 0) { [self _buildAndCacheVCardsForContacts:uncachedEnterprises type:@"enterprise" result:result inContext:_context]; } if ([uncachedTeams count] > 0) { [self _buildAndCacheVCardsForContacts:uncachedTeams type:@"team" result:result inContext:_context]; } [uncachedPersons release]; [uncachedEnterprises release]; [uncachedTeams release]; } else result = [[NSArray alloc] init]; // TODO: the build response should not be used [self setReturnValue:(self->buildResponse) ? [self _buildResponseForVCards:result inContext:_context] : result]; [result release]; } /* accessors */ - (void)setGlobalIDs:(NSArray *)_gids { ASSIGN(self->gids,_gids); } - (NSArray *)globalIDs { return self->gids; } - (void)setGlobalID:(EOGlobalID *)_gid { [self setGlobalIDs:[NSArray arrayWithObject:_gid]]; } - (EOGlobalID *)globalID { return [[self globalIDs] lastObject]; } - (void)setBuildResponse:(BOOL)_flag { #if DEBUG if (_flag) { [self logWithFormat: @"Note: uses vcard command to generate WOResponse which is " @"deprecated"]; } #endif self->buildResponse = _flag; } - (BOOL)buildResponse { return self->buildResponse; } - (void)setAttributes:(NSArray *)_attributes { ASSIGN(self->attributes, _attributes); } - (NSArray *)attributes { return self->attributes; } - (void)setGroupBy:(NSString *)_group { ASSIGNCOPY(self->groupBy,_group); } - (NSString *)groupBy { return self->groupBy; } /* key-value coding */ - (void)takeValue:(id)_value forKey:(NSString *)_key { if ([_key isEqualToString:@"gid"]) [self setGlobalID:_value]; else if ([_key isEqualToString:@"gids"]) [self setGlobalIDs:_value]; else if ([_key isEqualToString:@"buildResponse"]) [self setBuildResponse:[_value boolValue]]; else if ([_key isEqualToString:@"attributes"]) [self setAttributes:_value]; else if ([_key isEqualToString:@"groupBy"]) [self setGroupBy:_value]; else [super takeValue:_value forKey:_key]; } - (id)valueForKey:(NSString *)_key { id v; if ([_key isEqualToString:@"gid"]) v = [self globalID]; else if ([_key isEqualToString:@"gids"]) v = [self globalIDs]; else if ([_key isEqualToString:@"buildResponse"]) v = [NSNumber numberWithBool:[self buildResponse]]; else if ([_key isEqualToString:@"attributes"]) v = [self attributes]; else if ([_key isEqualToString:@"groupBy"]) v = [self groupBy]; else v = [super valueForKey:_key]; return v; } @end /* LSGetVCardForGlobalIDsCommand */