Handling File System Events in OSX - objective-c

So im using the EventStream to watch a folder for change. Now it all works fine and I can see a log call back when I alter files in the folder, but I cant seem to call my folderWatch, it gives the error "use of undeclared identifier 'self'". I can use this function everywhere else, just not in the fsEventsCallback. Any help would be appreciated!
void fsEventsCallback(ConstFSEventStreamRef streamRef,
void *clientCallBackInfo,
size_t numEvents,
void *eventPaths,
const FSEventStreamEventFlags eventFlags[],
const FSEventStreamEventId eventIds[]){
[self folderWatch];
NSLog(#"2");
}

The reason is that fsEventsCallback is a C function and not an Objective-C instance method, so fsEventsCallback does not know anything about self.
You can use the info field in the FSEventStreamContext to pass self to the callback function. The following example assumes that your class is called Watcher.
(If you don't use ARC, you can omit all the __bridge casts.)
- (void)folderWatch
{
}
void fsEventsCallback(ConstFSEventStreamRef streamRef,
void *info,
size_t numEvents,
void *eventPaths,
const FSEventStreamEventFlags eventFlags[],
const FSEventStreamEventId eventIds[])
{
Watcher *watcher = (__bridge Watcher *)info;
[watcher folderWatch];
}
- (void)startWatching
{
FSEventStreamContext context;
context.info = (__bridge void *)(self);
context.version = 0;
context.retain = NULL;
context.release = NULL;
context.copyDescription = NULL;
NSArray *pathsToWatch = #[#"/path/to/watch"];
self.eventStream = FSEventStreamCreate(NULL,
&fsEventsCallback,
&context,
(__bridge CFArrayRef)(pathsToWatch),
kFSEventStreamEventIdSinceNow,
1.0,
kFSEventStreamCreateFlagFileEvents
);
}

Related

Why Does This Objective C/C++ Code Require main.m instead of main.mm?

I get strange code errors when I rename the following command line program from main.m to main.mm. Works just fine as main.m. Anyone know why?
https://stackoverflow.com/a/36469891/105539
SOURCE
#import <Foundation/Foundation.h>
void detectNewFile (
ConstFSEventStreamRef streamRef,
void *clientCallBackInfo,
size_t numEvents,
void *eventPaths,
const FSEventStreamEventFlags eventFlags[],
const FSEventStreamEventId eventIds[])
{
int i;
char **paths = eventPaths;
printf("GOT AN EVENT!!!!\n");
for (i=0; i<numEvents; i++) {
printf("Change %llu in %s, flags %u\n", eventIds[i], paths[i], (unsigned int)eventFlags[i]);
}
}
int main(int argc, const char * argv[]) {
#autoreleasepool {
short nPathCount = 2;
CFStringRef mypath[nPathCount];
mypath[0] = CFSTR("/Users/mike/Documents");
mypath[1] = CFSTR("/Users/mike/Downloads");
CFArrayRef pathsToWatch = CFArrayCreate(NULL, (const void **)&mypath, nPathCount, NULL);
void *callbackInfo = NULL;
CFAbsoluteTime latency = 1.0; // seconds
FSEventStreamRef hStream = FSEventStreamCreate(NULL,
&detectNewFile,
callbackInfo,
pathsToWatch,
kFSEventStreamEventIdSinceNow,
latency,
kFSEventStreamCreateFlagFileEvents
);
FSEventStreamScheduleWithRunLoop(hStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
FSEventStreamStart(hStream);
printf("Waiting on new file creations...\n");
CFRunLoopRun(); // runs in an endless loop, only letting the callback function run
} // end autorelease pool
return 0;
}
ERRORS
FOR:
char **paths = eventPaths;
Cannot initialize a variable of type 'char **' with an lvalue of type 'void *'
FOR:
FSEventStreamRef hStream = FSEventStreamCreate(NULL,
&detectNewFile,
callbackInfo,
pathsToWatch,
kFSEventStreamEventIdSinceNow,
latency,
kFSEventStreamCreateFlagFileEvents
);
No matching function for call to 'FSEventStreamCreate'
Thanks to #johnelemans, I found the problems. In C, it's legal to have automatic casting from void * to char **, but not in C++, which is what the .mm file would switch this into. The fix is to use casting:
char **paths = (char **)eventPaths;
Then, on the FSEventStreamCreate, it didn't like the void * instead of this:
FSEventStreamContext *callbackInfo = NULL;
...and didn't like the CFAbsoluteTime instead of:
CFTimeInterval latency = 1.0; // seconds
Then, you need to add CoreServices.framework library to the build steps.
I made those changes and it compiles now.

FSEventStreamCreate crashing in obj-c app

I'm trying to do file monitoring on a directory, took the code from the apple docs. Its crashing on the fseventstreamcreate function call with this error:
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x0000000000000008
I'm fairly new to objective C (although I know this is C code, its part of an obj-c app) and really have no idea why this is crashing, prob a noob mistake here. I appreciate any help with this.
void monitorCallback(
ConstFSEventStreamRef streamRef,
void *clientCallBackInfo,
size_t numEvents,
void *eventPaths,
const FSEventStreamEventFlags eventFlags[],
const FSEventStreamEventId eventIds[])
{
int i;
char **paths = (char**)eventPaths;
// printf("Callback called\n");
for (i=0; i<numEvents; i++)
{
/* flags are unsigned long, IDs are uint64_t */
printf("Change %llu in %s, flags %u\n", eventIds[i], paths[i], eventFlags[i]);
}
}
- (void) monitorFolder
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSString * monitorFolder = [[NSUserDefaults standardUserDefaults] objectForKey:#"monitorFolder"];
//Create stream to monitor for changes.
CFArrayRef pathsToWatch = CFArrayCreate(NULL, (const void **)&monitorFolder, 1, NULL);
FSEventStreamRef stream;
CFAbsoluteTime latency = 3.0; /* Latency in seconds */
/* Create the stream, passing in a callback */
stream = FSEventStreamCreate(
NULL,
(FSEventStreamCallback)&monitorCallback,
NULL,
pathsToWatch,
kFSEventStreamEventIdSinceNow,
latency,
kFSEventStreamCreateFlagNone );
...
}

Mixing Objective-C with C and code organization

I'm working on a desktop application that watch folders using the fileevent api, so basically this is my code :
#import "PNAppDelegate.h"
void callback(
ConstFSEventStreamRef streamRef,
void *clientCallBackInfo,
size_t numEvents,
void *eventPaths,
const FSEventStreamEventFlags eventFlags[],
const FSEventStreamEventId eventIds[])
{
[(__bridge PNAppDelegate *)clientCallBackInfo reloadStatus];
};
#implementation PNAppDelegate
#synthesize window = _window;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSArray *pathsToWatch = [NSArray arrayWithObject: #"/Users/romainpouclet/Projects/foo"];
void *appPointer = (__bridge void *)self;
FSEventStreamContext context = {0, appPointer, NULL, NULL, NULL};
FSEventStreamRef stream;
CFAbsoluteTime latency = 3.0;
stream = FSEventStreamCreate(NULL,
&callback,
&context,
(__bridge CFArrayRef) pathsToWatch,
kFSEventStreamEventIdSinceNow,
latency,
kFSEventStreamCreateFlagNone);
NSLog(#"Schedule with run loop");
FSEventStreamScheduleWithRunLoop(stream, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
FSEventStreamStart(stream);
[self reloadStatus];
}
-(void)reloadStatus
{
}
#end
No problem, it works pretty well for a POC as simple as this one, BUT it feels kinda ugly (and it probably is, I'm not really used to mix Objective-C and C). So here are my questions :
where should I declare my callback? It feels weird having it at the top of my file, just because it worked there.
is it possible to have some kind of #selector-based approach instead of callbacks? (I find them reassuring :D)
Thanks for your time !
Why not put the callback declaration in either PNAppDelegate.h, or its own header file (if you don't want to spread it around your app). That way you can just include the header file and put the function definition anywhere you want. Doing so is standard C functionality.
// Header file callback.h
void callback(
ConstFSEventStreamRef streamRef,
void *clientCallBackInfo,
size_t numEvents,
void *eventPaths,
const FSEventStreamEventFlags eventFlags[],
const FSEventStreamEventId eventIds[]);
// PNAppDelegate.m
#import "PNAppDelegate.h"
#import "callback.h"
#implementation PNAppDelegate
...
#end
void callback(
ConstFSEventStreamRef streamRef,
void *clientCallBackInfo,
size_t numEvents,
void *eventPaths,
const FSEventStreamEventFlags eventFlags[],
const FSEventStreamEventId eventIds[])
{
[(__bridge PNAppDelegate *)clientCallBackInfo reloadStatus];
};
You are correct, that code IS ugly. However, bridging C and Obj-C is no small task, so you really only have a few options:
Create an Objective-C wrapper around the C-based API. This would be my recommended approach, especially if the API is not too complex. It gives you the advantage of using either delegates or blocks, instead of functions.
Use blocks for callbacks, by getting their internal function pointer:
// internal structure of a block
struct blockPtr {
void *__isa;
int __flags;
int __reserved;
void *__FuncPtr;
void *__descriptor;
};
int main()
{
#autoreleasepool {
__block int b = 0;
void (^blockReference)(void *) = ^(void *arg) {
NSLog(#"<%s>: %i", arg, b++);
};
void *blockFunc = ((__bridge struct blockPtr *) blockReference)->__FuncPtr;
void (*castedFunction)(void *, void *) = blockFunc;
// the first argument to any block funciton is the block
// reference itself, similar to how the first argument to
// any objc function is 'self', however, in most cases you
// don't need the block reference (unless reading __block variables), it's just difficult to
// get that first argument from inside the block
castedFunction((__bridge void *) blockReference, "one");
castedFunction((__bridge void *) blockReference, "two");
}
}
I really don't think this is practical in most situations, but if you can find a way to make it work, more power to you.
Stick with how you are currently doing it. It sucks, but that is how C works.

from within a static function how to place info into iVars?

And note that I can not pass in a ViewController pointer due to this function being passed into another function.
static int callback(void *NotUsed, int argc, char **argv, char **azColName)
{
NSString *str = #"";
int i;
for(i=0; i<argc; i++)
{
printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
str = [NSString stringWithFormat:#"%#\n%s = %s\n", str, azColName[i], argv[i] ? argv[i] : "NULL"];
}
printf("\n");
//tvDisplay is a UITextView
[tvDisplay setText:str]; // <---- ??? how to get to an iVar
return 0;
}
the call:
rc = sqlite3_exec(db, pSQL[i], callback, 0, &zErrMsg);
Callback functions typically have an argument that allows you to pass along arbitrary data (it's usually a void * called context or something similar). You can pass in the object that you need to access when you set up the callback function, and then retrieve it within the callback function:
static void myCallback(int someResult, void *context) {
SomeClass *someObject = (SomeClass *)context;
[someObject doStuff];
}
In your particular case, the place for the "arbitrary data that you want to access in the callback function" is the void * argument right after the callback function itself that you have presently set to 0:
int sqlite3_exec(
sqlite3*, /* An open database */
const char *sql, /* SQL to be evaluated */
int (*callback)(void*,int,char**,char**), /* Callback function */
void *, /* 1st argument to callback */
char **errmsg /* Error msg written here */
);
Keep in mind that you're responsible for ensuring that any data you stick in there remains valid while the callback has not yet returned, and, if necessary, free it in the callback.

How to get read/write streams after dns_sd DNSServiceResolve in iOS

The goal is to get read/write streams after a service is successfully resolved by with the dns_sd API. I started with Apple's DNSSDObjects sample project and I'm editing the DNSSDService.m file to get read and write streams after the service is resolved.
Here's what I've got so far. It seems like it should work, but it does't :(
I got this far by following the code on this thread, though I'm not entirely sure that it's how this should be done.
EDIT: Apple's documentation here confirms that this is how it should be done..."So, once you've resolved the service using DNSServiceResolve, you should pass the service's DNS name (the hosttarget parameter to your DNSServiceResolveReply callback) to a connect-by-name API (like CFStreamCreatePairWithSocketToHost)."
// Called by DNS-SD when something happens with the resolve operation.
static void ResolveReplyCallback(
DNSServiceRef sdRef,
DNSServiceFlags flags,
uint32_t interfaceIndex,
DNSServiceErrorType errorCode,
const char * fullname,
const char * hosttarget,
uint16_t port,
uint16_t txtLen,
const unsigned char * txtRecord,
void * context
)
{
CFStringRef host = CFStringCreateWithCString(kCFAllocatorDefault,
hosttarget,
kCFStringEncodingUTF8);
DNSSDService * obj;
#pragma unused(interfaceIndex)
assert([NSThread isMainThread]); // b/c sdRef dispatches to the main queue
obj = (__bridge DNSSDService *) context;
assert([obj isKindOfClass:[DNSSDService class]]);
assert(sdRef == obj->sdRef_);
#pragma unused(sdRef)
#pragma unused(flags)
#pragma unused(fullname)
#pragma unused(txtLen)
#pragma unused(txtRecord)
if (errorCode == kDNSServiceErr_NoError) {
[obj resolveReplyWithTarget:[NSString stringWithUTF8String:hosttarget]
port:ntohs(port)];
} else {
[obj stopWithError:[NSError errorWithDomain:NSNetServicesErrorDomain
code:errorCode
userInfo:nil]
notify:YES];
}
//now let's get read&write streams?
CFReadStreamRef readStream = NULL;
CFWriteStreamRef writeStream = NULL;
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault,
host,
port,
&readStream,
&writeStream
);
if (readStream && writeStream) {
CFReadStreamSetProperty(readStream,
kCFStreamPropertyShouldCloseNativeSocket,
kCFBooleanTrue);
CFWriteStreamSetProperty(writeStream,
kCFStreamPropertyShouldCloseNativeSocket,
kCFBooleanTrue);
obj.inputStream = (__bridge_transfer NSInputStream *) readStream;
obj.outputStream = (__bridge_transfer NSOutputStream *) writeStream;
}
CFRelease(host);
}