Connection Kit Basics - cocoa

I need to implement file transfer in my application and it seems that in Cocoa ConnectionKit is unofficial standard. I was able to compile it but I have struggled finding any documentation or examples of it's use. The only example I found was outdated and even with modification I could not get it to work. Does anyone know the basic functions (creating connections, uploading...).
Thanks for any help

Its pretty simple once you figure out how its laid out.
Heres the code to create an ftp connection
CKConnectionRequest *request = [CKConnectionRequest requestWithURL:url];
CKFTPConnection *ftpConn = [[CKFTPConnection alloc] initWithRequest:request];
[ftpConn setDelegate:self];
[ftpConn connect];
self.connection = ftpConn;
[ftpConn release];
Then you would interact with the Connection Delegate methods found in CKConnectionProtocol.h
To authenticate to the server above you would use this delegate method where "cred" is an instance of NSURLCredential you have set up with the credentials you need.
- (void)connection:(id <CKConnection>)connection
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
[[challenge sender] useCredential:cred forAuthenticationChallenge:challenge];
}
For an upload you would just call one of the upload methods specified in CKConnectionProtocol.h inside of a delegate method like below
- (void)connection:(id <CKConnection>)con
didOpenAtPath:(NSString *)dirPath error:(NSError *)error {
CKTransferRecord *tr = [con uploadLocalItem:localPath
toRemoteDirectory:remotePath
ignoreHiddenItems:YES];
}
Then you would receive callbacks as to the status of that upload via the Uploading Methods specified in CKConnectionProtocol.h
Hopefully that helps. Forgive any small errors in the code I typed it out not copy/paste

Related

Asynchronous NSURLConnection Scheme Tutorial

