in FirebaseMLModelDownloader/Sources/ModelDownloadTask.swift [185:365]
func handleResponse(response: HTTPURLResponse, tempURL: URL, completion: @escaping Completion) {
guard (200 ..< 299).contains(response.statusCode) else {
switch response.statusCode {
case 400:
// Possible failure due to download URL expiry. Check if download URL has expired.
guard remoteModelInfo.urlExpiryTime < Date() else {
DeviceLogger.logEvent(level: .debug,
message: ModelDownloadTask.ErrorDescription
.invalidArgument(remoteModelInfo.name),
messageCode: .invalidArgument)
telemetryLogger?.logModelDownloadEvent(
eventName: .modelDownload,
status: .failed,
model: CustomModel(name: remoteModelInfo.name,
size: remoteModelInfo.size,
path: "",
hash: remoteModelInfo.modelHash),
downloadErrorCode: .httpError(code: response.statusCode)
)
completion(.failure(.invalidArgument))
return
}
DeviceLogger.logEvent(level: .debug,
message: ModelDownloadTask.ErrorDescription.expiredModelInfo,
messageCode: .expiredModelInfo)
telemetryLogger?.logModelDownloadEvent(
eventName: .modelDownload,
status: .failed,
model: CustomModel(name: remoteModelInfo.name,
size: remoteModelInfo.size,
path: "",
hash: remoteModelInfo.modelHash),
downloadErrorCode: .urlExpired
)
completion(.failure(.expiredDownloadURL))
case 401, 403:
DeviceLogger.logEvent(level: .debug,
message: ModelDownloadTask.ErrorDescription.permissionDenied,
messageCode: .permissionDenied)
telemetryLogger?.logModelDownloadEvent(
eventName: .modelDownload,
status: .failed,
model: CustomModel(name: remoteModelInfo.name,
size: remoteModelInfo.size,
path: "",
hash: remoteModelInfo.modelHash),
downloadErrorCode: .httpError(code: response.statusCode)
)
completion(.failure(.permissionDenied))
case 404:
DeviceLogger.logEvent(level: .debug,
message: ModelDownloadTask.ErrorDescription
.modelNotFound(remoteModelInfo.name),
messageCode: .modelNotFound)
telemetryLogger?.logModelDownloadEvent(
eventName: .modelDownload,
status: .failed,
model: CustomModel(name: remoteModelInfo.name,
size: remoteModelInfo.size,
path: "",
hash: remoteModelInfo.modelHash),
downloadErrorCode: .httpError(code: response.statusCode)
)
completion(.failure(.notFound))
default:
let description = ModelDownloadTask.ErrorDescription
.modelDownloadFailed(response.statusCode)
DeviceLogger.logEvent(level: .debug,
message: description,
messageCode: .modelDownloadError)
telemetryLogger?.logModelDownloadEvent(
eventName: .modelDownload,
status: .failed,
model: CustomModel(name: remoteModelInfo.name,
size: remoteModelInfo.size,
path: "",
hash: remoteModelInfo.modelHash),
downloadErrorCode: .httpError(code: response.statusCode)
)
completion(.failure(.internalError(description: description)))
}
return
}
/// Construct local model file URL.
guard var modelFileURL = ModelFileManager.getDownloadedModelFileURL(
appName: appName,
modelName: remoteModelInfo.name
) else {
// Could not create Application Support directory to store model files.
let description = ModelDownloadTask.ErrorDescription.noModelsDirectory
DeviceLogger.logEvent(level: .debug,
message: description,
messageCode: .downloadedModelSaveError)
// Downloading the file succeeding but saving failed.
telemetryLogger?.logModelDownloadEvent(eventName: .modelDownload,
status: .succeeded,
model: CustomModel(name: remoteModelInfo.name,
size: remoteModelInfo.size,
path: "",
hash: remoteModelInfo.modelHash),
downloadErrorCode: .downloadFailed)
completion(.failure(.internalError(description: description)))
return
}
do {
// Try disabling iCloud backup for model files, because UserDefaults is not backed up.
var resourceValue = URLResourceValues()
resourceValue.isExcludedFromBackup = true
do {
try modelFileURL.setResourceValues(resourceValue)
} catch {
DeviceLogger.logEvent(level: .debug,
message: ModelDownloadTask.ErrorDescription.disableBackupError,
messageCode: .disableBackupError)
}
// Save model file to device.
try ModelFileManager.moveFile(
at: tempURL,
to: modelFileURL,
size: Int64(remoteModelInfo.size)
)
DeviceLogger.logEvent(level: .debug,
message: ModelDownloadTask.DebugDescription.savedModelFile,
messageCode: .downloadedModelFileSaved)
// Generate local model info.
let localModelInfo = LocalModelInfo(from: remoteModelInfo)
// Write model info to user defaults.
localModelInfo.writeToDefaults(defaults, appName: appName)
DeviceLogger.logEvent(level: .debug,
message: ModelDownloadTask.DebugDescription.savedLocalModelInfo,
messageCode: .downloadedModelInfoSaved)
// Build model from model info and local path.
let model = CustomModel(localModelInfo: localModelInfo, path: modelFileURL.path)
DeviceLogger.logEvent(level: .debug,
message: ModelDownloadTask.DebugDescription.modelDownloaded,
messageCode: .modelDownloaded)
telemetryLogger?.logModelDownloadEvent(
eventName: .modelDownload,
status: .succeeded,
model: model,
downloadErrorCode: .noError
)
completion(.success(model))
} catch let error as DownloadError {
if error == .notEnoughSpace {
DeviceLogger.logEvent(level: .debug,
message: ModelDownloadTask.ErrorDescription.notEnoughSpace,
messageCode: .notEnoughSpace)
} else {
DeviceLogger.logEvent(level: .debug,
message: ModelDownloadTask.ErrorDescription.modelSaveError,
messageCode: .downloadedModelSaveError)
}
// Downloading the file succeeding but saving failed.
telemetryLogger?.logModelDownloadEvent(eventName: .modelDownload,
status: .succeeded,
model: CustomModel(name: remoteModelInfo.name,
size: remoteModelInfo.size,
path: "",
hash: remoteModelInfo.modelHash),
downloadErrorCode: .downloadFailed)
completion(.failure(error))
return
} catch {
DeviceLogger.logEvent(level: .debug,
message: ModelDownloadTask.ErrorDescription.modelSaveError,
messageCode: .downloadedModelSaveError)
// Downloading the file succeeding but saving failed.
telemetryLogger?.logModelDownloadEvent(eventName: .modelDownload,
status: .succeeded,
model: CustomModel(name: remoteModelInfo.name,
size: remoteModelInfo.size,
path: "",
hash: remoteModelInfo.modelHash),
downloadErrorCode: .downloadFailed)
completion(.failure(.internalError(description: error.localizedDescription)))
return
}
}