Some other improvments

- Add preferences window
- Allow user to choose to use the 10.6 mode on 10.7
- Allow user to choose if he wants to resume listening at startup
- Allow use to choose if XCC should react to inode modification (file level API)
- Support for clean mirrored path when origin files has some _ in their names
- Other various fixes
This commit is contained in:
Antoine Mercadal committed 2011-11-05 16:46:34 +01:00
1 parent edc58e358b
commit a29096ca5c
7 files changed
+1000 -225

No files matched your search

+21 -16
View File
@@ -23,22 +23,26 @@
@interface AppController : NSObject <NSMenuDelegate>
{
IBOutlet NSMenu *statusMenu;
IBOutlet NSMenuItem *menuItemOpenXCode;
IBOutlet NSMenuItem *menuItemStartStop;
IBOutlet NSPanel *errorsPanel;
IBOutlet NSTableView *errorsTable;
IBOutlet NSPanel *aboutWindow;
IBOutlet NSWindow *helpWindow;
IBOutlet NSTextView *helpTextView;
IBOutlet NSTextField *labelVersion;
TNXCodeCapp *xcc;
NSImage *_iconActive;
NSImage *_iconInactive;
NSImage *_iconWorking;
NSStatusItem *_statusItem;
PRHEmptyGrowlDelegate *growlDelegateRef;
IBOutlet NSMenu *statusMenu;
IBOutlet NSMenuItem *menuItemOpenXCode;
IBOutlet NSMenuItem *menuItemStartStop;
IBOutlet NSPanel *errorsPanel;
IBOutlet NSTableView *errorsTable;
IBOutlet NSPanel *aboutWindow;
IBOutlet NSWindow *helpWindow;
IBOutlet NSTextView *helpTextView;
IBOutlet NSTextField *labelVersion;
IBOutlet NSPopUpButton *buttonPreferencesAPIMode;
IBOutlet NSButton *checkBoxPreferencesReactMode;
IBOutlet NSUserDefaultsController *preferencesController;
IBOutlet TNXCodeCapp *xcc;
IBOutlet NSWindow *preferencesWindow;
NSImage *_iconActive;
NSImage *_iconInactive;
NSImage *_iconWorking;
NSStatusItem *_statusItem;
PRHEmptyGrowlDelegate *growlDelegateRef;
}
@property BOOL supportsFileModeListening;
@@ -59,6 +63,7 @@
- (IBAction)stopListener:(id)aSender;
- (IBAction)openHelp:(id)aSender;
- (IBAction)openAbout:(id)aSender;
- (IBAction)updatePreferences:(id)aSender;
- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(int)aRow;
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row;
+25 -2
View File
@@ -75,8 +75,7 @@ AppController *SharedAppControllerInstance = nil;
[[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithInt:0] forKey:@"FirstLaunch"];
[self openHelp:self];
}
xcc = [[TNXCodeCapp alloc] init];
[xcc setDelegate:self];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(XCodeCappConversionDidStart:) name:XCCConversionStartNotification object:xcc];
@@ -101,6 +100,9 @@ AppController *SharedAppControllerInstance = nil;
[appDefaults setObject:streamEventIdSinceNow forKey:@"lastEventId"];
[appDefaults setObject:[NSNumber numberWithInt:1] forKey:@"FirstLaunch"];
[appDefaults setObject:[NSNumber numberWithInt:0] forKey:@"XCCAPIMode"];
[appDefaults setObject:[NSNumber numberWithInt:1] forKey:@"XCCReactMode"];
[appDefaults setObject:[NSNumber numberWithInt:1] forKey:@"XCCReopenLastProject"];
[defaults registerDefaults:appDefaults];
}
@@ -185,6 +187,18 @@ AppController *SharedAppControllerInstance = nil;
#pragma mark -
#pragma mark Actions
/*!
Save preferences
@param aSender the sender of the action
*/
- (IBAction)updatePreferences:(id)aSender
{
[preferencesController save:aSender];
NSLog(@"Preferences change notified");
[xcc configure];
}
/*!
Open the folder chooser and eventually start to listen
@param aSender the sender of the action
@@ -284,6 +298,15 @@ AppController *SharedAppControllerInstance = nil;
[self openCenteredWindow:aboutWindow];
}
/*!
Open the preferences window
@param aSender the sender of the action
*/
- (IBAction)openPreferences:(id)aSender
{
[self openCenteredWindow:preferencesWindow];
}
/*!
Open a centered window.
*/
File diff suppressed because it is too large. Load diff
Binary file not shown.
+24 -7
View File
@@ -38,9 +38,19 @@ void fsevents_callback(ConstFSEventStreamRef streamRef,
for (i = 0; i < numEvents; i++)
{
NSString *path = [[(NSArray *)eventPaths objectAtIndex:i] stringByStandardizingPath];
BOOL isDir = NO;
[[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir];
if (useFileBasedListening)
{
// kFSEventStreamEventFlagItemIsFile = 0x00010000
// kFSEventStreamEventFlagItemRemoved = 0x00000200
if (eventFlags[i] & 0x00010000 && eventFlags[i] & 0x00000200)
{
[xcc tidyShadowedFiles:path];
}
// kFSEventStreamEventFlagItemIsFile = 0x00010000
if (!(eventFlags[i] & 0x00010000)
|| [xcc isPathMatchingIgnoredPaths:path]
@@ -55,9 +65,17 @@ void fsevents_callback(ConstFSEventStreamRef streamRef,
}
// kFSEventStreamEventFlagItemCreated = 0x00000200
if (eventFlags[i] & 0x00000100)
// kFSEventStreamEventFlagItemModified = 0x00001000
if (eventFlags[i] & 0x00000100 || eventFlags[i] & 0x00001000)
{
DLog(@"event type: kFSEventStreamEventFlagItemCreated for path %@", path);
DLog(@"event type: kFSEventStreamEventFlagItemCreated or kFSEventStreamEventFlagItemModified for path %@", path);
[xcc handleFileModification:path notify:YES];
}
// kFSEventStreamEventFlagItemInodeMetaMod = 0x00000400
if (eventFlags[i] & 0x00000400 && [xcc reactToInodeModification])
{
DLog(@"event type: kFSEventStreamEventFlagItemInodeMetaMod for path %@", path);
[xcc handleFileModification:path notify:YES];
}
@@ -80,11 +98,10 @@ void fsevents_callback(ConstFSEventStreamRef streamRef,
{
NSArray *subpaths = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL];
// Uncomment to test under 10.7
// BOOL isDir;
// [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir];
// if (!isDir)
// continue;
// If for some reasons the path is not a directory,
// we don't want to deal with it in this mode.
if (!isDir)
continue;
[xcc tidyShadowedFiles:path];
+13 -2
View File
@@ -33,6 +33,7 @@ extern NSString * const XCCListeningStartNotification;
NSMutableArray *errorList;
NSMutableSet *ignoredFilePaths;
NSNumber *lastEventId;
NSString *currentAPIMode;
NSString *currentProjectName;
NSString *parserPath;
NSString *XCodeSupportPBXPath;
@@ -48,6 +49,10 @@ extern NSString * const XCCListeningStartNotification;
NSDate *appStartedTimestamp;
NSMutableDictionary *pathModificationDates;
BOOL supportsFileBasedListening;
BOOL reactToInodeModification;
BOOL isListening;
BOOL isUsingFileLevelAPI;
BOOL supportFileLevelAPI;
}
@property (retain) NSObject* delegate;
@@ -55,7 +60,12 @@ extern NSString * const XCCListeningStartNotification;
@property (retain) NSURL* XCodeSupportProject;
@property (retain) NSURL* currentProjectURL;
@property (retain) NSString* currentProjectName;
@property (retain) NSString* currentAPIMode;
@property BOOL supportsFileBasedListening;
@property BOOL reactToInodeModification;
@property BOOL isListening;
@property BOOL supportFileLevelAPI;
@property BOOL isUsingFileLevelAPI;
- (BOOL)isObjJFile:(NSString*)path;
- (void)computeIgnoredPaths;
@@ -64,6 +74,7 @@ extern NSString * const XCCListeningStartNotification;
- (BOOL)isXCCIgnoreFile:(NSString *)path;
- (BOOL)prepareXCodeSupportProject;
- (NSURL*)shadowURLForSourceURL:(NSURL*)aSourceURL;
- (NSURL*)sourceURLForShadowName:(NSString *)aString;
- (void)handleFileModification:(NSString*)fullPath notify:(BOOL)shouldNotify;
- (void)handleFileRemoval:(NSString*)fullPath;
- (void)initializeEventStreamWithPath:(NSString*)aPath;
@@ -72,6 +83,8 @@ extern NSString * const XCCListeningStartNotification;
- (void)listenProjectAtPath:(NSString *)path;
- (void)clear;
- (void)start;
- (void)configure;
- (void)tidyShadowedFiles:(NSString*)basePath;
@end
@@ -80,8 +93,6 @@ extern NSString * const XCCListeningStartNotification;
- (void)updateLastModificationDate:(NSDate *)date forPath:(NSString *)path;
- (NSDate*)lastModificationDateForPath:(NSString *)path;
- (NSString *)unshadowURLForString:(NSString *)aString;
- (void)tidyShadowedFiles:(NSString*)basePath;
@end
+127 -52
View File
@@ -33,6 +33,14 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification
@synthesize currentProjectURL;
@synthesize currentProjectName;
@synthesize supportsFileBasedListening;
@synthesize reactToInodeModification;
@synthesize currentAPIMode;
@synthesize isListening;
@synthesize supportFileLevelAPI;
@synthesize isUsingFileLevelAPI;
#pragma mark -
#pragma mark Initialization
/*!
Initialize the AppController
@@ -50,20 +58,20 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification
lastEventId = [[NSUserDefaults standardUserDefaults] objectForKey:@"lastEventId"];
appStartedTimestamp = [NSDate date];
[self setIsListening:NO];
[self setIsUsingFileLevelAPI:NO];
SInt32 versionMajor = 0;
SInt32 versionMinor = 0;
Gestalt(gestaltSystemVersionMajor, &versionMajor);
Gestalt(gestaltSystemVersionMinor, &versionMinor);
supportsFileBasedListening = versionMajor >= 10 && versionMinor >= 7;
// Uncomment to test under 10.7
// supportsFileBasedListening = NO;
if (supportsFileBasedListening)
DLog(@"using 10.7+ mode listening (clean)");
else
DLog(@"using 10.6 mode listening (dirty)");
[self setSupportFileLevelAPI:versionMajor >= 10 && versionMinor >= 7];
// Uncomment to simulate 10.6 mode
//[self setSupportFileLevelAPI:NO];
[self configure];
if([fm fileExistsAtPath:[@"~/.bash_profile" stringByExpandingTildeInPath]])
profilePath = [@"source ~/.bash_profile" stringByExpandingTildeInPath];
else if([fm fileExistsAtPath:[@"~/.profile" stringByExpandingTildeInPath]])
@@ -89,6 +97,9 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification
- (void)start
{
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"XCCReopenLastProject"])
return;
NSString *lastOpenedPath = [[NSUserDefaults standardUserDefaults] objectForKey:@"LastOpenedPath"];
if (lastOpenedPath)
@@ -135,8 +146,13 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification
FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
FSEventStreamStart(stream);
[self setIsListening:YES];
}
#pragma mark -
#pragma mark Utilities
/*!
Stop listening the FSEvent stream if active
*/
@@ -148,6 +164,8 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification
FSEventStreamInvalidate(stream);
stream = nil;
}
[self setIsListening:NO];
}
/*!
@@ -161,9 +179,46 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification
[self stopEventStream];
}
/*!
Choose the API mode according to default
*/
- (void)configure
{
if (![self supportFileLevelAPI])
{
DLog(@"System doesn't support file level API");
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithInt:2] forKey:@"XCCAPIMode"];
}
#pragma mark -
#pragma mark Utilities
switch ([[NSUserDefaults standardUserDefaults] integerForKey:@"XCCAPIMode"])
{
case 0:
supportsFileBasedListening = [self supportFileLevelAPI] ? YES : NO;
break;
case 1:
supportsFileBasedListening = YES;
break;
case 2:
supportsFileBasedListening = NO;
break;
}
if (supportsFileBasedListening)
{
DLog(@"using 10.7+ mode listening (clean)");
[self setCurrentAPIMode:@"File level (Lion)"];
[self setIsUsingFileLevelAPI:YES];
reactToInodeModification = [[NSUserDefaults standardUserDefaults] boolForKey:@"XCCReactMode"];
}
else
{
DLog(@"using 10.6 mode listening (dirty)");
reactToInodeModification = NO;
[self setCurrentAPIMode:@"Folder level (Snow Leopard)"];
[self setIsUsingFileLevelAPI:NO];
}
}
/*!
Update the last event ID. We use a method because
@@ -398,12 +453,16 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification
{
[delegate performSelector:@selector(growlWithTitle:message:) withObject:@"Loading project" withObject:[currentProjectURL path]];
[self handleFileModification:[NSString stringWithFormat:@"%@", [currentProjectURL path]] notify:NO];
NSArray *subdirs = [fm subpathsAtPath:[currentProjectURL path]];
for (NSString *p in subdirs)
[self handleFileModification:[NSString stringWithFormat:@"%@/%@", [currentProjectURL path], p] notify:NO];
//[self handleFileModification:[NSString stringWithFormat:@"%@", [currentProjectURL path]] notify:NO];
NSArray *subdpaths = [fm subpathsAtPath:[currentProjectURL path]];
for (NSString *p in subdpaths)
{
NSString *filePath = [NSString stringWithFormat:@"%@/%@", [currentProjectURL path], p];
DLog(@"Original import of %@", filePath);
[self handleFileModification:filePath notify:NO];
}
NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:currentProjectURL, @"URL", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:XCCDidPopulateProjectNotification object:self userInfo:info];
}
@@ -413,12 +472,17 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification
@param aSourceURL the origin path
@return NSURL representing the shadow path
*/
- (NSURL*)shadowURLForSourceURL:(NSURL*)aSourceURL
- (NSURL *)shadowURLForSourceURL:(NSURL*)aSourceURL
{
if (!aSourceURL)
[NSException raise:NSInvalidArgumentException format:@"shadowURLForSourceURL: aSource URL must not be null"];
NSMutableString *flattenedPath = [NSMutableString stringWithString:[aSourceURL path]];
[flattenedPath replaceOccurrencesOfString:@"_"
withString:@"-OLDUNDERSCORE-"
options:NSCaseInsensitiveSearch
range:NSMakeRange(0, [[aSourceURL path] length])];
[flattenedPath replaceOccurrencesOfString:@"/"
withString:@"_"
options:NSCaseInsensitiveSearch
@@ -430,6 +494,29 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification
return [NSURL URLWithString:basename relativeToURL:XCodeSupportProjectSources];
}
- (NSURL *)sourceURLForShadowName:(NSString *)aString
{
NSMutableString * unshadowedPath = [NSMutableString stringWithString:aString];
[unshadowedPath replaceOccurrencesOfString:@"_"
withString:@"/"
options:NSCaseInsensitiveSearch
range:NSMakeRange(0, [unshadowedPath length])];
[unshadowedPath replaceOccurrencesOfString:@"-OLDUNDERSCORE-"
withString:@"_"
options:NSCaseInsensitiveSearch
range:NSMakeRange(0, [unshadowedPath length])];
[unshadowedPath replaceOccurrencesOfString:@".h"
withString:@".j"
options:NSCaseInsensitiveSearch
range:NSMakeRange(0, [unshadowedPath length])];
return [NSURL URLWithString:[NSString stringWithString:unshadowedPath]];
}
/*!
Compute the ignored paths according to any existing
.xcodecapp-ignore file
@@ -438,12 +525,12 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification
{
NSString *ignorePath = [NSString stringWithFormat:@"%@/.xcodecapp-ignore", [currentProjectURL path]];
[ignoredFilePaths removeAllObjects];
if ([fm fileExistsAtPath:ignorePath])
{
NSString *ignoreFileContent = [NSString stringWithContentsOfFile:ignorePath encoding:NSUTF8StringEncoding error:nil];
NSArray *ignoredPatterns = [ignoreFileContent componentsSeparatedByString:@"\n"];
for (NSString *pattern in ignoredPatterns)
{
if ([pattern length])
@@ -531,6 +618,28 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification
[[NSUserDefaults standardUserDefaults] setObject:[currentProjectURL path] forKey:@"LastOpenedPath"];
}
/*!
Clean the support folder according to files present in given path
@param basePath the path of the folder to check
*/
- (void)tidyShadowedFiles:(NSString*)basePath
{
NSArray *subpaths = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[XCodeSupportProjectSources path] error:NULL];
for (NSString *subpath in subpaths)
{
NSString *unshadowed = [[self sourceURLForShadowName:subpath] path];
NSString *shadowFullPath = [NSString stringWithFormat:@"%@/%@", [XCodeSupportProjectSources path], subpath];
if (![fm fileExistsAtPath:unshadowed])
{
DLog(@"cleaning shadow file: %@", subpath);
[fm removeItemAtPath:shadowFullPath error:nil];
if (![self supportFileLevelAPI] && [self respondsToSelector:@selector(updateLastModificationDate:forPath:)])
[self performSelector:@selector(updateLastModificationDate:forPath:) withObject:nil withObject:unshadowed];
}
}
}
@end
@@ -570,38 +679,4 @@ NSString * const XCCListeningStartNotification = @"XCCListeningStartNotification
return appStartedTimestamp;
}
- (NSString *)unshadowURLForString:(NSString *)aString
{
NSMutableString * unshadowedPath = [NSMutableString stringWithString:aString];
[unshadowedPath replaceOccurrencesOfString:@"_"
withString:@"/"
options:NSCaseInsensitiveSearch
range:NSMakeRange(0, [unshadowedPath length])];
[unshadowedPath replaceOccurrencesOfString:@".h"
withString:@".j"
options:NSCaseInsensitiveSearch
range:NSMakeRange(0, [unshadowedPath length])];
return [NSString stringWithString:unshadowedPath];
}
- (void)tidyShadowedFiles:(NSString*)basePath
{
NSArray *subpaths = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[XCodeSupportProjectSources path] error:NULL];
for (NSString *subpath in subpaths)
{
NSString *unshadowed = [self unshadowURLForString:subpath];
NSString *shadowFullPath = [NSString stringWithFormat:@"%@/%@", [XCodeSupportProjectSources path], subpath];
if (![fm fileExistsAtPath:unshadowed])
{
DLog(@"cleaning shadow file: %@", subpath);
[fm removeItemAtPath:shadowFullPath error:nil];
[self updateLastModificationDate:nil forPath:unshadowed];
}
}
}
@end