How to get folder changes notifications(folder watcher) in cocoa - cocoa

I am new to Cocoa Application development. I want my application to be notified when any file under a given directory is modified(folder watcher). Modified means deleted, added, content of file is changed. I tried using FSEvents also with using NSWorkspace's notification center or delegate messages as in UKKQueue at http://www.zathras.de/angelweb/sourcecode.htm#UKKQueue. My application got notification when any file under directory is modified. But the problem is that its not giving name or path of specific file which is modified. It gives path of directory but not path of specific file.
Any idea how can I watch folder for modification in specific file??

You have to write code to keep track of the contents of the folder and then whenever you receive an FSEvent notification that the folder contents have changed, you need to compare your stored information about the folder contents with the actual, current contents.
This could be something as simple as a mutable array ivar named something like folderContents, which contains a set of file attributes dictionaries. You could use the dictionary returned from the -attributesOfItemAtPath:error: method of NSFileManager or a subset of it.
All you'd need to do when you receive a folder notification is iterate through the stored dictionaries and check to see whether any files have been added, removed or modified. The NSFileManager attributes dictionary contains all the info you need to do this.
You'd then need to update your stored information about the folder with the updated information.

NSMetadataQuery works well for watching folders:
- (void)setupWatchedFolder {
NSString *watchedFolder = #"/path/to/foo";
NSMetadataQuery *query = [[NSMetadataQuery alloc] init];
[query setSearchScopes:#[watchedFolder]];
[query setPredicate:[NSPredicate predicateWithFormat:#"%K LIKE '*.*'", NSMetadataItemFSNameKey]];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(queryFoundStuff:) name:NSMetadataQueryDidFinishGatheringNotification object:query];
[nc addObserver:self selector:#selector(queryFoundStuff:) name:NSMetadataQueryDidUpdateNotification object:query];
[query startQuery];
}
- (void)queryFoundStuff:(NSNotification *)notification {
NSMetadataQuery *query = self.metadataQuery;
[query disableUpdates];
NSMutableArray *results = [NSMutableArray arrayWithCapacity:self.metadataQuery.resultCount];
for (NSUInteger i=0; i<self.metadataQuery.resultCount; i++) {
[results addObject:[[self.metadataQuery resultAtIndex:i] valueForAttribute:NSMetadataItemPathKey]];
}
// do something with you search results
// self.results = results;
[query enableUpdates];
}

Related

Mac CGDataConsumerCreateWithFilename: failed to open `.myfile.pdf' for writing: Operation not permitted

I am generating a PDF document and offering the user the option to save it to disk. I present them with an NSSavePanel for them to select the filename to save it as. If they choose a file that already exists, it prompts them if they are sure and want to Replace that file. If they agree to replace the file, then I generate the PDF and write it to that chosen URL.
However, my write fails with the following error:
CGDataConsumerCreateWithFilename: failed to open `/path/to/.myfile.pdf' for writing: Operation not permitted
I have the appropriate Entitlements to write to files on disk. The file that's in the way is the one I generated during a previous test. Do I need to explicitly delete the existing file before I write mine to that URL or is there some way I can tell the system that it may overwrite the existing file?
This is the code I use to launch the NSSavePanel (my code saves their last chosen directory so that it can default to the same location for all future saves):
- (NSURL*) requestSaveFilenameForExtension:(NSString*)fileExtension previousSaveDirectorySettingKey:(NSString*)previousDirectorySettingKey defaultFilename:(NSString*)defaultFilename {
NSSavePanel *panel = [NSSavePanel savePanel];
[panel setAllowedFileTypes:[NSArray arrayWithObject:fileExtension]];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *previousSaveDirectory = [defaults stringForKey:previousDirectorySettingKey];
if (previousSaveDirectory == nil) {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
previousSaveDirectory = [paths objectAtIndex:0];
}
[panel setExtensionHidden:NO];
[panel setNameFieldStringValue:defaultFilename];
[panel setDirectoryURL:[NSURL fileURLWithPath:previousSaveDirectory]];
NSInteger ret = [panel runModal];
if (ret == NSFileHandlingPanelOKButton) {
NSString *saveDirectory = [[[panel URL] absoluteString] stringByDeletingLastPathComponent];
[defaults setValue:saveDirectory forKey:previousDirectorySettingKey];
return [panel URL];
} else {
return nil;
}
}
and here is the code I'm using to write the file to that given URL, it's just standard PDFKit -writeToURL::
PDFDocument *document = [self generateDocument];
[document writeToURL:documentURL];
i ran into same problem.
Go to app target capabilities: check
File Access Permission & Access as "read/write"
for all the folders.
Then your app will have access rights to write the file at User selected, download, pictures, music and movies folders. Not the other folders.
And it is recommended to use "NSSavePanel" to interact with users for save the file, this way, you will get the hint, whether the access rights is set correctly when you build the project.. Otherwise, you will end up getting the error like now, without having a clue why. sucks..

Read plist into nsmutabledictionary, update the dictionary and then save it again

I am really new to saving files in objective C but what I'm trying to accomplish is reading a plist file located in the documents directory on launch or creating it if it doesn't exist.
It should be read in to a NSMutableDictionary. Later on in the app I should be able to save items to the NSMutableDict with categories as keys + text.
The before launch in the viewWillUnload the NSMutableDictionary should be saved into the plist file again.
I have created the plist but I need a way to write to the NSMutableDictionary the right way (category and my result.text string.
And I also need to save the NSMutableDictionary to the plist file and read the plist into the dictionary on launch.
Some help with this would be awsome :D
Thanks guys.
In the savefile void I am doing this:
storeDict = [[ NSMutableDictionary alloc]
init];
[storeDict setObject:resultText.text forKey:#"kvitto"];
[storeDict setObject:kategori forKey:#"kategori"];
[storeDict writeToFile:[self saveFilePath] atomically:YES];
saveFilePath looks like this:
- (NSString *) saveFilePath {
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
return [[path objectAtIndex:0] stringByAppendingString:#"savefile.plist"];
}
The values are strings collected from a code that the user have scanned so don't worry bout them.
Well so how do I save this correctly keeping the data that already exists in the savefile.plist.
Thanks again
If you want dictionaries and arrays contained in the dictionary to be mutable as well then do the following:
NSData *data = [NSData dataWithContentsOfFile:path];
NSError *error;
storeDict =
[NSPropertyListSerialization
propertyListWithData:data
options:NSPropertyListMutableContainersAndLeaves
format:nil
error: &error];
Why can't you use writeToFile api? Before writing question here you must google or check apple documentation for NSMutableDictionary.
[dict writeToFile:path atomically:YES];
To load saved dictionary
dict = [NSMutableDictionary dictionaryWithContentsOfFile:path];
Happy coding!

Getting filename and path from nsfilewrapper / nstextattachment in NSAttributedString

I have a basic NSTextView with rich text and graphics enabled (in IB). What I'd like to get is the path and filename of any images dragged in so I can pass those to another class.
I'm new to NSAttributedString but I've got a loop using enumerateAttributesInRange:options:usingBlock: looking for NSAttachmentAttributeName and that's all working fine. But going deeper, I get to the fileWrapper class and it's apparent inability to give me the path of the item.
How would I go about getting the name and path of the NSTextAttachment?
Related: Is there an easier way to get them all then stepping through the attributes?
Thanks much!
While I personally hold the design of NSFileWrapper in contempt, if you just need the data of each attachment you can access it as an NSData instance via NSFileWrapper's regularFileContents method. However, I needed a valid and explicit pathname to the attachment for my application. To get it is much more work than it should be:
You can subclass your NSTextView and override the NSDraggingDestination Protocol method draggingEntered: and you can traverse the NSPasteboardItem objects passed to your application during the dragging operation. I chose to keep the pathname and its inode number in an NSMutableDictionary, as NSFileWrapper can provide you with the inode of the referenced file. Later, when I access the NSTextView contents via an NSAttributedString, I can fetch the pathname of an attachment using the inode as an index.
- (NSDragOperation)draggingEntered:(id < NSDraggingInfo >)sender {
// get pasteboard from dragging operation
NSPasteboard *pasteboard = [sender draggingPasteboard];
NSArray *pasteboardItems = [pasteboard pasteboardItems];
for ( NSPasteboardItem *pasteboardItem in pasteboardItems ) {
// look for a file url type from the pasteboard item
NSString *draggedURLString = [pasteboardItem stringForType:#"public.file-url"];
if (draggedURLString != nil) {
NSURL *draggedURL = [NSURL URLWithString:draggedURLString];
NSString *draggedPath = [draggedURL path];
NSLog(#"pathname: %#", draggedPath);
// do something with the path
// get file attributes
NSDictionary *draggedAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:draggedPath error:nil];
if ( draggedAttributes == nil)
continue;
// the NSFileWrapper allows access to the absolute file via NSFileSystemFileNumber
// put the path and the inode (returned as an NSNumber) into a NSMutableDictionary
NSNumber *draggedInode = [draggedAttributes objectForKey:NSFileSystemFileNumber];
[draggedFiles setObject:draggedPath forKey:draggedInode];
}
}
return [super draggingEntered:sender];
}
One issue with my solution, that doesn't effect my application, is that multiple files dragged into the view (either singly or together) which are hard links to the same file, will only be indexed as the last pathname added to the dictionary which shares the inode. Depending on how the pathnames are utilized by your application this could be an issue.

Reading a plist

I'm trying to read ~/Library/Preferences/com.apple.mail.plist (on Snow Leopard) to get the email address and other information to enter into the about dialog. I'm using the following code, which is obviously wrong:
NSBundle* bundle;
bundle = [NSBundle mainBundle];
NSString *plistPath = [bundle pathForResource:#"~/Library/Preferences/com.apple.mail.plist" ofType:#"plist"];
NSDictionary *plistData = [NSDictionary dictionaryWithContentsOfFile:plistPath];
NSString *item = [plistData valueForKeyPath:#"MailAccounts.Item 2.AccountName"];
NSLog(#"Result = %#", item);
Moreover, the value I need to read is MailAcounts -> Item 2 -> AccountName and I am not sure I am doing this correctly (due to the space in the Item 2 key).
I tried reading Apple's developer guide to plist files but no help there.
How can I read a plist and extract the values as an NSString?
Thanks.
The first level is an array, so you need to use "MailAccounts.AccountName" and treat it as NSArray*:
NSString *plistPath = [#"~/Library/Preferences/com.apple.mail.plist" stringByExpandingTildeInPath];
NSDictionary *plistData = [NSDictionary dictionaryWithContentsOfFile:plistPath];
NSArray *item = [plistData valueForKeyPath:#"MailAccounts.AccountName"];
NSLog(#"Account: %#", [item objectAtIndex:2]);
Alternatively you can go by keys and pull the array from "MailAccounts" first using valueForKey: (which will yield NSArray*) and then objectAtIndex: to get the dictionary of that particular account (useful if you need more than the name).
Two things:
You don't want or need to use NSBundle to get the path to the file. The file lies outside of the app bundle. So you should just have
NSString *plistPath = #"~/Library/Preferences/com.apple.mail.plist";
You have to expand the tilde in the path to the user directory. NSString has a method for this. Use something like
NSString *plistPath = [#"~/Library/Preferences/com.apple.mail.plist" stringByExpandingTildeInPath];

List files available in iCloud

Is there a manner to list all files (documents+data) I have in the iCloud (from a Mac) ?
I believe that the NSMetadataQuery object can help me with that, but is there any sample code out there ?
Thanks !
Here some sample code to do a query for txt files in your iCloud folder. If you want to look for other files, simple replace the predicate (NSPredicate *pred = [NSPredicate predicateWithFormat:#"%K ENDSWITH '.txt'", NSMetadataItemFSNameKey];).
To list all files, you could simply do #"NOT %K.pathExtension = '.'" but I'm not sure if this is the most elegant method. Suggestions welcome.
Have a look at this post to get the context and full code sample. Here is just the method to look for files.
-(void)loadDocument {
// (2) iCloud query: Looks if there are txt files in the cloud
NSMetadataQuery *query = [[NSMetadataQuery alloc] init];
_query = query;
//SCOPE
[query setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];
//PREDICATE
NSPredicate *pred = [NSPredicate predicateWithFormat:#"%K ENDSWITH '.txt'", NSMetadataItemFSNameKey];
[query setPredicate:pred];
//FINISHED?
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(queryDidFinishGathering:) name:NSMetadataQueryDidFinishGatheringNotification object:query];
[query startQuery];
}

Resources