I'm trying to monitor a directory and get notified when files are added/removed/renamed. I'm using the CDEvents Objective-C wrapper. Here's the code I'm using:
self.events = [[CDEvents alloc] initWithURLs:#[self.programsFolder]
delegate:self
onRunLoop:[NSRunLoop currentRunLoop]
sinceEventIdentifier:kFSEventStreamEventIdSinceNow
notificationLantency:2
ignoreEventsFromSubDirs:NO
excludeURLs:nil
streamCreationFlags:kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagWatchRoot];
and the delegate method:
- (void)URLWatcher:(CDEvents *)URLWatcher eventOccurred:(CDEvent *)event {
NSLog(#"event apparantly happened");
// just redo everything for now
NSFileManager *manager = [NSFileManager defaultManager];
NSArray *startMenuFiles = [manager contentsOfDirectoryAtURL:_programsFolder
includingPropertiesForKeys:#[NSURLNameKey]
options:0
error:NULL];
NSMutableArray *newItems = [NSMutableArray new];
for (NSURL *startMenuFile in startMenuFiles) {
if (![[startMenuFile path] hasSuffix:#".plist"])
continue;
[newItems addObject:[[StartMenuItem alloc] initFromFile:startMenuFile]];
}
self.mutableItems = newItems;
}
But when I create files or folders in the folder it's monitoring, nothing happens. You can see I added an NSLog so I'll know when an event happens. Nothing is ever logged.
What could be the problem?
If you need more context, all of the code for this project is at http://github.com/vindo-app/vindo. Look in Code > Start > StartMenu.m in Xcode.
The problem was in initializing the FSEvents source. You have to provide a run loop to monitor for events. This code was being called from a thread other than the main thread, so [NSRunLoop currentRunLoop] was not the main thread's run loop. I changed it to [NSRunLoop mainRunLoop], and that fixed the problem.
Related
I am trying to run a program in a background process that will register every shutdown event in the system.
Doing so by registering to NSWorkspaceWillPowerOffNotification as show below:
#import <AppKit/AppKit.h>
#interface ShutDownHandler : NSObject <NSApplicationDelegate>
- (void)computerWillShutDownNotification:(NSNotification *)notification;
#end
int main(int argc, char* argv[]) {
NSNotificationCenter *notCenter;
notCenter = [[NSWorkspace sharedWorkspace] notificationCenter];
ShutDownHandler* sdh = [ShutDownHandler new];
[NSApplication sharedApplication].delegate = sdh;
[notCenter addObserver:sdh
selector:#selector(computerWillShutDownNotification:)
name:NSWorkspaceWillPowerOffNotification
object:nil];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[[NSFileManager defaultManager] createFileAtPath:#"./output.txt" contents:nil attributes:nil];
});
[[NSRunLoop currentRunLoop] run];
return 0;
}
#implementation ShutDownHandler
- (void)computerWillShutDownNotification:(NSNotification *)notification {
NSFileHandle* file = [NSFileHandle fileHandleForUpdatingAtPath: #"./output.txt"];
[file seekToEndOfFile];
NSDateFormatter* fmt = [NSDateFormatter new];
[fmt setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
NSDate* current = [NSDate date];
NSString* dateStr = [fmt stringFromDate:current];
[dateStr writeToFile:#"./output.txt" atomically:YES encoding:NSUTF8StringEncoding error:nil];
}
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
return NSTerminateCancel;
}
#end
For some reason that I cannot understand, the NSWorkspaceWillPowerOffNotification handler is never called!
Also added the following to my .plist:
<key>NSSupportsSuddenTermination</key>
<false/>
but still no notification is register when shutting down my system.
Any idea as to why??
For future reference:
I was not able to register to the above event and see it fire.
Finally, I went with a different approach:
apple open-source power management
Here, you can see we have notifications names such as:
"com.apple.system.loginwindow.logoutNoReturn"
You can register to those, and that will do the trick.
Hope this will help someone one day :)
When you say you've ran your code as a background process, do you mean that it's based on launchd daemon handled according to plist file in /Library/LaunchDeamon ? In this case, the notification will not be sent. Try running it under Cocoa application
Seems like this notification doesn't work on LaunchDaemon linked against AppKit.
I'm still looking for a way to get these notifications on background deamon process.
i can make NSWorkspaceWillPowerOffNotification and applicationShouldTerminate: to work by adding NSApplicationActivationPolicyAccessory and replacing the run loop with [NSApp run]. it still won't be very useful if you want to continue to monitor all logouts (and/or power off attempts) since it only works if the process is launched while the user has already logged in, and it can only detect the current UI login session's power-off/logout notification and "app should terminate" delegate.
"apps" that belong to the current UI login session get terminated by the OS when the user logs out. non-app (no [NSApp run]) processes will continue to run even after the user logs out.
so, if you wan to continue to monitor the user logouts (or power off attempts), you would need a non-app (no [NSApp run]) process.
NSWorkspaceSessionDidBecomeActiveNotification and NSWorkspaceSessionDidResignActiveNotification work without [NSApp run] if you want to monitor if the user switched out or back in.
or you can try the System Configuration APIs if it works for your use case.
"Technical Q&A QA1133: Determining console user login status" https://developer.apple.com/library/archive/qa/qa1133/_index.html
sounds like com.apple.system.loginwindow.logoutNoReturn was the best option for whatever you wanted to do. good to know!
here is the updated code (although it's not really useful):
// cc powreoff-notification.m -o poweroff-notification -framework AppKit
#import <AppKit/AppKit.h>
#interface ShutDownHandler : NSObject <NSApplicationDelegate>
- (void)computerWillShutDownNotification:(NSNotification *)notification;
#end
int main(int argc, char* argv[]) {
#autoreleasepool {
NSLog(#"%s", __func__);
NSNotificationCenter* notCenter = [[NSWorkspace sharedWorkspace] notificationCenter];
ShutDownHandler* sdh = [ShutDownHandler new];
[NSApplication sharedApplication].delegate = sdh;
[notCenter addObserver:sdh
selector:#selector(computerWillShutDownNotification:)
name:NSWorkspaceWillPowerOffNotification
object:nil];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[[NSFileManager defaultManager] createFileAtPath:#"./output.txt" contents:nil attributes:nil];
});
// without NSApplicationActivationPolicyAccessory, the [NSApp run] process gets killed on
// poweroff/logout instead of getting the power off notification or applicationShouldTerminate:.
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
// without [NSApp run], you won't get the power off notification or applicationShouldTerminate:.
//[[NSRunLoop currentRunLoop] run];
[NSApp run];
// this process needs to be launched while the user has already logged in. otherwise,
// you won't get the power off notification or applicationShouldTerminate:.
}
return 0;
}
#implementation ShutDownHandler
- (void)computerWillShutDownNotification:(NSNotification *)notification {
NSLog(#"%s", __func__);
NSFileHandle* file = [NSFileHandle fileHandleForUpdatingAtPath: #"./output.txt"];
[file seekToEndOfFile];
NSDateFormatter* fmt = [NSDateFormatter new];
[fmt setDateFormat:#"yyyy-MM-dd HH:mm:ss\n"];
NSDate* current = [NSDate date];
NSString* dateStr = [fmt stringFromDate:current];
[dateStr writeToFile:#"./output.txt" atomically:YES encoding:NSUTF8StringEncoding error:nil];
}
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
NSLog(#"%s", __func__);
return NSTerminateCancel;
}
#end
I am new to cocoa programming , I am trying to download binary file from a url to disk. Unfortunately the methods are not calledback for some reason. The call to downloadFile is made from a background thread started via [self performSelectorOnBackground] etc. Any ideas what am I doing wrong ?
NOTE
When I make call to downloadFile from main UI thread, it seems to work, what's up with background thread ?
-(BOOL) downloadFile
{
BOOL downloadStarted = NO;
// Create the request.
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:versionLocation]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// Create the connection with the request and start loading the data.
NSURLDownload *theDownload = [[NSURLDownload alloc] initWithRequest:theRequest
delegate:self];
if (theDownload) {
// Set the destination file.
[theDownload setDestination:#"/tmp" allowOverwrite:YES];
downloadStarted = YES;
} else {
// inform the user that the download failed.
downloadStarted = NO;
}
return downloadStarted;
}
- (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error
{
// Release the connection.
[download release];
// Inform the user.
NSLog(#"Download failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
- (void)downloadDidFinish:(NSURLDownload *)download
{
// Release the connection.
[download release];
// Do something with the data.
NSLog(#"%#",#"downloadDidFinish");
}
I think you should start the run loop to which your NSURLDownload object is attached. By default it will use the current thread's run loop, so you probably should do something like this after initialization of NSURLDownload object:
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
while (!self.downloaded && !self.error && [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]])
;
Properties self.downloaded and self.error should be set in your callbacks.
In main thread run loop probably started by the NSApplication object.
I'm new into Cocoa and am writing a simple app to learn working with Core Data, but it crashes with EXC_BAD_ACCESS. Tried several things and haven't find the solution yet. As I said, I'm not very experienced in Cocoa.
I have followed the usual Core Data tutorials.
This is my Model:
I've added these two entities as NSArrayController in my Nib file and have two NSTableViews with Value Binding to the entity objects.
And here's the code:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSManagedObjectContext *context = [self managedObjectContext];
TaskList *list = [NSEntityDescription
insertNewObjectForEntityForName:#"TaskList"
inManagedObjectContext: context]; // EXC_BAD_ACCESS happens here
[list setTitle:#"Inbox"];
Task *task = [NSEntityDescription
insertNewObjectForEntityForName:#"Task"
inManagedObjectContext: context];
[task setKey:#"Remember the milk"];
[task setList:list];
NSError *error;
if (![context save:&error]) {
NSLog(#"Error: %#", [error localizedDescription]);
}
}
That's it! That's all my program. I am using Xcode 4.2, developing a Mac app, and ARC is enabled.
UPDATE: jrturton asked me to include implementation of [self managedObjectContext]. I didn't write this code, but here's what I found in AppDelegate.h:
#property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
And this is from AppDelegate.m:
#synthesize managedObjectContext = __managedObjectContext;
...
/**
Returns the managed object context for the application (which is already
bound to the persistent store coordinator for the application.)
*/
- (NSManagedObjectContext *)managedObjectContext {
if (__managedObjectContext) {
return __managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (!coordinator) {
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setValue:#"Failed to initialize the store" forKey:NSLocalizedDescriptionKey];
[dict setValue:#"There was an error building up the data file." forKey:NSLocalizedFailureReasonErrorKey];
NSError *error = [NSError errorWithDomain:#"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict];
[[NSApplication sharedApplication] presentError:error];
return nil;
}
__managedObjectContext = [[NSManagedObjectContext alloc] init];
[__managedObjectContext setPersistentStoreCoordinator:coordinator];
return __managedObjectContext;
}
Check your managed object model. Make sure all the entity and attribute names are spelled correctly. Also check your object class files and make sure they contain what you expect.
Maybe the debugger does not show you the correct row when crashing: I noticed, that you have a method setKey:, but no attribute called keyin your Task entity. Try setting all the attributes with the dot notation, like list.title = #"Inbox". (This is generally easier to read and avoids similar errors.)
As suggested, before the line inserting the new entity, set a breakpoint and make sure the managed object context is not null.
Finally, perhaps you have to cast your object. insertNewObjectForEntityForName: returns an object of type NSManagedObject, but you are assigning it to a type TaskList. Try adding the cast before you use the object:
TaskList *list = (TaksList *) [NSEntityDescription
insertNewObjectForEntityForName:#"TaskList"
inManagedObjectContext: context];
I had this same issue. I resolved it like Mostafa said above. If you create a project with Core Data enabled, it will automatically create a file for you. Use this .xcdatamodeld file instead of a custom one. If you have one already created, just delete the originally created file and rename your datamodel file to the originally created file name.
I'm trying to get my Gmail unread email count using Cocoa (Mac) and the PubSub framework. I've seen one or two links showing using PubSub and Gmail, here's my code so far.
PSClient *client = [PSClient applicationClient];
NSURL *url = [NSURL URLWithString:#"https://mail.google.com/mail/feed/atom/inbox"];
PSFeed *feed = [client addFeedWithURL:url];
[feed setLogin: #"myemailhere"];
[feed setPassword: #"mypasswordhere"];
NSLog(#"Error: %#", feed.lastError);
Anyone know how I can get the unread count?
Thanks :)
You have two problems: one which there's a solution for and one which seems to be a perpetual problem.
The first: Feed refreshes happen asynchronously. So you need to listen to the PSFeedRefreshingNotification and PSFeedEntriesChangedNotification notifications to see when the feed gets refreshed and updated. The notification's object will be the PSFeed in question.
As an example:
-(void)feedRefreshing:(NSNotification*)n
{
PSFeed *f = [n object];
NSLog(#"Is Refreshing: %#", [f isRefreshing] ? #"Yes" : #"No");
NSLog(#"Feed: %#", f);
NSLog(#"XML: %#", [f XMLRepresentation]);
NSLog(#"Last Error: %#", [f lastError]);
if(![f isRefreshing])
{
NSInteger emailCount = 0;
NSEnumerator *e = [f entryEnumeratorSortedBy:nil];
id entry = nil;
while(entry = [e nextObject])
{
emailCount++;
NSLog(#"Entry: %#", entry);
}
NSLog(#"Email Count: %ld", emailCount);
}
}
-(void)feedUpdated:(NSNotification*)n
{
NSLog(#"Updated");
}
-(void)pubSubTest
{
PSClient *client = [PSClient applicationClient];
NSURL *url = [NSURL URLWithString:#"https://mail.google.com/mail/feed/atom/inbox"];
PSFeed *feed = [client addFeedWithURL:url];
[feed setLogin: #"correctUserName#gmail.com"];
[feed setPassword: #"correctPassword"];
NSError *error = nil;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(feedUpdated:) name:PSFeedEntriesChangedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(feedRefreshing:) name:PSFeedRefreshingNotification object:nil];
[feed refresh:&error];
if(error)
NSLog(#"Error: %#", error);
}
The second (and far worse) issue is that PubSub doesn't handle authenticated feeds correctly. I saw this at http://www.dizzey.com/development/fetching-emails-from-gmail-using-cocoa/ and I've reproduced the same behavior on my own system. I don't know if this bug is 10.7 specific or if it affects previous versions of OS X.
The "workaround" is to use NSURLConnection to perform an authenticated retrieval of the raw feed XML. You can then shove that into a PSFeed using its initWithData:URL: method. The very serious drawbacks with this is that you aren't actually PubSubing anymore. You'll have to run a timer and manually refresh the feed whenever appropriate.
The best I was able to do to help you out was to file a bug: rdar://problem/10475065 (OpenRadar: 1430409 ).
You should probably file a duplicate bug to try to increase the chances that Apple will fix it.
Good luck.
CD's been an enormous learning curve for me and there's still a bit for me to go, but any help on the following could enable me to lift the current weight on my shoulders!
I'm trying to write a method that implements a "Save As.." for the user in my CD app.
So far I've got:
[saveAsPanel beginSheetModalForWindow:window completionHandler:^(NSInteger userResult)
{
if (userResult == NSOKButton) {
NSPersistentStoreCoordinator *psc = [self persistentStoreCoordinator];
NSURL *oldURL = [self URLOfInternalStore]; //returns the current store's URL
NSURL *newURL = [saveAsPanel URL];
NSError *error = nil;
NSPersistentStore *oldStore = [psc persistentStoreForURL:oldURL];
NSPersistentStore *sqLiteStore = [psc migratePersistentStore:oldStore
toURL:newURL
options:nil
withType:NSXMLStoreType
error:&error];
}
}];
Unfortunately, I just get the error:
Object's persistent store is not reachable from this NSManagedObjectContext's coordinator.
Should I 'remove' and then 'addPersistentStore...' to update it to the new URL? The doc's seem to suggest that all will be handled with in the 'migrate' method.
Thanks in advance!
Edit:
Ok, well, I've come up with my own 'dirty' method. I can imagine that this isn't an approved way of doing things, but there's no error thrown up and the app works as expected at all times (not often I can say that, either!):
-(IBAction)saveAsAction:(id)sender
{
NSSavePanel *saveAsPanel = [NSSavePanel savePanel];
[saveAsPanel beginSheetModalForWindow:window completionHandler:^(NSInteger userResult)
{
if (userResult == NSOKButton) {
[self saveAction:#"saveAsCalling"];
NSURL *newURL = [saveAsPanel URL];
NSError *error = nil;
[[NSFileManager defaultManager] copyItemAtURL:[NSURL fileURLWithPath:internalStore] toURL:newURL error:&error];
//internalStore is a hard-wired NSString that holds the path to the bundle's database
}
}];
}
-(IBAction)loadAction:(id)sender
{
NSOpenPanel *loadPanel = [NSOpenPanel openPanel];
[loadPanel beginSheetModalForWindow:window completionHandler:^(NSInteger userResult)
{
if (userResult == NSOKButton) {
[self saveAction:#"loadCalling"];
NSURL *newURL = [loadPanel URL];
NSURL *oldURL = [NSURL fileURLWithPath:internalStore];
NSError *error = nil;
NSPersistentStoreCoordinator *psc = [SELF_MOC persistentStoreCoordinator];
[psc removePersistentStore:[[self persistentStoreCoordinator] persistentStoreForURL:oldURL] error:&error];
[psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:newURL options:nil error:&error];
[[NSFileManager defaultManager] removeItemAtURL:oldURL error:&error];
[[NSFileManager defaultManager] copyItemAtURL:newURL toURL:oldURL error:&error];
[psc removePersistentStore:[[self persistentStoreCoordinator] persistentStoreForURL:newURL] error:&error];
[psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:oldURL options:nil error:&error];
}
}];
}
The basic reasoning is this: to do a 'SaveAs...' I simply copy out the SQLLite store file in the mainBundle to wherever the user selects and rename it to what they want - as per TechZen's suggestion.
To do a 'Load' then I first removePersistentStore from the bundle's file, add the one that the user's just chosen. Delete the bundle store (which in theory isn't now being used) and then copy the user's choice back into the bundle. Finally, the two operations of remove and addPersistentStore are performed to point the app back to it's bundle's file which is now the user's choice.
Hope that makes sense. If anyone has any thoughts on just how unprofessional a methodology this is then please - be kind as I'm fairly new - let me know. I can't find anything that is more elegant.
I know Apple don't like you using removePersistentStore and addPersistentStore but, as I say no errors are reported (in my actual code I scattered NSLog lines throughout to report what error is holding).
You only use a SaveAs... in a document based app. If you use Core Data as your model, you need to use NSPersistentDocument to save your data. It provide the SaveAs... functionality you seek.
Straight Core Data is used for more database-like apps in which the entire app operates from one data set (more or less.)