func handleResponse()

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
    }
  }