Skip to content

Commit c64ca06

Browse files
authored
More on-device logging and better error handling. (#7397)
* Restores ML Pods after M77. * Fix Package.swift * Re-add catalyst to GHA workflow. * Add new error type for expired url + fix tests. * On-device logging + updates to tests. * Add new error type for expired url + fix tests. * On-device logging + updates to tests. * Revert to enum for ModelFileManager.swift * Remove GoogleService-Info.plist * Remove GoogleService-Info.plist * Improve errors, tests, on-device logging. * Add device compatibility to check for available space. * Use CocoaError to check for space constraints.
1 parent 06c1a50 commit c64ca06

File tree

8 files changed

+299
-34
lines changed

8 files changed

+299
-34
lines changed

FirebaseMLModelDownloader/Sources/DeviceLogger.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ enum LoggerMessageCode: Int {
4040
case invalidOptions
4141
case retryDownload
4242
case anotherDownloadInProgressError
43+
case invalidModelName
44+
case permissionDenied
45+
case notEnoughSpace
4346
case validModelDownloadResponse
4447
case hostnameError
4548
case invalidDownloadSessionError

FirebaseMLModelDownloader/Sources/ModelDownloadConditions.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@ import Foundation
1818
public struct ModelDownloadConditions {
1919
/// Allow model downloading on a cellular connection. Default is `YES`.
2020
public var allowsCellularAccess = true
21-
/// Public init
21+
/// Public init.
2222
public init() {}
2323
}

FirebaseMLModelDownloader/Sources/ModelDownloadTask.swift

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -126,16 +126,23 @@ extension ModelDownloadTask {
126126
/// Handle model download response.
127127
func handleResponse(response: HTTPURLResponse, tempURL: URL, completion: @escaping Completion) {
128128
guard (200 ..< 299).contains(response.statusCode) else {
129+
switch response.statusCode {
129130
/// Possible failure due to download URL expiry.
130-
if response.statusCode == 400 {
131+
case 400:
132+
let currentDateTime = Date()
133+
/// Check if download url has expired.
134+
guard currentDateTime > remoteModelInfo.urlExpiryTime else {
135+
completion(.failure(.invalidArgument))
136+
return
137+
}
131138
completion(.failure(.expiredDownloadURL))
132-
return
139+
case 401, 403: completion(.failure(.permissionDenied))
140+
case 404: completion(.failure(.notFound))
141+
default:
142+
let description = ModelDownloadTask.ErrorDescription
143+
.modelDownloadFailed(response.statusCode)
144+
completion(.failure(.internalError(description: description)))
133145
}
134-
let description = ModelDownloadTask.ErrorDescription.modelDownloadFailed(response.statusCode)
135-
DeviceLogger.logEvent(level: .debug,
136-
message: description,
137-
messageCode: .modelDownloadError)
138-
completion(.failure(.internalError(description: description)))
139146
return
140147
}
141148

@@ -145,7 +152,11 @@ extension ModelDownloadTask {
145152
)
146153

147154
do {
148-
try ModelFileManager.moveFile(at: tempURL, to: modelFileURL)
155+
try ModelFileManager.moveFile(
156+
at: tempURL,
157+
to: modelFileURL,
158+
size: Int64(remoteModelInfo.size)
159+
)
149160
DeviceLogger.logEvent(level: .debug,
150161
message: ModelDownloadTask.DebugDescription.savedModelFile,
151162
messageCode: .downloadedModelFileSaved)
@@ -168,9 +179,15 @@ extension ModelDownloadTask {
168179
} catch let error as DownloadError {
169180
downloadStatus = .failed
170181
telemetryLogger?.logModelDownloadEvent(eventName: .modelDownload, status: downloadStatus)
171-
DeviceLogger.logEvent(level: .debug,
172-
message: ModelDownloadTask.ErrorDescription.saveModel,
173-
messageCode: .downloadedModelSaveError)
182+
if error == .notEnoughSpace {
183+
DeviceLogger.logEvent(level: .debug,
184+
message: ModelDownloadTask.ErrorDescription.notEnoughSpace,
185+
messageCode: .notEnoughSpace)
186+
} else {
187+
DeviceLogger.logEvent(level: .debug,
188+
message: ModelDownloadTask.ErrorDescription.saveModel,
189+
messageCode: .downloadedModelSaveError)
190+
}
174191
completion(.failure(error))
175192
return
176193
} catch {
@@ -209,6 +226,7 @@ extension ModelDownloadTask {
209226
"Could not get valid server response for model downloading."
210227
static let unknownDownloadError = "Unable to download model due to unknown error."
211228
static let saveModel = "Unable to save downloaded remote model file."
229+
static let notEnoughSpace = "Not enough space on device."
212230
static let expiredModelInfo = "Unable to update expired model info."
213231
static let anotherDownloadInProgress = "Download already in progress."
214232
}

FirebaseMLModelDownloader/Sources/ModelDownloader.swift

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ public enum DownloadError: Error, Equatable {
2828
case notEnoughSpace
2929
/// Malformed model name.
3030
case invalidArgument
31-
/// Expired download URL.
32-
case expiredDownloadURL
3331
/// Other errors with description.
3432
case internalError(description: String)
3533
}
@@ -44,6 +42,13 @@ public enum DownloadedModelError: Error {
4442
case internalError(description: String)
4543
}
4644

45+
/// Extension to handle internally meaningful errors.
46+
extension DownloadError {
47+
static let expiredDownloadURL: DownloadError = {
48+
DownloadError.internalError(description: "Expired model download URL.")
49+
}()
50+
}
51+
4752
/// Possible ways to get a custom model.
4853
public enum ModelDownloadType {
4954
/// Get local model stored on device.
@@ -380,15 +385,25 @@ extension ModelDownloader {
380385
.modelDownloadFailed(error),
381386
messageCode: .modelDownloadError)
382387
switch error {
388+
case .notFound:
389+
DeviceLogger.logEvent(level: .debug,
390+
message: ModelDownloader.ErrorDescription
391+
.modelNotFound(modelName),
392+
messageCode: .modelNotFound)
393+
self.mainQueueHandler(completion(.failure(.notFound)))
394+
case .invalidArgument:
395+
DeviceLogger.logEvent(level: .debug,
396+
message: ModelDownloader.ErrorDescription
397+
.invalidModelName(modelName),
398+
messageCode: .invalidModelName)
399+
self.mainQueueHandler(completion(.failure(.invalidArgument)))
400+
case .permissionDenied:
401+
DeviceLogger.logEvent(level: .debug,
402+
message: ModelDownloader.ErrorDescription.permissionDenied,
403+
messageCode: .permissionDenied)
404+
self.mainQueueHandler(completion(.failure(.permissionDenied)))
383405
/// This is the error returned when URL expired.
384-
// TODO: Should we use a different error here?
385406
case .expiredDownloadURL:
386-
let currentDateTime = Date()
387-
/// Check if download url has expired.
388-
guard currentDateTime > remoteModelInfo.urlExpiryTime else {
389-
self.mainQueueHandler(completion(.failure(error)))
390-
return
391-
}
392407
/// Check if retries are allowed.
393408
guard self.numberOfRetries > 0 else {
394409
self
@@ -429,8 +444,8 @@ extension ModelDownloader {
429444

430445
self.mainQueueHandler(completion(.success(localModel)))
431446
}
432-
case let .failure(downloadError):
433-
self.mainQueueHandler(completion(.failure(downloadError)))
447+
case let .failure(error):
448+
self.mainQueueHandler(completion(.failure(error)))
434449
}
435450
}
436451
}
@@ -490,10 +505,15 @@ extension ModelDownloader {
490505
"No model found with name: \(name)"
491506
}
492507

508+
static let invalidModelName = { (name: String) in
509+
"Invalid model name: \(name)"
510+
}
511+
493512
static let modelDownloadFailed = { (error: Error) in
494513
"Model download failed with error: \(error)"
495514
}
496515

516+
static let permissionDenied = "Invalid or missing permissions to download model."
497517
static let outdatedModelPath = "Outdated model paths in local storage."
498518
static let deletedLocalModelInfo =
499519
"Model unavailable due to deleted local model info."

FirebaseMLModelDownloader/Sources/ModelFileManager.swift

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
import Foundation
1616

1717
/// Manager for common file operations.
18-
class ModelFileManager: FileManager {
18+
// TODO: Consider mocking this for tests?
19+
enum ModelFileManager {
1920
private static let nameSeparator = "__"
2021
private static let modelNamePrefix = "fbml_model"
2122
private static let fileManager = FileManager.default
@@ -61,7 +62,7 @@ class ModelFileManager: FileManager {
6162
}
6263

6364
/// Move file at a location to another location.
64-
static func moveFile(at sourceURL: URL, to destinationURL: URL) throws {
65+
static func moveFile(at sourceURL: URL, to destinationURL: URL, size: Int64) throws {
6566
if isFileReachable(at: destinationURL) {
6667
do {
6768
try fileManager.removeItem(at: destinationURL)
@@ -75,7 +76,9 @@ class ModelFileManager: FileManager {
7576
}
7677
}
7778
do {
78-
try FileManager.default.moveItem(at: sourceURL, to: destinationURL)
79+
try fileManager.moveItem(at: sourceURL, to: destinationURL)
80+
} catch CocoaError.fileWriteOutOfSpace {
81+
throw DownloadError.notEnoughSpace
7982
} catch {
8083
throw DownloadError
8184
.internalError(description: ModelFileManager
@@ -134,5 +137,13 @@ extension ModelFileManager {
134137
static let replaceFile = { (error: String) in
135138
"Could not replace existing model file: \(error)"
136139
}
140+
141+
static let availableStorage = { (error: String?) -> String in
142+
if let error = error {
143+
return "Failed to check storage capacity on device: \(error)"
144+
} else {
145+
return "Failed to check storage capacity on device."
146+
}
147+
}
137148
}
138149
}

FirebaseMLModelDownloader/Sources/ModelInfoRetriever.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,10 @@ class ModelInfoRetriever {
186186
return
187187
}
188188
completion(.success(.notModified))
189+
case 400:
190+
completion(.failure(.invalidArgument))
191+
case 401, 403:
192+
completion(.failure(.permissionDenied))
189193
case 404:
190194
completion(.failure(.notFound))
191195
// TODO: Handle more http status codes

FirebaseMLModelDownloader/Sources/TelemetryLogger.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ extension SystemInfo {
2323
// TODO: Reconsider using app version.
2424
let appVersionKey = "CFBundleShortVersionString"
2525
appVersion = Bundle.main.infoDictionary?[appVersionKey] as? String ?? "unknownAppVersion"
26-
// TODO: May also need to log SDK version.
26+
// TODO: Need to log SDK version.
2727
self.apiKey = apiKey ?? "unknownAPIKey"
2828
firebaseProjectID = projectID ?? "unknownProjectID"
2929
}

0 commit comments

Comments
 (0)