Files
firebase-ios-sdk/FirebaseMLModelDownloader/Sources/FileDownloader.swift
T
manjanac 9597ffe51b Removes saved path on device (#7439)
* Restores ML Pods after M77.

* Fix Package.swift

* Re-add catalyst to GHA workflow.

* Add tests for list models.

* Failable emptying of model directory in tests.

* Remove path from local model info and user defaults.

* Clean up user defaults on delete + add interpreter to test app.

* Replace .absoluteString with .path. This makes model.path work directly with TFLite interpreter.

* Fix paths in tests.

* Unit test for successful get model.

* Rename modelPath to modelURL.

* Use defaults.integer instead of casting to Int.

* Remove force casting.

* Mostly minor fixes that came out of the bugbash (#7462)

* Use appropriate errors for network offline and api disabled.

* Error + test for resourceExhausted.

* Surface backend error message in logs whenever available.

* Add disclaimer to empty directories for testing only.

* Add Firelog events for model info retrieval.

* Add documentation + other minor fixes. (#7465)

* Add docs + clean up ModelDownloader.swift.

* Add docs + clean up ModelDownloadTask.swift.

* Add docs + clean up FileDownloader.swift.

* Add docs + clean up LocalModelInfo.swift. Handle TODO for userdefaults init failure.

* Add docs + clean up RemoteModelInfo.swift, DownloaderUserDefaults.swift.

* Add docs + clean up ModelInfoRetriever.swift.

* Add docs + clean up ModelDownloadConditions.swift.

* Add docs + clean up DeviceLogger.swift.

* Add docs + clean up TelemetryLogger.swift.

* Add docs + clean up ModelFileManager.swift.

* Remove resolved TODOs + other minor fixes.

* Update model download conditions init.

* Fix comment formatting.

* Minor newline formatting.

* Minor comment formatting.

* Add docs for ModelDownloadConditions.

* Basic fix for user defaults race condition.

* Minor updates to tests.

* Minor updates to tests.

* Final fixes (#7471)

* Refactor error parsing in model info response.

* Add Firelog for delete event + minor fixes.

* Temporary - test to check telemetry for delete event.

* Disable backup of model files; handle inability to locate model storage dir.

* Minor fixes.

* Add tflite path extension to model file.
2021-02-10 09:47:07 -08:00

120 lines
4.3 KiB
Swift

// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import Foundation
/// File downloader response.
struct FileDownloaderResponse {
var urlResponse: HTTPURLResponse
var fileURL: URL
}
/// Possible file downloader errors.
enum FileDownloaderError: Error {
case unexpectedResponseType
case networkError(Error)
}
/// Protocol to download a file from server.
protocol FileDownloader {
typealias CompletionHandler = (Result<FileDownloaderResponse, Error>) -> Void
typealias ProgressHandler = (_ bytesWritten: Int64, _ bytesExpectedToWrite: Int64) -> Void
func downloadFile(with url: URL,
progressHandler: @escaping ProgressHandler,
completion: @escaping CompletionHandler)
}
/// Downloader to get model files from server.
class ModelFileDownloader: NSObject, FileDownloader {
/// Model conditions for download.
private let conditions: ModelDownloadConditions
/// URL session configuration.
private let configuration: URLSessionConfiguration
/// Task to handle model file download.
private var downloadTask: URLSessionDownloadTask?
/// URLSession to handle model downloads.
private lazy var downloadSession: URLSession = URLSession(
configuration: configuration,
delegate: self,
delegateQueue: nil
)
/// Successful download completion handler.
private var completion: FileDownloader.CompletionHandler?
/// Download progress handler.
private var progressHandler: FileDownloader.ProgressHandler?
init(conditions: ModelDownloadConditions) {
self.conditions = conditions
configuration = URLSessionConfiguration.ephemeral
/// Wait for network connectivity.
if #available(iOS 11.0, macOS 10.13, macCatalyst 13.0, tvOS 11.0, watchOS 4.0, *) {
self.configuration.waitsForConnectivity = true
/// Wait for 10 minutes.
self.configuration.timeoutIntervalForResource = 600
}
configuration.allowsCellularAccess = conditions.allowsCellularAccess
}
func downloadFile(with url: URL,
progressHandler: @escaping (Int64, Int64) -> Void,
completion: @escaping (Result<FileDownloaderResponse, Error>) -> Void) {
// TODO: Fail if download already in progress.
self.completion = completion
self.progressHandler = progressHandler
let downloadTask = downloadSession.downloadTask(with: url)
// Begin or resume model download.
downloadTask.resume()
self.downloadTask = downloadTask
}
}
/// Extension to handle delegate methods.
extension ModelFileDownloader: URLSessionDownloadDelegate {
// Handle client-side errors.
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
guard let error = error else { return }
session.finishTasksAndInvalidate()
// Unable to resolve hostname or connect to host.
completion?(.failure(FileDownloaderError.networkError(error)))
}
/// Download completion.
func urlSession(_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL) {
guard let response = downloadTask.response,
let urlResponse = response as? HTTPURLResponse else {
completion?(.failure(FileDownloaderError.unexpectedResponseType))
return
}
let downloaderResponse = FileDownloaderResponse(urlResponse: urlResponse, fileURL: location)
session.finishTasksAndInvalidate()
completion?(.success(downloaderResponse))
}
/// Download progress.
func urlSession(_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didWriteData bytesWritten: Int64,
totalBytesWritten: Int64,
totalBytesExpectedToWrite: Int64) {
progressHandler?(totalBytesWritten, totalBytesExpectedToWrite)
}
}