Skip to content

Instantly share code, notes, and snippets.

@Nirma
Last active December 30, 2023 02:11
Show Gist options
  • Star 25 You must be signed in to star a gist
  • Fork 9 You must be signed in to fork a gist
  • Save Nirma/fb9991be776107d17fdcd6ed2aa02876 to your computer and use it in GitHub Desktop.
Save Nirma/fb9991be776107d17fdcd6ed2aa02876 to your computer and use it in GitHub Desktop.
Upload a file via FTP on iOS or macOS
import Foundation
import CFNetwork
public class FTPUpload {
fileprivate let ftpBaseUrl: String
fileprivate let directoryPath: String
fileprivate let username: String
fileprivate let password: String
public init(baseUrl: String, userName: String, password: String, directoryPath: String) {
self.ftpBaseUrl = baseUrl
self.username = userName
self.password = password
self.directoryPath = directoryPath
}
}
// MARK: - Steam Setup
extension FTPUpload {
private func setFtpUserName(for ftpWriteStream: CFWriteStream, userName: CFString) {
let propertyKey = CFStreamPropertyKey(rawValue: kCFStreamPropertyFTPUserName)
CFWriteStreamSetProperty(ftpWriteStream, propertyKey, userName)
}
private func setFtpPassword(for ftpWriteStream: CFWriteStream, password: CFString) {
let propertyKey = CFStreamPropertyKey(rawValue: kCFStreamPropertyFTPPassword)
CFWriteStreamSetProperty(ftpWriteStream, propertyKey, password)
}
fileprivate func ftpWriteStream(forFileName fileName: String) -> CFWriteStream? {
let fullyQualifiedPath = "ftp://\(ftpBaseUrl)/\(directoryPath)/\(fileName)"
guard let ftpUrl = CFURLCreateWithString(kCFAllocatorDefault, fullyQualifiedPath as CFString, nil) else { return nil }
let ftpStream = CFWriteStreamCreateWithFTPURL(kCFAllocatorDefault, ftpUrl)
let ftpWriteStream = ftpStream.takeRetainedValue()
setFtpUserName(for: ftpWriteStream, userName: username as CFString)
setFtpPassword(for: ftpWriteStream, password: password as CFString)
return ftpWriteStream
}
}
// MARK: - FTP Write
extension FTPUpload {
public func send(data: Data, with fileName: String, success: @escaping ((Bool)->Void)) {
guard let ftpWriteStream = ftpWriteStream(forFileName: fileName) else {
success(false)
return
}
if CFWriteStreamOpen(ftpWriteStream) == false {
print("Could not open stream")
success(false)
return
}
let fileSize = data.count
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: fileSize)
data.copyBytes(to: buffer, count: fileSize)
defer {
CFWriteStreamClose(ftpWriteStream)
buffer.deallocate(capacity: fileSize)
}
var offset: Int = 0
var dataToSendSize: Int = fileSize
repeat {
let bytesWritten = CFWriteStreamWrite(ftpWriteStream, &buffer[offset], dataToSendSize)
if bytesWritten > 0 {
offset += bytesWritten.littleEndian
dataToSendSize -= bytesWritten
continue
} else if bytesWritten < 0 {
// ERROR
print("FTPUpload - ERROR")
break
} else if bytesWritten == 0 {
// SUCCESS
print("FTPUpload - Completed!!")
break
}
} while CFWriteStreamCanAcceptBytes(ftpWriteStream)
success(true)
}
}
@sk-chanch
Copy link

Fix for freeze when upload.

`
DispatchQueue.global(qos: .background).async {[weak self] in
guard let _self = self else{return}

        let fileSize = data.count
        let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: fileSize)
        data.copyBytes(to: buffer, count: fileSize)
        
        defer {
            CFWriteStreamClose(ftpWriteStream)
            buffer.deallocate()
        }
        
        
        
        var offset: Int = 0
        var dataToSendSize: Int = fileSize
        
        var shouldContinue = true
        
        while shouldContinue {
            let bytesWritten = CFWriteStreamWrite(ftpWriteStream, &buffer[offset], dataToSendSize)
            
            print("ftp bytes written: \(bytesWritten)")
            
            if bytesWritten > 0 {
                offset += bytesWritten.littleEndian
                dataToSendSize -= bytesWritten
                
            } else if bytesWritten < 0 {
                // ERROR
                
                print("FTPUpload - ERROR with \(CFWriteStreamGetError(ftpWriteStream))")
                shouldContinue = false
                success(false)
                
            } else if bytesWritten == 0 {
                // SUCCESS
                
                
                print( "FTPUpload - Completed!!")
                shouldContinue = false
                
                success(true)
                
            }
            
           
        }
    }`

