spiegel-keyman/ios/engine/KMEI/KeymanEngine/Classes/HTTPDownloader.swift

138 lines
4.5 KiB
Swift

//
// HTTPDownloader.swift
// KeymanEngine
//
// Created by Gabriel Wong on 2017-09-15.
// Copyright © 2017 SIL International. All rights reserved.
//
protocol HTTPDownloadDelegate: class {
func downloadRequestStarted(_ request: HTTPDownloadRequest)
func downloadRequestFinished(_ request: HTTPDownloadRequest)
func downloadRequestFailed(_ request: HTTPDownloadRequest)
func downloadQueueFinished(_ queue: HTTPDownloader)
}
class HTTPDownloader: NSObject, URLSessionDelegate, URLSessionTaskDelegate, URLSessionDownloadDelegate,
URLSessionDataDelegate {
var queue: [HTTPDownloadRequest] = []
// TODO: Make unowned
weak var handler: HTTPDownloadDelegate?
var currentRequest: HTTPDownloadRequest?
var downloadSession: URLSession!
public var userInfo: [String: Any] = [:]
init(_ handler: HTTPDownloadDelegate?) {
super.init()
self.handler = handler
let config = URLSessionConfiguration.default
downloadSession = URLSession(configuration: config, delegate: self, delegateQueue: nil)
}
func addRequest(_ request: HTTPDownloadRequest) {
queue.append(request)
}
func popRequest() -> HTTPDownloadRequest? {
if queue.isEmpty {
return nil
}
return queue.remove(at: 0)
}
// Starts the queue. The queue is managed via event messages in order to process them sequentially.
func run() {
if !queue.isEmpty {
runRequest()
}
}
// Runs the next step in the queue, if appropriate.
func runRequest() {
// Perhaps more of an 'if', relying on 'completed' messages to continue the loop?
if !queue.isEmpty {
let req: HTTPDownloadRequest! = popRequest()
currentRequest = req
if req.typeCode == .downloadFile {
req.task = downloadSession.downloadTask(with: req.url)
handler?.downloadRequestStarted(req)
}
req?.task?.resume()
} else {
handler?.downloadQueueFinished(self)
}
// The next step in the queue, should it exist, will be triggered by urlSession(_, task:, didCompleteWithError:)
// which calls this method. Thus, this method may serve as the single source of the 'queue finished' message.
}
// This is triggered before the 'didCompleteWithError' delegate method.
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL) {
guard let currentRequest = currentRequest else {
return
}
log.debug("Downloaded file \(currentRequest.url) as \(location), " +
"to be copied to \(currentRequest.destinationFile ?? "nil")")
// If a destination file for the download has already been specified, let's go ahead and copy it over.
if let destFile = currentRequest.destinationFile {
let destFileUrl = URL(fileURLWithPath: destFile)
do {
// Need to delete the file if it already exists (e.g. in case of a previous partial download)
if(FileManager.default.fileExists(atPath: destFileUrl.path)) {
try FileManager.default.removeItem(at: destFileUrl)
}
try FileManager.default.copyItem(at: location,
to: destFileUrl)
} catch {
log.error("Error saving the download: \(error)")
}
}
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask,
willCacheResponse proposedResponse: CachedURLResponse,
completionHandler: @escaping (CachedURLResponse?) -> Void) {
// We only evaluate one at a time, so the 'current request' holds the data task's original request data.
currentRequest?.rawResponseData = proposedResponse.data
completionHandler(proposedResponse)
// With current internal settings, this will cache the data as we desire.
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
let req = currentRequest
currentRequest = nil
if task.error != nil {
//Force the callback onto the main thread.
DispatchQueue.main.async {
self.handler?.downloadRequestFailed(req!)
}
} else {
DispatchQueue.main.async {
self.handler?.downloadRequestFinished(req!)
}
}
DispatchQueue.main.async {
self.runRequest()
}
}
func cancelAllOperations() {
while !queue.isEmpty {
_ = popRequest()
}
// Done second so that there is no 'next quest' that could possibly trigger. (This is probably overcautious.)
if let currentRequest = currentRequest {
currentRequest.task?.cancel()
}
}
var requestsCount: Int {
return queue.count
}
}