// // P4DiffTool.m // MBMenuExtra // // Created by Michael Bishop on 1/17/10. // Copyright 2010 Perforce Software. All rights reserved. // #import #import "P4DiffTool.h" #import "NGAUtilities.h" #import "NGAActiveFileMonitor.h" NSString * const kDiffToolExtension = @"p4difftool"; NSString * const kDiffToolTypeKey = @"P4DiffToolType"; NSString * const kDiffToolTypeAppleScript = @"applescript"; NSString * const kDiffToolTypeTask = @"task"; NSString * const kDiffToolRepresentedApplicationIdentifierKey = @"P4DiffToolRepresentedApplicationIdentifier"; NSString * const kDiffToolExcludedDocumentTypesKey = @"P4DiffToolExcludedDocumentTypes"; NSString * const kDiffToolOnlyForFileComparisonKey = @"P4DiffToolFileComparisonOnly"; NSString * const kDiffToolLaunchPathKey = @"P4DiffToolLaunchPath"; NSString * const kDiffToolLaunchArgumentsKey = @"P4DiffToolLaunchArguments"; static NSString * const kCompareFilesScriptHandlerFunction = @"compareFiles"; @interface P4DiffTool () @property (readwrite, copy) NSString * representedApplicationPath; @property (readwrite, copy) NSString * representedApplicationName; @property (readwrite, copy) NSImage * representedApplicationIcon; @end @implementation P4DiffTool @synthesize representedApplicationID = _representedApplicationID ,representedApplicationName = _representedApplicationName ,representedApplicationIcon = _representedApplicationIcon ,representedApplicationPath = _representedApplicationPath ,active = _active ; -(id)init { return [self initWithApplicationID:nil]; } -(id)initWithApplicationID:(NSString*)applicationIdentifier { if ( (self = [super init]) == nil ) return nil; _representedApplicationID = [applicationIdentifier copy]; NSWorkspace * workspace = [NSWorkspace sharedWorkspace]; NSString * appPath = [workspace absolutePathForAppBundleWithIdentifier:_representedApplicationID]; if ( !appPath ) LOG_CRITICAL(@"No App path for %@", applicationIdentifier); self.representedApplicationPath = appPath; self.representedApplicationIcon = [workspace iconForFile:appPath]; self.representedApplicationName = [[NSFileManager defaultManager] displayNameAtPath:appPath]; self.active = YES; return self; } -(NSString*)description { return [NSString stringWithFormat:@"%@<%@:%@:%@>", [self class], self.representedApplicationName, self.representedApplicationID, self.representedApplicationPath]; } -(BOOL)recognizesDocumentType:(NSString*)type { return NO; } -(BOOL)isOnlyForFileComparison { return NO; } -(BOOL)diffFileWithPath:(NSString*)file1 againstOlderFileWithPath:(NSString*)file2 { LOG_ERROR(@"Must override diffFileWithPath:againstFileWithPath:"); return NO; } @end @implementation P4DiffToolPlugin -(id)initWithBundle:(NSBundle*)diffToolBundle { NSString * appID = [diffToolBundle objectForInfoDictionaryKey:kDiffToolRepresentedApplicationIdentifierKey]; if ( appID != nil && (self = [super initWithApplicationID:appID]) == nil ) return nil; NSArray * unsupportedDocumentTypes = [diffToolBundle objectForInfoDictionaryKey:kDiffToolExcludedDocumentTypesKey]; _supportedDocumentTypes = [NSArray arrayWithObject:@"public.text"]; _supportedDocumentTypes = [[_supportedDocumentTypes arrayByRemovingObjects:unsupportedDocumentTypes] retain]; _onlyForFileComparison = [[diffToolBundle objectForInfoDictionaryKey:kDiffToolOnlyForFileComparisonKey] boolValue]; return self; } -(BOOL)recognizesDocumentType:(NSString*)type { NSWorkspace * workspace = [NSWorkspace sharedWorkspace]; for ( NSString * supportedType in _supportedDocumentTypes ) { if ( [workspace type:type conformsToType:supportedType] ) return YES; } return NO; } -(BOOL)isOnlyForFileComparison { return _onlyForFileComparison; } @end @implementation P4DiffToolAppleScript -(id)initWithBundle:(NSBundle*)bundle; { if ( (self = [super initWithBundle:bundle]) == nil ) return nil; NSURL * scriptUrl = [bundle URLForResource:@"main" withExtension:@"scpt"]; NSDictionary * errorDictionary = nil; _appleScript = [[[NSAppleScript alloc] initWithContentsOfURL:scriptUrl error:&errorDictionary] retain]; if ( !_appleScript ) LOG_ERROR(@"Could not open %@. Received error:%@", scriptUrl, errorDictionary); return self; } -(void)dealloc { [_appleScript release]; [_supportedDocumentTypes release]; [super dealloc]; } -(BOOL)recognizesDocumentType:(NSString*)type { return [_supportedDocumentTypes containsObject:type]; } -(BOOL)diffFileWithPath:(NSString*)localPath againstOlderFileWithPath:(NSString*)temporaryFile { NSAppleEventDescriptor *firstParameter = [NSAppleEventDescriptor descriptorWithString:localPath]; NSAppleEventDescriptor *secondParameter = [NSAppleEventDescriptor descriptorWithString:temporaryFile]; NSAppleEventDescriptor *parameters = [NSAppleEventDescriptor listDescriptor]; [parameters insertDescriptor:firstParameter atIndex:1]; [parameters insertDescriptor:secondParameter atIndex:2]; NSRunningApplication * diffApplication = [[NSRunningApplication runningApplicationsWithBundleIdentifier:self.representedApplicationID] objectAtIndex:0]; ProcessSerialNumber psn = { 0, 0 }; OSStatus error = GetProcessForPID( diffApplication.processIdentifier, &psn ); if ( error != noErr ) return NO; NSAppleEventDescriptor *target = [NSAppleEventDescriptor descriptorWithDescriptorType:typeProcessSerialNumber bytes:&psn length:sizeof(ProcessSerialNumber)]; // create an NSAppleEventDescriptor with the method name // note that the name must be lowercase (even if it is uppercase in AppleScript) NSAppleEventDescriptor *handler = [NSAppleEventDescriptor descriptorWithString:[kCompareFilesScriptHandlerFunction lowercaseString]]; NSAppleEventDescriptor *event = [NSAppleEventDescriptor appleEventWithEventClass:kASAppleScriptSuite eventID:kASSubroutineEvent targetDescriptor:target returnID:kAutoGenerateReturnID transactionID:kAnyTransactionID]; [event setParamDescriptor:handler forKeyword:keyASSubroutineName]; [event setParamDescriptor:parameters forKeyword:keyDirectObject]; NSDictionary * errorDictionary = nil; NSAppleEventDescriptor * result = nil; @try { result = [_appleScript executeAppleEvent:event error:&errorDictionary]; // at last, call the event in AppleScript if(errorDictionary) { LOG_ERROR(@"Could not execute the handler %@(param1, param2) for %@. Received error:%@", kCompareFilesScriptHandlerFunction, self.representedApplicationName, errorDictionary); return NO; } } @catch (NSException *exception) { LOG_DEBUG(@"Received exception %@", exception); return NO; } @finally { } LOG_DEBUG( @"AppleScript returned %@", result ); return YES; } @end @implementation P4DiffToolTask -(id)initWithBundle:(NSBundle*)bundle; { if ( (self = [super initWithBundle:bundle]) == nil ) return nil; _launchPath = [[bundle objectForInfoDictionaryKey:kDiffToolLaunchPathKey] retain]; _arguments = [[bundle objectForInfoDictionaryKey:kDiffToolLaunchArgumentsKey] retain]; return self; } -(void)dealloc { [_launchPath release]; [_arguments release]; [super dealloc]; } -(BOOL)diffFileWithPath:(NSString*)file1 againstOlderFileWithPath:(NSString*)file2 { NSMutableArray * substitutedArguments = [NSMutableArray arrayWithCapacity:[_arguments count]]; NSDictionary * variables = [NSDictionary dictionaryWithObjectsAndKeys: file1, @"file", file2, @"olderFile", self.representedApplicationPath, @"applicationPath", nil]; for (NSString * argument in _arguments) [substitutedArguments addObject:[argument NGA_stringBySubstitutingVariables:variables prefix:@"$"]]; NSString * pathToExecutable = [_launchPath NGA_stringBySubstitutingVariables:variables prefix:@"$"]; NSTask * task = [NSTask launchedTaskWithLaunchPath:pathToExecutable arguments:substitutedArguments]; return task != nil; } @end @implementation P4DiffToolManualComparison +(NSString*)applicationIdentifier { return @"com.numericalgarden.P4Menu.difftool.ManualComparison"; } -(id)init { if ( !(self = [super initWithApplicationID:[self.class applicationIdentifier]]) ) return nil; self.representedApplicationName = @"Manual Comparison"; return self; } -(BOOL)isOnlyForFileComparison { return YES; } -(NSString *)representedApplicationID { return [self.class applicationIdentifier]; } -(BOOL)recognizesDocumentType:(NSString*)type { return YES; } -(BOOL)diffFileWithPath:(NSString*)file1 againstOlderFileWithPath:(NSString*)file2 { return [[NSWorkspace sharedWorkspace] openFile:file2 withApplication:[[[NGAActiveFileMonitor sharedMonitor] monitoredApplication].executableURL path] andDeactivate:YES]; } @end