aboutsummaryrefslogtreecommitdiff
path: root/Foundation/GTMFileSystemKQueue.m
diff options
context:
space:
mode:
authorGravatar thomasvl@gmail.com <thomasvl@gmail.com@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2008-12-12 15:24:34 +0000
committerGravatar thomasvl@gmail.com <thomasvl@gmail.com@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2008-12-12 15:24:34 +0000
commit2e8516354aacef064d01425808da06d2cdcb4791 (patch)
tree9da4758828930280d32f18d54ece7a249df742c7 /Foundation/GTMFileSystemKQueue.m
parent9f64d056dd70f2f938ac6f5adb8e75b650dc2e1a (diff)
- 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.
Diffstat (limited to 'Foundation/GTMFileSystemKQueue.m')
-rw-r--r--Foundation/GTMFileSystemKQueue.m250
1 files changed, 250 insertions, 0 deletions
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 <unistd.h>
+
+#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