PerforceDirectory.m #10

  • //
  • guest/
  • jeff_argast/
  • P4Cocoa/
  • source/
  • Perforce/
  • PerforceDirectory.m
  • View
  • Commits
  • Open Download .zip Download (14 KB)
/*

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<DepotViewProtocol> 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<DepotViewProtocol> 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<DepotViewProtocol> 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<DepotViewProtocol> child = [fFileChildren objectAtIndex:n];
						
						[child expandPath:depotPath];
					}
				}
			}
		}
	}
}


@end



# Change User Description Committed
#10 4225 Jeff Argast Added resolve support, reveal in finder, drag and drop edit,
show local files, and showing added files.
#9 3984 Jeff Argast Updated version information
#8 3980 Jeff Argast Added distributed protocol
#7 3936 Jeff Argast Integrated changes from Paul Ferguson's branch
Fixed parse error in PerforceUser
#6 3931 Jeff Argast Fixed a hang when actin on large numbers of files
Added an icon for xcode
Switched to xcode
Tweaked the icons in the tabs for panther
#5 3147 Jeff Argast Testing std in blockage
#4 3134 Jeff Argast Added copy/paste support in the depot view
Added expand path to depot view
Added bookmarks
#3 3113 Jeff Argast Reduced the times the depot view completely collapses.
Now it won't collapse on refresh views or submit, but
still collapses when the defaults change.
#2 3111 Jeff Argast Made multiple selection smarter by operating on the entire selection as an atomic
operation with the server. Also partially fixed the read only window to not wrap
at the window boundary.  I did the same for the editable window, but now the problem
appears to be that p4 change -o is breaking its output at some character location
before the string gets into the editor (at least I think that is the problem).
#1 2732 Jeff Argast Initial submission of P4Cocoa