Implementing SimpleFTPSample in swift

| | August 6, 2015

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.

2 Responses to “Implementing SimpleFTPSample in swift”

  1. 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)
    
    }
    
  2. 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!

Leave a Reply