FileDownloader.swift 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  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. enum FileDownloaderError: Error {
  16. case unexpectedResponseType
  17. case networkError(Error)
  18. }
  19. struct FileDownloaderResponse {
  20. var urlResponse: HTTPURLResponse
  21. var fileURL: URL
  22. }
  23. /// URL Session to use while retrieving model info.
  24. protocol FileDownloader {
  25. typealias CompletionHandler = (Result<FileDownloaderResponse, Error>) -> Void
  26. typealias ProgressHandler = (_ bytesWritten: Int64, _ bytesExpectedToWrite: Int64) -> Void
  27. func downloadFile(with url: URL,
  28. progressHandler: @escaping ProgressHandler,
  29. completion: @escaping CompletionHandler)
  30. }
  31. /// Downloader to get model files from server.
  32. class ModelFileDownloader: NSObject, FileDownloader {
  33. /// Model conditions for download.
  34. private let conditions: ModelDownloadConditions
  35. /// URL session configuration.
  36. private let configuration: URLSessionConfiguration
  37. /// Task to handle model file download.
  38. private var downloadTask: URLSessionDownloadTask?
  39. /// URLSession to handle model downloads.
  40. private lazy var downloadSession: URLSession = URLSession(
  41. configuration: configuration,
  42. delegate: self,
  43. delegateQueue: nil
  44. )
  45. /// Successful download completion handler.
  46. private var completion: FileDownloader.CompletionHandler?
  47. /// Download progress handler.
  48. private var progressHandler: FileDownloader.ProgressHandler?
  49. init(conditions: ModelDownloadConditions) {
  50. self.conditions = conditions
  51. configuration = URLSessionConfiguration.ephemeral
  52. /// Wait for network connectivity, if unavailable.
  53. if #available(iOS 11.0, macOS 10.13, macCatalyst 13.0, tvOS 11.0, watchOS 4.0, *) {
  54. self.configuration.waitsForConnectivity = true
  55. /// Wait for 10 minutes.
  56. self.configuration.timeoutIntervalForResource = 600
  57. }
  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. downloadTask.resume()
  68. self.downloadTask = downloadTask
  69. }
  70. }
  71. /// Extension to handle delegate methods.
  72. extension ModelFileDownloader: URLSessionDownloadDelegate {
  73. /// Handle client-side errors.
  74. func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
  75. guard let error = error else { return }
  76. session.finishTasksAndInvalidate()
  77. /// Unable to resolve hostname or connect to host.
  78. completion?(.failure(FileDownloaderError.networkError(error)))
  79. }
  80. func urlSession(_ session: URLSession,
  81. downloadTask: URLSessionDownloadTask,
  82. didFinishDownloadingTo location: URL) {
  83. guard let response = downloadTask.response,
  84. let urlResponse = response as? HTTPURLResponse else {
  85. completion?(.failure(FileDownloaderError.unexpectedResponseType))
  86. return
  87. }
  88. let downloaderResponse = FileDownloaderResponse(urlResponse: urlResponse, fileURL: location)
  89. session.finishTasksAndInvalidate()
  90. completion?(.success(downloaderResponse))
  91. }
  92. func urlSession(_ session: URLSession,
  93. downloadTask: URLSessionDownloadTask,
  94. didWriteData bytesWritten: Int64,
  95. totalBytesWritten: Int64,
  96. totalBytesExpectedToWrite: Int64) {
  97. progressHandler?(totalBytesWritten, totalBytesExpectedToWrite)
  98. }
  99. }