@loldi
Copy link

loldi commented May 11, 2020

Can anyone assist with using this to upload a local video file on the device to FTP?

I've got it working with an image, just curious how I can arrange it to upload a .mp4/.mov file.

@jerrylinkinpark
Copy link

Can anyone assist with using this to upload a local video file on the device to FTP?

I've got it working with an image, just curious how I can arrange it to upload a .mp4/.mov file.

Can you tell me how it works with image or txt file?
I cannot find the right data type for the send function.
Thanks!!

@loldi
Copy link

loldi commented Aug 5, 2020

Can anyone assist with using this to upload a local video file on the device to FTP?
I've got it working with an image, just curious how I can arrange it to upload a .mp4/.mov file.

Can you tell me how it works with image or txt file?
I cannot find the right data type for the send function.
Thanks!!

Sure, for a .txt file, make sure the file is added to your project. To upload the file you would use:


let ftp = FTPUpload(baseUrl: String, userName: String, password: String, directoryPath: String)

do { 
let data = try Data(contentsOf: url (this is your .txt file location as a URL) as URL, options: [ .alwaysMapped, .uncached])

ftp.send(data: data , with: "name.txt") { (success) in 

        print("success") 
        }
        print(data)
        }
        catch {
        print(error)
    }
}

Hope this helps!

@jerrylinkinpark
Copy link

jerrylinkinpark commented Aug 28, 2020

Can anyone assist with using this to upload a local video file on the device to FTP?
I've got it working with an image, just curious how I can arrange it to upload a .mp4/.mov file.

Can you tell me how it works with image or txt file?
I cannot find the right data type for the send function.
Thanks!!

Sure, for a .txt file, make sure the file is added to your project. To upload the file you would use:


let ftp = FTPUpload(baseUrl: String, userName: String, password: String, directoryPath: String)

do { 
let data = try Data(contentsOf: url (this is your .txt file location as a URL) as URL, options: [ .alwaysMapped, .uncached])

ftp.send(data: data , with: "name.txt") { (success) in 

        print("success") 
        }
        print(data)
        }
        catch {
        print(error)
    }
}

Hope this helps!

I think I got it. But I have a problem with file access.
I got error message, although, I have switched off the AppSandbox already.

Error Domain=NSCocoaErrorDomain Code=257 "The file “XXX” couldn’t be opened because you don’t have permission to view it." UserInfo={NSFilePath=/Users/XXX, NSUnderlyingError=0x600000c8d4d0 {Error Domain=NSPOSIXErrorDomain Code=13 "Permission denied"}}
It seems I don't have access to that file?

@Pinturaj
Copy link

Pinturaj commented Sep 1, 2020

    repeat {
        let bytesWritten = CFWriteStreamWrite(ftpWriteStream, &buffer[offset], dataToSendSize)
        if bytesWritten > 0 {
            offset += bytesWritten.littleEndian
            dataToSendSize -= bytesWritten
            continue
        } else if bytesWritten < 0 {
            // ERROR
            print("FTPUpload - ERROR")
            break
        } else if bytesWritten == 0 {
            // SUCCESS
            print("FTPUpload - Completed!!")
            break
        }
    } while CFWriteStreamCanAcceptBytes(ftpWriteStream)
    
    success(true)
}

I am getting -1 in bytesWritten that why file is written and I have check dataToSendSize is 8000 in size but bytesWritten getting -1 in first time that why file in writing.

Please help me.

Thanks

@Pinturaj
Copy link

Pinturaj commented Sep 1, 2020

Works perfectly thank you sir.

not working for me bytesWritten return -1 at first time

@loldi
Copy link

loldi commented Sep 1, 2020

Can anyone assist with using this to upload a local video file on the device to FTP?
I've got it working with an image, just curious how I can arrange it to upload a .mp4/.mov file.

