Files

You can use Kinvey to store and retrieve binary files of size up to 5TB. Files can be of any format.

The files are automatically enabled for download using a Content Delivery Network (CDN) for massive scale and performance.

Kinvey does not directly serve or accept files. Instead, the Kinvey Files API works by providing a short-lived URL to a third-party cloud storage service from which file(s) can be uploaded or downloaded. Currently, the third-party service used is Google Cloud Storage.

You would typically use the Files API to upload and download:

  • images
  • video files
  • other application-specific files.

Kinvey can transmit to and from our file store service and place files in memory or at a supplied path. These are most commonly images, videos, and audio files, but can be any data. The service also supports streaming media.

The Apple App Store guidelines cover cases where apps may be rejected from the App Store due to transmitting/receiving large files. Please read the App Store guidelines for guidance.

File operations are done through the FileStore class using the overloaded upload() and download() methods.

Both upload and download operations are resumable. To resume a download operation, use the same File instance. To resume an upload operation, the File instance just needs to have the same fileId in order to resume a previous upload operation.

Upload

Uploading a file is done calling the upload() method and passing in a File instance, which represents the metadata of the file, and a second parameter which can be a String path, NSData, UIImage (which will be saved as a PNG file) or NSInputStream.

When using custom values for _id, you should avoid values that are exactly 12 or 24 symbols in length. Such values are automatically converted to BSON ObjectID and are not stored as strings in the database, which can interfere with querying and other operations.

To ensure that no items with 12 or 24 symbols long _id are stored in the collection, you can create a pre-save hook that either prevents saving such items, or appends an additional symbol (for example, underscore) to the _id:

if (_id.length === 12 || _id.length === 24) {
  _id += "_";
}

let fileStore = FileStore<File>()

let file = File()
file.publicAccessible = true
let path = "<#...#>" //String path for the file that will be upload
//Uploads a file using a file string path
fileStore.upload(file, path: path, options: nil) {
    switch $0 {
    case .success(let file):
        print(file)
    case .failure(let error):
        print(error)
    }
}

Download

In order to download a file, the fileId is required.

let fileStore = FileStore<File>()

let file = File()
file.fileId = "<#...#>" //File ID returned after you upload the file
fileStore.download(file, options: nil) { (result: Result<(File, Data), Swift.Error>) in
    switch result {
    case .success(let file, let data):
        print("File: \(file)")
        print("Data: \(data.count)")
    case .failure(let error):
        print("Error: \(error)")
    }
}

By default, files are cached in order to improve performance.

let fileStore = FileStore<File>()
let file = File()
file.fileId = "<#...#>" //File ID returned after you upload the file
fileStore.download(file, options: nil) { (result: Result<(File, URL), Swift.Error>) in
    switch result {
    case .success(let file, let url):
        print("File: \(file)")
        print("URL: \(url)")
    case .failure(let error):
        print("Error: \(error)")
    }
}

To disable the ability of caching files please use the storeType optional parameter.

fileStore.download(file, storeType: .network, options: nil) { (result: Result<(File, URL), Swift.Error>) in
    switch result {
    case .success(let file, let url):
        print("File: \(file)")
        print("URL: \(url)")
    case .failure(let error):
        print("Error: \(error)")
    }
}

Progress

In both operations, upload() or download(), there's a progress handler that allows you to be reported about the progress status of your request. The progress block can be called multiple times during the period of your request and will contain an instance of ProgressStatus which contains all the information that you need to be update your progress User Interface.

Uploading with progress:

let fileStore = FileStore<File>()
let file = File()
file.publicAccessible = true
let path = "<#...#>" //String path for the file that will be upload
//Uploads a file using a file string path
let request = fileStore.upload(file, path: path, options: nil) {
    switch $0 {
    case .success(let file):
        print(file)
    case .failure(let error):
        print(error)
    }
}
let observation = request.progress.observe(\Progress.fractionCompleted, options: [.initial, .new]) { (progress, change) in
    //progress handler
    if let fractionCompleted = change.newValue {
        print("Upload: \(fractionCompleted)")
    }
}
// don't forget to eventually call observation.invalidate()

Downloading with progress:

let request = fileStore.download(file, options: nil) { (result: Result<(File, URL), Swift.Error>) in
    //completion handler
}

let observation = request.progress.observe(\Progress.fractionCompleted, options: [.initial, .new]) { (progress, change) in
    //progress handler
    if let fractionCompleted = change.newValue {
        print("Download: \(fractionCompleted)")
    }
}
// don't forget to eventually call observation.invalidate()

Canceling

Both the upload and download operations return an AnyRequest instance which allows you to not only update the progress of the request, but also cancel the request if it is not finished yet. Use the cancel() method to cancel file operations.

let request = fileStore.download(file, options: nil) { result in
    // it will not be called since the request was canceled
}
request.cancel()

Adding Custom Properties to Files

Each file you upload to Kinvey is represented by an entity inside the Files collection. Similarly to any other entity, you can add properties to a file. To do that in Swift, subclass the File class.

import Kinvey

class MyFile: File {

    @objc dynamic var label: String?

    @available(*, deprecated, message: "Please use Swift.Codable instead")
    public convenience required init?(map: Map) {
        self.init(map: map)
    }

    @available(*, deprecated, message: "Please use Swift.Codable instead")
    override func mapping(map: Map) {
        super.mapping(map: map)

        label <- ("label", map["label"])
    }

}

Then, when instantiating a new FileStore, don't forget to specify the new subclass as file type.

let fileStore = FileStore<MyFile>()

All operations such as upload() and download() that you call on this FileStore instance will accept and return the subclassed file type.