Getting filename and path from nsfilewrapper / nstextattachment in NSAttributedString - cocoa

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.

Related

NSTreeController - Retrieving selected node

I added Book object in bookController (NSCreeController). Now i want to get stored Book object when i select the row.
- (IBAction)addClicked:(id)sender {
NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
// NSTimeInterval is defined as double
NSUInteger indexArr[] = {0,0};
Book *obj = [[Book alloc] init];
NSString *dateString = [NSDateFormatter localizedStringFromDate:[NSDate date] dateStyle:NSDateFormatterNoStyle timeStyle:NSDateFormatterLongStyle];
obj.title = [NSString stringWithFormat:#"New %#",dateString];
obj.filename = [NSString stringWithFormat:#"%d",arc4random()%100000];
[self.booksController insertObject:obj atArrangedObjectIndexPath:[NSIndexPath indexPathWithIndexes:indexArr length:2]];
}
I concede there perhaps could be a better solution--
I am unfamiliar with how NSTreeController works, but I looked a the class reference and noticed that it has a content property, similar to an NSArrayController (Which I am familiar with grabbing specific objects from).
I believe that if the content property is actually of type of some kind of tree data structure, my answer here probably won't work. The class reference says this about content:
The value of this property can be an array of objects, or a
single root object. The default value is nil. This property is
observable using key-value observing.
So this is what I historically have done with the expected results:
NSString *predicateString = [NSString stringWithFormat:NEVER_TRANSLATE(#"(filename == %#) AND (title == %#)"), #"FILENAME_ARGUMENT_HERE", #"TITLE_ARGUMENT_HERE"];
NSArray *matchingObjects = [[self content] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:predicateString]];
Then simply calling -objectAtIndex: will grab you your object. Note that the NSArray will be empty if the object doesn't exist, and if you have duplicate objects, there will be multiple objects in the array.
I also searched for an answer to your question, and found this SO thread:
Given model object, how to find index path in NSTreeController?
It looks pretty promising if my solution doesn't work, the author just steps through the tree and does an isEqual comparison.
If you could (if it's not too much trouble), leave a comment here to let me know what works for you, I'm actually curious :)

How to get folder changes notifications(folder watcher) in 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];
}

Cocoa encryption plain text?

I'm working on an encryption application that for now encrypts text-only files. I need some help in the connections and how to go about the actual encryption. I received this snippet of code to encrypt a file, but I am a bit confused. What I need to do is have a button (encrypt) that takes this text file and encrypts it. Am I supposed to extract the contents of the file first, then encrypt it? How so? The program must know what file has been selected so it encrypts it, and I'm a complete noob right now, and I need some help.
Step by step instructions would be greatly appreciated.
This was the code:
- (NSData*) encryptString:(NSString*)plaintext withKey:(NSString*)key {
return [[plaintext dataUsingEncoding:NSUTF8StringEncoding] AES256EncryptWithKey:key];
}
I've implemented a file chooser with the following snippet:
- (IBAction)fileChooser:(id)sender {
int i;
NSOpenPanel* openDlg = [NSOpenPanel openPanel];
[openDlg setCanChooseFiles:YES];
[openDlg setCanChooseDirectories:YES];
[openDlg setPrompt:#"Select"];
if ([openDlg runModalForDirectory:nil file:nil] == NSOKButton )
{
NSArray* files = [openDlg filenames];
for( i = 0; i < [files count]; i++ )
{
[files objectAtIndex:i];
}
}
}
This was the code:
- (NSData*) encryptString:(NSString*)plaintext withKey:(NSString*)key {
return [[plaintext dataUsingEncoding:NSUTF8StringEncoding] AES256EncryptWithKey:key];
}
First, look at the receiver of the AES256EncryptWithKey: message. This is another, nested message:
[plaintext dataUsingEncoding:NSUTF8StringEncoding]
What's plaintext? It's declared there in your encryptString:withKey: method: It is a variable holding a pointer to an NSString.
So you're sending the dataUsingEncoding: message to an NSString instance.
That's good if you intend to encrypt some plain-text user input, but it's not so good for encrypting files. NSStrings are for nothing but human characters; the user will probably want to encrypt files that are not plain text, such as images and video files.
I know you said that your application “for now encrypts text-only files”, but the solution is actually simpler when you throw out this restriction.
So you need to encrypt any data, not just a string. Delete your encryptString:withKey: method—it is useless.
From the implementation of that late method, we know that you were sending AES256EncryptWithKey: to an object obtained by sending dataUsingEncoding: to an NSString instance.
If you look in the documentation for NSString, you can see what dataUsingEncoding: returns. Spoiler: It returns an NSData object.
From there, it's one hyperlink away to the NSData documentation, where you will find two things:
It has no method by the selector AES256EncryptWithKey:.
It does have methods to create a data object from the contents of a file. (I'll let you find them.)
I'm assuming that you knew #1, and downloaded a category implementation from somewhere. I'm also assuming that, in fact, this category is on NSData; the category's #interface, in its header, will tell you.
In Objective-C, you should not use an index loop to iterate through an array unless you actually need the index for something, which you generally don't and you, specifically, don't. Instead, use fast enumeration:
for (NSString *path in [openPanel filenames]) {
}
You can see how, again, this makes the solution simpler. It's also faster.
Inside that loop, pass that path to the NSData class method that creates an NSData object from the contents of a file. Then, send that data object the AES256EncryptWithKey: message to obtain the ciphertext, which you should probably write out to a separate file. I'll refer you back to the NSString documentation for the path-manipulation methods you'll need to compute the output file path, and the NSData documentation for the method you'll need to write the ciphertext data out to the output file.

Core Data Transformable attributes NOT working with NSPredicate

I often use Transformable for Core Data attributes, so I can change them later.
However, it seems like, if I want to use NSPredicate to find a NSManagedObject, using "uniqueKey == %#", or "uniqueKey MATCHES[cd] %#", it's not working as it should.
It always misses matching objects, until I change the attributes of the uniqueKey of the matching object to have specific class like NSString, or NSNumber.
Can someone explain the limitation of using NSPredicate with Transformable attributes?
Note: I'm not sure when/if this has changed since 5/2011 (from Scott Ahten's accepted answer), but you can absolutely search with NSPredicate on transformable attributes. Scott correctly explained why your assumptions were broken, but if Can someone explain the limitation of using NSPredicate with Transformable attributes? was your question, he implied that it is not possible, and that is incorrect.
Since the is the first google hit for "Core Data transformable value search nspredicate" (what I searched for trying to find inspiration), I wanted to add my working answer.
How to use NSPredicate with transformable properties
Short, heady answer: you need to be smart about your data transformers. You need to transfrom the value to NSData that contains what I'll call "primitive identifying information", i.e. the smallest, most identifying set of bytes that can be used to reconstruct your object. Long answer, ...
Foremost, consider:
Did you actual mean to use a transformable attribute? If any supported data type -- even binary data -- will suffice, use it.
Do you understand what transformable attributes actually are? How they pack and unpack data to and from the store? Review Non-Standard Persistent Attributes in Apple's documentation.
After reading the above, ask: does custom code that hides a supported type "backing attribute" work for you? Possibly use that technique.
Now, past those considerations, transformable attributes are rather slick. Frankly, writing an NSValueTransformer "FooToData" for Foo instances to NSData seemed cleaner than writing a lot of adhoc custom code. I haven't found a case where Core Data doesn't know it needs to transform the data using the registered NSValueTransformer.
To proceed simply address these concerns:
Did you tell Core Data what transformer to use? Open the Core Data model in table view, click the entity, click the attribute, load the Data Model Inspector pane. Under "Attribute Type: Transformable", set "Name" to your transformer.
Use a default transformer (again, see the previous Apple docs) or write your own transformer -- transformedValue: must return NSData.
NSKeyedUnarchiveFromDataTransformerName is the default transformer and may not suffice, or may draw in somewhat-transient instance data that can make two similar objects be different when they are equal.
The transformed value should contain only -- what I'll call -- "primitive identifying information". The store is going to be comparing bytes, so every byte counts.
You may also register your transformer globally. I have to do this since I actually reuse them elsewhere in the app -- e.g. NSString *name = #"FooTrans"; [NSValueTransformer setValueTransformer:[NSClassFromString(name) new] forName:name];
You probably don't want to use transforms heavily queried data operations - e.g. a large import where the primary key information uses transformers - yikes!
And then in the end, I simply use this to test for equality for high-level object attributes on models with NSPredicates -- e.g. "%K == %#" -- and it works fine. I haven't tried some of the various matching terms, but I wouldn't be surprised if they worked sometimes, and others not.
Here's an example of an NSURL to NSData transformer. Why not just store the string? Yeah, that's fine -- that's a good example of custom code masking the stored attribute. This example illustrates that an extra byte is added to the stringified URL to record if it was a file URL or not -- allowing us to know what constructors to use when the object is unpacked.
// URLToDataTransformer.h - interface
extern NSString *const kURLToDataTransformerName;
#interface URLToDataTransformer : NSValueTransformer
#end
...
// URLToDataTransformer.m - implementation
#import "URLToDataTransformer.h"
NSString *const kURLToDataTransformerName = #"URLToDataTransformer";
#implementation URLToDataTransformer
+ (Class)transformedValueClass { return [NSData class]; }
+ (BOOL)allowsReverseTransformation { return YES; }
- (id)transformedValue:(id)value
{
if (![value isKindOfClass:[NSURL class]])
{
// Log error ...
return nil;
}
NSMutableData *data;
char fileType = 0;
if ([value isFileURL])
{
fileType = 1;
data = [NSMutableData dataWithBytes:&fileType length:1];
[data appendData:[[(NSURL *)value path] dataUsingEncoding:NSUTF8StringEncoding]];
}
else
{
fileType = -1;
data = [NSMutableData dataWithBytes:&fileType length:1];
[data appendData:[[(NSURL *)value absoluteString] dataUsingEncoding:NSUTF8StringEncoding]];
}
return data;
}
- (id)reverseTransformedValue:(id)value
{
if (![value isKindOfClass:[NSData class]])
{
// Log error ...
return nil;
}
NSURL *url = nil;
NSData *data = (NSData *)value;
char fileType = 0;
NSRange range = NSMakeRange(1, [data length]-1);
[data getBytes:&fileType length:1];
if (1 == fileType)
{
NSData *actualData = [data subdataWithRange:range];
NSString *str = [[NSString alloc] initWithData:actualData encoding:NSUTF8StringEncoding];
url = [NSURL fileURLWithPath:str];
}
else if (-1 == fileType)
{
NSData *actualData = [data subdataWithRange:range];
NSString *str = [[NSString alloc] initWithData:actualData encoding:NSUTF8StringEncoding];
url = [NSURL URLWithString:str];
}
else
{
// Log error ...
return nil;
}
return url;
}
#end
Transformable attributes are usually persisted as archived binary data. As such, you are attempting to compare an instance of NSData with an instance of NSString or NSNumber.
Since these classes interpret the same data in different ways, they are not considered a match.
you can try this way
NSExpression *exprPath = [NSExpression expressionForKeyPath:#"transformable_field"];
NSExpression *exprKeyword = [NSExpression expressionForConstantValue:nsdataValue];
NSPredicate *predicate = [NSComparisonPredicate predicateWithLeftExpression:exprPath rightExpression:exprKeyword modifier:NSDirectPredicateModifier type:NSEqualToPredicateOperatorType options:0];

Working with images (CGImage), exif data, and file icons

What I am trying to do (under 10.6)....
I have an image (jpeg) that includes an icon in the image file (that is you see an icon based on the image in the file, as opposed to a generic jpeg icon in file open dialogs in a program). I wish to edit the exif metadata, save it back to the image in a new file. Ideally I would like to save this back to an exact copy of the file (i.e. preserving any custom embedded icons created etc.), however, in my hands the icon is lost.
My code (some bits removed for ease of reading):
// set up source ref I THINK THE PROBLEM IS HERE - NOT GRABBING THE INITIAL DATA
CGImageSourceRef source = CGImageSourceCreateWithURL( (CFURLRef) URL,NULL);
// snag metadata
NSDictionary *metadata = (NSDictionary *) CGImageSourceCopyPropertiesAtIndex(source,0,NULL);
// make metadata mutable
NSMutableDictionary *metadataAsMutable = [[metadata mutableCopy] autorelease];
// grab exif
NSMutableDictionary *EXIFDictionary = [[[metadata objectForKey:(NSString *)kCGImagePropertyExifDictionary] mutableCopy] autorelease];
<< edit exif >>
// add back edited exif
[metadataAsMutable setObject:EXIFDictionary forKey:(NSString *)kCGImagePropertyExifDictionary];
// get source type
CFStringRef UTI = CGImageSourceGetType(source);
// set up write data
NSMutableData *data = [NSMutableData data];
CGImageDestinationRef destination = CGImageDestinationCreateWithData((CFMutableDataRef)data,UTI,1,NULL);
//add the image plus modified metadata PROBLEM HERE? NOT ADDING THE ICON
CGImageDestinationAddImageFromSource(destination,source,0, (CFDictionaryRef) metadataAsMutable);
// write to data
BOOL success = NO;
success = CGImageDestinationFinalize(destination);
// save data to disk
[data writeToURL:saveURL atomically:YES];
//cleanup
CFRelease(destination);
CFRelease(source);
I don't know if this is really a question of image handling, file handing, post-save processing (I could use sip), or me just being think (I suspect the last).
Nick
Don't you just hate it when you post something and then find the answer....
The way to deal with this is to use:
// grab the original unique icon
NSImage *theicon = [[NSWorkspace sharedWorkspace] iconForFile:full_file_path_of_original]];
// add it to the file after you have saved
BOOL done = [[NSWorkspace sharedWorkspace] setIcon:theicon forFile:full_file_path_to_new_file options:NSExcludeQuickDrawElementsIconCreationOption];
Doh!

Resources