Can you tell me how it works with image or txt file?
I cannot find the right data type for the send function.
Thanks!!

Sure, for a .txt file, make sure the file is added to your project. To upload the file you would use:


let ftp = FTPUpload(baseUrl: String, userName: String, password: String, directoryPath: String)

do { 
let data = try Data(contentsOf: url (this is your .txt file location as a URL) as URL, options: [ .alwaysMapped, .uncached])

ftp.send(data: data , with: "name.txt") { (success) in 

        print("success") 
        }
        print(data)
        }
        catch {
        print(error)
    }
}

Hope this helps!

I think I got it. But I have a problem with file access.
I got error message, although, I have switched off the AppSandbox already.

Error Domain=NSCocoaErrorDomain Code=257 "The file “XXX” couldn’t be opened because you don’t have permission to view it." UserInfo={NSFilePath=/Users/XXX, NSUnderlyingError=0x600000c8d4d0 {Error Domain=NSPOSIXErrorDomain Code=13 "Permission denied"}}
It seems I don't have access to that file?

Are you trying to access the file on your development machine? Make sure you've added the file you are trying to access to your xcode project file. That might be the cause.

@jerrylinkinpark
Copy link

Can anyone assist with using this to upload a local video file on the device to FTP?
I've got it working with an image, just curious how I can arrange it to upload a .mp4/.mov file.

Can you tell me how it works with image or txt file?
I cannot find the right data type for the send function.
Thanks!!

Sure, for a .txt file, make sure the file is added to your project. To upload the file you would use:


let ftp = FTPUpload(baseUrl: String, userName: String, password: String, directoryPath: String)

do { 
let data = try Data(contentsOf: url (this is your .txt file location as a URL) as URL, options: [ .alwaysMapped, .uncached])

ftp.send(data: data , with: "name.txt") { (success) in 

        print("success") 
        }
        print(data)
        }
        catch {
        print(error)
    }
}

Hope this helps!

I think I got it. But I have a problem with file access.
I got error message, although, I have switched off the AppSandbox already.
Error Domain=NSCocoaErrorDomain Code=257 "The file “XXX” couldn’t be opened because you don’t have permission to view it." UserInfo={NSFilePath=/Users/XXX, NSUnderlyingError=0x600000c8d4d0 {Error Domain=NSPOSIXErrorDomain Code=13 "Permission denied"}}
It seems I don't have access to that file?

Are you trying to access the file on your development machine? Make sure you've added the file you are trying to access to your xcode project file. That might be the cause.

But I would like to upload a file each time when I create by my app. So it is no possible for me to add those files into the project file, right? Is there another way? Is there another method to access files?

@Pinturaj
Copy link

Pinturaj commented Sep 2, 2020

### Now working for me.

@jrcabreraca
Copy link

How create folder in server?

@Pinturaj
Copy link

Pinturaj commented Sep 4, 2020

How to download/view file from FTP Server.

@Pinturaj
Copy link

How create folder in server?

Manually

@jerrylinkinpark
Copy link

Now working for me.

I have solved my problem but facing the same problem as you.
The bytesWritten equals to -1 and it is not going to upload anything...
Did you solve your problem?

@jerrylinkinpark
Copy link

Works perfectly thank you sir.

not working for me bytesWritten return -1 at first time

I got it. I have looked through the meaning of bytesWritten equals to -1 and it turns out there is error by connection.
By my side it is maybe because of too many connections already existed so I restart my device and it worked then.

anyway, the bytesWritten equals to -1 doesn't mean you have a problem with the file, but it is mostly because of the connection.

@Pinturaj
Copy link

Works perfectly thank you sir.

not working for me bytesWritten return -1 at first time

I got it. I have looked through the meaning of bytesWritten equals to -1 and it turns out there is error by connection.
By my side it is maybe because of too many connections already existed so I restart my device and it worked then.

anyway, the bytesWritten equals to -1 doesn't mean you have a problem with the file, but it is mostly because of the connection.

It can be because of problem in credentials.

@Pinturaj
Copy link

Pinturaj commented Sep 24, 2020

Is there any Lib or same as above class that we can get file URL so that we can load the url or download that attachment?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment