// // ConduitsManager.m // Task Views // // Created by Chris Karr on 2/23/09. // Copyright 2009 Chris J. Karr. All rights reserved. // #import #import "ConduitsManager.h" #import "BasecampConduit.h" #import "iCalConduit.h" #import "ThingsConduit.h" #import "EntourageConduit.h" #import "FogBugzConduit.h" @implementation ConduitsManager - (Conduit *) conduitForDefinition:(NSDictionary *) conduitDef { if ([[conduitDef valueForKey:@"type"] isEqual:@"Basecamp"]) { BasecampConduit * conduit = [[BasecampConduit alloc] init]; return [conduit autorelease]; } else if ([[conduitDef valueForKey:@"type"] isEqual:@"iCal"]) { iCalConduit * conduit = [[iCalConduit alloc] init]; return [conduit autorelease]; } else if ([[conduitDef valueForKey:@"type"] isEqual:@"Things"]) { ThingsConduit * conduit = [[ThingsConduit alloc] init]; return [conduit autorelease]; } else if ([[conduitDef valueForKey:@"type"] isEqual:@"Entourage"]) { EntourageConduit * conduit = [[EntourageConduit alloc] init]; return [conduit autorelease]; } else if ([[conduitDef valueForKey:@"type"] isEqual:@"FogBugz"]) { FogBugzConduit * conduit = [[FogBugzConduit alloc] init]; return [conduit autorelease]; } else { NSLog (@"Unknown conduit: %@", [conduitDef valueForKey:@"type"]); return nil; } } - (Conduit *) currentConduit { return [self conduitForDefinition:[[conduits selectedObjects] lastObject]]; } - (IBAction) conduits:(id) sender { [conduitsWindow makeKeyAndOrderFront:sender]; } - (IBAction) add:(id) sender { NSMutableDictionary * newConduit = [NSMutableDictionary dictionary]; [newConduit setValue:@"New Conduit" forKey:@"name"]; [newConduit setValue:@"Select Conduit Type" forKey:@"type"]; [newConduit setValue:@"" forKey:@"site"]; [newConduit setValue:@"" forKey:@"username"]; [conduits addObject:newConduit]; [conduits setSelectedObjects:[NSArray arrayWithObject:newConduit]]; } - (void) awakeFromNib { [conduits addObserver:self forKeyPath:@"selection" options:0 context:NULL]; } - (void) observeValueForKeyPath: (NSString *) keyPath ofObject:(id) object change:(NSDictionary *) change context:(void *) context { [self willChangeValueForKey:@"password"]; [self didChangeValueForKey:@"password"]; } - (NSString *) passwordForDefinition:(NSDictionary *) def { NSString * kcPassword = nil; if (def != nil) { UInt32 length = 256; void * password = NULL; NSString * name = [NSString stringWithFormat:@"Task Views: %@: %@", [def valueForKey:@"type"], [def valueForKey:@"site"], nil]; const char * utf8 = [name UTF8String]; NSString * username = [def valueForKey:@"username"]; const char * user = [username UTF8String]; OSStatus status = SecKeychainFindGenericPassword (NULL, strlen (utf8), utf8, strlen (user), user, &length, &password, NULL); if (status == errSecItemNotFound) NSLog (@"No password. returning nil."); else if (status == noErr) { NSData * passwordData = [NSData dataWithBytes:password length:length]; kcPassword = [[[NSString alloc] initWithData:passwordData encoding:NSUTF8StringEncoding] autorelease]; } else NSLog (@"error retrieving keychain password %d", status); SecKeychainItemFreeContent (NULL, password); } return kcPassword; } - (NSString *) password { NSDictionary * def = [[conduits selectedObjects] lastObject]; return [self passwordForDefinition:def]; } - (void) setPassword:(NSString *) newPassword { if (newPassword == nil) newPassword = @""; NSDictionary * def = [[conduits selectedObjects] lastObject]; if (def != nil) { NSString * name = [NSString stringWithFormat:@"Task Views: %@: %@", [def valueForKey:@"type"], [def valueForKey:@"site"], nil]; const char * utf8 = [name UTF8String]; NSString * username = [def valueForKey:@"username"]; const char * user = [username UTF8String]; if ([self password] == nil) SecKeychainAddGenericPassword (NULL, strlen (utf8), utf8, strlen (user), user, [newPassword lengthOfBytesUsingEncoding:NSUTF8StringEncoding], [newPassword UTF8String], NULL); else { SecKeychainItemRef itemRef = NULL; SecKeychainFindGenericPassword (NULL, strlen (utf8), utf8, strlen (user), user, 0, NULL, &itemRef); SecKeychainItemModifyAttributesAndData (itemRef, NULL, [newPassword lengthOfBytesUsingEncoding:NSUTF8StringEncoding], [newPassword UTF8String]); } } } - (IBAction) remove:(id) sender { NSDictionary * conduit = [[conduits selectedObjects] lastObject]; NSString * title = [NSString stringWithFormat:@"Delete '%@'?", [conduit valueForKey:@"name"]]; NSString * message = [NSString stringWithFormat:@"Are you sure that you wish to delete the conduit '%@'?", [conduit valueForKey:@"name"]]; if (NSAlertDefaultReturn == NSRunAlertPanel(title, message, @"Yes", @"No", nil)) [conduits removeObject:conduit]; } - (IBAction) openExternalSite:(id) sender { id conduit = [self currentConduit]; [conduit openExternalSite:[[conduits selectedObjects] lastObject]]; } - (NSArray *) tasksWithField:(NSString *) field matching:(NSObject *) value { NSMutableArray * matches = [NSMutableArray array]; for (NSDictionary * task in [tasks arrangedObjects]) { if ([[task valueForKey:field] isEqual:value]) [matches addObject:task]; } return matches; } - (BOOL) taskExists:(NSDictionary *) options identifier:(NSString *) identifier { NSArray * matches = [self tasksWithField:identifier matching:[options valueForKey:identifier]]; if ([matches count] > 0) return YES; return NO; } - (void) mergeTask:(NSDictionary *) options identifier:(NSString *) identifier definition:(NSDictionary *) definition replace:(BOOL) doReplace { NSDictionary * mapping = [definition valueForKey:@"mappings"]; if (mapping == nil) mapping = [NSDictionary dictionary]; NSArray * matches = [self tasksWithField:identifier matching:[options valueForKey:identifier]]; for (NSMutableDictionary * task in matches) { NSEnumerator * keys = [[options allKeys] objectEnumerator]; NSString * key = nil; while (key = [keys nextObject]) { NSObject * defValue = [options valueForKey:key]; if (doReplace) { if ([key isEqual:@"name"]) [task setValue:[defValue description] forKey:@"name"]; else if ([key isEqual:@"source"]) [task setValue:[defValue description] forKey:@"source"]; NSString * mapKey = [mapping valueForKey:key]; if (mapKey != nil) key = mapKey; } if (doReplace || [task valueForKey:key] == nil) [task setValue:defValue forKey:key]; } } } - (void) refreshConduit:(Conduit *) conduit definition:(NSDictionary *) definition { NSDictionary * mapping = [definition valueForKey:@"mappings"]; if (mapping == nil) mapping = [NSDictionary dictionary]; NSMutableDictionary * def = [NSMutableDictionary dictionaryWithDictionary:definition]; [def setValue:[self passwordForDefinition:def] forKey:@"password"]; NSArray * fetchedTasks = [conduit fetchTasks:def]; for (NSDictionary * newTask in fetchedTasks) { if (![self taskExists:newTask identifier:[conduit identifier]]) { NSMutableDictionary * task = [NSMutableDictionary dictionary]; [task setValue:[newTask valueForKey:@"name"] forKey:@"name"]; [task setValue:[newTask valueForKey:@"source"] forKey:@"source"]; for (NSString * key in [newTask allKeys]) { NSObject * defValue = [newTask valueForKey:key]; NSString * mapKey = [mapping valueForKey:key]; if (mapKey != nil) key = mapKey; [task setValue:defValue forKey:key]; } [tasks addObject:task]; } else { [self mergeTask:newTask identifier:[conduit identifier] definition:definition replace:[[[[conduits selectedObjects] lastObject] valueForKey:@"replace"] boolValue]]; } } [[NSNotificationCenter defaultCenter] postNotificationName:@"refresh_fields" object:nil]; } - (void) updateMappings:(NSMutableDictionary *) def withConduit:(Conduit *) conduit { NSMutableDictionary * mappings = [def valueForKey:@"mappings"]; if (mappings == nil) mappings = [NSMutableDictionary dictionary]; for (NSDictionary * field in [fields arrangedObjects]) { NSString * name = [field valueForKey:@"name"]; NSString * value = [mappings valueForKey:name]; if (value == nil) [mappings setValue:name forKey:name]; } [def setValue:mappings forKey:@"mappings"]; } - (IBAction) refresh:(id) sender { Conduit * conduit = [self currentConduit]; NSMutableDictionary * def = [[conduits selectedObjects] lastObject]; NSWindow * keyWindow = [NSApp keyWindow]; if (keyWindow != nil) { [progressBar setUsesThreadedAnimation:YES]; [progressBar startAnimation:sender]; [progressMessage setStringValue:[NSString stringWithFormat:@"Refreshing %@...", [def valueForKey:@"name"], nil]]; [NSApp beginSheet:progressWindow modalForWindow:keyWindow modalDelegate:self didEndSelector:nil contextInfo:NULL]; } [self refreshConduit:conduit definition:def]; [self updateMappings:def withConduit:conduit]; if (keyWindow != nil) { [NSApp endSheet:progressWindow]; [progressWindow orderOut:self]; [progressBar stopAnimation:sender]; } } - (IBAction) refreshAll:(id) sender { NSEnumerator * defIter = [[conduits arrangedObjects] objectEnumerator]; NSMutableDictionary * def = nil; while (def = [defIter nextObject]) { Conduit * conduit = [self conduitForDefinition:def]; NSWindow * keyWindow = [NSApp keyWindow]; if (keyWindow != nil) { [progressBar setUsesThreadedAnimation:YES]; [progressBar startAnimation:sender]; [progressMessage setStringValue:[NSString stringWithFormat:@"Refreshing %@...", [def valueForKey:@"name"], nil]]; [NSApp beginSheet:progressWindow modalForWindow:keyWindow modalDelegate:self didEndSelector:nil contextInfo:NULL]; } [self refreshConduit:conduit definition:def]; [self updateMappings:def withConduit:conduit]; if (keyWindow != nil) { [NSApp endSheet:progressWindow]; [progressWindow orderOut:self]; [progressBar stopAnimation:sender]; } } } - (IBAction) finishMapping:(id) sender { [NSApp endSheet:mappingWindow]; [mappingWindow orderOut:self]; } - (IBAction) showMapping:(id) sender { [NSApp beginSheet:mappingWindow modalForWindow:conduitsWindow modalDelegate:self didEndSelector:nil contextInfo:NULL]; } @end