From 2e8516354aacef064d01425808da06d2cdcb4791 Mon Sep 17 00:00:00 2001 From: "thomasvl@gmail.com" Date: Fri, 12 Dec 2008 15:24:34 +0000 Subject: - GTMStackTrace works on 10.5+ (and iPhone) using NSThread to build the call stack. - Added GTM_EXTERN that makes it easier to mix and match objc and objc++ code. - Added GTMHotKeysTextField for display and editing of hot key settings. - Added GTMCarbonEvent for dealing with Carbon Events and HotKeys in a ObjC like way. - Backported the Atomic Barrier Swap functions for Objective C back to Tiger. - Added a variety of new functions to GTMUnitTestingUtilities for checking if the screensaver is in the way, waiting on user events, and generating keystrokes. - If you are using any Carbon routines that log (DebugStr, AssertMacros.h) and use GTMUnitTestDevLog, the log routines now go through _GTMDevLog so that they can be caught in GTMUnitTestDevLog and verified like any _GTMDevLog calls you may make. For an example of this in action see GTMCarbonEventTest.m. - Added GTMFileSystemKQueue. It provides a simple wrapper for kqueuing something in the file system and tracking changes to it. - RunIPhoneUnitTest.sh now cleans up the user home directory and creates a documents directory within it, used when requesting a NSDocumentDirectory. - Added GTMNSFileManager+Carbon which contains routines for path <-> Alias conversion and path <-> FSRef conversion. - Added GTMNSArray+Merge for merging one array into another with or without a custom merging function, returning a new array with the merged contents. --- Foundation/GTMFileSystemKQueue.m | 250 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 Foundation/GTMFileSystemKQueue.m (limited to 'Foundation/GTMFileSystemKQueue.m') diff --git a/Foundation/GTMFileSystemKQueue.m b/Foundation/GTMFileSystemKQueue.m new file mode 100644 index 0000000..e2c399e --- /dev/null +++ b/Foundation/GTMFileSystemKQueue.m @@ -0,0 +1,250 @@ +// +// GTMFileSystemKQueue.m +// +// Copyright 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +// + +#import "GTMFileSystemKQueue.h" +#import + +#import "GTMDebugSelectorValidation.h" + + +// File descriptor for the kqueue that will hold all of our file system events. +static int gFileSystemKQueueFileDescriptor = 0; + +// A wrapper around the kqueue file descriptor so we can put it into a +// runloop. +static CFSocketRef gRunLoopSocket = NULL; + + +@interface GTMFileSystemKQueue (PrivateMethods) +- (void)notify:(GTMFileSystemKQueueEvents)eventFFlags; +- (void)addFileDescriptorMonitor:(int)fd; +- (int)registerWithKQueue; +- (void)unregisterWithKQueue; +@end + + +@implementation GTMFileSystemKQueue + +-(id)init { + // Folks shouldn't call init directly, so they get what they deserve. + _GTMDevLog(@"Don't call init, use " + @"initWithPath:forEvents:acrossReplace:target:action:"); + return [self initWithPath:nil forEvents:0 acrossReplace:NO + target:nil action:nil]; +} + + +- (id)initWithPath:(NSString *)path + forEvents:(GTMFileSystemKQueueEvents)events + acrossReplace:(BOOL)acrossReplace + target:(id)target + action:(SEL)action { + + if ((self = [super init])) { + + fd_ = -1; + path_ = [path copy]; + events_ = events; + acrossReplace_ = acrossReplace; + + target_ = target; // Don't retain since target will most likely retain us. + action_ = action; + if ([path_ length] == 0 || !events_ || !target_ || !action_) { + [self release]; + return nil; + } + + // Make sure it imples what we expect + GTMAssertSelectorNilOrImplementedWithArguments(target_, + action_, + @encode(GTMFileSystemKQueue*), + @encode(GTMFileSystemKQueueEvents), + NULL); + + fd_ = [self registerWithKQueue]; + if (fd_ < 0) { + [self release]; + return nil; + } + } + return self; +} + +- (void)finalize { + [self unregisterWithKQueue]; + + [super finalize]; +} + +- (void)dealloc { + [self unregisterWithKQueue]; + [path_ release]; + + [super dealloc]; +} + +- (NSString *)path { + return path_; +} + +// Cribbed from Advanced Mac OS X Programming. +static void SocketCallBack(CFSocketRef socketref, CFSocketCallBackType type, + CFDataRef address, const void *data, void *info) { + struct kevent event; + + if (kevent(gFileSystemKQueueFileDescriptor, NULL, 0, &event, 1, NULL) == -1) { + _GTMDevLog(@"could not pick up kqueue event. Errno %d", errno); // COV_NF_LINE + } else { + GTMFileSystemKQueue *fskq = (GTMFileSystemKQueue *)event.udata; + [fskq notify:event.fflags]; + } + +} + +// Cribbed from Advanced Mac OS X Programming +- (void)addFileDescriptorMonitor:(int)fd { + _GTMDevAssert(gRunLoopSocket == NULL, @"socket should be NULL at this point"); + + CFSocketContext context = { 0, NULL, NULL, NULL, NULL }; + gRunLoopSocket = CFSocketCreateWithNative(kCFAllocatorDefault, + fd, + kCFSocketReadCallBack, + SocketCallBack, + &context); + if (gRunLoopSocket == NULL) { + _GTMDevLog(@"could not CFSocketCreateWithNative"); // COV_NF_LINE + goto bailout; // COV_NF_LINE + } + + CFRunLoopSourceRef rls; + rls = CFSocketCreateRunLoopSource(NULL, gRunLoopSocket, 0); + if (rls == NULL) { + _GTMDevLog(@"could not create a run loop source"); // COV_NF_LINE + goto bailout; // COV_NF_LINE + } + + CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, + kCFRunLoopDefaultMode); + CFRelease(rls); + + bailout: + return; + +} + +// Returns the FD we got in registering +- (int)registerWithKQueue { + + // Make sure we have our kqueue. + if (gFileSystemKQueueFileDescriptor == 0) { + gFileSystemKQueueFileDescriptor = kqueue(); + + if (gFileSystemKQueueFileDescriptor == -1) { + // COV_NF_START + _GTMDevLog(@"could not make filesystem kqueue. Errno %d", errno); + return -1; + // COV_NF_END + } + + // Add the kqueue file descriptor to the runloop. + [self addFileDescriptorMonitor:gFileSystemKQueueFileDescriptor]; + } + + int newFD = open([path_ fileSystemRepresentation], O_EVTONLY, 0); + if (newFD >= 0) { + + // Add a new event for the file. + struct kevent filter; + EV_SET(&filter, newFD, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, + events_, 0, self); + + const struct timespec noWait = { 0, 0 }; + if (kevent(gFileSystemKQueueFileDescriptor, &filter, 1, NULL, 0, &noWait) == -1) { + // COV_NF_START + _GTMDevLog(@"could not add event for path %@. Errno %d", path_, errno); + close(newFD); + newFD = -1; + // COV_NF_END + } + } + + return newFD; +} + +- (void)unregisterWithKQueue { + // Short-circuit cases where we didn't actually register a kqueue event. + if (fd_ < 0) return; + + struct kevent filter; + EV_SET(&filter, fd_, EVFILT_VNODE, EV_DELETE, 0, 0, self); + + const struct timespec noWait = { 0, 0 }; + if (kevent(gFileSystemKQueueFileDescriptor, &filter, 1, NULL, 0, &noWait) != 0) { + _GTMDevLog(@"could not remove event for path %@. Errno %d", path_, errno); // COV_NF_LINE + } + + // Now close the file down + close(fd_); + fd_ = -1; +} + + +- (void)notify:(GTMFileSystemKQueueEvents)eventFFlags { + + // Some notifications get a little bit of overhead first + + if (eventFFlags & NOTE_REVOKE) { + // COV_NF_START - no good way to do this in a unittest + // Assume revoke means unmount and give up + [self unregisterWithKQueue]; + // COV_NF_END + } + + if (eventFFlags & NOTE_DELETE) { + [self unregisterWithKQueue]; + if (acrossReplace_) { + fd_ = [self registerWithKQueue]; + } + } + + if (eventFFlags & NOTE_RENAME) { + // If we're doing it across replace, we move to the new fd for the new file + // that might have come onto the path. if we aren't doing accross replace, + // nothing to do, just stay on the file. + if (acrossReplace_) { + int newFD = [self registerWithKQueue]; + if (newFD >= 0) { + [self unregisterWithKQueue]; + fd_ = newFD; + } + } + } + + // Now, fire the selector + NSMethodSignature *methodSig = [target_ methodSignatureForSelector:action_]; + _GTMDevAssert(methodSig != nil, @"failed to get the signature?"); + NSInvocation *invocation + = [NSInvocation invocationWithMethodSignature:methodSig]; + [invocation setTarget:target_]; + [invocation setSelector:action_]; + [invocation setArgument:self atIndex:2]; + [invocation setArgument:&eventFFlags atIndex:3]; + [invocation invoke]; +} + +@end -- cgit v1.2.3