func downloadModelInfo()

in FirebaseMLModelDownloader/Sources/ModelInfoRetriever.swift [138:468]


  func downloadModelInfo(completion: @escaping (Result<DownloadModelInfoResult, DownloadError>)
    -> Void) {
    authTokenProvider { result in
      switch result {
      // Successfully received FIS token.
      case let .success(authToken):
        DeviceLogger.logEvent(level: .debug,
                              message: ModelInfoRetriever.DebugDescription
                                .receivedAuthToken,
                              messageCode: .validAuthToken)
        // Get model info fetch URL with appropriate HTTP headers.
        guard let request = self.getModelInfoFetchURLRequest(token: authToken) else {
          DeviceLogger.logEvent(level: .debug,
                                message: ModelInfoRetriever.ErrorDescription
                                  .invalidModelInfoFetchURL,
                                messageCode: .invalidModelInfoFetchURL)
          self.telemetryLogger?.logModelInfoRetrievalEvent(eventName: .modelDownload,
                                                           status: .modelInfoRetrievalFailed,
                                                           model: CustomModel(name: self.modelName,
                                                                              size: 0,
                                                                              path: "",
                                                                              hash: ""),
                                                           modelInfoErrorCode: .connectionFailed)
          completion(.failure(.internalError(description: ModelInfoRetriever.ErrorDescription
              .invalidModelInfoFetchURL)))
          return
        }
        // Download model info.
        self.session.getModelInfo(with: request) {
          data, response, error in
          if let downloadError = error {
            let description = ModelInfoRetriever.ErrorDescription
              .failedModelInfoRetrieval(downloadError.localizedDescription)
            DeviceLogger.logEvent(level: .debug,
                                  message: description,
                                  messageCode: .modelInfoRetrievalError)
            self.telemetryLogger?.logModelInfoRetrievalEvent(eventName: .modelDownload,
                                                             status: .modelInfoRetrievalFailed,
                                                             model: CustomModel(
                                                               name: self.modelName,
                                                               size: 0,
                                                               path: "",
                                                               hash: ""
                                                             ),
                                                             modelInfoErrorCode: .connectionFailed)
            completion(.failure(.internalError(description: description)))
          } else {
            guard let httpResponse = response as? HTTPURLResponse else {
              DeviceLogger.logEvent(level: .debug,
                                    message: ModelInfoRetriever.ErrorDescription
                                      .invalidHTTPResponse,
                                    messageCode: .invalidHTTPResponse)
              self.telemetryLogger?.logModelInfoRetrievalEvent(eventName: .modelDownload,
                                                               status: .modelInfoRetrievalFailed,
                                                               model: CustomModel(
                                                                 name: self.modelName,
                                                                 size: 0,
                                                                 path: "",
                                                                 hash: ""
                                                               ),
                                                               modelInfoErrorCode: .connectionFailed)
              completion(.failure(.internalError(description: ModelInfoRetriever.ErrorDescription
                  .invalidHTTPResponse)))
              return
            }
            DeviceLogger.logEvent(level: .debug,
                                  message: ModelInfoRetriever.DebugDescription
                                    .receivedServerResponse,
                                  messageCode: .validHTTPResponse)
            switch httpResponse.statusCode {
            case 200:
              guard let modelHash = httpResponse
                .allHeaderFields[ModelInfoRetriever.etagHTTPHeader] as? String else {
                DeviceLogger.logEvent(level: .debug,
                                      message: ModelInfoRetriever.ErrorDescription.missingModelHash,
                                      messageCode: .missingModelHash)
                self.telemetryLogger?.logModelInfoRetrievalEvent(eventName: .modelDownload,
                                                                 status: .modelInfoRetrievalFailed,
                                                                 model: CustomModel(
                                                                   name: self.modelName,
                                                                   size: 0,
                                                                   path: "",
                                                                   hash: ""
                                                                 ),
                                                                 modelInfoErrorCode: .noHash)
                completion(.failure(.internalError(description: ModelInfoRetriever.ErrorDescription
                    .missingModelHash)))
                return
              }
              guard let data = data else {
                DeviceLogger.logEvent(level: .debug,
                                      message: ModelInfoRetriever.ErrorDescription
                                        .invalidHTTPResponse,
                                      messageCode: .invalidHTTPResponse)
                self.telemetryLogger?.logModelInfoRetrievalEvent(eventName: .modelDownload,
                                                                 status: .modelInfoRetrievalFailed,
                                                                 model: CustomModel(
                                                                   name: self.modelName,
                                                                   size: 0,
                                                                   path: "",
                                                                   hash: ""
                                                                 ),
                                                                 modelInfoErrorCode: .unknown)
                completion(.failure(.internalError(description: ModelInfoRetriever.ErrorDescription
                    .invalidHTTPResponse)))
                return
              }
              do {
                // Parse model info from HTTP response.
                let modelInfo = try self.getRemoteModelInfoFromResponse(data, modelHash: modelHash)
                DeviceLogger.logEvent(level: .debug,
                                      message: ModelInfoRetriever.DebugDescription
                                        .modelInfoDownloaded,
                                      messageCode: .modelInfoDownloaded)
                self.telemetryLogger?.logModelInfoRetrievalEvent(eventName: .modelDownload,
                                                                 status: .modelInfoRetrievalSucceeded,
                                                                 model: CustomModel(
                                                                   name: self.modelName,
                                                                   size: modelInfo.size,
                                                                   path: "",
                                                                   hash: modelInfo.modelHash
                                                                 ),
                                                                 modelInfoErrorCode: .noError)
                completion(.success(.modelInfo(modelInfo)))
              } catch {
                let description = ModelInfoRetriever.ErrorDescription
                  .invalidModelInfoJSON(error.localizedDescription)
                DeviceLogger.logEvent(level: .debug,
                                      message: description,
                                      messageCode: .invalidModelInfoJSON)
                self.telemetryLogger?.logModelInfoRetrievalEvent(eventName: .modelDownload,
                                                                 status: .modelInfoRetrievalFailed,
                                                                 model: CustomModel(
                                                                   name: self.modelName,
                                                                   size: 0,
                                                                   path: "",
                                                                   hash: ""
                                                                 ),
                                                                 modelInfoErrorCode: .unknown)
                completion(
                  .failure(.internalError(description: description))
                )
              }
            case 304:
              // For this case to occur, local model info has to already be available on device.
              guard let localInfo = self.localModelInfo else {
                DeviceLogger.logEvent(level: .debug,
                                      message: ModelInfoRetriever.ErrorDescription
                                        .unexpectedModelInfoDeletion,
                                      messageCode: .modelInfoDeleted)
                self.telemetryLogger?.logModelInfoRetrievalEvent(eventName: .modelDownload,
                                                                 status: .modelInfoRetrievalFailed,
                                                                 model: CustomModel(
                                                                   name: self.modelName,
                                                                   size: 0,
                                                                   path: "",
                                                                   hash: ""
                                                                 ),
                                                                 modelInfoErrorCode: .unknown)
                completion(
                  .failure(.internalError(description: ModelInfoRetriever.ErrorDescription
                      .unexpectedModelInfoDeletion))
                )
                return
              }
              guard let modelHash = httpResponse
                .allHeaderFields[ModelInfoRetriever.etagHTTPHeader] as? String else {
                DeviceLogger.logEvent(level: .debug,
                                      message: ModelInfoRetriever.ErrorDescription
                                        .missingModelHash,
                                      messageCode: .noModelHash)
                self.telemetryLogger?.logModelInfoRetrievalEvent(eventName: .modelDownload,
                                                                 status: .modelInfoRetrievalFailed,
                                                                 model: CustomModel(
                                                                   name: self.modelName,
                                                                   size: 0,
                                                                   path: "",
                                                                   hash: ""
                                                                 ),
                                                                 modelInfoErrorCode: .noHash)
                completion(.failure(.internalError(description: ModelInfoRetriever.ErrorDescription
                    .missingModelHash)))
                return
              }
              // Ensure that there is local model info on device with matching hash.
              guard modelHash == localInfo.modelHash else {
                DeviceLogger.logEvent(level: .debug,
                                      message: ModelInfoRetriever.ErrorDescription
                                        .modelHashMismatch,
                                      messageCode: .modelHashMismatchError)
                self.telemetryLogger?.logModelInfoRetrievalEvent(eventName: .modelDownload,
                                                                 status: .modelInfoRetrievalFailed,
                                                                 model: CustomModel(
                                                                   name: self.modelName,
                                                                   size: 0,
                                                                   path: "",
                                                                   hash: modelHash
                                                                 ),
                                                                 modelInfoErrorCode: .hashMismatch)
                completion(.failure(.internalError(description: ModelInfoRetriever.ErrorDescription
                    .modelHashMismatch)))
                return
              }
              DeviceLogger.logEvent(level: .debug,
                                    message: ModelInfoRetriever.DebugDescription
                                      .modelInfoUnmodified,
                                    messageCode: .modelInfoUnmodified)
              self.telemetryLogger?.logModelInfoRetrievalEvent(eventName: .modelDownload,
                                                               status: .modelInfoRetrievalSucceeded,
                                                               model: CustomModel(
                                                                 name: self.modelName,
                                                                 size: localInfo.size,
                                                                 path: "",
                                                                 hash: localInfo.modelHash
                                                               ),
                                                               modelInfoErrorCode: .noError)
              completion(.success(.notModified))
            case 400:
              let errorMessage = self.getErrorFromResponse(data)
              let description = errorMessage ?? ModelInfoRetriever.ErrorDescription
                .invalidArgument(self.modelName)
              DeviceLogger.logEvent(level: .debug,
                                    message: description,
                                    messageCode: .invalidArgument)
              self.telemetryLogger?.logModelInfoRetrievalEvent(eventName: .modelDownload,
                                                               status: .modelInfoRetrievalFailed,
                                                               model: CustomModel(
                                                                 name: self.modelName,
                                                                 size: 0,
                                                                 path: "",
                                                                 hash: ""
                                                               ),
                                                               modelInfoErrorCode: .httpError(code: httpResponse
                                                                 .statusCode))
              completion(.failure(.invalidArgument))
            case 401, 403:
              // Error could be due to FirebaseML API not enabled for project, or invalid permissions.
              let errorMessage = self.getErrorFromResponse(data)
              let description = errorMessage ?? ModelInfoRetriever.ErrorDescription.permissionDenied
              DeviceLogger.logEvent(level: .debug,
                                    message: description,
                                    messageCode: .permissionDenied)
              self.telemetryLogger?.logModelInfoRetrievalEvent(eventName: .modelDownload,
                                                               status: .modelInfoRetrievalFailed,
                                                               model: CustomModel(
                                                                 name: self.modelName,
                                                                 size: 0,
                                                                 path: "",
                                                                 hash: ""
                                                               ),
                                                               modelInfoErrorCode: .httpError(code: httpResponse
                                                                 .statusCode))
              completion(.failure(.permissionDenied))
            case 404:
              let errorMessage = self.getErrorFromResponse(data)
              let description = errorMessage ?? ModelInfoRetriever.ErrorDescription
                .modelNotFound(self.modelName)
              DeviceLogger.logEvent(level: .debug,
                                    message: description,
                                    messageCode: .modelNotFound)
              self.telemetryLogger?.logModelInfoRetrievalEvent(eventName: .modelDownload,
                                                               status: .modelInfoRetrievalFailed,
                                                               model: CustomModel(
                                                                 name: self.modelName,
                                                                 size: 0,
                                                                 path: "",
                                                                 hash: ""
                                                               ),
                                                               modelInfoErrorCode: .httpError(code: httpResponse
                                                                 .statusCode))
              completion(.failure(.notFound))
            case 429:
              let errorMessage = self.getErrorFromResponse(data)
              let description = errorMessage ?? ModelInfoRetriever.ErrorDescription
                .resourceExhausted
              DeviceLogger.logEvent(level: .debug,
                                    message: description,
                                    messageCode: .resourceExhausted)
              self.telemetryLogger?.logModelInfoRetrievalEvent(eventName: .modelDownload,
                                                               status: .modelInfoRetrievalFailed,
                                                               model: CustomModel(
                                                                 name: self.modelName,
                                                                 size: 0,
                                                                 path: "",
                                                                 hash: ""
                                                               ),
                                                               modelInfoErrorCode: .httpError(code: httpResponse
                                                                 .statusCode))
              completion(.failure(.resourceExhausted))
            default:
              let errorMessage = self.getErrorFromResponse(data)
              let description = errorMessage ?? ModelInfoRetriever.ErrorDescription
                .modelInfoRetrievalFailed(httpResponse.statusCode)
              DeviceLogger.logEvent(level: .debug,
                                    message: description,
                                    messageCode: .modelInfoRetrievalError)
              self.telemetryLogger?.logModelInfoRetrievalEvent(eventName: .modelDownload,
                                                               status: .modelInfoRetrievalFailed,
                                                               model: CustomModel(
                                                                 name: self.modelName,
                                                                 size: 0,
                                                                 path: "",
                                                                 hash: ""
                                                               ),
                                                               modelInfoErrorCode: .httpError(code: httpResponse
                                                                 .statusCode))
              completion(.failure(.internalError(description: description)))
            }
          }
        }
      // Error retrieving auth token.
      case .failure:
        DeviceLogger.logEvent(level: .debug,
                              message: ModelInfoRetriever.ErrorDescription
                                .authTokenError,
                              messageCode: .authTokenError)
        self.telemetryLogger?.logModelInfoRetrievalEvent(eventName: .modelDownload,
                                                         status: .modelInfoRetrievalFailed,
                                                         model: CustomModel(
                                                           name: self.modelName,
                                                           size: 0,
                                                           path: "",
                                                           hash: ""
                                                         ),
                                                         modelInfoErrorCode: .unknown)
        completion(.failure(.internalError(description: ModelInfoRetriever.ErrorDescription
            .authTokenError)))
        return
      }
    }
  }