/*
* Copyright (c) 2003 Apple Computer, Inc. All rights reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
/*!
* @header PathManager
*/
#import "PathManager.h"
#import "PathDirService.h"
#import "PathNode.h"
#import "PathNodeConfig.h"
#import "PathNodeSearch.h"
#import "DSoDirectory.h"
#import "DSoNode.h"
#import "DSoException.h"
#import "NSStringEscPath.h"
BOOL gRawMode = NO;
@implementation PathManager
// ----------------------------------------------------------------------------
// Initialization / teardown
#pragma mark ******** Initialization / teardown ********
- init
{
[super init];
_stack = [[NSMutableArray alloc] init];
_pushdPopdStack = [[NSMutableArray alloc] init];
_stackBackup = nil;
return self;
}
// Open a connection to the local machine.
- initWithLocal
{
id dirBase;
[self init];
dirBase = [[PathDirService alloc] initWithLocal];
[_stack addObject:dirBase];
[dirBase release];
return self;
}
// Open a conection to a slimmed version of DS daemon that likely ONLY has a local node in it.
- initWithLocalPath:(NSString*)filePath
{
id dirBase;
[self init];
dirBase = [[PathDirService alloc] initWithLocalPath:filePath];
[_stack addObject:dirBase];
[dirBase release];
return self;
}
// Open a conection to a remote machine using DS Proxy.
- initWithHost:(NSString*)hostName user:(NSString*)user password:(NSString*)password
{
id dirBase;
[self init];
dirBase = [[PathDirService alloc] initWithHost:hostName user:user password:password];
[_stack addObject:dirBase];
[dirBase release];
return self;
}
- initWithNodeEnum:(int)inNodeEnum
{
DSoDirectory *dir = nil;
DSoNode *node = nil;
PathNode *dirBase = nil;
[self init];
NS_DURING
dir = [[DSoDirectory alloc] initWithLocal];
node = [dir findNodeViaEnum:inNodeEnum];
dirBase = [[PathNode alloc] initWithNode:node path:@"/"];
[dir release];
NS_HANDLER
[dir release];
[self release];
if ([localException isKindOfClass:[DSoException class]])
{
[dirBase release];
dirBase = nil;
}
else
[localException raise];
NS_ENDHANDLER
if (dirBase)
{
[dirBase setEnableSubNodes:NO];
[_stack addObject:dirBase];
[dirBase release];
return self;
}
else
return nil;
}
- initWithNodeName:(NSString*)inNodeName
{
DSoDirectory *dir = nil;
DSoNode *node = nil;
PathNode *dirBase = nil;
[self init];
NS_DURING
dir = [[DSoDirectory alloc] initWithLocal];
node = [dir findNode:inNodeName];
if ([inNodeName isEqualToString:@"/Search"]
|| [inNodeName isEqualToString:@"/Search/Contacts"]) {
dirBase = [[PathNodeSearch alloc] initWithNode:node path:@"/"];
} else if ([inNodeName isEqualToString:@"/Configure"]) {
dirBase = [[PathNodeConfig alloc] initWithNode:node path:@"/"];
} else {
dirBase = [[PathNode alloc] initWithNode:node path:@"/"];
}
[dir release];
NS_HANDLER
[dir release];
[self release];
if ([localException isKindOfClass:[DSoException class]])
{
[dirBase release];
dirBase = nil;
}
else
[localException raise];
NS_ENDHANDLER
if (dirBase)
{
[dirBase setEnableSubNodes:NO];
[_stack addObject:dirBase];
[dirBase release];
return self;
}
else
return nil;
}
- initWithNodeName:(NSString*)inNodeName user:(NSString*)inUsername password:(NSString*)inPassword
{
PathNode *node = nil;
tDirStatus status = eDSNoErr;
if ([self initWithNodeName:inNodeName] == nil)
return nil;
node = [_stack objectAtIndex:0];
status = [node authenticateName:inUsername withPassword:inPassword];
if (status != eDSNoErr)
{
[_stack release];
[_stackBackup release];
return nil;
}
return self;
}
- initWithLocalNode
{
DSoDirectory *dir = nil;
DSoNode *localNode = nil;
PathNode *dirBase = nil;
[self init];
dir = [[DSoDirectory alloc] initWithLocal];
localNode = [dir localNode];
dirBase = [[PathNode alloc] initWithNode:localNode path:@"/"];
[dirBase setEnableSubNodes:NO];
[_stack addObject:dirBase];
[dirBase release];
[dir release];
return self;
}
- initWithLocalNodeAuthUser:(NSString*)inUsername password:(NSString*)inPassword
{
PathNode *node = nil;
tDirStatus status = eDSNoErr;
[self initWithLocalNode];
node = [_stack objectAtIndex:0];
status = [node authenticateName:inUsername withPassword:inPassword];
if (status != eDSNoErr)
{
[_stack release];
[_stackBackup release];
return nil;
}
return self;
}
- (void)dealloc
{
[_stack release];
[_pushdPopdStack release];
[_stackBackup release]; // just in case.
[super dealloc];
}
// ----------------------------------------------------------------------------
// Command actions
#pragma mark ******** Command Actions ********
- (tDirStatus)authenticateUser:(NSString*)inUsername password:(NSString*)inPassword authOnly:(BOOL)inAuthOnly
{
tDirStatus authStatus = eDSNoErr;
authStatus = [(PathNode*)[_stack lastObject] authenticateName:inUsername withPassword:inPassword authOnly:inAuthOnly];
if (authStatus != eDSNoErr)
{
printf("Authentication for node authStatus, [[DSoStatus sharedInstance] cStringForStatus:authStatus]);
}
return authStatus;
}
- (void)list:(NSString*)inPath key:(NSString*)inKey
{
if (inPath == nil)
{
[(PathItem*)[_stack lastObject] list:inPath key:inKey];
}
else
{
[self backupStack];
[self cd:inPath];
[(PathItem*)[_stack lastObject] list:inPath key:inKey];
[self restoreStack];
}
}
- (tDirStatus)createRecord:(NSString*)inRecordPath key:(NSString*)inKey values:(NSArray*)inValues
{
tDirStatus status = eDSNoErr;
if (inRecordPath == nil || [inRecordPath isEqualToString:@"."] )
{
status = [[_stack lastObject] createKey:inKey withValues:inValues];
}
else
{
[self backupStack];
status = [self createAndCd:inRecordPath];
if (status == eDSNoErr && inKey != nil)
status = [[_stack lastObject] createKey:inKey withValues:inValues];
[self restoreStack];
}
return status;
}
- (tDirStatus)appendToRecord:(NSString*)inRecordPath key:(NSString*)inKey values:(NSArray*)inValues
{
tDirStatus status = eDSNoErr;
if (inRecordPath == nil || [inRecordPath isEqualToString:@"."] )
{
status = [[_stack lastObject] appendKey:inKey withValues:inValues];
}
else
{
[self backupStack];
[self cd:inRecordPath];
status = [[_stack lastObject] appendKey:inKey withValues:inValues];
[self restoreStack];
}
return status;
}
- (tDirStatus)deleteInRecord:(NSString*)inRecordPath key:(NSString*)inKey values:(NSArray*)inValues
{
tDirStatus status = eDSNoErr;
if (inRecordPath == nil || [inRecordPath isEqualToString:@"."] )
{
status = [[_stack lastObject] deleteKey:inKey withValues:inValues];
}
else
{
[self backupStack];
[self cd:inRecordPath];
status = [[_stack lastObject] deleteKey:inKey withValues:inValues];
[self restoreStack];
}
return status;
}
- (tDirStatus)deleteRecord:(NSString*)inRecordPath
{
tDirStatus status = eDSNoErr;
if (inRecordPath == nil || [inRecordPath isEqualToString:@"."] )
{
status = [[_stack lastObject] deleteItem];
if (status == eDSNoErr)
{
[_stack removeLastObject];
}
}
else
{
[self backupStack];
[self cd:inRecordPath];
status = [[_stack lastObject] deleteItem];
[self restoreStack];
}
return status;
}
- (tDirStatus)mergeToRecord:(NSString*)inRecordPath key:(NSString*)inKey values:(NSArray*)inValues
{
tDirStatus status = eDSNoErr;
if (inRecordPath == nil || [inRecordPath isEqualToString:@"."] )
{
status = [[_stack lastObject] mergeKey:inKey withValues:inValues];
}
else
{
[self backupStack];
[self cd:inRecordPath];
status = [[_stack lastObject] mergeKey:inKey withValues:inValues];
[self restoreStack];
}
return status;
}
- (tDirStatus)changeInRecord:(NSString*)inRecordPath key:(NSString*)inKey values:(NSArray*)inValues
{
tDirStatus status = eDSNoErr;
if (inRecordPath == nil || [inRecordPath isEqualToString:@"."] )
{
status = [[_stack lastObject] changeKey:inKey oldAndNewValues:inValues];
}
else
{
[self backupStack];
[self cd:inRecordPath];
status = [[_stack lastObject] changeKey:inKey oldAndNewValues:inValues];
[self restoreStack];
}
return status;
}
- (tDirStatus)changeInRecordByIndex:(NSString*)inRecordPath key:(NSString*)inKey values:(NSArray*)inValues
{
tDirStatus status = eDSNoErr;
if (inRecordPath == nil || [inRecordPath isEqualToString:@"."] )
{
status = [[_stack lastObject] changeKey:inKey indexAndNewValue:inValues];
}
else
{
[self backupStack];
[self cd:inRecordPath];
status = [[_stack lastObject] changeKey:inKey indexAndNewValue:inValues];
[self restoreStack];
}
return status;
}
- (tDirStatus)read:(NSString*)inPath keys:(NSArray*)inKeys
{
tDirStatus status = eDSNoErr;
if (inPath == nil)
{
status = (tDirStatus)[[_stack lastObject] read:inKeys];
}
else
{
[self backupStack];
// Mount records can look like a path, but need to be treated as a
// single entity. Mount records look like: "machine:/path/to/somewhere".
// Need to handle both: "machine:/path/to/somewhere" and
// "/LDAPv3/127.0.0.1/machine:/path/to/somewhere".
NSRange colon = [inPath rangeOfString:@":"];
if (colon.location == NSNotFound)
{
// Regular path (not a mount record).
[self cd:[inPath stringByDeletingLastPathComponent]];
status = (tDirStatus)[[_stack lastObject] read:[inPath lastPathComponent] keys:inKeys];
}
else
{
// Mount record. Split the DS path from the mount record itself.
NSString* mountRecString;
// Find the first slash that preceeds the colon - that will be the
// end of the DS path.
colon.length = colon.location;
colon.location = 0;
NSRange dsPathEnd = [inPath rangeOfString:@"/" options:NSBackwardsSearch range:colon];
if (dsPathEnd.location == NSNotFound)
{
// No DS path preceeding the mount record.
mountRecString = inPath;
}
else
{
// Split the DS path from the mount record.
NSString* dsPath = [inPath substringToIndex:dsPathEnd.location];
mountRecString = [inPath substringFromIndex:dsPathEnd.location + 1];
[self cd:dsPath];
}
status = (tDirStatus)[[_stack lastObject] read:mountRecString keys:inKeys];
}
[self restoreStack];
}
return status;
}
- (tDirStatus)readAll:(NSString*)inPath keys:(NSArray*)inKeys
{
tDirStatus status = eDSNoErr;
if (inPath == nil)
{
status = [[_stack lastObject] readAll:inKeys];
}
else
{
[self backupStack];
[self cd:inPath];
status = [[_stack lastObject] readAll:inKeys];
[self restoreStack];
}
return status;
}
- (tDirStatus)read:(NSString*)inPath key:(NSString*)inKey plistPath:(NSString*)inPlistPath
{
tDirStatus status = eDSNoErr;
if (inPath != nil)
{
[self backupStack];
[self cd:inPath];
status = (tDirStatus)[[_stack lastObject] read:inKey plistPath:inPlistPath];
[self restoreStack];
}
return status;
}
- (tDirStatus)read:(NSString*)inPath key:(NSString*)inKey atIndex:(int)index plistPath:(NSString*)inPlistPath
{
tDirStatus status = eDSNoErr;
if (inPath != nil)
{
[self backupStack];
[self cd:inPath];
status = (tDirStatus)[[_stack lastObject] read:inKey atIndex:index plistPath:inPlistPath];
[self restoreStack];
}
return status;
}
- (tDirStatus)create:(NSString*)inPath key:(NSString*)inKey plistPath:(NSString*)inPlistPath values:(NSArray*)inValues
{
tDirStatus status = eDSNoErr;
if (inPath != nil)
{
[self backupStack];
[self cd:inPath];
status = (tDirStatus)[[_stack lastObject] create:inKey plistPath:inPlistPath values:inValues];
[self restoreStack];
}
return status;
}
- (tDirStatus)create:(NSString*)inPath key:(NSString*)inKey atIndex:(int)index plistPath:(NSString*)inPlistPath values:(NSArray*)inValues
{
tDirStatus status = eDSNoErr;
if (inPath != nil)
{
[self backupStack];
[self cd:inPath];
status = (tDirStatus)[[_stack lastObject] create:inKey atIndex:index plistPath:inPlistPath values:inValues];
[self restoreStack];
}
return status;
}
- (tDirStatus)delete:(NSString*)inPath key:(NSString*)inKey plistPath:(NSString*)inPlistPath values:(NSArray*)inValues
{
tDirStatus status = eDSNoErr;
if(inPath != nil)
{
[self backupStack];
[self cd:inPath];
status = (tDirStatus)[[_stack lastObject] delete:inKey plistPath:inPlistPath values:inValues];
[self restoreStack];
}
return status;
}
- (tDirStatus)delete:(NSString*)inPath key:(NSString*)inKey atIndex:(int)index plistPath:(NSString*)inPlistPath values:(NSArray*)inValues
{
tDirStatus status = eDSNoErr;
if(inPath != nil)
{
[self backupStack];
[self cd:inPath];
status = (tDirStatus)[[_stack lastObject] delete:inKey atIndex:index plistPath:inPlistPath values:inValues];
[self restoreStack];
}
return status;
}
- (void)diff:(NSString*)inPath1 otherPath:(NSString*)inPath2 keys:(NSArray*)inKeys
{
NSDictionary* dict1 = nil;
NSDictionary* dict2 = nil;
if (inPath1 != nil && inPath2 != nil)
{
// should always be true but just in case
[self backupStack];
[self cd:inPath1];
dict1 = [[_stack lastObject] getDictionary:inKeys];
[self restoreStack];
[self backupStack];
[self cd:inPath2];
dict2 = [[_stack lastObject] getDictionary:inKeys];
[self restoreStack];
// Get the union of the keys from both dictionaries and then sort them in to an array
NSArray *keys = [dict1 allKeys];
NSMutableSet *keysSet = [[NSMutableSet alloc] initWithArray:keys];
keys = [dict2 allKeys];
[keysSet addObjectsFromArray:keys];
keys = [keysSet allObjects];
[keys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
NSEnumerator *enumerator = [keys objectEnumerator];
id key;
while(key = [enumerator nextObject])
{
id object1;
id object2;
object1 = [dict1 objectForKey:key];
object2 = [dict2 objectForKey:key];
if(![object2 isEqual:object1])
{
if(object1 != nil)
{
printAttribute(key, object1, @"- ");
}
if(object2 != nil)
{
printAttribute(key, object2, @"+ ");
}
}
}
[keysSet release];
}
}
- (tDirStatus) setPasswordForUser:(NSString*)inRecordPath withParams:(NSArray*)inParams
{
tDirStatus status = eDSNoErr;
if ([inRecordPath isEqualToString:@"."])
{
status = [[_stack lastObject] setPassword:inParams];
}
else
{
[self backupStack];
[self cd:inRecordPath];
status = [[_stack lastObject] setPassword:inParams];
[self restoreStack];
}
return status;
}
- (tDirStatus)createAndCd:(NSString*)inPath
{
NSArray *pathComponents = [inPath unescapedPathComponents];
NSString *pathComp = nil;
unsigned int i = 0;
tDirStatus status = eDSNoErr;
int cntLimit = 0;
if ([[pathComponents objectAtIndex:0] isEqualToString:@"/"])
i = 1;
cntLimit = [pathComponents count];
for(; i < cntLimit; i++)
{
pathComp = [pathComponents objectAtIndex:i];
status = [[_stack lastObject] createKey:pathComp withValues:nil];
if (status == eDSRecordAlreadyExists)
status = eDSNoErr;
else if (status != eDSNoErr)
break;
[self cd: [pathComp escapedString]];
}
return status;
}
- (void)cd:(NSString*)dest
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
PathItem *p = nil;
NSArray *pathElements = nil;
NSMutableArray *newPath = [[NSMutableArray alloc] initWithArray:_stack];
NSString *s = nil;
int i = 0;
int start = 0;
BOOL failure = NO;
pathElements = [dest unescapedPathComponents];
if ( [pathElements count] )
{
// If the first element is empty, then we have specified an absolute path.
// So strip the new stack down to the base element.
if ([[pathElements objectAtIndex:0] isEqualToString:@"/"])
{
if ( [_stack count] > 1)
{
NSRange r = NSMakeRange(1,[_stack count] - 1);
[newPath removeObjectsInRange:r];
}
start = 1;
}
else
{
start = 0;
}
// Now iterate through the elements and successively 'cd' into each one.
NS_DURING
int cntLimit = [pathElements count];
for (i = start; i < cntLimit && !failure; i++)
{
s = [pathElements objectAtIndex:i];
// If it is "..", then go up a path by removing the item from the stack.
if ([s isEqualToString:@".."])
{
// If they have already reached the top, then don't go any farther.
if ([newPath count] > 1)
[newPath removeLastObject];
}
// If it is empty or they have specifed the extraneous "root" after "/NetInfo", then just skip this element.
else if ([s length] == 0 || [s isEqualToString:@"."] ||
([s isEqualToString:@"root"] && [[[newPath lastObject] name] isEqualToString:@"NetInfo/root"]) )
{
continue;
}
// Otherwise, add the next item down.
else
{
// don't do anything to s, PathItem takes unescaped strings only
p = [(PathItem*)[newPath lastObject] cd:s];
if (p != nil)
[newPath addObject:p];
else
failure = YES;
}
}
NS_HANDLER
// If there was an exception (such as too many "cd .." causing the newPath
// Array to empty, then fail the whole command.
failure = YES;
NS_ENDHANDLER
if (!failure)
{
// Success! replace the _stack variable with our new, updated copy.
[_stack release];
_stack = newPath;
}
else
{
// Failure, release the copy, pool, and raise error.
[newPath release];
[pool release];
[NSException raise:@"DSCL" format:@"Invalid Path"];
}
}
[pool release];
}
- (void)pushd:(NSString*)dest
{
if (dest == nil)
{
NSMutableArray *swapStack = nil;
if ([_pushdPopdStack count] == 0)
[NSException raise:@"DSCL" format:@"no other directory"];
// No new destination was specified. Swap the current path and the top path:
swapStack = [[_pushdPopdStack lastObject] retain];
[_pushdPopdStack removeLastObject];
[_pushdPopdStack addObject:_stack];
[_stack release];
_stack = swapStack;
}
else
{
[_pushdPopdStack addObject:_stack];
NS_DURING
[self cd:dest];
NS_HANDLER
[_pushdPopdStack removeLastObject];
[localException raise];
NS_ENDHANDLER
}
[self printPushdPopdStack];
}
- (void)popd
{
if ([_pushdPopdStack count] == 0)
[NSException raise:@"DSCL" format:@"Directory stack empty."];
[_stack release];
_stack = [[_pushdPopdStack lastObject] retain];
[_pushdPopdStack removeLastObject];
[self printPushdPopdStack];
}
- (NSString*)cwd
{
NSString *outCwd = nil;
NSEnumerator *stackEnum = [_stack objectEnumerator];
NSMutableArray *pathArray = [NSMutableArray array];
PathItem *pathItem;
// loop over all of them and add names
while( pathItem = [stackEnum nextObject] )
{
[pathArray addObject:[pathItem name]];
}
if( [pathArray count] == 0 ) {
outCwd = @"/";
} else {
outCwd = [NSString escapablePathFromArray: pathArray];
}
return outCwd;
}
- (NSArray*)getCurrentList:(NSString*)inPath
{
NSArray *retValue = nil;
NSArray *inPathComponents = [inPath unescapedPathComponents];
// First drop the last item on the inPath...
// (Later we may pick it back up and pass it to the getList methods)
if ([inPathComponents count] > 1)
inPath = [NSString escapablePathFromArray: [inPathComponents subarrayWithRange: NSMakeRange(0, [inPathComponents count] - 1)]];
else
inPath = nil;
if (inPath == nil)
{
retValue = [[_stack lastObject] getList];
}
else
{
[self backupStack];
[self cd:[inPath escapedString]];
retValue = [[_stack lastObject] getList];
[self restoreStack];
}
return retValue;
}
- (NSArray*)getPossibleCompletionsFor:(NSString*)inPathAndPrefix
{
NSArray *retValue = nil;
NSArray *pathComponents = [inPathAndPrefix unescapedPathComponents];
NSString *prefix = [[pathComponents lastObject] lowercaseString];
unsigned int pathCount = [pathComponents count];
if (pathCount > 1)
{
NSArray *tempArray = [pathComponents subarrayWithRange: NSMakeRange(0, pathCount - 1)];
[self backupStack];
[self cd: [NSString escapablePathFromArray: tempArray]];
retValue = [[_stack lastObject] getPossibleCompletionsFor: prefix];
[self restoreStack];
}
else if( prefix )
{
retValue = [[_stack lastObject] getPossibleCompletionsFor: prefix];
}
return retValue;
}
- (void)searchInPath:(NSString*)inPath forKey:(NSString*)inKey withValue:(NSString*)inValue matchType:(NSString*)inType
{
BOOL isCurrentDir = [inPath isEqualToString:@"."];
if (!isCurrentDir)
{
[self backupStack];
[self cd:inPath];
}
[[_stack lastObject] searchForKey:inKey withValue:inValue matchType:inType];
if (!isCurrentDir)
[self restoreStack];
}
// ----------------------------------------------------------------------------
// Utility methods
#pragma mark ******** Utility methods ********
- (void)backupStack
{
_stackBackup = [[NSMutableArray alloc] initWithArray:_stack];
}
- (void)restoreStack
{
if (_stackBackup != nil)
{
[_stack release];
_stack = _stackBackup;
_stackBackup = nil;
}
}
- (void)printPushdPopdStack
{
int i = 0;
int j = 0;
int cnt = 0;
printf(" cnt = [_pushdPopdStack count];
for (i=cnt-1; i >= 0 ; i--)
{
NSArray *pathStack = [_pushdPopdStack objectAtIndex:i];
int cntLimit = [pathStack count];
if (cntLimit == 1)
printf("/");
else
{
for (j=1; j < cntLimit; j++)
{
NSString *pathItemName = [[pathStack objectAtIndex:j] name];
printf("/ }
}
printf(" ");
}
printf("\n");
}
-(NSArray*) stack
{
// ATM - give PlugInManager access to the stack
return _stack;
}
@end