/* 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 "PerforceFile.h" #import "IconController.h" #import "IconDefs.h" #import "PerforceAction.h" #import "PerforceActionFstat.h" #import "PerforceActionFilelog.h" #import "PerforceActionPrint.h" #import "PerforceActionDiff.h" #import "AppUtils.h" #import "AppDefaults.h" #import "MessageDefs.h" #import "RevisionsController.h" enum { kIsNoRevision = -2, kIsNewRevision = -1, kIsHeadRevision = 0, kIsOldRevision = 1, kIsMissingRevision = 2 }; static NSString* kFstatPrefixTag = @"... "; static NSString* kIndentTag = @"... ... "; static NSString* kDepotFileTag = @"depotFile"; static NSString* kClientFileTag = @"clientFile"; static NSString* kHeadActionTag = @"headAction"; static NSString* kHeadTypeTag = @"headType"; static NSString* kHeadRevTag = @"headRev"; static NSString* kHeadChangeTag = @"headChange"; static NSString* kHaveRevTag = @"haveRev"; static NSString* kActionTag = @"action"; static NSString* kChangeTag = @"change"; static NSString* kUnresolvedTag = @"unresolved"; static NSString* kOtherOpenTag = @"otherOpen"; static NSString* kOtherOpenSpaceTag = @"otherOpen "; static NSString* kOtherLockTag = @"otherLock"; static NSString* kOurLockTag = @"ourLock"; static NSString* kOtherActionTag = @"otherAction"; static NSString* kDeleteStatus_Have = @"<-deleted->"; static NSString* kDeleteStatus_Old = @"<-head rev deleted->"; static NSString* kAddStatus = @" <-add->"; static NSString* kNoSuchStatus = @" <-not in depot->"; static NSString* kDeleteAction = @"delete"; static NSString* kAddAction = @"add"; static NSString* kEditAction = @"edit"; static NSString* kSpaceTag = @" "; static NSString* kNewlineTag = @"\n"; static NSString* kZeroRev = @"0"; static NSString* kEmptyString = @""; static NSString* kNoSuchRev = @"-1"; @interface PerforceFile (Private) - (void) releaseData; @end @implementation PerforceFile + (PerforceFile*) fileFromLocalPath: (NSString*) localPath { return [[[PerforceFile alloc] initFromLocalPath: localPath] autorelease]; } + (PerforceFile*) fileWithString: (NSString*) string { return [[[PerforceFile alloc] initWithString: string] autorelease]; } - (id) initFromLocalPath: (NSString*) localPath; { NSMutableDictionary* fileDictionary = [NSMutableDictionary dictionary]; NSMutableArray* otherOpenArray = [NSMutableArray array]; NSMutableArray* otherActionArray = [NSMutableArray array]; [fileDictionary setObject:localPath forKey:kClientFileTag]; [fileDictionary setObject:kNoSuchRev forKey:kHaveRevTag]; return [self initWithDict:fileDictionary otherOpen:otherOpenArray otherAction:otherActionArray]; } - (id) initWithString: (NSString*) string { NSArray* split = [string componentsSeparatedByString: kNewlineTag]; NSMutableDictionary* fileDictionary = [NSMutableDictionary dictionaryWithCapacity:[split count]]; NSMutableArray* otherOpenArray = [NSMutableArray array]; NSMutableArray* otherActionArray = [NSMutableArray array]; NSString* keyString; NSString* valueString; NSString* s; NSEnumerator* e = [split objectEnumerator]; while ( s = [e nextObject] ) { NSScanner* scanner = [NSScanner scannerWithString: s]; // First check for the indent if ( [scanner scanString:kIndentTag intoString:nil] ) { // we only care about otherOpen# where # is one or more digits if ( ![scanner scanString:kOtherOpenSpaceTag intoString:nil] ) { NSMutableArray* targetArray = otherOpenArray; if ( [scanner scanString:kOtherActionTag intoString:nil] ) { targetArray = otherActionArray; } [scanner scanUpToString:kSpaceTag intoString:nil]; [scanner scanString:kSpaceTag intoString:nil]; [scanner scanUpToString:kNewlineTag intoString:&valueString]; [targetArray addObject:valueString]; } } else if ( [scanner scanString:kFstatPrefixTag intoString:nil] ) { [scanner scanUpToString:kSpaceTag intoString:&keyString]; [scanner scanString:kSpaceTag intoString:nil]; [scanner scanUpToString:kNewlineTag intoString:&valueString]; [fileDictionary setObject:valueString forKey:keyString]; } } return [self initWithDict:fileDictionary otherOpen:otherOpenArray otherAction:otherActionArray]; } - (id) initWithDict: (NSDictionary*) fileDictionary otherOpen: (NSArray*) otherOpenArray otherAction: (NSArray*) otherActionArray { BOOL otherEdit = NO; BOOL otherDelete = NO; NSMutableString* displayString = nil; fRevType = 0; NSString* valueString = nil; self = [super init]; /////////////////////////////// valueString = [fileDictionary objectForKey:kDepotFileTag]; if ( valueString ) { fDepotPath = [valueString retain]; } else { fDepotPath = [[NSString alloc] initWithString:kEmptyString]; } /////////////////////////////// valueString = [fileDictionary objectForKey:kClientFileTag]; if ( valueString ) { fClientPath = [valueString retain]; } else { fClientPath = [[NSString alloc] initWithString:kEmptyString]; } fFileName = ExtractLastPathComponent (fClientPath); [fFileName retain]; /////////////////////////////// valueString = [fileDictionary objectForKey:kHeadActionTag]; if ( valueString ) { fHeadAction = [valueString retain]; } else { fHeadAction = [[NSString alloc] initWithString:kEmptyString]; } /////////////////////////////// valueString = [fileDictionary objectForKey:kHeadTypeTag]; if ( valueString ) { fHeadType = [valueString retain]; } else { fHeadType = [[NSString alloc] initWithString:kEmptyString]; } /////////////////////////////// valueString = [fileDictionary objectForKey:kHeadRevTag]; if ( valueString ) { fHeadRev = [valueString retain]; } else { fHeadRev = [[NSString alloc] initWithString:kEmptyString]; } /////////////////////////////// valueString = [fileDictionary objectForKey:kHeadChangeTag]; if ( valueString ) { fHeadChange = [valueString retain]; } else { fHeadChange = [[NSString alloc] initWithString:kEmptyString]; } /////////////////////////////// valueString = [fileDictionary objectForKey:kHaveRevTag]; if ( valueString ) { fHaveRev = [valueString retain]; } else { fHaveRev = [[NSString alloc] initWithString:kZeroRev]; // NOTE: the zero instead of empty string } /////////////////////////////// valueString = [fileDictionary objectForKey:kActionTag]; if ( valueString ) { fAction = [valueString retain]; } else { fAction = [[NSString alloc] initWithString:kEmptyString]; } /////////////////////////////// valueString = [fileDictionary objectForKey:kChangeTag]; if ( valueString ) { fChange = [valueString retain]; } else { fChange = [[NSString alloc] initWithString:kEmptyString]; } /////////////////////////////// valueString = [fileDictionary objectForKey:kUnresolvedTag]; if ( valueString ) { fUnresolved = [valueString retain]; } else { fUnresolved = [[NSString alloc] initWithString:kEmptyString]; } /////////////////////////////// valueString = [fileDictionary objectForKey:kOtherOpenTag]; if ( valueString ) { fOtherOpen = [valueString retain]; } else { fOtherOpen = [[NSString alloc] initWithString:kEmptyString]; } /////////////////////////////// valueString = [fileDictionary objectForKey:kOtherLockTag]; if ( valueString ) { fOtherLock = [valueString retain]; } else { fOtherLock = [[NSString alloc] initWithString:kEmptyString]; } /////////////////////////////// valueString = [fileDictionary objectForKey:kOurLockTag]; if ( valueString ) { fOurLock = [valueString retain]; } else { fOurLock = [[NSString alloc] initWithString:kEmptyString]; } fOtherUsers = [otherOpenArray retain]; fOtherActions = [otherActionArray retain]; // Is this file equal to head, out of date, or not on clients machine? if ( [fAction isEqualToString:kAddAction] ) { fRevType = kIsNewRevision; } else if ( [fHaveRev isEqualToString:fHeadRev] ) { fRevType = kIsHeadRevision; } else if ( [fHaveRev isEqualToString:kZeroRev] ) { fRevType = kIsMissingRevision; } else if ( [fHaveRev isEqualToString:kNoSuchRev] ) { fRevType = kIsNoRevision; } else { fRevType = kIsOldRevision; } // Build the display string displayString = [[NSMutableString alloc] initWithString:fFileName]; if ( fRevType > kIsNewRevision ) { [displayString appendString:@" #"]; [displayString appendString:fHaveRev]; [displayString appendString:@"/"]; [displayString appendString:fHeadRev]; [displayString appendString:@" <"]; [displayString appendString:fHeadType]; [displayString appendString:@"> "]; } if ( [fHeadAction isEqualToString:kDeleteAction] ) { if ( fRevType == kIsOldRevision ) { [displayString appendString:kDeleteStatus_Old]; } else { [displayString appendString:kDeleteStatus_Have]; } } else if ( fRevType == kIsNoRevision ) { [displayString appendString:kNoSuchStatus]; } else if ( fRevType == kIsNewRevision ) { [displayString appendString:kAddStatus]; } // force into an NSString so that later when we return it // it is faster (assuming Cocoa implemented NSString copy // as a simple retain) fDisplayString = [[NSString alloc] initWithCString:[displayString cString]]; if ( fOtherActions ) { int numActions = [fOtherActions count]; int n; for ( n = 0; (n < numActions) && !(otherEdit && otherDelete); n++ ) { NSString* otherAction = [fOtherActions objectAtIndex:n]; if ( !otherEdit && [otherAction isEqualTo:kEditAction] ) { otherEdit = YES; } if ( !otherDelete && [otherAction isEqualTo:kDeleteAction] ) { otherDelete = YES; } } } int iconID = 0; // Figure out icon id if ( fRevType == kIsHeadRevision ) { if ( [fAction isEqualTo:kEditAction] ) { if ( otherDelete ) { iconID = kHaveFileOurLockOtherDeleteIcon; } else if ( otherEdit ) { iconID = kHaveFileBothLockIcon; } else { iconID = kHaveFileOurLockIcon; } } else if ( [fAction isEqualTo:kDeleteAction] ) { if ( otherDelete ) { iconID = kHaveFileBothDeleteIcon; } else if ( otherEdit ) { iconID = kHaveFileOurDeleteOtherLockIcon; } else { iconID = kHaveFileDeleteIcon; } } else { if ( otherDelete ) { iconID = kHaveFileOtherDeleteIcon; } else if ( otherEdit ) { iconID = kHaveFileOtherLockIcon; } else { iconID = kHaveFileIcon; } } } else if ( fRevType == kIsOldRevision ) { if ( [fAction isEqualTo:kEditAction] ) { if ( otherDelete ) { iconID = kOldFileOurLockOtherDeleteIcon; } else if ( otherEdit ) { iconID = kOldFileBothLockIcon; } else { iconID = kOldFileOurLockIcon; } } else if ( [fAction isEqualTo:kDeleteAction] ) { if ( otherDelete ) { iconID = kOldFileBothDeleteIcon; } else if ( otherEdit ) { iconID = kOldFileOurDeleteOtherLockIcon; } else { iconID = kOldFileDeleteIcon; } } else { if ( otherDelete ) { iconID = kOldFileOtherDeleteIcon; } else if ( otherEdit ) { iconID = kOldFileOtherLockIcon; } else { iconID = kOldFileIcon; } } } else if ( fRevType == kIsMissingRevision ) { if ( otherDelete ) { iconID = kMissingFileOtherDeleteIcon; } else if ( otherEdit ) { iconID = kMissingFileOtherLockIcon; } else { iconID = kMissingFileIcon; } } else if ( fRevType == kIsNewRevision ) { iconID = kAddFileIcon; } else { iconID = kNoSuchFileIcon; } IconController* iconController = [IconController defaultIconController]; fIcon = [[iconController getIcon:iconID] retain]; return self; } - (void) copyFrom: (PerforceFile*) fromFile { [self releaseData]; fFileName = [fromFile->fFileName retain]; fDepotPath = [fromFile->fDepotPath retain]; fClientPath = [fromFile->fClientPath retain]; fHeadAction = [fromFile->fHeadAction retain]; fHeadType = [fromFile->fHeadType retain]; fHeadRev = [fromFile->fHeadRev retain]; fHeadChange = [fromFile->fHeadChange retain]; fHaveRev = [fromFile->fHaveRev retain]; fAction = [fromFile->fAction retain]; fChange = [fromFile->fChange retain]; fUnresolved = [fromFile->fUnresolved retain]; fOtherOpen = [fromFile->fOtherOpen retain]; fOtherLock = [fromFile->fOtherLock retain]; fOurLock = [fromFile->fOurLock retain]; fOtherUsers = [fromFile->fOtherUsers retain]; fOtherActions = [fromFile->fOtherActions retain]; fDisplayString = [fromFile->fDisplayString retain]; fIcon = [fromFile->fIcon retain]; fRevType = fromFile->fRevType; } - (void) dealloc { [self releaseData]; [super dealloc]; } - (BOOL) isDeleted { if ( [fHeadAction isEqualToString:kDeleteAction] && [fHaveRev isEqualToString:kZeroRev] ) { return YES; } return NO; } - (BOOL) isAdd { return [fAction isEqualToString:kAddAction]; } - (int) getNumberOfChildren { return 0; } - (BOOL) hasChildren { return NO; } - (id) getChildAtIndex: (int) index { return nil; } - (id) getCellText { return fDisplayString; } - (id) getCellImage { return fIcon; } // // Perforce Protocol // // // data // // // Enablement // - (BOOL) canAdd { if ( fRevType == kIsNoRevision ) { return YES; } return NO; } - (BOOL) canSyncToRevision { if ( (fRevType == kIsNewRevision) || (fRevType == kIsNoRevision) ) { return NO; } return YES; } - (BOOL) canSyncToHead { if ( (fRevType == kIsNewRevision) || (fRevType == kIsNoRevision) ) { return NO; } if ( [fHaveRev isEqualTo:fHeadRev] ) { return NO; } if ( [fHeadAction isEqualTo:kDeleteAction] && [fHaveRev isEqualTo:kZeroRev] ) { return NO; } return YES; } - (BOOL) canSyncToNone { if ( (fRevType == kIsNewRevision) || (fRevType == kIsNoRevision) ) { return NO; } if ( ![fAction isEqualTo:kEmptyString] ) { return NO; } if ( [fHaveRev isEqualTo:kZeroRev] ) { return NO; } return YES; } - (BOOL) canEdit { if ( (fRevType == kIsNewRevision) || (fRevType == kIsNoRevision) ) { return NO; } if ( ![fAction isEqualTo:kEmptyString] ) { return NO; } if ( [fHaveRev isEqualTo:kZeroRev] ) { return NO; } return YES; } - (BOOL) canDelete { if ( (fRevType == kIsNewRevision) || (fRevType == kIsNoRevision) ) { return NO; } if ( ![fAction isEqualTo:kEmptyString] ) { return NO; } if ( [fHaveRev isEqualTo:kZeroRev] ) { return NO; } return YES; } - (BOOL) canResolve { if ( ![fAction isEqualTo:kEmptyString] ) { return YES; } if ( [fHaveRev isEqualTo:kZeroRev] ) { return NO; } return NO; } - (BOOL) canRevert { if ( ![fAction isEqualTo:kEmptyString] ) { return YES; } return NO; } - (BOOL) canDiff { if ( (fRevType == kIsNewRevision) || (fRevType == kIsNoRevision) ) { return NO; } if ( [fAction isEqualTo:kEditAction] ) { return YES; } return NO; } - (BOOL) canGetRevisions { if ( (fRevType == kIsNewRevision) || (fRevType == kIsNoRevision) ) { return NO; } return YES; } - (BOOL) canRevealInFinder { return [self canViewInEditor]; } - (BOOL) canViewInEditor { if ( [fHaveRev isEqualTo:kZeroRev] && [fHeadAction isEqualTo:kDeleteAction] ) { return NO; } return YES; } // // Actions // - (void) resultFromPerforceFstat: (PerforceActionFstat*) action { if ( [action wasSuccess] ) { [self copyFrom:[[action getFiles] objectAtIndex:0]]; SendNotificationWithObject (kDepotControllerChildChanged, self); } } - (void) resultFromActionDoFstat: (id) nothing { [PerforceActionFstat defaultRunFor:self selector:@selector(resultFromPerforceFstat:) withPath:fDepotPath includeDeletes:YES]; } - (void) resultFromPerforceFilelog: (PerforceActionFilelog*) action { if ( [action wasSuccess] ) { [RevisionsController showWithRevisions:[action getRevisions]]; } } - (void) showRevisionHistory { [PerforceActionFilelog defaultRunFor:self selector:@selector(resultFromPerforceFilelog:) withPath:fDepotPath]; } - (void) resultFromActionDoDiff: (NSString*) outputPath { DiffFiles (fClientPath, outputPath); } - (void) diff { [PerforceActionDiff depotPath:fDepotPath]; } - (void) viewFromTempFile { NSString* fileName = GetTempFileName (fFileName, fHeadRev); [PerforceActionPrint defaultRunFor:self selector:@selector(viewAfterPrint:) withPath:fDepotPath outputPath:fileName]; } - (void) revealInFinder { RevealInFinder (fClientPath); } - (void) viewInEditor { if ( [fHaveRev isEqualTo:kZeroRev] ) { [self viewFromTempFile]; } else { ViewFileInEditor (fClientPath); } } - (NSString*) getPasteboardData { return fDepotPath; } - (void) viewAfterPrint:(PerforceActionPrint*) action { if ( [action wasSuccess] ) { ViewFileInEditor ([action getOutputPath]); } } - (void) pathChanged:(NSString*)depotPath theFile:(PerforceFile**)theFile { if ( [depotPath isEqualToString:fDepotPath] ) { *theFile = self; } else if ( fRevType == kIsNoRevision ) { if ( [fFileName isEqualTo:[depotPath lastPathComponent]] ) { *theFile = self; } } } - (NSString*) getDepotPath { return fDepotPath; } - (NSString*) getPerforceActionPath { return fDepotPath; } - (NSString*) getAddPath { return fClientPath; } - (void) expandPath:(NSString*)depotPath { if ( [fFileName isEqualTo:[depotPath lastPathComponent]] ) { SendNotificationWithObject (kDepotControllerExpandChild, self); } } - (NSString*) clientPath { return fClientPath; } - (NSComparisonResult) caseSensitiveFilenameCompare: (PerforceFile*) rhs { return [fFileName compare:rhs->fFileName]; } - (unsigned) hash { return [fClientPath hash]; } - (BOOL) isEqual: (PerforceFile*) rhs { return [fClientPath isEqualToString:rhs->fClientPath]; } @end @implementation PerforceFile(Private) - (void) releaseData { [fFileName release]; [fDepotPath release]; [fClientPath release]; [fHeadAction release]; [fHeadType release]; [fHeadRev release]; [fHeadChange release]; [fHaveRev release]; [fAction release]; [fChange release]; [fUnresolved release]; [fOtherOpen release]; [fOtherUsers release]; [fOtherActions release]; [fOtherLock release]; [fOurLock release]; [fDisplayString release]; [fIcon release]; } @end