FileDownloader.swift 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  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 = URLSession(
  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. if #available(iOS 11.0, macOS 10.13, macCatalyst 13.0, tvOS 11.0, watchOS 4.0, *) {
  56. self.configuration.waitsForConnectivity = true
  57. /// Wait for 10 minutes.
  58. self.configuration.timeoutIntervalForResource = 600
  59. }
  60. configuration.allowsCellularAccess = conditions.allowsCellularAccess
  61. }
  62. func downloadFile(with url: URL,
  63. progressHandler: @escaping (Int64, Int64) -> Void,
  64. completion: @escaping (Result<FileDownloaderResponse, Error>) -> Void) {
  65. // TODO: Fail if download already in progress.
  66. self.completion = completion
  67. self.progressHandler = progressHandler
  68. let downloadTask = downloadSession.downloadTask(with: url)
  69. // Begin or resume model download.
  70. downloadTask.resume()
  71. self.downloadTask = downloadTask
  72. }
  73. }
  74. /// Extension to handle delegate methods.
  75. extension ModelFileDownloader: URLSessionDownloadDelegate {
  76. // Handle client-side errors.
  77. func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
  78. guard let error = error else { return }
  79. session.finishTasksAndInvalidate()
  80. // Unable to resolve hostname or connect to host.
  81. completion?(.failure(FileDownloaderError.networkError(error)))
  82. }
  83. /// Download completion.
  84. func urlSession(_ session: URLSession,
  85. downloadTask: URLSessionDownloadTask,
  86. didFinishDownloadingTo location: URL) {
  87. guard let response = downloadTask.response,
  88. let urlResponse = response as? HTTPURLResponse else {
  89. completion?(.failure(FileDownloaderError.unexpectedResponseType))
  90. return
  91. }
  92. let downloaderResponse = FileDownloaderResponse(urlResponse: urlResponse, fileURL: location)
  93. session.finishTasksAndInvalidate()
  94. completion?(.success(downloaderResponse))
  95. }
  96. /// Download progress.
  97. func urlSession(_ session: URLSession,
  98. downloadTask: URLSessionDownloadTask,
  99. didWriteData bytesWritten: Int64,
  100. totalBytesWritten: Int64,
  101. totalBytesExpectedToWrite: Int64) {
  102. progressHandler?(totalBytesWritten, totalBytesExpectedToWrite)
  103. }
  104. }