#import "P4MenuApplicationDelegate.h" #import "NGAActiveFileMonitor.h" #import "NGAUtilities.h" #import "P4DiffTool.h" #import "DBPrefsWindowController.h" #import "P4MenuController.h" #import "P4MenuLocalFile.h" SOLogger *gLogger; NSString *gLogFilePath; NSString * const DiffOptionName1 = @"DiffOptionName1"; NSString * const DiffOptionName2 = @"DiffOptionName2"; NSString * const DiffOptionUTIHint = @"DiffOptionUTIHint"; @interface P4MenuApplicationDelegate () -(void)_updateDiffTools; -(P4DiffTool*)MB_textDiffTool; @property (nonatomic,readwrite,copy) NSArray * diffTools; @property (nonatomic,readwrite,copy) NSArray * textualDiffTools; @end static NSString * const kFileMonitorKeyPath = @"pathToActiveFile"; static NSString * const kFirstRunKey = @"InitialLaunch"; static NSString * const kTextDiffToolKey = @"TextualDiffToolApplicationBundleIdentifier"; static NSString * const kP4MergeBundleIdentifier = @"com.perforce.p4merge"; @implementation P4MenuApplicationDelegate @synthesize diffTools = _diffTools ,textualDiffTools = _textualDiffTools ; +(void) initialize { static dispatch_once_t pred; dispatch_once(&pred, ^{ [[[NSUserDefaultsController sharedUserDefaultsController] defaults] registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], kFirstRunKey, nil]]; [[[NSUserDefaultsController sharedUserDefaultsController] defaults] registerDefaults:[NSDictionary dictionaryWithObjectsAndKeys:kP4MergeBundleIdentifier, kTextDiffToolKey, nil]]; gLogger = [[SOLogger alloc] initWithFacility:[[NSBundle mainBundle] bundleIdentifier] options:SOLoggerDefaultASLOptions]; #ifdef DEBUG gLogger.severityFilterMask = ASL_FILTER_MASK_UPTO (ASL_LEVEL_DEBUG); #endif /* Create a path to the desktop log file. */ NSMutableArray *pathComponents = [[NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) mutableCopy] autorelease]; [pathComponents addObject:@"Logs"]; NSError * error = nil; if (![[NSFileManager defaultManager] createDirectoryAtPath:[NSString pathWithComponents:pathComponents] withIntermediateDirectories:YES attributes:nil error:&error]) { NSLog(@"%@", error); } [pathComponents addObject:[[[NSBundle mainBundle] bundleIdentifier] stringByAppendingPathExtension:@"log"]]; gLogFilePath = [[NSString pathWithComponents:pathComponents] retain]; FILE * file = fopen([gLogFilePath fileSystemRepresentation], "w+"); if ( file ) [gLogger addFileDescriptor:fileno(file)]; else NSLog(@"Couldn't open log file due to error \"%s\"(%d)", strerror(errno), errno); LOG_INFO(@"Host Name: %@", [[NSProcessInfo processInfo] hostName]); LOG_INFO(@"Operating System: %@", [[NSProcessInfo processInfo] operatingSystemName]); LOG_INFO(@"Operating System Version: %@", [[NSProcessInfo processInfo] operatingSystemVersionString]); LOG_INFO(@"Physical Memory: %uGB", [[NSProcessInfo processInfo] physicalMemory] / 1024 / 1024 / 1024); LOG_INFO(@"Processor Count: %u", [[NSProcessInfo processInfo] processorCount]); LOG_INFO(@"Environment: %@", [[NSProcessInfo processInfo] environment]); }); } -(id)init { if ( (self = [super init]) == nil ) return nil; _p4MenuController = [[P4MenuController alloc] initWithMainController:self]; return self; } - (void)dealloc { RELEASE(_diffTools); RELEASE(_textualDiffTools); RELEASE(_p4MenuController); [super dealloc]; } -(NSWindow*)statusBarWindow { static Class NSStatusBarWindowClass = 0; if ( NSStatusBarWindowClass == 0 ) NSStatusBarWindowClass = NSClassFromString(@"NSStatusBarWindow"); for (id window in [NSApp windows]) { if ( [window isKindOfClass:NSStatusBarWindowClass] ) return window; } return nil; } - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { // Swiped from the UIElementInspector sample code if (!AXAPIEnabled()) { NSInteger ret = NSRunAlertPanel( [NSString stringWithFormat:NSLocalizedString(@"%@ requires that the Accessibility API be enabled. Would you like me to launch System Preferences so that you can turn on \"Enable access for assistive devices\".", "Note: Shows when Accesibility is not on"), [[NSRunningApplication currentApplication] localizedName]], @"", NSLocalizedString(@"Ok", @"OK"), NSLocalizedString(@"Quit", @"Quit"), NSLocalizedString(@"Cancel", @"Cancel")); switch (ret) { case NSAlertDefaultReturn: [[NSWorkspace sharedWorkspace] openFile:@"/System/Library/PreferencePanes/UniversalAccessPref.prefPane"]; break; case NSAlertAlternateReturn: [NSApp terminate:self]; return; break; case NSAlertOtherReturn: // just continue default: break; } } @try { LOG_INFO(@"starting p4 menu controller"); _p4MenuController.enabled = true; _p4MenuController.currentLocalFile = [P4MenuLocalFile localFileWithLocalFilepath:[[NGAActiveFileMonitor sharedMonitor] mainWindowDocumentPath]]; [[DBPrefsWindowController sharedPrefsWindowController] setWindowFrameAutosaveName:@"Preferences" ]; DBPrefsWindowController.sharedPrefsWindowController.currentIdentifierAutosaveName = @"CurrentPreferencePane"; LOG_INFO(@"adding perforce preference view"); [[DBPrefsWindowController sharedPrefsWindowController] addView:_p4MenuController.preferencePaneView label:_p4MenuController.preferencePaneTitle image:_p4MenuController.preferencePaneIcon]; LOG_INFO(@"adding comparison preference view"); [[DBPrefsWindowController sharedPrefsWindowController] addView:_preferenceView label:NSLocalizedString( @"File Comparison", @"File Comparison Preference Pane Label" ) image:[NSImage imageNamed:@"DiffPreference"]]; LOG_INFO(@"setting ignored apps"); [NGAActiveFileMonitor sharedMonitor].ignoredProcessBundleIDs = [NSSet setWithObjects: #ifdef DEBUG @"com.apple.dt.Xcode", @"com.apple.Console", #endif [[NSRunningApplication currentApplication] bundleIdentifier], nil]; [[NGAActiveFileMonitor sharedMonitor] startMonitoring]; LOG_INFO(@"updating diff tools"); [self _updateDiffTools]; LOG_INFO(@"finished updating diff tools"); [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:KEYPATH(@"values",kTextDiffToolKey) options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionInitial context:NULL]; [textualDiffToolsController addObserver:self forKeyPath:@"selectionIndex" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:NULL]; if ( [[[NSUserDefaultsController sharedUserDefaultsController] defaults] boolForKey:kFirstRunKey] == YES ) { [self showPrefs:self]; [[[NSUserDefaultsController sharedUserDefaultsController] defaults] setBool:NO forKey:kFirstRunKey]; } } @catch (NSException *exception) { LOG_INFO(@"Exception while starting the application. %@", exception); [[NSApplication sharedApplication] terminate:self]; } @finally { } } -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ( NSSTRING_ISEQUALTOSTRING(keyPath, @"selectionIndex") ) { NSUInteger index = textualDiffToolsController.selectionIndex; id obj = [[textualDiffToolsController.arrangedObjects objectAtIndex:index] representedApplicationID]; LOG_INFO(@"Setting defaults to %@", obj); [[[NSUserDefaultsController sharedUserDefaultsController] values] setValue:obj forKey:kTextDiffToolKey]; return; } if ( NSSTRING_ISEQUALTOSTRING(keyPath, KEYPATH(@"values",kTextDiffToolKey) ) ) { NSString * applicationID = [[[NSUserDefaultsController sharedUserDefaultsController] values] valueForKey:kTextDiffToolKey]; NSUInteger index = [textualDiffToolsController.arrangedObjects indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) { if ( NSSTRING_ISEQUALTOSTRING([obj representedApplicationID], applicationID) ) { *stop = YES; return YES; } return NO; }]; if (index == NSNotFound) index = 0; LOG_INFO(@"Setting selectionIndex to %u", index); textualDiffToolsController.selectionIndex = index; return; } [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } - (void)applicationDidBecomeActive:(NSNotification *)aNotification { static Class NSStatusBarWindowClass = 0; if ( NSStatusBarWindowClass == 0 ) NSStatusBarWindowClass = NSClassFromString(@"NSStatusBarWindow"); for (id window in [NSApp windows]) { if ( [window isKindOfClass:NSStatusBarWindowClass] ) continue; if ( [window isVisible] ) { return; } } [[[NGAActiveFileMonitor sharedMonitor] monitoredApplication] activateWithOptions:0]; } - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication { [NSApp deactivate]; [[[NGAActiveFileMonitor sharedMonitor] monitoredApplication] activateWithOptions:0]; return NO; } - (IBAction)showPrefs:(id)sender { [NSApp activateIgnoringOtherApps:YES]; [[DBPrefsWindowController sharedPrefsWindowController] showWindow:self]; } - (void)applicationWillTerminate:(NSNotification *)aNotification { } - (BOOL)MB_activateApplicationWithBundleIdentifier:(NSString*)identifier { NSArray * runningApplications = [NSRunningApplication runningApplicationsWithBundleIdentifier:identifier]; for (NSRunningApplication * runningApplication in runningApplications) [runningApplication activateWithOptions:NSApplicationActivateIgnoringOtherApps]; return YES; } - (void)_updateDiffTools { // Look through the plugins and find all of them // If they open a text type, add to text list // If they open an image type, add to image list // Add each to the list of global tools // Get a reference to the Script folder NSString * pluginToolsPath = [[NSBundle mainBundle] builtInPlugInsPath]; NSString * diffToolsPath = [pluginToolsPath stringByAppendingPathComponent:@"Diff"]; NSArray * diffToolNames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:diffToolsPath error:nil]; NSMutableArray * loadedDiffTools = [NSMutableArray arrayWithCapacity:[diffToolNames count]]; NSMutableArray * applicationOverrideTools = [NSMutableArray arrayWithCapacity:[diffToolNames count]]; NSMutableArray * textualDiffTools = [NSMutableArray arrayWithCapacity:[diffToolNames count]]; for (NSString * diffToolName in diffToolNames) { if ( ![[diffToolName pathExtension] isEqualToString:kDiffToolExtension] ) { LOG_ERROR( @"%@ does not have an extension of .%@ Skipping.", diffToolName, kDiffToolExtension ); continue; } NSString * diffToolBundlePath = [diffToolsPath stringByAppendingPathComponent:diffToolName]; NSBundle * diffToolBundle = [NSBundle bundleWithPath:diffToolBundlePath]; if ( !diffToolBundle ) { LOG_ERROR( @"Could not create a bundle from path %@ Skipping.", diffToolBundlePath ); continue; } NSString * pluginType = [diffToolBundle objectForInfoDictionaryKey:kDiffToolTypeKey]; P4DiffTool * tool = nil; // Test to see if the plugin has an applscript of task type and load // with the appropriate class if ( [pluginType isEqualToString:kDiffToolTypeAppleScript] ) tool = [[[P4DiffToolAppleScript alloc] initWithBundle:diffToolBundle] autorelease]; else if ( [pluginType isEqualToString:kDiffToolTypeTask] ) tool = [[[P4DiffToolTask alloc] initWithBundle:diffToolBundle] autorelease]; if ( !tool ) { LOG_ERROR( @"Bundle at %@ is malformed. Skipping.", diffToolBundlePath ); continue; } // Check to see if this application is even on the disk if ( ![[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier:tool.representedApplicationID] ) { LOG_ERROR( @"Skipping diff tool for %@ because it cannot be found on this computer", tool.representedApplicationID ); continue; } [loadedDiffTools addObject:tool]; } // Add Manual Comparison // NOTE: This is ALWAYS available [loadedDiffTools addObject:[[P4DiffToolManualComparison new] autorelease]]; for (P4DiffTool * tool in loadedDiffTools) { if ( [tool recognizesDocumentType:@"public.text"] ) [textualDiffTools addObject:tool]; if ( ![tool isOnlyForFileComparison] ) [applicationOverrideTools addObject:tool]; if ( [tool isOnlyForFileComparison] && ![tool recognizesDocumentType:@"public.text"] ) LOG_ERROR( @"Tool representing application %@ is only for diffing but doesn't recognize the text filetype so it will not appear in the preferences.", tool.representedApplicationName ); } self.diffTools = [NSArray arrayWithArray:applicationOverrideTools]; self.textualDiffTools = [NSArray arrayWithArray:textualDiffTools]; } -(NSString*)UTIOfFileAtPath:(NSString*)filePath { return [[NSWorkspace sharedWorkspace] typeOfFile:filePath error:nil]; } -(P4DiffTool*)MB_textDiffTool { NSString * textToolAppID = [[NSUserDefaults standardUserDefaults] objectForKey:kTextDiffToolKey]; for (P4DiffTool * tool in self.textualDiffTools) { if ( [textToolAppID isEqualToString:tool.representedApplicationID] ) return tool; } return nil; } -(BOOL)diffFileAtPath:(NSString*)path1 againstOlderFileAtPath:(NSString*)path2 options:(NSDictionary*)options { NSString * name1 = [options objectForKey:DiffOptionName1]; NSString * name2 = [options objectForKey:DiffOptionName2]; LOG_DEBUG(@"path1:'%@' name1:'%@' path2:'%@' name2:'%@'", path1, name1, path2, name2); NSString * fileUTI = [self UTIOfFileAtPath:path1]; NSString * fileUTIHint = [options objectForKey:DiffOptionUTIHint]; if ( !fileUTI || [fileUTI isEqualToString:(NSString*)kUTTypeData] ) { if ( fileUTIHint ) { LOG_DEBUG(@"path1:'%@' has no UTI (or is the generic 'public.data'). Using UTIHint: '%@'", path1, fileUTIHint); fileUTI = fileUTIHint; } else { LOG_DEBUG(@"path1:'%@' has no UTI and there is no hint. Reverting to Manual Comparison.", path1); } } else { LOG_DEBUG(@"path1:'%@' has UTI: '%@'", path1, fileUTI); } NSString * diffApplicationIdentifier = [[NGAActiveFileMonitor sharedMonitor] monitoredApplication].bundleIdentifier; // First, try the application-specific tools for (P4DiffTool * tool in self.diffTools) { if ( !tool.active ) continue; if ([tool.representedApplicationID isEqualToString:[[NGAActiveFileMonitor sharedMonitor] monitoredApplication].bundleIdentifier]) { LOG_DEBUG(@"First trying with Application tool: %@", tool); if ( [tool diffFileWithPath:path1 againstOlderFileWithPath:path2] ) return [self MB_activateApplicationWithBundleIdentifier:diffApplicationIdentifier]; else LOG_DEBUG(@"Failed diffing with tool %@. Trying textual comparison", tool); } } // Then, if the file is text, try a text tool if ( [fileUTI conformsToUTI:@"public.text"] ) { P4DiffTool * textDiffTool = [self MB_textDiffTool]; LOG_DEBUG(@"'%@' is a text file. Using tool: %@", path1, textDiffTool); if ([textDiffTool diffFileWithPath:path1 againstOlderFileWithPath:path2]) return [self MB_activateApplicationWithBundleIdentifier:textDiffTool.representedApplicationID]; else LOG_DEBUG(@"Failed diffing with tool %@. Reverting to manual comparison", textDiffTool); } else { LOG_DEBUG(@"'%@' does not conform to UTI: 'public.text'. Reverting to Manual comparison.", path1); } // As a last resort, we simply open the temporary file in the active process LOG_DEBUG(@"Application differ not available and text file diffing didn't work. Reverting to manual comparison"); return [[NSWorkspace sharedWorkspace] openFile:path2 withApplication:[[[NGAActiveFileMonitor sharedMonitor] monitoredApplication].executableURL path] andDeactivate:YES]; } @end