/* ** ITAddressBookMgr.m ** ** Copyright (c) 2002, 2003 ** ** Author: Fabian, Ujwal S. Setlur ** Initial code by Kiichi Kusama ** ** Project: iTerm ** ** Description: keeps track of the address book data. ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** This program 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 General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #import "ITAddressBookMgr.h" #import #import #import #import #import #include #include #define SAFENODE(n) ((TreeNode*)((n)?(n):(bookmarks))) static NSString* ADDRESS_BOOK_FILE = @"~/Library/Application Support/iTerm/AddressBook"; static TreeNode *defaultBookmark = nil; @implementation ITAddressBookMgr + (id)sharedInstance; { static ITAddressBookMgr* shared = nil; if (!shared) shared = [[ITAddressBookMgr alloc] init]; return shared; } - (id)init; { self = [super init]; return self; } - (void)dealloc; { [bookmarks release]; [bonjourGroup release]; [bonjourServices removeAllObjects]; [bonjourServices release]; [sshBonjourBrowser stop]; [ftpBonjourBrowser stop]; [telnetBonjourBrowser stop]; [sshBonjourBrowser release]; [ftpBonjourBrowser release]; [telnetBonjourBrowser release]; [super dealloc]; } // FIXME (GNUstep): /* - (void) locateBonjourServices { sshBonjourBrowser = [[NSNetServiceBrowser alloc] init]; ftpBonjourBrowser = [[NSNetServiceBrowser alloc] init]; telnetBonjourBrowser = [[NSNetServiceBrowser alloc] init]; bonjourServices = [[NSMutableArray alloc] init]; [sshBonjourBrowser setDelegate: self]; [ftpBonjourBrowser setDelegate: self]; [telnetBonjourBrowser setDelegate: self]; [sshBonjourBrowser searchForServicesOfType: @"_ssh._tcp." inDomain: @""]; [ftpBonjourBrowser searchForServicesOfType: @"_ftp._tcp." inDomain: @""]; [telnetBonjourBrowser searchForServicesOfType: @"_telnet._tcp." inDomain: @""]; } */ - (void) setBookmarks: (NSDictionary *) aDict { //NSLog(@"%s: %@", __PRETTY_FUNCTION__, aDict); [bookmarks release]; bookmarks = [TreeNode treeFromDictionary: aDict]; [bookmarks setIsLeaf: NO]; [bookmarks retain]; // make sure we have a default bookmark if([self _checkForDefaultBookmark: bookmarks defaultBookmark: &defaultBookmark] == NO) { NSMutableDictionary *aDict; char *userShell, *thisUser; NSString *shell; NSString *aName; TreeNode *childNode; aDict = [[NSMutableDictionary alloc] init]; // Get the user's default shell if((thisUser = getenv("USER")) != NULL) { shell = [NSString stringWithFormat: @"login -fp %s", thisUser]; } else if((userShell = getenv("SHELL")) != NULL) { shell = [NSString stringWithCString: userShell]; } else { shell = @"/bin/bash --login"; } aName = NSLocalizedStringFromTableInBundle(@"Default",@"iTerm", [NSBundle bundleForClass: [self class]], @"Terminal Profiles"); [aDict setObject: aName forKey: KEY_NAME]; [aDict setObject: shell forKey: KEY_COMMAND]; [aDict setObject: shell forKey: KEY_DESCRIPTION]; [aDict setObject: NSHomeDirectory() forKey: KEY_WORKING_DIRECTORY]; [aDict setObject: [[iTermTerminalProfileMgr singleInstance] defaultProfileName] forKey: KEY_TERMINAL_PROFILE]; [aDict setObject: [[iTermKeyBindingMgr singleInstance] globalProfileName] forKey: KEY_KEYBOARD_PROFILE]; [aDict setObject: [[iTermDisplayProfileMgr singleInstance] defaultProfileName] forKey: KEY_DISPLAY_PROFILE]; [aDict setObject: @"Yes" forKey: KEY_DEFAULT_BOOKMARK]; childNode = [[TreeNode alloc] initWithData: aDict parent: nil children: [NSArray array]]; [childNode setIsLeaf: YES]; [bookmarks insertChild: childNode atIndex: [bookmarks numberOfChildren]]; [aDict release]; [childNode release]; defaultBookmark = childNode; } // add any bonjour services if we have any if([bonjourGroup numberOfChildren] > 0) { [bookmarks insertChild: bonjourGroup atIndex: [bookmarks numberOfChildren]]; } } - (NSDictionary *) bookmarks { NSDictionary *aDict; int anIndex; //NSLog(@"%s", __PRETTY_FUNCTION__); // remove bonjour group since we do not want to save that anIndex = [[bookmarks children] indexOfObject: bonjourGroup]; [bonjourGroup retain]; [bookmarks removeChild: bonjourGroup]; aDict = [bookmarks dictionary]; if(anIndex != NSNotFound) [bookmarks insertChild: bonjourGroup atIndex: anIndex]; [bonjourGroup release]; return (aDict); } - (BOOL) mayDeleteBookmarkNode: (TreeNode *) aNode { BOOL mayDeleteNode = YES; if([defaultBookmark isDescendantOfNode: aNode]) mayDeleteNode = NO; if([aNode isDescendantOfNode: bonjourGroup]) mayDeleteNode = NO; return (mayDeleteNode); } // Model for NSOutlineView tree structure - (id) child:(int)index ofItem:(id)item { return ([SAFENODE(item) childAtIndex: index]); } - (BOOL) isExpandable:(id)item { //NSLog(@"%s: %@", __PRETTY_FUNCTION__, item); return (![SAFENODE(item) isLeaf]); } - (int) numberOfChildrenOfItem:(id)item { //NSLog(@"%s", __PRETTY_FUNCTION__); return ([SAFENODE(item) numberOfChildren]); } - (id) objectForKey: (id) key inItem: (id) item { NSDictionary *data = [SAFENODE(item) nodeData]; return ([data objectForKey: key]); } - (void) setObjectValue: (id) object forKey: (id) key inItem: (id) item { NSMutableDictionary *aDict; aDict = [[NSMutableDictionary alloc] initWithDictionary: [SAFENODE(item) nodeData]]; [aDict setObject: object forKey: key]; [SAFENODE(item) setNodeData: aDict]; [aDict release]; // Post a notification for all listeners that bookmarks have changed [[NSNotificationCenter defaultCenter] postNotificationName: @"iTermReloadAddressBook" object: nil userInfo: nil]; } - (void) addFolder: (NSString *) folderName toNode: (TreeNode *) aNode { TreeNode *targetNode, *childNode; NSMutableDictionary *aDict; // NSLog(@"%s: %@", __PRETTY_FUNCTION__, folderName); targetNode = SAFENODE(aNode); aDict = [[NSMutableDictionary alloc] init]; [aDict setObject: folderName forKey: KEY_NAME]; [aDict setObject: @"" forKey: KEY_DESCRIPTION]; childNode = [[TreeNode alloc] initWithData: aDict parent: nil children: [NSArray array]]; [childNode setIsLeaf: NO]; [targetNode insertChild: childNode atIndex: [targetNode numberOfChildren]]; [aDict release]; [childNode release]; // Post a notification for all listeners that bookmarks have changed [[NSNotificationCenter defaultCenter] postNotificationName: @"iTermReloadAddressBook" object: nil userInfo: nil]; } - (void) addBookmarkWithData: (NSDictionary *) data toNode: (TreeNode *) aNode; { TreeNode *targetNode, *childNode; NSMutableDictionary *aDict; //NSLog(@"%s", __PRETTY_FUNCTION__); if(data == nil) return; targetNode = SAFENODE(aNode); aDict = [[NSMutableDictionary alloc] initWithDictionary: data]; childNode = [[TreeNode alloc] initWithData: aDict parent: nil children: [NSArray array]]; [childNode setIsLeaf: YES]; [targetNode insertChild: childNode atIndex: [targetNode numberOfChildren]]; [aDict release]; [childNode release]; // Post a notification for all listeners that bookmarks have changed [[NSNotificationCenter defaultCenter] postNotificationName: @"iTermReloadAddressBook" object: nil userInfo: nil]; } - (void) setBookmarkWithData: (NSDictionary *) data forNode: (TreeNode *) aNode { NSMutableDictionary *aDict; aDict = [[NSMutableDictionary alloc] initWithDictionary: [SAFENODE(aNode) nodeData]]; [aDict addEntriesFromDictionary: data]; [SAFENODE(aNode) setNodeData: aDict]; [aDict release]; // Post a notification for all listeners that bookmarks have changed [[NSNotificationCenter defaultCenter] postNotificationName: @"iTermReloadAddressBook" object: nil userInfo: nil]; } - (void) deleteBookmarkNode: (TreeNode *) aNode { [aNode removeFromParent]; // Post a notification for all listeners that bookmarks have changed [[NSNotificationCenter defaultCenter] postNotificationName: @"iTermReloadAddressBook" object: nil userInfo: nil]; } - (TreeNode *) rootNode { return (bookmarks); } - (TreeNode *) defaultBookmark { return (defaultBookmark); } - (void) setDefaultBookmark: (TreeNode *) aNode { NSMutableDictionary *aMutableDict; if(aNode == nil) return; // get the current default bookmark aMutableDict = [NSMutableDictionary dictionaryWithDictionary: [defaultBookmark nodeData]]; [aMutableDict removeObjectForKey: KEY_DEFAULT_BOOKMARK]; [defaultBookmark setNodeData: aMutableDict]; // set the new default bookmark aMutableDict = [NSMutableDictionary dictionaryWithDictionary: [aNode nodeData]]; [aMutableDict setObject: @"Yes" forKey: KEY_DEFAULT_BOOKMARK]; [aNode setNodeData: aMutableDict]; defaultBookmark = aNode; } - (NSDictionary *) defaultBookmarkData { return ([defaultBookmark nodeData]); } - (NSDictionary *) dataForBookmarkWithName: (NSString *) bookmarkName { NSArray *pathComponents; NSEnumerator *anEnumerator; NSString *aPathComponent; TreeNode *theNode; NSDictionary *theData; // break up into path components pathComponents = [bookmarkName componentsSeparatedByString: @"/"]; anEnumerator = [pathComponents objectEnumerator]; theNode = bookmarks; while((aPathComponent = [anEnumerator nextObject]) != NULL && theNode != NULL) { theNode = [self _getBookmarkNodeWithName: aPathComponent searchFromNode: theNode]; } if([theNode isGroup]) theData = nil; else theData = [theNode nodeData]; return (theData); } // migrate any old bookmarks in the old format we might have - (void) migrateOldBookmarks { NSMutableArray *_addressBookArray = [[NSUnarchiver unarchiveObjectWithFile: [ADDRESS_BOOK_FILE stringByExpandingTildeInPath]] retain]; NSDictionary *_adEntry; NSMutableDictionary *aBookmarkData; int i; NSFileManager *fileManager; TreeNode *childNode; NSString *shortcut; for (i = 0; i < [_addressBookArray count]; i++) { _adEntry = [_addressBookArray objectAtIndex: i]; // add all entries except for default entry if([[_adEntry objectForKey:@"DefaultEntry"] boolValue] == NO) { aBookmarkData = [[NSMutableDictionary alloc] init]; [aBookmarkData setObject: [_adEntry objectForKey: @"Name"] forKey: KEY_NAME]; [aBookmarkData setObject: [_adEntry objectForKey: @"Command"] forKey: KEY_DESCRIPTION]; [aBookmarkData setObject: [_adEntry objectForKey: @"Command"] forKey: KEY_COMMAND]; [aBookmarkData setObject: [_adEntry objectForKey: @"Directory"] forKey: KEY_WORKING_DIRECTORY]; [aBookmarkData setObject: [[iTermTerminalProfileMgr singleInstance] defaultProfileName] forKey: KEY_TERMINAL_PROFILE]; [aBookmarkData setObject: [[iTermKeyBindingMgr singleInstance] globalProfileName] forKey: KEY_KEYBOARD_PROFILE]; [aBookmarkData setObject: [[iTermDisplayProfileMgr singleInstance] defaultProfileName] forKey: KEY_DISPLAY_PROFILE]; shortcut=([[_adEntry objectForKey:@"Shortcut"] intValue]? [NSString stringWithFormat:@"%c",[[_adEntry objectForKey:@"Shortcut"] intValue]]:@""); shortcut = [shortcut lowercaseString]; [aBookmarkData setObject: shortcut forKey: KEY_SHORTCUT]; childNode = [[TreeNode alloc] initWithData: aBookmarkData parent: nil children: [NSArray array]]; [childNode setIsLeaf: YES]; [bookmarks insertChild: childNode atIndex: [bookmarks numberOfChildren]]; [childNode release]; [aBookmarkData release]; } } // delete old addressbook file. fileManager = [NSFileManager defaultManager]; if([fileManager isDeletableFileAtPath: [ADDRESS_BOOK_FILE stringByExpandingTildeInPath]]) [fileManager removeFileAtPath: [ADDRESS_BOOK_FILE stringByExpandingTildeInPath] handler: nil]; } // FIXME (GNUstep): #ifdef FOO // NSNetServiceBrowser delegate methods - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing { NSMutableDictionary *aDict; // Get the OS info without using Carbon. This method is recommended by Apple NSDictionary *systemVersionDict = [NSDictionary dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"]; NSString *versionString = [systemVersionDict objectForKey:@"ProductVersion"]; NSArray *osArray = [versionString componentsSeparatedByString:@"."]; int osElementCount = [osArray count]; int osMajor = (osElementCount >= 1) ? [[osArray objectAtIndex:0] intValue] : 0; int osMinor = (osElementCount >= 2) ? [[osArray objectAtIndex:1] intValue] : 0; //int osBugfix = (osElementCount >= 3) ? [[osArray objectAtIndex:2] intValue] : 0; //NSLog(@"%s: %@", __PRETTY_FUNCTION__, aNetService); if(bonjourGroup == nil) { aDict = [[NSMutableDictionary alloc] init]; [aDict setObject: @"Bonjour" forKey: KEY_NAME]; [aDict setObject: @"" forKey: KEY_DESCRIPTION]; [aDict setObject: @"Yes" forKey: KEY_BONJOUR_GROUP]; bonjourGroup = [[TreeNode alloc] initWithData: aDict parent: nil children: [NSArray array]]; [bonjourGroup setIsLeaf: NO]; [aDict release]; } // add a subgroup for this service if it does not already exist [self _getBonjourServiceTypeNode: [aNetService type]]; // resolve the service // add to temporary array to retain it so that resolving works. [bonjourServices addObject: aNetService]; [aNetService setDelegate: self]; if (osMajor == 10 && osMinor >= 4) [aNetService resolveWithTimeout: (NSTimeInterval)5]; else [aNetService resolve]; } - (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didRemoveService:(NSNetService *)aNetService moreComing:(BOOL)moreComing { TreeNode *serviceNode, *childNode, *parentNode; NSDictionary *nodeData; NSEnumerator *anEnumerator; BOOL sshService = NO; //NSLog(@"%s: %@", __PRETTY_FUNCTION__, aNetService); if(aNetService == nil) return; // grab the service group node in the tree serviceNode = [self _getBonjourServiceTypeNode: [aNetService type]]; // remove host entry from this group anEnumerator = [[serviceNode children] objectEnumerator]; while ((childNode = [anEnumerator nextObject])) { nodeData = [childNode nodeData]; if([[nodeData objectForKey: KEY_NAME] isEqualToString: [aNetService name]]) { // check for ssh service to remove sftp service below if([[[serviceNode nodeData] objectForKey: KEY_BONJOUR_SERVICE] isEqualToString: @"ssh"]) sshService = YES; parentNode = [childNode nodeParent]; [childNode removeFromParent]; if([parentNode numberOfChildren] == 0) [parentNode removeFromParent]; break; } } // if this was an ssh service, remove associated sftp service also if(sshService == YES) { // grab the service group node in the tree serviceNode = [self _getBonjourServiceTypeNode: @"_sftp.tcp."]; // remove host entry from this group anEnumerator = [[serviceNode children] objectEnumerator]; while ((childNode = [anEnumerator nextObject])) { nodeData = [childNode nodeData]; if([[nodeData objectForKey: KEY_NAME] isEqualToString: [aNetService name]]) { parentNode = [childNode nodeParent]; [childNode removeFromParent]; if([parentNode numberOfChildren] == 0) [parentNode removeFromParent]; break; } } } // if bonjour group is empty, remove it if([bonjourGroup numberOfChildren] == 0) [bonjourGroup removeFromParent]; // Post a notification for all listeners that bookmarks have changed [[NSNotificationCenter defaultCenter] postNotificationName: @"iTermReloadAddressBook" object: nil userInfo: nil]; } // NSNetService delegate - (void)netServiceDidResolveAddress:(NSNetService *)sender { NSMutableDictionary *aDict; NSData *address = nil; struct sockaddr_in *socketAddress; NSString *ipAddressString = nil; TreeNode *serviceNode; //NSLog(@"%s: %@", __PRETTY_FUNCTION__, sender); // cancel the resolution [sender stop]; if([bonjourServices containsObject: sender] == NO) return; // now that we have at least one resolved service, add the bonjour group to the bookmarks. if([[bookmarks children] containsObject: bonjourGroup] == NO) { [bookmarks insertChild: bonjourGroup atIndex: [bookmarks numberOfChildren]]; } // grab the address address = [[sender addresses] objectAtIndex: 0]; socketAddress = (struct sockaddr_in *)[address bytes]; ipAddressString = [NSString stringWithFormat:@"%s", inet_ntoa(socketAddress->sin_addr)]; aDict = [[NSMutableDictionary alloc] init]; serviceNode = [self _getBonjourServiceTypeNode: [sender type]]; [aDict setObject: [NSString stringWithFormat: @"%@", [sender name]] forKey: KEY_NAME]; [aDict setObject: [NSString stringWithFormat: @"%@", [sender name]] forKey: KEY_DESCRIPTION]; [aDict setObject: [NSString stringWithFormat: @"%@ %@", [[serviceNode nodeData] objectForKey: KEY_BONJOUR_SERVICE], ipAddressString] forKey: KEY_COMMAND]; [aDict setObject: @"" forKey: KEY_WORKING_DIRECTORY]; [aDict setObject: [[iTermTerminalProfileMgr singleInstance] defaultProfileName] forKey: KEY_TERMINAL_PROFILE]; [aDict setObject: [[iTermKeyBindingMgr singleInstance] globalProfileName] forKey: KEY_KEYBOARD_PROFILE]; [aDict setObject: [[iTermDisplayProfileMgr singleInstance] defaultProfileName] forKey: KEY_DISPLAY_PROFILE]; [aDict setObject: ipAddressString forKey: KEY_BONJOUR_SERVICE_ADDRESS]; [[ITAddressBookMgr sharedInstance] addBookmarkWithData: aDict toNode: serviceNode]; // No bonjour service for sftp. Rides over ssh, so try to detect that if([[[serviceNode nodeData] objectForKey: KEY_BONJOUR_SERVICE] isEqualToString: @"ssh"]) { serviceNode = [self _getBonjourServiceTypeNode: @"_sftp._tcp."]; [aDict setObject: [NSString stringWithFormat: @"%@", [sender name]] forKey: KEY_NAME]; [aDict setObject: [NSString stringWithFormat: @"%@", [sender name]] forKey: KEY_DESCRIPTION]; [aDict setObject: [NSString stringWithFormat: @"%@ %@", [[serviceNode nodeData] objectForKey: KEY_BONJOUR_SERVICE], ipAddressString] forKey: KEY_COMMAND]; [aDict setObject: @"" forKey: KEY_WORKING_DIRECTORY]; [aDict setObject: [[iTermTerminalProfileMgr singleInstance] defaultProfileName] forKey: KEY_TERMINAL_PROFILE]; [aDict setObject: [[iTermKeyBindingMgr singleInstance] globalProfileName] forKey: KEY_KEYBOARD_PROFILE]; [aDict setObject: [[iTermDisplayProfileMgr singleInstance] defaultProfileName] forKey: KEY_DISPLAY_PROFILE]; [[ITAddressBookMgr sharedInstance] addBookmarkWithData: aDict toNode: serviceNode]; } [aDict release]; // remove from array now that resolving is done if([bonjourServices containsObject: sender]) [bonjourServices removeObject: sender]; // Post a notification for all listeners that bookmarks have changed [[NSNotificationCenter defaultCenter] postNotificationName: @"iTermReloadAddressBook" object: nil userInfo: nil]; } - (void)netService:(NSNetService *)aNetService didNotResolve:(NSDictionary *)errorDict { //NSLog(@"%s: %@", __PRETTY_FUNCTION__, aNetService); [aNetService stop]; } - (void)netServiceWillResolve:(NSNetService *)aNetService { //NSLog(@"%s: %@", __PRETTY_FUNCTION__, aNetService); } - (void)netServiceDidStop:(NSNetService *)aNetService { //NSLog(@"%s: %@", __PRETTY_FUNCTION__, aNetService); } #endif (// FIXME (GNUstep):) @end @implementation ITAddressBookMgr (Private); - (BOOL) _checkForDefaultBookmark: (TreeNode *) rootNode defaultBookmark: (TreeNode **) aNode { BOOL haveDefaultBookmark = NO; NSEnumerator *entryEnumerator; NSDictionary *dataDict; TreeNode *entry; entryEnumerator = [[rootNode children] objectEnumerator]; while ((entry = [entryEnumerator nextObject])) { if([entry isGroup]) haveDefaultBookmark = [self _checkForDefaultBookmark: entry defaultBookmark: aNode]; else { dataDict = [entry nodeData]; if([[dataDict objectForKey: KEY_DEFAULT_BOOKMARK] isEqualToString: @"Yes"]) { if(aNode) *aNode = entry; haveDefaultBookmark = YES; } } if(haveDefaultBookmark) break; } return (haveDefaultBookmark); } - (TreeNode *) _getBookmarkNodeWithName: (NSString *) aName searchFromNode: (TreeNode *) aNode { NSEnumerator *entryEnumerator; NSDictionary *dataDict; TreeNode *entry, *theNode; dataDict = nil; entryEnumerator = [[aNode children] objectEnumerator]; while ((entry = [entryEnumerator nextObject])) { dataDict = [entry nodeData]; if([[dataDict objectForKey: KEY_NAME] isEqualToString: aName]) { return (entry); } else if([entry isGroup]) { theNode = [self _getBookmarkNodeWithName: aName searchFromNode: entry]; if(theNode != nil) return (theNode); } } return (nil); } // FIXME (GNUstep): /* - (TreeNode *) _getBonjourServiceTypeNode: (NSString *) aType { NSEnumerator *keyEnumerator; BOOL aBool; TreeNode *childNode; NSRange aRange; NSString *serviceType = aType; NSMutableDictionary *aDict; if([aType length] <= 0) return (nil); aRange = [serviceType rangeOfString: @"."]; if(aRange.location != NSNotFound) { serviceType = [serviceType substringWithRange: NSMakeRange(1, aRange.location - 1)]; } aBool = NO; keyEnumerator = [[bonjourGroup children] objectEnumerator]; while ((childNode = [keyEnumerator nextObject])) { if([[[childNode nodeData] objectForKey: KEY_NAME] isEqualToString: serviceType]) { aBool = YES; break; } } if(aBool == NO) { aDict = [[NSMutableDictionary alloc] init]; [aDict setObject: serviceType forKey: KEY_NAME]; [aDict setObject: @"" forKey: KEY_DESCRIPTION]; [aDict setObject: serviceType forKey: KEY_BONJOUR_SERVICE]; childNode = [[TreeNode alloc] initWithData: aDict parent: nil children: [NSArray array]]; [childNode setIsLeaf: NO]; [aDict release]; [bonjourGroup insertChild: childNode atIndex: [bonjourGroup numberOfChildren]]; [childNode release]; } return (childNode); } */ @end