I am looking for a good tutorial on using the NSURLConnection Asynchronous request. I have been looking around in stackoverflow and Google but could not find one. This can be a duplicate of zillions of questions like this here. But please direct me to the correct tutorial, I have used ASIHTTPRequest before, but I have not used the Apple provided library before.
I would provide you with one written myself, however I would HIGHLY recommend using AFNetworking, it's a wrapper above the NSURLConnection / NSURLRequest system that has a much cleaner flow, you can also use basic NSURLRequests / Connections with this, along with regular NSOperationQueues. The library also uses cocoa pods, and to be honest you really can't get much cleaner then that.
NSOperationQueue *mainQueue = [[NSOperationQueue alloc] init];
[mainQueue setMaxConcurrentOperationCount:5];
NSURL *url = [NSURL URLWithString:#"http://192.168.0.63:7070/api/Misc/GetFuelTypes"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
[request setHTTPMethod:#"GET"];
[request setAllHTTPHeaderFields:#{#"Accepts-Encoding": #"gzip", #"Accept": #"application/json"}];
[NSURLConnection sendAsynchronousRequest:request queue:mainQueue completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *error) {
NSHTTPURLResponse *urlResponse = (NSHTTPURLResponse *)response;
if (!error) {
NSLog(#"Status Code: %li %#", (long)urlResponse.statusCode, [NSHTTPURLResponse localizedStringForStatusCode:urlResponse.statusCode]);
NSLog(#"Response Body: %#", [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]);
}
else {
NSLog(#"An error occured, Status Code: %i", urlResponse.statusCode);
NSLog(#"Description: %#", [error localizedDescription]);
NSLog(#"Response Body: %#", [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]);
}
}];
The mainQueue object is used for routing requests and managing how many can be sent at once. This can be used in many ways, I tend to use them for categorized request (Authentication, Main, Upload Queue)
once inside the block you build a local NSHTTPURLResponse using the returned response. This is needed if you want the status code returned. (doesn't exist in the standard NSURLResponse object)
responseData is the data that can usually be converted right to a string or run through a deserializer to obtain human readable data.
Pretty simple explanation, delegates get you in trouble if you have no idea how to manage multiple requests from the same object (probably why I prefer blocks) :-)
Like always delegates or blocks you want to trigger your UI to update after you receive the response, but not be held back waiting for the request to complete, if you were loading data into a table you would call the request on load and supply some form of progress hud telling them a request is being made, once the data is received you remove the hud and reload the table data. HUDs MUST be called on the main thread so you will definitely need to handle that, I usually just build extensions and use performSelectorOnMainThread, however a better way might be to wrap your function in dispatch_async and call you're hud show / hide code outside of that.

Need help with NSURLCredential initWithTrust:

I have been beating my head against the wall for the last few hours and cannot find even a glimmer of a solution anywhere.
I am working on an app to learn how to work with JSON so I am just building a simple app that at first just returns a list of all of my repos on GitHub.
I am able to extract the JSON response from the data returned by NSURLConnection with no problem, unfortunately when I try to access anything that requires authentication the response is always {
message = "Not Found";
}
If I send a request to just get public information about my username I get back the correct response with all the correct info so I know that I am successfully communicating with api.github.com.
First off to start the request I use:
-(IBAction)retriveRepos:(id)sender
{
responseData = [[NSMutableData data] retain];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"https://api.github.com/user/repos"]];
[request setHTTPMethod:#"GET"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
When - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge gets called the authentication method comes back as NSURLAuthenticationMethodServerTrust (which I find strange since I feel like I should be supplying a username and password to login). According to Apple's docs I need to create my NSURLCredential with initWithTrust:. I Googled for answers and finally found a few places that said to just take the serverTrust from the protectionSpace of the NSURLAuthenticationChallenge that was passed in. Doing this shows no signs of having any effect at all, in fact it looks like my NSURLCredential has pretty much nothing in it.
if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodServerTrust) {
NSLog(#"challenge.protectionSpace.serverTrust: %#", challenge.protectionSpace.serverTrust); // This logs "challenge.protectionSpace.serverTrust: <SecTrust 0x10340b2e0 [0x7fff7cc12ea0]>"
NSURLCredential *credential = [[NSURLCredential alloc] initWithTrust:challenge.protectionSpace.serverTrust];
NSLog(#"credential: %#", credential); // Always logs something like "credential: <NSURLCredential: 0x1002d2f20>: (null)"
NSURLCredential *otherCredential = [[NSURLCredential alloc] initWithUser:#"user" password:#"password" persistence:NSURLCredentialPersistencePermanent];
NSLog(#"otherCredential: %#", otherCredential); // This logs otherCredential: <NSURLCredential: 0x1018d0840>: user"
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
}
I used more extensive logging as well that showed that credential did in fact exist (at first I thought it hadn't been created at all) but that it had none of it's attributes set certificates: (null)
hasPassword: 0
identity: (null)
password: (null)
. I can only assume that the problem is in my authentication but I followed the examples I found to the letter. I am using the GM preview of Lion, so I guess it is possible I found a bug, or that there is some bug in the GitHub API, but given my skill level I find it much more likely that I am an idiot that is looking over the answer sitting right in front of me.
If anyone knows of a good guide that actually walks you through all the steps of authenticating (preferably in all the styles) rather than the severely lacking Apple URL loading guide that just says to use a method but doesn't say where the SecTrustRef comes from it would be greatly appreciated.
NSURLAuthenticationMethodServerTrust is what you will get back as it negotiates an SSL connection. It's giving you the opportunity to evaluate the trust of the server (or vice versa). Technote 2232 describes this in a bit of detail. Your delegate needs to handle it, the technote links to the source for the sample code Advanced URL Connections which should show you how to respond to that challenge.
Don't despair, if there is an HTTP authentication step coming from your server, you will see the delegate get that later.

How to properly save a QTMovie after editing using QTKit?

I am making minor edits to a QTMovie in an application using NSDocument architecture (such as adding a track, as shown below). After the edit, I want to save to the original file. However, I keep getting a 'file is busy' error. I assume this is due to some oversight I made in the handling of the files, or a failure in how I am using NSDocument. Any tips would be helpful! Here is (some of) the relevant code:
// open file that has the track I want to add to my movie
QTMovie* tempMovie = [QTMovie movieWithURL:outputFileURL error:nil];
// Use old API to add the track
AddMovieSelection([movie quickTimeMovie], [tempMovie quickTimeMovie]);
// get the filename from the NSDocument
NSString *filename = [[self fileURL] path];
NSLog(#"writing to filename: %#", filename);
// FLATTEN the movie file so I don't get external references
NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithCapacity:1];
[attributes setObject:[NSNumber numberWithBool:YES] forKey:QTMovieFlatten];
// attempt to write
NSError *error;
// this is where I get the "file is busy"
if (![movie writeToFile:filename withAttributes:attributes error:&error]) {
NSLog(#"Error: %#", [error localizedDescription]);
NSRunAlertPanel(#"Error", [error localizedDescription], nil, nil, nil);
}
Do I have to first release the movie in my NSDocument? What is the "proper" way to do that? Keep in mind, I am not necessarily finished with this document, I am not closing it. I have just finished this operation, and I want the file on disk to reflect my changes. I would love to use [movie updateMovieFile], but that call doesn't seem to flatten the movie. I don't want any external references in my file.
I am not too familiar with the QuickTime C API, so I honestly can't tell you anything about what is going wrong there. Absolute guesswork: Maybe a call to EndMediaEdits is missing?
Though that shouldn't be required by AddMovieSelection, you said "[...] such as adding a track [...]". So maybe there is something else going on, like AddMediaSample or something similar?
That said, if you don't need to target anything below 10.5 and all you need to do is add some segment from another movie, you can achieve that without dropping down to the C API:
Have a look at
-[QTMovie insertSegmentOfTrack:fromRange:scaledToRange:]
and
-[QTMovie insertSegmentOfMovie:fromRange:scaledToRange:], if you want to have the inserted segment "overlayed" (temporally speaking).
-[QTMovie insertSegmentOfMovie:timeRange:atTime:] and -[QTMovie insertSegmentOfTrack:timeRange:atTime:], if you want { movieA.firstPart, movieB, movieA.secondPart }.
Do I have to first release the movie in my NSDocument?
You mean in order to write it to disk? No: That should even result in a crash.
The role of release is to handle memory-management. It doesn't have much to do with the busy-state of a file.
Turns out I just wasn't using the NSDocument architecture properly. When I changed it to use Save/SaveAs properly, this problem went away.

How do I distinguish between NSURLConnections in delegate methods?

I'm writing a RSS feeder and using the NSXMLParser to parse an XML page for me.
Everything works well.
This is the code that handles the asynchronously connection:
static NSString *feedURLString = #"http://www.example.com/data.xml";
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:feedURLString]];
feed = [[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease];
Now i'm trying to add another website to be parsed using the same code above, but i need to do different action, on a different URL
I'm implementing the delegate function:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
The problem is i can't figure out which website was called, i only got the data.
How can i figure out which URL the connection is resolved to?
For example in the :
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
I can check the NSString in NSURL URL that comes from the NSURLResponse but in the above function I can't.
If you implement both delegate methods, shouldn't you receive the latter notification first? If so, you can associate the URL string with the NSURLConnection (which will assumedly be different for each site) in an instance variable and use it when you get the data afterward. I would generally suggest a dictionary, although you can't use the connection as a key in an NS(Mutable)Dictionary since it doesn't conform to NSCopying. You could use the URL string as the key, but that complicates lookup. Perhaps a pair of arrays?
More to the point though, why write an RSS reader from scratch? On 10.5+ you can use the PubSub.framework to do the work for you. This framework handles all sorts of weird formats and invalid XML, and can really save you a lot of time. Maybe it's a good fit for what you're trying to do?

How do I share a Core Data store between processes using NSDistributedNotifications?

Background
I've already posted a question about the basics of sharing a Core Data store between processes.
I'm trying to implement the recommendations given and I'm running into problems.
My Goal
I have two processes - the Helper App and the UI. They both share a single data store. I want the UI to update it's NSManagedObjectContext when the Helper App has saved new data to the store.
Current Program Flow
The Helper App Process writes data to the Store.
In the Helper App, I listen for NSManagedObjectContextDidSaveNotification notifications.
When the context is saved, I encode the inserted, deleted and updated objects using their URI representations and NSArchiver.
I send an NSNotification to the NSDistributedNotificationCenter with this encoded dictionary as the userInfo.
The UI Process is listening for the save notification. When it receives the notification, it unarchives the userInfo using NSUnarchiver.
It looks up all the updated/inserted/deleted objects from the URIs given and replaces them with NSManagedObjects.
It constructs an NSNotification with the updated/inserted/deleted objects.
I call mergeChangesFromContextDidSaveNotification: on the Managed Object Context of the UI Process, passing in the NSNotification I constructed in the previous step.
The Problem
Inserted objects are faulted into the UI Managed Object Context fine and they appear in the UI. The problem comes with updated objects. They just don't update.
What I've tried
The most obvious thing to try would
be to pass the save Notification
from the Helper App process to the
UI process. Easy, right? Well, no.
Distributed Notifications won't
allow me to do that as the userInfo
dictionary is not in the right
format. That's why I'm doing all the
NSArchiving stuff.
I've tried calling
refreshObject:mergeChanges:YES on
the NSManagedObjects to be updated,
but this doesn't seem to have any
effect.
I've tried performing the
mergeChangesFromContextDidSaveNotification:
selector on the main thread and the
current thread. Neither seems to
affect the result.
I've tried using
mergeChangesFromContextDidSaveNotification:
before between threads, which of
course is much simpler and it worked
perfectly. But I need this same
functionality between processes.
Alternatives?
Am I missing something here? I'm consistently getting the feeling I'm making this much more complex than it needs to be, but after reading the documentation several times and spending a few solid days on this, I can't see any other way of refreshing the MOC of the UI.
Is there a more elegant way of doing this? Or am I just making a silly mistake somewhere in my code?
The Code
I've tried to make it as readable as possible, but it's still a mess. Sorry.
Helper App Code
-(void)workerThreadObjectContextDidSave:(NSNotification *)saveNotification {
NSMutableDictionary *savedObjectsEncodedURIs = [NSMutableDictionary dictionary];
NSArray *savedObjectKeys = [[saveNotification userInfo] allKeys];
for(NSString *thisSavedObjectKey in savedObjectKeys) {
// This is the set of updated/inserted/deleted NSManagedObjects.
NSSet *thisSavedObjectSet = [[saveNotification userInfo] objectForKey:thisSavedObjectKey];
NSMutableSet *thisSavedObjectSetEncoded = [NSMutableSet set];
for(id thisSavedObject in [thisSavedObjectSet allObjects]) {
// Construct a set of URIs that will be encoded as NSData
NSURL *thisSavedObjectURI = [[(NSManagedObject *)thisSavedObject objectID] URIRepresentation];
[thisSavedObjectSetEncoded addObject:thisSavedObjectURI];
}
// Archive the set of URIs.
[savedObjectsEncodedURIs setObject:[NSArchiver archivedDataWithRootObject:thisSavedObjectSetEncoded] forKey:thisSavedObjectKey];
}
if ([[savedObjectsEncodedURIs allValues] count] > 0) {
// Tell UI process there are new objects that need merging into it's MOC
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:#"com.synapticmishap.lapsus.save" object:#"HelperApp" userInfo:(NSDictionary *)savedObjectsEncodedURIs];
}
}
UI Code
-(void)mergeSavesIntoMOC:(NSNotification *)notification {
NSDictionary *objectsToRefresh = [notification userInfo];
NSMutableDictionary *notificationUserInfo = [NSMutableDictionary dictionary];
NSArray *savedObjectKeys = [[notification userInfo] allKeys];
for(NSString *thisSavedObjectKey in savedObjectKeys) {
// Iterate through all the URIs in the decoded set. For each URI, get the NSManagedObject and add it to a set.
NSSet *thisSavedObjectSetDecoded = [NSUnarchiver unarchiveObjectWithData:[[notification userInfo] objectForKey:thisSavedObjectKey]];
NSMutableSet *savedManagedObjectSet = [NSMutableSet set];
for(NSURL *thisSavedObjectURI in thisSavedObjectSetDecoded) {
NSManagedObject *thisSavedManagedObject = [managedObjectContext objectWithID:[persistentStoreCoordinator managedObjectIDForURIRepresentation:thisSavedObjectURI]];
[savedManagedObjectSet addObject:thisSavedManagedObject];
// If the object is to be updated, refresh the object and merge in changes.
// This doesn't work!
if ([thisSavedObjectKey isEqualToString:NSUpdatedObjectsKey]) {
[managedObjectContext refreshObject:thisSavedManagedObject mergeChanges:YES];
[managedObjectContext save:nil];
}
}
[notificationUserInfo setObject:savedManagedObjectSet forKey:thisSavedObjectKey];
}
// Build a notification suitable for merging changes into MOC.
NSNotification *saveNotification = [NSNotification notificationWithName:#"" object:nil userInfo:(NSDictionary *)notificationUserInfo];
[managedObjectContext performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:)
withObject:saveNotification
waitUntilDone:YES];
}
I used the method in
http://www.mlsite.net/blog/?p=518
then every object is correctly faulted but the faults are fetch in cache so still no update
I had to do
[moc stalenessInterval = 0];
And it finally worked, with relationship.
You're looking for - (void)refreshObject:(NSManagedObject *)object mergeChanges:(BOOL)flag I believe.
This will refresh the object with the info in the persistent store, merging changes if you want.
I'd go with Mike's suggestion and just watch the store file for changes.
Though it may not be the most efficient, I've had success using - [NSManagedObjectContext reset] from a second process when there's a change to a store. In my case case, the code is fairly linear — all I do is run a fetch request for some data after resetting. I don't know how this will work with bindings and a complicated UI, but you may be able to post a notification to manually update things if it's not handled automatically.
I had this exact same issue with an iPhone app that I've been working on. In my case, the solution involved setting the Context's stalenessInterval to something suitably infinitesimal (e.g., 0.5 seconds).
This works, except for sandboxes apps. You can't send a notification with a user info dict. Instead consider some other IPC like XPC or DO.
On a side note, using NSDustributedNotificationCenter is not always 100% if the system is busy.
Setting stalenessInterval of managed object context works. My case involves multiple threads instead of process though.
Starting with iOS 9, you should now use mergeChangesFromRemoteContextSave:intoContexts:. See this for an explanation: https://www.innoq.com/en/blog/ios-writing-core-data-in-today-extension/

Resources