/* Copyright (C) 2002-2003, Jeffrey D. Argast. The authors make NO WARRANTY or representation, either express or implied, with respect to this software, its quality, accuracy, merchantability, or fitness for a particular purpose. This software is provided "AS IS", and you, its user, assume the entire risk as to its quality and accuracy. Permission is hereby granted to use, copy, modify, and distribute this software or portions thereof for any purpose, without fee, subject to these conditions: (1) If any part of the source code is distributed, then this statement must be included, with this copyright and no-warranty notice unaltered. (2) Permission for use of this software is granted only if the user accepts full responsibility for any undesirable consequences; the authors accept NO LIABILITY for damages of any kind. */ #import "PerforceDirectory.h" #import "PerforceActionDirsAndFstat.h" #import "PerforceActionDirs.h" #import "PerforceActionFstat.h" #import "IconController.h" #import "IconDefs.h" #import "AppUtils.h" #import "MessageDefs.h" #import "PerforceActionWhere.h" #import "AppDefaults.h" #import "PerforceFile.h" static NSString* kNibExt = @"nib"; static NSString* kPBExt = @"pbproj"; static NSString* kXCodeExt = @"xcode"; @implementation PerforceDirectory + (PerforceDirectory *) directoryWithPath: (NSString *) fullPath { return [[[PerforceDirectory alloc] initWithPath: fullPath] autorelease]; } - (id) initWithPath: (NSString *) fullPath { if ( self = [super init] ) { fDepotPath = [fullPath retain]; fName = ExtractLastPathComponent (fDepotPath); [fName retain]; fDirChildren = nil; fFileChildren = nil; fTempFstatFiles = nil; fTempLocalFiles = nil; fClientPath = nil; fShouldBuildChildren = YES; fExpandAfterBuilding = NO; fBuildingDirs = NO; fBuildingFiles = NO; fBuildingFstatFiles = NO; fBuildingLocalFiles = NO; fExpandPaths = [[NSMutableArray alloc] init]; fTotalNumChildren = 0; fNumFiles = 0; fNumDirs = 0; IconController* iconController = [IconController defaultIconController]; NSString* dirExtension = [fName pathExtension]; if ( dirExtension ) { if ( [dirExtension isEqualToString:kNibExt] ) { fIcon = [[iconController getIcon:kDirNibIcon] retain]; } else if ( [dirExtension isEqualToString:kPBExt] ) { fIcon = [[iconController getIcon:kDirPBIcon] retain]; } else if ( [dirExtension isEqualToString:kXCodeExt] ) { fIcon = [[iconController getIcon:kDirXCodeIcon] retain]; } else { fIcon = [[iconController getIcon:kDirIcon] retain]; } } else { fIcon = [[iconController getIcon:kDirIcon] retain]; } } return self; } - (void) dealloc { [fDirChildren release]; [fFileChildren release]; [fName release]; [fDepotPath release]; [fExpandPaths release]; [fIcon release]; [fTempFstatFiles release]; [fTempLocalFiles release]; [fClientPath release]; [super dealloc]; } - (void) expandIfNeeded { if ( fBuildingFiles || fBuildingDirs ) return; if ( fExpandAfterBuilding ) { int numPaths = [fExpandPaths count]; int n; for ( n = 0; n < numPaths; n++ ) { [self expandPath:[fExpandPaths objectAtIndex:n]]; } [fExpandPaths removeAllObjects]; } fExpandAfterBuilding = NO; } - (void) releaseChildren { fShouldBuildChildren = YES; int n; for ( n = 0; n < fNumDirs; n++ ) { PerforceDirectory* childDir = [fDirChildren objectAtIndex:n]; [childDir releaseChildren]; } } - (void) resultFromDirs: (PerforceActionDirs*) action { NSArray* newDirs = [action getDirs]; // first off, if we have no dirs, then just copy if ( !fDirChildren ) { fDirChildren = [[NSMutableArray alloc] initWithArray:newDirs]; } else { // avoid changing the dir pointer so that the outline view still thinks it // is valid thereby updating it without collapsing it. // we assume that perforce dirs will always return the dirs in the same order // we'll march down the list of dirs and any new ones will be added before // the existing one. At the end of the incoming list if there are some // in the existing list not yet found, then we remove them -- should we? // isn't that a never should happen error case? int numExisting = [fDirChildren count]; int numNew = [newDirs count]; int existingIndex = numExisting - 1; int newIndex = numNew - 1; // This is a valid case: Hiding deleted files on, delete all files in a directory, // submit. numNew is less than numExisting in that case // NSAssert (numNew >= numExisting, @"Perforce dirs returned fewer directories than last time"); // we go backwards because we are inserting elements into fDirChildren and we don't // want to skip the inserts while ( (existingIndex >= 0) && (newIndex >= 0) ) { PerforceDirectory* existingDir = [fDirChildren objectAtIndex:existingIndex]; PerforceDirectory* newDir = [newDirs objectAtIndex:newIndex]; if ( [existingDir->fDepotPath isEqualTo:newDir->fDepotPath] ) { existingIndex--; newIndex--; } else { // according to the documentation of NSMutableArray insertObject:atIndex: // an index equal to the number of elements in the array is valid // and appends the item to the list [fDirChildren insertObject:newDir atIndex:(existingIndex + 1)]; newIndex--; } } int insertIndex = existingIndex + 1; while ( newIndex >= 0 ) { PerforceDirectory* newDir = [newDirs objectAtIndex:newIndex]; [fDirChildren insertObject:newDir atIndex:insertIndex]; newIndex--; } if ( insertIndex > 0 ) { int removeStart = 0; int removeLen = insertIndex; [fDirChildren removeObjectsInRange:NSMakeRange (removeStart, removeLen)]; } } fNumDirs = [fDirChildren count]; fTotalNumChildren = fNumFiles + fNumDirs; fBuildingDirs = NO; [self expandIfNeeded]; SendNotificationWithObject (kDepotControllerChildChanged, self); } - (void) setChildrenFiles { id filesArray = fTempFstatFiles; if ( fTempLocalFiles ) { NSFileManager* nsFileManager = [NSFileManager defaultManager]; id anObject = nil; // Create a set from the file paths NSMutableSet* tempSet = [NSMutableSet set]; NSEnumerator* pfileEnumer = [fTempFstatFiles objectEnumerator]; while ( anObject = [pfileEnumer nextObject] ) { [tempSet addObject:[anObject clientPath]]; } filesArray = [NSMutableArray arrayWithArray:fTempFstatFiles]; NSEnumerator* enumer = [fTempLocalFiles objectEnumerator]; BOOL isDir = NO; while ( anObject = [enumer nextObject] ) { if ( ![anObject isEqualTo:@".DS_Store"] ) { NSString* objectPath = [NSString stringWithFormat:@"%@/%@", fClientPath, anObject]; if ( ![tempSet containsObject:objectPath] ) { if ( [nsFileManager fileExistsAtPath:objectPath isDirectory:&isDir] ) { if ( !isDir ) { PerforceFile* localFile = [PerforceFile fileFromLocalPath:objectPath]; [filesArray addObject:localFile]; } } } } } [filesArray sortUsingSelector:@selector(caseSensitiveFilenameCompare:)]; } [fFileChildren release]; fFileChildren = [filesArray retain]; fNumFiles = [fFileChildren count]; fTotalNumChildren = fNumFiles + fNumDirs; fBuildingFiles = NO; [fTempFstatFiles release]; [fTempLocalFiles release]; fTempFstatFiles = nil; fTempLocalFiles = nil; [self expandIfNeeded]; SendNotificationWithObject (kDepotControllerChildChanged, self); } - (void) resultFromPerforceWhere: (PerforceActionWhere*) action { [fTempLocalFiles release]; fTempLocalFiles = nil; [fClientPath release]; fClientPath = nil; if ( [action wasSuccess] ) { fClientPath = [[action getFilePath] retain]; // Collect a list of files in dirPath NSFileManager* nsFileManager = [NSFileManager defaultManager]; NSArray* dirContents = [nsFileManager directoryContentsAtPath:fClientPath]; if ( dirContents ) { fTempLocalFiles = [dirContents retain]; } } fBuildingLocalFiles = NO; if ( !fBuildingFstatFiles ) { [self setChildrenFiles]; } } - (void) resultFromFstat: (PerforceActionFstat*) action { [fTempFstatFiles release]; fTempFstatFiles = [[action getFiles] retain]; fBuildingFstatFiles = NO; if ( !fBuildingLocalFiles ) { [self setChildrenFiles]; } } - (void) buildChildren { NSMutableString* dirPath = [NSMutableString stringWithString:fDepotPath]; [dirPath appendString:@"/*"]; [PerforceAction abortAllOwnersActions:self]; [PerforceActionDirs defaultRunFor:self selector:@selector(resultFromDirs:) withPath:dirPath]; [PerforceActionFstat defaultRunFor:self selector:@selector(resultFromFstat:) withPath:dirPath]; fBuildingDirs = YES; fBuildingFiles = YES; fBuildingFstatFiles = YES; if ( [[AppDefaults defaultAppDefaults] getShowLocalFiles] ) { [PerforceActionWhere defaultRunFor:self selector:@selector(resultFromPerforceWhere:) withPath:fDepotPath]; fBuildingLocalFiles = YES; } fShouldBuildChildren = NO; } - (NSString*) getName { return fName; } - (int) getNumberOfChildren { if ( fShouldBuildChildren ) { [self buildChildren]; } return fTotalNumChildren; } - (BOOL) hasChildren { // Depots/Directories always have children return YES; } - (id) getChildAtIndex: (int) index { if ( fShouldBuildChildren ) { [self buildChildren]; } if ( index < fNumDirs ) { return [fDirChildren objectAtIndex:index]; } if ( index < fTotalNumChildren ) { return [fFileChildren objectAtIndex:(index - fNumDirs)]; } return nil; } - (id) getCellText { return fName; } - (id) getCellImage { return fIcon; } - (NSString*) buildPath: (NSString*) extraPath { return [NSString stringWithFormat: @"%@%@", fDepotPath, extraPath]; } // // Perforce Protocol // // // data // // // Enablement // - (BOOL) canAdd { return NO; } - (BOOL) canSyncToRevision { return YES; } - (BOOL) canSyncToHead { return YES; } - (BOOL) canSyncToNone { return YES; } - (BOOL) canEdit { return YES; } - (BOOL) canDelete { return YES; } - (BOOL) canResolve { return YES; } - (BOOL) canRevert { return YES; } - (BOOL) canDiff { return NO; } - (BOOL) canGetRevisions { return NO; } - (BOOL) canViewInEditor { return NO; } - (BOOL) canRevealInFinder { return YES; } // // Actions // - (void) showRevisionHistory { } - (void) diff { // do nothing } - (void) viewInEditor { // do nothing; } - (void) resultRevealInFinder: (PerforceActionWhere*) action { if ( [action wasSuccess] ) { RevealInFinder ([action getFilePath]); } } - (void) revealInFinder { // we don't have the client path stored locally // p4win does an fstat to get the location of the file // p4 where should work just as well [PerforceActionWhere defaultRunFor:self selector:@selector(resultRevealInFinder:) withPath:fDepotPath]; } - (NSString*) getPasteboardData { NSMutableString* dragPath = [NSMutableString stringWithString:fDepotPath]; [dragPath appendString:@"/..."]; return dragPath; } - (void) pathChanged:(NSString*)depotPath theFile:(PerforceFile**)theFile { int myLen = [fDepotPath length]; int otherLen = [depotPath length]; if ( myLen < otherLen ) { NSComparisonResult compareResult = [depotPath compare:fDepotPath options:0 range:NSMakeRange (0, myLen)]; if ( compareResult == NSOrderedSame ) { // are the next 4 chars /... ? if ( (otherLen - myLen) == 4 ) { compareResult = [depotPath compare:@"/..." options:0 range:NSMakeRange (myLen, 4)]; if ( compareResult == NSOrderedSame ) { [self releaseChildren]; return; } } if ( fFileChildren ) { int numChildren = [fFileChildren count]; int n; for ( n = 0; (n < numChildren) && (*theFile == nil); n++ ) { id child = [fFileChildren objectAtIndex:n]; [child pathChanged:depotPath theFile:theFile]; } } if ( fDirChildren ) { int numChildren = [fDirChildren count]; int n; for ( n = 0; (n < numChildren) && (*theFile == nil); n++ ) { id child = [fDirChildren objectAtIndex:n]; [child pathChanged:depotPath theFile:theFile]; } } } } } - (NSString*) getDepotPath { return fDepotPath; } - (NSString*) getAddPath { return nil; } - (NSString*) getPerforceActionPath { return [self buildPath:@"/..."]; } - (void) expandPath:(NSString*)depotPath { int myLen = [fDepotPath length]; int otherLen = [depotPath length]; if ( myLen <= otherLen ) { NSComparisonResult compareResult = [depotPath compare:fDepotPath options:0 range:NSMakeRange (0, myLen)]; // We only want to match if the expand path is a folder named the same as this one // The above comparison didn't match through the final '/' because we don't store that // last character. So check if the next character in expand path is '/' if ( compareResult == NSOrderedSame ) { if ( otherLen > myLen ) { compareResult = [depotPath compare:@"/" options:0 range:NSMakeRange (myLen, 1)]; } } if ( compareResult == NSOrderedSame ) { if ( fShouldBuildChildren ) { fExpandAfterBuilding = YES; [fExpandPaths addObject:depotPath]; [self buildChildren]; } else if ( fBuildingDirs || fBuildingFiles ) { fExpandAfterBuilding = YES; [fExpandPaths addObject:depotPath]; } else { // Send notification before calling expand on children so // that the messages are received in the correct order // by the depot controller SendNotificationWithObject (kDepotControllerExpandChild, self); if ( fDirChildren ) { int numChildren = [fDirChildren count]; int n; for ( n = 0; n < numChildren; n++ ) { id child = [fDirChildren objectAtIndex:n]; [child expandPath:depotPath]; } } // Call children just in case the file is what we are to expand to if ( fFileChildren ) { int numChildren = [fFileChildren count]; int n; for ( n = 0; n < numChildren; n++ ) { id child = [fFileChildren objectAtIndex:n]; [child expandPath:depotPath]; } } } } } } @end