FileDownloader.swift 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. // Copyright 2021 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. import Foundation
  15. /// File downloader response.
  16. struct FileDownloaderResponse {
  17. var urlResponse: HTTPURLResponse
  18. var fileURL: URL
  19. }
  20. /// Possible file downloader errors.
  21. enum FileDownloaderError: Error {
  22. case unexpectedResponseType
  23. case networkError(Error)
  24. }
  25. /// Protocol to download a file from server.
  26. protocol FileDownloader {
  27. typealias CompletionHandler = (Result<FileDownloaderResponse, Error>) -> Void
  28. typealias ProgressHandler = (_ bytesWritten: Int64, _ bytesExpectedToWrite: Int64) -> Void
  29. func downloadFile(with url: URL,
  30. progressHandler: @escaping ProgressHandler,
  31. completion: @escaping CompletionHandler)
  32. }
  33. /// Downloader to get model files from server.
  34. class ModelFileDownloader: NSObject, FileDownloader {
  35. /// Model conditions for download.
  36. private let conditions: ModelDownloadConditions
  37. /// URL session configuration.
  38. private let configuration: URLSessionConfiguration
  39. /// Task to handle model file download.
  40. private var downloadTask: URLSessionDownloadTask?
  41. /// URLSession to handle model downloads.
  42. private lazy var downloadSession: URLSession = .init(
  43. configuration: configuration,
  44. delegate: self,
  45. delegateQueue: nil
  46. )
  47. /// Successful download completion handler.
  48. private var completion: FileDownloader.CompletionHandler?
  49. /// Download progress handler.
  50. private var progressHandler: FileDownloader.ProgressHandler?
  51. init(conditions: ModelDownloadConditions) {
  52. self.conditions = conditions
  53. configuration = URLSessionConfiguration.ephemeral
  54. /// Wait for network connectivity.
  55. configuration.waitsForConnectivity = true
  56. /// Wait for 10 minutes.
  57. configuration.timeoutIntervalForResource = 600
  58. configuration.allowsCellularAccess = conditions.allowsCellularAccess
  59. }
  60. func downloadFile(with url: URL,
  61. progressHandler: @escaping (Int64, Int64) -> Void,
  62. completion: @escaping (Result<FileDownloaderResponse, Error>) -> Void) {
  63. // TODO: Fail if download already in progress.
  64. self.completion = completion
  65. self.progressHandler = progressHandler
  66. let downloadTask = downloadSession.downloadTask(with: url)
  67. // Begin or resume model download.
  68. downloadTask.resume()
  69. self.downloadTask = downloadTask
  70. }
  71. }
  72. /// Extension to handle delegate methods.
  73. extension ModelFileDownloader: URLSessionDownloadDelegate {
  74. // Handle client-side errors.
  75. func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
  76. guard let error = error else { return }
  77. session.finishTasksAndInvalidate()
  78. // Unable to resolve hostname or connect to host.
  79. completion?(.failure(FileDownloaderError.networkError(error)))
  80. }
  81. /// Download completion.
  82. func urlSession(_ session: URLSession,
  83. downloadTask: URLSessionDownloadTask,
  84. didFinishDownloadingTo location: URL) {
  85. guard let response = downloadTask.response,
  86. let urlResponse = response as? HTTPURLResponse else {
  87. completion?(.failure(FileDownloaderError.unexpectedResponseType))
  88. return
  89. }
  90. let downloaderResponse = FileDownloaderResponse(urlResponse: urlResponse, fileURL: location)
  91. session.finishTasksAndInvalidate()
  92. completion?(.success(downloaderResponse))
  93. }
  94. /// Download progress.
  95. func urlSession(_ session: URLSession,
  96. downloadTask: URLSessionDownloadTask,
  97. didWriteData bytesWritten: Int64,
  98. totalBytesWritten: Int64,
  99. totalBytesExpectedToWrite: Int64) {
  100. progressHandler?(totalBytesWritten, totalBytesExpectedToWrite)
  101. }
  102. }