I am trying to convert an Objective-C app to swift.
I am reaching the part where I need to send a file through FTP to a distant server. I was able to to that in Objective-C but only because I had the SimpleFTPSample project that apple made to help the developers.
Right now I have almost complete the conversion between the two language but I am stuck.
I need to convert that code (from SimpleFTPSample+personal):
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
// An NSStream delegate callback that's called when events happen on our
// network stream.
{
#pragma unused(aStream)
assert(aStream == self.networkStream);
switch (eventCode) {
case NSStreamEventOpenCompleted: {
[self updateStatus:#"Opened connection"];
} break;
case NSStreamEventHasBytesAvailable: {
assert(NO); // should never happen for the output stream
} break;
case NSStreamEventHasSpaceAvailable: {
[self updateStatus:#"Sending"];
// If we don't have any data buffered, go read the next chunk of data.
if (self.bufferOffset == self.bufferLimit) {
NSInteger bytesRead;
bytesRead = [self.fileStream read:self.buffer maxLength:kSendBufferSize];
if (bytesRead == -1) {
[self stopSendWithStatus:#"File read error"];
} else if (bytesRead == 0) {
[self stopSendWithStatus:nil];
} else {
self.bufferOffset = 0;
self.bufferLimit = bytesRead;
}
}
// If we're not out of data completely, send the next chunk.
if (self.bufferOffset != self.bufferLimit) {
NSInteger bytesWritten;
bytesWritten = [self.networkStream write:&self.buffer[self.bufferOffset] maxLength:self.bufferLimit - self.bufferOffset];
assert(bytesWritten != 0);
if (bytesWritten == -1) {
[self stopSendWithStatus:#"Network write error"];
} else {
self.bufferOffset += bytesWritten;
}
}
} break;
case NSStreamEventErrorOccurred: {
[self stopSendWithStatus:#"Stream open error"];
} break;
case NSStreamEventEndEncountered: {
// ignore
} break;
default: {
assert(NO);
} break;
}
}
For now I managed to do that :
// An NSStream delegate callback that's called when events happen on our
// network stream.
func stream(theStream: NSStream!, handleEvent streamEvent: NSStreamEvent) {
switch (streamEvent) {
case NSStreamEvent.OpenCompleted:
println("Opened connection")
case NSStreamEvent.HasBytesAvailable:
println("Not suposed to happend")
case NSStreamEvent.HasSpaceAvailable:
println("Sending")
// If we don't have any data buffered, go read the next chunk of data.
if self.bufferOffset == self.bufferLimit {
var bytesRead: Int
bytesRead = self.fileStream.read(self.buffer, maxLength: 32768)
if bytesRead == -1 { self.stopSendWithStatus("File read error") }
else if bytesRead == 0 { self.stopSendWithStatus(nil) }
else {
self.bufferOffset = 0
self.bufferLimit = size_t(bytesRead)
}
}
// If we're not out of data completely, send the next chunk.
if self.bufferOffset != self.bufferLimit {
var bytesWritten: Int
bytesWritten = self.networkStream.write(&self.buffer[self.bufferOffset], maxLength: self.bufferLimit - self.bufferOffset)
if bytesWritten == -1 { self.stopSendWithStatus("Network write error") }
else { self.bufferOffset += size_t(bytesWritten) }
}
case NSStreamEvent.ErrorOccurred:
self.stopSendWithStatus("Stream open error")
case NSStreamEvent.EndEncountered:
println("ignore")
default:
println("default")
}
}
The kind of the used variables is :
var isSending = Bool()
var networkStream: NSOutputStream! = NSOutputStream()
var fileStream: NSInputStream! = NSInputStream()
var buffer: CConstPointer<UInt8>
var bufferOffset = size_t()
var bufferLimit = size_t()
I am stuck for the if self.bufferOffset != self.bufferLimit part.
Apparently &self.buffer[self.bufferOffset] is not correct (does not have a member name subscript), as well as self.bufferLimit - self.bufferOffset (could not find an overload '-' that accepts the supplied arguments)
To be honest I am not even sure to get what &self.buffer[self.bufferOffset] means. (not the variable but the notation.
So if someone has an idea on how to fixe that, he will be very welcomed !
You can find the SimpleFTPSample project here.
Sorry if my english is not perfect.
Can't you just compile SimpleFTP into a framework and use its methods directly from swift? No need to rewrite everything, Xcode does that for you. I use that for many custom libraries and it works like a charm. Just add target, and put in the required frameworks, include the framework and you're all set!
Here is my plagiarism of apple's SimpleFTPExample to solve my problem of uploading a csv file from an iOS app to a ftp server.
I took the PutController objective-c file, cut lots and lots (too much?) out of it an included it into my swift application.
I modified the parameters passed to the main PutController function, to pass in the data I wanted to send, the URL it has to go to and the username and password to get onto the server.
The cut down PutController.m
#import "PutController.h"
#include <CFNetwork/CFNetwork.h>
enum {
kSendBufferSize = 32768
};
#interface PutController () <NSStreamDelegate>
#property (nonatomic, strong, readwrite) NSOutputStream * networkStream;
#property (nonatomic, strong, readwrite) NSInputStream * fileStream;
#property (nonatomic, assign, readonly ) uint8_t * buffer;
#property (nonatomic, assign, readwrite) size_t bufferOffset;
#property (nonatomic, assign, readwrite) size_t bufferLimit;
#end
#implementation PutController
{
uint8_t _buffer[kSendBufferSize];
}
// Because buffer is declared as an array, you have to use a custom getter.
// A synthesised getter doesn't compile.
- (uint8_t *)buffer
{
return self->_buffer;
}
- (void)startSend:(NSData *)dataToUpload withURL:(NSURL *)toURL withUsername:(NSString *)username andPassword:(NSString *)password
{
printf(__FUNCTION__);
self.fileStream = [NSInputStream inputStreamWithData:dataToUpload];
[self.fileStream open];
// Open a CFFTPStream for the URL.
self.networkStream = CFBridgingRelease(
CFWriteStreamCreateWithFTPURL(NULL, (__bridge CFURLRef) toURL)
);
[self.networkStream setProperty:username forKey:(id)kCFStreamPropertyFTPUserName];
[self.networkStream setProperty:password forKey:(id)kCFStreamPropertyFTPPassword];
self.networkStream.delegate = self;
[self.networkStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.networkStream open];
}
- (void)stopSendWithStatus:(NSString *)statusString
{
printf(__FUNCTION__);
if (self.networkStream != nil) {
[self.networkStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
self.networkStream.delegate = nil;
[self.networkStream close];
self.networkStream = nil;
}
if (self.fileStream != nil) {
[self.fileStream close];
self.fileStream = nil;
}
}
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
// An NSStream delegate callback that's called when events happen on our
// network stream.
{
printf(__FUNCTION__);
switch (eventCode) {
case NSStreamEventOpenCompleted: {
printf("Opened connection");
} break;
case NSStreamEventHasBytesAvailable: {
printf("should never happen for the output stream");
} break;
case NSStreamEventHasSpaceAvailable: {
printf("Sending");
// If we don't have any data buffered, go read the next chunk of data.
if (self.bufferOffset == self.bufferLimit) {
NSInteger bytesRead;
bytesRead = [self.fileStream read:self.buffer maxLength:kSendBufferSize];
if (bytesRead == -1) {
[self stopSendWithStatus:#"File read error"];
} else if (bytesRead == 0) {
[self stopSendWithStatus:nil];
} else {
self.bufferOffset = 0;
self.bufferLimit = bytesRead;
}
}
// If we're not out of data completely, send the next chunk.
if (self.bufferOffset != self.bufferLimit) {
NSInteger bytesWritten;
bytesWritten = [self.networkStream write:&self.buffer[self.bufferOffset] maxLength:self.bufferLimit - self.bufferOffset];
assert(bytesWritten != 0);
if (bytesWritten == -1) {
[self stopSendWithStatus:#"Network write error"];
} else {
self.bufferOffset += bytesWritten;
}
}
} break;
case NSStreamEventErrorOccurred: {
[self stopSendWithStatus:#"Stream open error"];
} break;
case NSStreamEventEndEncountered: {
// ignore
} break;
default: {
assert(NO);
} break;
}
}
#end
Then the PutController.h
#import <UIKit/UIKit.h>
#interface PutController : NSObject
- (void)startSend:(NSData *)dataToUpload withURL:(NSURL *)toURL withUsername:(NSString *)username andPassword:(NSString *)password;
#end
This is the bridging file, that is project wide, to allow the swift application to pick up the objective-C header files. The objective-C code detail can then be mostly ignored. It really is one line.
// PlantRoomChecks-Bridging-Header.h
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "PutController.h"
This is my project file that call the PutController. I have cut it to show just the relevant parts.
// Showing that I am not including any networking libraries
import UIKit
import CoreGraphics
class GraphVC: UIViewController {
var sendFile : PutController = PutController()
let username = "arthur_dent"
let password = "six_times_seven"
func saveData(){
var filename = "ftp://www.domain.co.uk/subdirectory/datafile"
// Converting the messing filename into one that can be used as a URL
let convertedStringForURL = filename.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
let uploadUrl = NSURL(string: convertedStringForURL!)
Let dataForFile : NSData = dataFromElseWhere
let dataToUpload = dataForFile.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
// USE THE OBJECTIVE-C VARIABLE
sendFile.startSend(dataToUpload, withURL: uploadUrl, withUsername: username, andPassword: password)
}
Related
Well. I'm back with another memory leak question. I recently asked a similar question here, but it turns out that code doesn't entirely do what I am trying to accomplish (which is to perform an action whenever any USB device connects/disconnects, and not just when an HID devices does).
I've started over from scratch, and have adapted Apple's USBPrivateDataSample and some of GitHub project ORSSerialPort into something that seems to work just like I want it to -- except --there are memory leaks. :(
I've tried applying the knowledge gained from my previous posts about dealing with memory leaks, but I'm stuck again. What am I missing? Thanks very much in advance.
Here's the code:
**AppDelegate.h:**
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject <NSApplicationDelegate>
#property (strong) NSMutableDictionary *deviceDictionary;
#end
**AppDelegate.m:**
#import "AppDelegate.h"
#include <IOKit/usb/IOUSBLib.h>
typedef struct DeviceData
{
io_object_t notification;
CFStringRef deviceName;
CFStringRef serialNum;
} DeviceData;
static IONotificationPortRef gNotifyPort;
static io_iterator_t gAddedIter;
static CFRunLoopRef gRunLoop;
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
self.deviceDictionary = [[NSMutableDictionary alloc] init];
CFMutableDictionaryRef matchingDict;
CFRunLoopSourceRef runLoopSource;
kern_return_t kr;
matchingDict = IOServiceMatching(kIOUSBDeviceClassName);
if (matchingDict == NULL)
{
fprintf(stderr, "IOServiceMatching returned NULL.\n");
}
// Create a notification port and add its run loop event source to our run loop
// This is how async notifications get set up.
gNotifyPort = IONotificationPortCreate(kIOMasterPortDefault);
runLoopSource = IONotificationPortGetRunLoopSource(gNotifyPort);
gRunLoop = CFRunLoopGetCurrent();
CFRunLoopAddSource(gRunLoop, runLoopSource, kCFRunLoopDefaultMode);
// Now set up a notification to be called when a device is first matched by I/O Kit.
kr = IOServiceAddMatchingNotification(gNotifyPort, // notifyPort
kIOFirstMatchNotification, // notificationType
matchingDict, // matching
DeviceAdded, // callback
NULL, // refCon
&gAddedIter // notification
);
// Iterate once to get already-present devices and arm the notification
DeviceAdded(NULL, gAddedIter);
// Start the run loop. Now we'll receive notifications.
CFRunLoopRun();
// can't do these, causes EXC_BAD_ACCESS crash
//CFRelease(matchingDict);
//CFRelease(gNotifyPort);
//CFRelease(kr);
CFRelease(runLoopSource);
CFRelease(gRunLoop);
}
void DeviceNotification(void *refCon, io_service_t service, natural_t messageType, void *messageArgument)
{
kern_return_t kr;
DeviceData *deviceDataRef = (DeviceData *) refCon;
if (messageType == kIOMessageServiceIsTerminated)
{
kr = IOObjectRelease(deviceDataRef->notification);
AppDelegate *appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
if ((deviceDataRef->serialNum) && (deviceDataRef->deviceName))
{
// remove device from dict
[appDelegate updateDeviceDict:
(__bridge NSString *)deviceDataRef->serialNum:
(__bridge NSString *)deviceDataRef->deviceName:
NO];
}
free(deviceDataRef);
}
}
void DeviceAdded(void *refCon, io_iterator_t iterator)
{
kern_return_t kr;
io_service_t usbDevice;
while ((usbDevice = IOIteratorNext(iterator)))
{
io_name_t deviceName;
CFStringRef deviceNameCF;
DeviceData *deviceDataRef = NULL;
// Add some app-specific information about this device.
// Create a buffer to hold the data.
deviceDataRef = malloc(sizeof(DeviceData));
bzero(deviceDataRef, sizeof(DeviceData));
// Get the USB device's name.
kr = IORegistryEntryGetName(usbDevice, deviceName);
if (KERN_SUCCESS != kr)
{
deviceName[0] = '\0';
}
CFMutableDictionaryRef usbProperties = 0;
if (IORegistryEntryCreateCFProperties(usbDevice, &usbProperties, kCFAllocatorDefault, kNilOptions) != KERN_SUCCESS)
{
IOObjectRelease(usbDevice);
continue;
}
NSDictionary *properties = CFBridgingRelease(usbProperties);
NSString *serialNum = properties[(__bridge NSString *)CFSTR("USB Serial Number")];
NSNumber *builtIn = properties[(__bridge NSString *)CFSTR("Built-In")];
deviceNameCF = CFStringCreateWithCString(kCFAllocatorDefault, deviceName, kCFStringEncodingASCII);
if (builtIn.integerValue != 1)
{
// Save the device's name to our private data.
deviceDataRef->deviceName = deviceNameCF;
deviceDataRef->serialNum = (__bridge CFStringRef)(serialNum);
AppDelegate *appDelegate = (AppDelegate *)[[NSApplication sharedApplication] delegate];
[appDelegate updateDeviceDict:
(__bridge NSString *)deviceDataRef->serialNum:
(__bridge NSString *)deviceDataRef->deviceName:
YES];
}
// Register for an interest notification of this device being removed. Use a reference to our
// private data as the refCon which will be passed to the notification callback.
kr = IOServiceAddInterestNotification(gNotifyPort, // notifyPort
usbDevice, // service
kIOGeneralInterest, // interestType
DeviceNotification, // callback
deviceDataRef, // refCon
&(deviceDataRef->notification) // notification
);
// Done with this USB device; release the reference added by IOIteratorNext
kr = IOObjectRelease(usbDevice);
}
}
- (void) updateDeviceDict : (NSString *) deviceSerial : (NSString *) deviceName : (bool) keepDevice
{
if (deviceName)
{
if ((!deviceSerial) || ([deviceSerial isEqualToString:#""])) deviceSerial = deviceName;
#try
{
//#throw [[NSException alloc] initWithName:#"test" reason:#"test" userInfo:nil];
// if the device needs to be added
if ((keepDevice) && (deviceSerial))
{
if (![self.deviceDictionary objectForKey:deviceSerial])
{
[self.deviceDictionary setObject:[NSDictionary dictionaryWithObjectsAndKeys:deviceName, #"name", nil] forKey:deviceSerial];
NSLog(#"Device added: %#", deviceName);
}
}
else // if the device needs to be removed
{
[self.deviceDictionary removeObjectForKey:deviceSerial];
NSLog(#"Device removed: %#", deviceName);
}
}
#catch (NSException *exception)
{
[self.deviceDictionary removeAllObjects];
CFRunLoopRun();
}
#finally
{
NSLog(#"\n%#", self.deviceDictionary);
}
}
else // name is nil
{
[self.deviceDictionary removeAllObjects];
CFRunLoopRun();
}
}
I have several controller managing their own connection and data which are working perfectly. I simply would like to centralize the canAuthenticateAgainstProtectionSpace and didReceiveAuthenticationChallenge to avoid duplicating the code.
I tried to override the class without success.
The class and protocol :
#import <Foundation/Foundation.h>
#protocol SSLPiningDelegate;
#interface SSLPiningDelegate : NSURLConnection <NSURLConnectionDelegate>
{
id<SSLPiningDelegate> delegate;
}
#property (nonatomic,assign) id<SSLPiningDelegate> delegate;
#end
#protocol SSLPiningDelegate <NSURLConnectionDelegate>
#optional
-(id)initWithDelegate:(id)delegate;
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace;
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
#end
the code :
#import "SSLPiningDelegate.h"
#interface SSLPiningDelegate ()
{
CFArrayRef caChainArrayRef;
BOOL checkHostname;
}
#end
#implementation SSLPiningDelegate
-(id)initWithDelegate:(id)delegate {
//CGRect rect = [[UIScreen mainScreen] bounds];
if ((self = [super init])) {
_delegate = delegate;
}
return self;
}
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
// TODO manage this correctly
checkHostname = YES;
NSString * derCaPath;
NSMutableArray * chain = [NSMutableArray array];
for(int i=0; i<= 3; i++)
{
if (i==0)
derCaPath = [[NSBundle mainBundle] pathForResource:#"xxx" ofType:#"cer"];
else if (i==1)
derCaPath = [[NSBundle mainBundle] pathForResource:#"comodorsacertificationauthority" ofType:#"cer"];
else if (i==2)
derCaPath = [[NSBundle mainBundle] pathForResource:#"xxx" ofType:#"cer"];
else
derCaPath = [[NSBundle mainBundle] pathForResource:#"xxx" ofType:#"cer"];
NSData *derCA = [NSData dataWithContentsOfFile:derCaPath];
SecCertificateRef caRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)derCA);
[chain addObject:(__bridge id)(caRef)];
}
caChainArrayRef = CFBridgingRetain(chain);
NSLog(#"Loading::didReceiveAuthenticationChallenge --- chain of trust length %lu", (unsigned long)[chain count]);
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
{
SecTrustRef trust = nil;
SecTrustResultType result = kSecTrustResultInvalid;
OSStatus err = errSecSuccess;
{
NSLog(#"Chain received from the server (working 'up'):");
CFIndex certificateCount = SecTrustGetCertificateCount(challenge.protectionSpace.serverTrust);
for(int i = 0; i < certificateCount; i++) {
SecCertificateRef certRef = SecTrustGetCertificateAtIndex(challenge.protectionSpace.serverTrust, i);
CFStringRef str = SecCertificateCopySubjectSummary(certRef);
NSLog(#" %02i: %#", 1+i, str);
CFRelease(str);
}
NSLog(#"Local Roots we trust:");
for(int i = 0; i < CFArrayGetCount(caChainArrayRef); i++) {
SecCertificateRef certRef = (SecCertificateRef) CFArrayGetValueAtIndex(caChainArrayRef, i);
CFStringRef str = SecCertificateCopySubjectSummary(certRef);
NSLog(#" %02i: %#", 1+i, str);
CFRelease(str);
}
}
if (checkHostname) {
// We use the standard Policy of SSL - which also checks hostnames.
// -- see SecPolicyCreateSSL() for details.
//
trust = challenge.protectionSpace.serverTrust;
//
#if DEBUG
NSLog(#"The certificate is expected to match '%#' as the hostname",
challenge.protectionSpace.host);
#endif
}
else {
// Create a new Policy - which goes easy on the hostname.
//
// Extract the chain of certificates provided by the server.
//
CFIndex certificateCount = SecTrustGetCertificateCount(challenge.protectionSpace.serverTrust);
NSMutableArray * chain = [NSMutableArray array];
for(int i = 0; i < certificateCount; i++) {
SecCertificateRef certRef = SecTrustGetCertificateAtIndex(challenge.protectionSpace.serverTrust, i);
[chain addObject:(__bridge id)(certRef)];
}
for(int i = 0; i < CFArrayGetCount(caChainArrayRef); i++) {
SecCertificateRef certRef = (SecCertificateRef) CFArrayGetValueAtIndex(caChainArrayRef, i);
[chain addObject:(__bridge id)(certRef)];
}
// And create a bland policy which only checks signature paths.
//
if (err == errSecSuccess)
err = SecTrustCreateWithCertificates((__bridge CFArrayRef)(chain),
SecPolicyCreateBasicX509(), &trust);
#if DEBUG
NSLog(#"The certificate is NOT expected to match the hostname '%#' ",
challenge.protectionSpace.host);
#endif
}
// Explicity specify the list of certificates we actually trust (i.e. those I have hardcoded
// in the app - rather than those provided by some randon server on the internet).
//
if (err == errSecSuccess)
err = SecTrustSetAnchorCertificates(trust,caChainArrayRef);
// And only use above - i.e. do not check the system its global keychain or something
// else the user may have fiddled with.
//
if (err == errSecSuccess)
err = SecTrustSetAnchorCertificatesOnly(trust, YES);
if (err == errSecSuccess)
err = SecTrustEvaluate(trust, &result);
if (err == errSecSuccess) {
switch (result) {
case kSecTrustResultProceed:
// User gave explicit permission to trust this specific
// root at some point (in the past).
//
NSLog(#"GOOD. kSecTrustResultProceed - the user explicitly trusts this CA");
[challenge.sender useCredential:[NSURLCredential credentialForTrust:trust]
forAuthenticationChallenge:challenge];
goto done;
break;
case kSecTrustResultUnspecified:
// The chain is technically valid and matches up to the root
// we provided. The user has not had any say in this though,
// hence it is not a kSecTrustResultProceed.
//
NSLog(#"GOOD. kSecTrustResultUnspecified - So things are technically trusted. But the user was not involved.");
[challenge.sender useCredential:[NSURLCredential credentialForTrust:trust]
forAuthenticationChallenge:challenge];
goto done;
break;
case kSecTrustResultInvalid:
NSLog(#"FAIL. kSecTrustResultInvalid");
break;
case kSecTrustResultDeny:
NSLog(#"FAIL. kSecTrustResultDeny (i.e. user said no explicitly)");
break;
case kSecTrustResultFatalTrustFailure:
NSLog(#"FAIL. kSecTrustResultFatalTrustFailure");
break;
case kSecTrustResultOtherError:
NSLog(#"FAIL. kSecTrustResultOtherError");
break;
case kSecTrustResultRecoverableTrustFailure:
NSLog(#"FAIL. kSecTrustResultRecoverableTrustFailure (i.e. user could say OK, but has not been asked this)");
break;
default:
NSAssert(NO,#"Unexpected result: %d", result);
break;
}
// Reject.
[challenge.sender cancelAuthenticationChallenge:challenge];
goto done;
};
//CFStringRef str =SecCopyErrorMessageString(err,NULL);
//NSLog(#"Internal failure to validate: result %#", str);
//CFRelease(str);
[[challenge sender] cancelAuthenticationChallenge:challenge];
done:
if (!checkHostname)
CFRelease(trust);
return;
}
// In this example we can cancel at this point - as we only do
// canAuthenticateAgainstProtectionSpace against ServerTrust.
//
// But in other situations a more gentle continue may be appropriate.
//
// [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
NSLog(#"Not something we can handle - so we're canceling it.");
[challenge.sender cancelAuthenticationChallenge:challenge];
}
#end
I then declare the controller
#import <UIKit/UIKit.h>
#import "TagUIViewControllerLoading.h"
#import <Foundation/Foundation.h>
#import "SSLPiningDelegate.h"
#interface Loading : TagUIViewControllerLoading <SSLPiningDelegate>
But the delegate is never called.
Any help would be welcomed.
Solved in fact I was trying to delegate at a third level (a controller calling a controller build with a delegate). The code enclosed at the second controller level is working perfectly.
This code works fine, but im trying to implement a resume action after an NSStreamEventErrorOccurred, for example after internet connection lost.
What i have done is to save the number of _totalBytesSend and redo the upload only with the remaining bytes, but im facing that only the remaining bytes will be stored instead of the complete file.
I couldn't able to figure out how to resume file upload or if is there a way to merge the streams when an error ocurred?
- (void)enviar:(NSData *)aDatos pathArchivo:(NSString *)path bytesEnviados:(size_t *)aBytesEnviados
{
BOOL success;
NSURL *url;
long liBytesInicio = aBytesEnviados;
NSData *newData = [aDatos subdataWithRange:NSMakeRange(liBytesInicio, [aDatos length] - liBytesInicio)];
_currentBytesEnviados = aBytesEnviados;
_currentDatos = aDatos;
_currentPathArchivos = path;
assert(self.networkStream == nil); // don't tap send twice in a row!
assert(self.fileStream == nil); // ditto
// First get and check the URL.
url = [[NetworkManager sharedInstance] smartURLForString:[Sesion sharedSesion].configuracion.servidorFTP];
success = (url != nil);
if (success) {
// Add the last part of the file name to the end of the URL to form the final
// URL that we're going to put to.
url = CFBridgingRelease(CFURLCreateCopyAppendingPathComponent(NULL, (__bridge CFURLRef) url, (__bridge CFStringRef)path, false));
success = (url != nil);
}
// If the URL is bogus, let the user know. Otherwise kick off the connection.
// Open a stream for the file we're going to send. We do not open this stream;
// NSURLConnection will do it for us.
self.fileStream = [NSInputStream inputStreamWithData:newData];
assert(self.fileStream != nil);
[self.fileStream open];
// Open a CFFTPStream for the URL.
self.networkStream = CFBridgingRelease(CFWriteStreamCreateWithFTPURL(NULL, (__bridge CFURLRef) url));
assert(self.networkStream != nil);
success = [self.networkStream setProperty:[Sesion sharedSesion].configuracion.cuentaFTP forKey:(id)kCFStreamPropertyFTPUserName];
assert(success);
success = [self.networkStream setProperty:[Sesion sharedSesion].configuracion.passwordFTP forKey:(id)kCFStreamPropertyFTPPassword];
assert(success);
self.networkStream.delegate = self;
[self.networkStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.networkStream open];
// Tell the UI we're sending.
}
- (void)detenerEnvio:(NSString *)estado {
if (self.networkStream != nil) {
[self.networkStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
self.networkStream.delegate = nil;
[self.networkStream close];
self.networkStream = nil;
}
if (self.fileStream != nil) {
[self.fileStream close];
self.fileStream = nil;
}
[self envioDetenido:estado];
}
- (void)continuaEnvio
{
double delayInSeconds = 12.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(#"After Delay code");
if ([self.networkStream streamStatus] != NSStreamStatusOpen) {
PutController *ftp = [[PutController alloc] init];
ftp.delegate = self;
_currentBytesEnviados = _totalBytesSend;//_currentDatos.length;
NSLog(#"Current total bytes ** %i", _currentBytesEnviados);
[ftp enviar:_currentDatos pathArchivo:_currentPathArchivos bytesEnviados:_currentBytesEnviados];
self.ftpController = ftp;
}
});
}
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
// An NSStream delegate callback that's called when events happen on our
// network stream.
{
#pragma unused(aStream)
assert(aStream == self.networkStream);
switch (eventCode) {
case NSStreamEventOpenCompleted: {
[self iniciarEnvio:#"Abriendo conexion"];
} break;
case NSStreamEventHasBytesAvailable: {
assert(NO); // should never happen for the output stream
} break;
case NSStreamEventHasSpaceAvailable: {
// If we don't have any data buffered, go read the next chunk of data.
if (self.bufferOffset == self.bufferLimit) {
NSInteger bytesRead;
bytesRead = [self.fileStream read:self.buffer maxLength:kSendBufferSize];
if (bytesRead == -1) {
[self detenerEnvio:#"Error al leer archivo."];
} else if (bytesRead == 0) {
[self detenerEnvio:nil];
break;
} else {
self.bufferOffset = 0;
self.bufferLimit = bytesRead;
}
}
// If we're not out of data completely, send the next chunk.
if (self.bufferOffset != self.bufferLimit) {
NSInteger bytesWritten;
bytesWritten = [self.networkStream write:&self.buffer[self.bufferOffset] maxLength:self.bufferLimit - self.bufferOffset];
assert(bytesWritten != 0);
if (bytesWritten == -1) {
[self detenerEnvio:#"Error al escribir."];
} else {
self.bufferOffset += bytesWritten;
_totalBytesSend += bytesWritten;
[self actualizaEnvio:bytesWritten];
}
}
} break;
case NSStreamEventErrorOccurred: {
//**** Here is where im trying to resume the upload *****
[self detenerEnvio:#"Error al abrir el stream."];
[self continuaEnvio];
} break;
case NSStreamEventEndEncountered: {
// ignore
} break;
default: {
assert(NO);
} break;
}
}
Thanks in advance!
How can a connection be closed when the NSOutputStream has finished sending data?
After searching around i have found that the event NSStreamEventEndEncountered is only called if the server drops the connection. not if the OutputStream has finished the data to send.
StreamStatus is Always returning 0 (connection closed) or 2 (connection open) but never 4 (writing data).
since both methods mentioned above are not telling me enough about the write process i am not able to find a way do determine if the Stream is still writing or if it has finished and i can close the connection now.
After 5 days of googleling and trying i am totally out of ideas... Any help appreciated. Thanks
EDIT ADDED CODE AS REQUESTED:
- (void)startSend:(NSString *)filePath
{
BOOL success;
NSURL * url;
assert(filePath != nil);
assert([[NSFileManager defaultManager] fileExistsAtPath:filePath]);
assert( [filePath.pathExtension isEqual:#"png"] || [filePath.pathExtension isEqual:#"jpg"] );
assert(self.networkStream == nil); // don't tap send twice in a row!
assert(self.fileStream == nil); // ditto
// First get and check the URL.
...
....
.....
// If the URL is bogus, let the user know. Otherwise kick off the connection.
...
....
.....
if ( ! success) {
self.statusLabel.text = #"Invalid URL";
} else {
// Open a stream for the file we're going to send. We do not open this stream;
// NSURLConnection will do it for us.
self.fileStream = [NSInputStream inputStreamWithFileAtPath:filePath];
assert(self.fileStream != nil);
[self.fileStream open];
// Open a CFFTPStream for the URL.
self.networkStream = CFBridgingRelease(
CFWriteStreamCreateWithFTPURL(NULL, (__bridge CFURLRef) url)
);
assert(self.networkStream != nil);
if ([self.usernameText.text length] != 0) {
success = [self.networkStream setProperty:self.usernameText.text forKey:(id)kCFStreamPropertyFTPUserName];
assert(success);
success = [self.networkStream setProperty:self.passwordText.text forKey:(id)kCFStreamPropertyFTPPassword];
assert(success);
}
self.networkStream.delegate = self;
[self.networkStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
///////******** LINE ADDED BY ME TO DISONNECT FROM FTP AFTER CLOSING CONNECTION *********////////////
[self.networkStream setProperty:(id)kCFBooleanFalse forKey:(id)kCFStreamPropertyFTPAttemptPersistentConnection];
///////******** END LINE ADDED BY ME *********////////////
[self.networkStream open];
// Tell the UI we're sending.
[self sendDidStart];
}
}
- (void)stopSendWithStatus:(NSString *)statusString
{
if (self.networkStream != nil) {
[self.networkStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
self.networkStream.delegate = nil;
[self.networkStream close];
self.networkStream = nil;
}
if (self.fileStream != nil) {
[self.fileStream close];
self.fileStream = nil;
}
[self sendDidStopWithStatus:statusString];
}
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
// An NSStream delegate callback that's called when events happen on our
// network stream.
{
#pragma unused(aStream)
assert(aStream == self.networkStream);
switch (eventCode) {
case NSStreamEventOpenCompleted: {
[self updateStatus:#"Opened connection"];
} break;
case NSStreamEventHasBytesAvailable: {
assert(NO); // should never happen for the output stream
} break;
case NSStreamEventHasSpaceAvailable: {
[self updateStatus:#"Sending"];
// If we don't have any data buffered, go read the next chunk of data.
if (self.bufferOffset == self.bufferLimit) {
NSInteger bytesRead;
bytesRead = [self.fileStream read:self.buffer maxLength:kSendBufferSize];
if (bytesRead == -1) {
[self stopSendWithStatus:#"File read error"];
} else if (bytesRead == 0) {
[self stopSendWithStatus:nil];
} else {
self.bufferOffset = 0;
self.bufferLimit = bytesRead;
}
}
// If we're not out of data completely, send the next chunk.
if (self.bufferOffset != self.bufferLimit) {
NSInteger bytesWritten;
bytesWritten = [self.networkStream write:&self.buffer[self.bufferOffset] maxLength:self.bufferLimit - self.bufferOffset];
assert(bytesWritten != 0);
if (bytesWritten == -1) {
[self stopSendWithStatus:#"Network write error"];
} else {
self.bufferOffset += bytesWritten;
}
}
} break;
case NSStreamEventErrorOccurred: {
[self stopSendWithStatus:#"Stream open error"];
} break;
case NSStreamEventEndEncountered: {
// FOR WHATEVER REASON THIS IS NEVER CALLED!!!!
} break;
default: {
assert(NO);
} break;
}
}
There can be two interpretations to your question. If what you are asking is "I have a NSOutputStream and I'm finished writing to it how do I signal this?" then the answer is as simple as call the close method on it.
Alternately, If what you are really saying is "I have a NSInputStream and I want to know when I've reached the end-of-stream" then you can look at hasBytesAvailable or streamStatus == NSStreamStatusAtEnd.
For your information, to actually get the status NSStreamStatusWriting you would need to be calling the streamStatus method from another thread while this thread is calling write:maxLength:.
--- Edit: Code Suggestion
The reason you would never get notified is that an output stream is never finished (unless it's a fixed size stream, which an FTP stream is not). It's the input stream that gets "finished" at which point you can close your output stream. That's the answer to your original question.
As a further suggestion, I would skip run loop scheduling and the "event processing" except for handling errors on the output stream. Then I would put the read/write code into a NSOperation subclass and send it off into a NSOperationQueue. By keeping a reference to the NSOperations in that queue you would be able to cancel them easily and even show a progress bar by adding a percentComplete property. I've tested the code below and it works. Replace my memory output stream with your FTP output stream. You will notice that I have skipped the validations, which you should keep of course. They should probably be done outside the NSOperation to make it easier to query the user.
#interface NSSendFileOperation : NSOperation<NSStreamDelegate> {
NSInputStream *_inputStream;
NSOutputStream *_outputStream;
uint8_t *_buffer;
}
#property (copy) NSString* sourceFilePath;
#property (copy) NSString* targetFilePath;
#property (copy) NSString* username;
#property (copy) NSString* password;
#end
#implementation NSSendFileOperation
- (void) main
{
static int kBufferSize = 4096;
_inputStream = [NSInputStream inputStreamWithFileAtPath:self.sourceFilePath];
_outputStream = [NSOutputStream outputStreamToMemory];
_outputStream.delegate = self;
[_inputStream open];
[_outputStream open];
_buffer = calloc(1, kBufferSize);
while (_inputStream.hasBytesAvailable) {
NSInteger bytesRead = [_inputStream read:_buffer maxLength:kBufferSize];
if (bytesRead > 0) {
[_outputStream write:_buffer maxLength:bytesRead];
NSLog(#"Wrote %ld bytes to output stream",bytesRead);
}
}
NSData *outputData = [_outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey];
NSLog(#"Wrote a total of %lu bytes to output stream.", outputData.length);
free(_buffer);
_buffer = NULL;
[_outputStream close];
[_inputStream close];
}
- (void) stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
// Handle output stream errors such as disconnections here
}
#end
int main (int argc, const char * argv[])
{
#autoreleasepool {
NSOperationQueue *sendQueue = [[NSOperationQueue alloc] init];
NSSendFileOperation *sendOp = [[NSSendFileOperation alloc] init];
sendOp.username = #"test";
sendOp.password = #"test";
sendOp.sourceFilePath = #"/Users/eric/bin/data/english-words.txt";
sendOp.targetFilePath = #"/Users/eric/Desktop/english-words.txt";
[sendQueue addOperation:sendOp];
[sendQueue waitUntilAllOperationsAreFinished];
}
return 0;
}
I'm trying to measure actual transfer speed during ftp download, download itself is working, streams are hooked up in run loop. Measurment is done in NSStreamEventHasBytesAvailable using CFTimeGetCurrent on event start and at the end, after data is written to file elapsed time is computed with (double)previousTimestamp-CFAbsoluteTimeGetCurrent, but the time I get is absolutely unreasonable. Tested on simulator and device, can anyone enlighten me?
code:
switch (eventCode) {
case NSStreamEventOpenCompleted: {
} break;
case NSStreamEventHasBytesAvailable: {
if ([[self.fileStream propertyForKey:NSStreamFileCurrentOffsetKey] intValue]==0) {
previousTimestamp = CFAbsoluteTimeGetCurrent();
}
NSInteger bytesRead;
uint8_t buffer[32768];
bytesRead = [self.networkStream read:buffer maxLength:sizeof(buffer)];
if (bytesRead == -1)
{
[self _stopReceiveWithStatus:#"Err"];
}
else if (bytesRead == 0)
{
[self _stopReceiveWithStatus:nil];
}
else
{
[self completition:bytesRead];
NSInteger bytesWritten;
NSInteger bytesWrittenSoFar;
// Write to the file.
bytesWrittenSoFar = 0;
do {
bytesWritten = [self.fileStream write:&buffer[bytesWrittenSoFar] maxLength:bytesRead - bytesWrittenSoFar];
assert(bytesWritten != 0);
if (bytesWritten == -1) {
[self _stopReceiveWithStatus:#"File err"];
break;
} else {
bytesWrittenSoFar += bytesWritten;
}
} while (bytesWrittenSoFar != bytesRead);
[self downloadSpeedSave:bytesRead :previousTimestamp-CFAbsoluteTimeGetCurrent()];
previousTimestamp = CFAbsoluteTimeGetCurrent();
An alternative that I have used is to use the time.h and c routines to capture time.
http://www.cplusplus.com/reference/clibrary/ctime/time/
Another good link on SO
iPhone: How to get current milliseconds?