Skip to content

Commit 8fcd37c

Browse files
authored
Clear cache (#2313)
* Clean up/remove failed or completed download attempts. Removes old download attempts from the download managers and resets the models in shared preferences. * fix guarded by issues * minor updates * Code to handle weird states caused by cache clearing. * fix merge error * remove thrown exception - pass back as task exception. * remove thrown exception - pass back as task exception. * fixing incorrect upload word usage.
1 parent 3dee255 commit 8fcd37c

File tree

9 files changed

+433
-179
lines changed

9 files changed

+433
-179
lines changed

firebase-ml-modeldownloader/api.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ package com.google.firebase.ml.modeldownloader {
5656
method @NonNull public com.google.android.gms.tasks.Task<java.lang.Void> deleteDownloadedModel(@NonNull String);
5757
method @NonNull public static com.google.firebase.ml.modeldownloader.FirebaseModelDownloader getInstance();
5858
method @NonNull public static com.google.firebase.ml.modeldownloader.FirebaseModelDownloader getInstance(@NonNull com.google.firebase.FirebaseApp);
59-
method @NonNull public com.google.android.gms.tasks.Task<com.google.firebase.ml.modeldownloader.CustomModel> getModel(@NonNull String, @NonNull com.google.firebase.ml.modeldownloader.DownloadType, @Nullable com.google.firebase.ml.modeldownloader.CustomModelDownloadConditions) throws java.lang.Exception;
59+
method @NonNull public com.google.android.gms.tasks.Task<com.google.firebase.ml.modeldownloader.CustomModel> getModel(@NonNull String, @NonNull com.google.firebase.ml.modeldownloader.DownloadType, @Nullable com.google.firebase.ml.modeldownloader.CustomModelDownloadConditions);
6060
method @NonNull public com.google.android.gms.tasks.Task<java.util.Set<com.google.firebase.ml.modeldownloader.CustomModel>> listDownloadedModels();
6161
method public void setStatsCollectionEnabled(boolean);
6262
}

firebase-ml-modeldownloader/src/main/java/com/google/firebase/ml/modeldownloader/CustomModel.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
/**
2525
* Used to store information about custom models that are being downloaded or are already downloaded
2626
* on a device. The model file associated with this model can be updated, once the new model file is
27-
* fully uploaded, the original model file will be removed as soon as it is safe to do so.
27+
* fully downloaded, the original model file will be removed as soon as it is safe to do so.
2828
*/
2929
public class CustomModel {
3030
private final String name;
@@ -126,7 +126,7 @@ public String getName() {
126126
* The local model file. If null is returned, use the download Id to check the download status.
127127
*
128128
* @return the local file associated with the model, if the original file download is still in
129-
* progress, returns null, if file update is in progress returns last fully uploaded model.
129+
* progress, returns null, if file update is in progress returns last fully downloaded model.
130130
*/
131131
@Nullable
132132
public File getFile() throws FirebaseMlException {
@@ -137,7 +137,7 @@ public File getFile() throws FirebaseMlException {
137137
* The local model file. If null is returned, use the download Id to check the download status.
138138
*
139139
* @return the local file associated with the model. If the original file download is still in
140-
* progress, returns null. If file update is in progress, returns the last fully uploaded
140+
* progress, returns null. If file update is in progress, returns the last fully downloaded
141141
* model.
142142
*/
143143
@Nullable
@@ -160,9 +160,17 @@ File getFile(ModelFileDownloadService fileDownloadService) throws FirebaseMlExce
160160
return modelFile;
161161
}
162162

163+
boolean isModelFilePresent() {
164+
try {
165+
return getFile() != null;
166+
} catch (Exception ex) {
167+
return false;
168+
}
169+
}
170+
163171
/**
164172
* The size of the file currently associated with this model. If a download is in progress, this
165-
* will be the size of the current model, not the new model currently being uploaded.
173+
* will be the size of the current model, not the new model currently being downloaded.
166174
*
167175
* @return the local model size
168176
*/
@@ -209,7 +217,7 @@ public String toString() {
209217
if (downloadUrl != null && !downloadUrl.isEmpty()) {
210218
stringHelper.add("downloadUrl", downloadUrl);
211219
}
212-
if (downloadUrlExpiry != 0L && !localFilePath.isEmpty()) {
220+
if (downloadUrlExpiry != 0L) {
213221
stringHelper.add("downloadUrlExpiry", downloadUrlExpiry);
214222
}
215223

firebase-ml-modeldownloader/src/main/java/com/google/firebase/ml/modeldownloader/FirebaseModelDownloader.java

Lines changed: 152 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -121,34 +121,114 @@ public static FirebaseModelDownloader getInstance(@NonNull FirebaseApp app) {
121121
public Task<CustomModel> getModel(
122122
@NonNull String modelName,
123123
@NonNull DownloadType downloadType,
124-
@Nullable CustomModelDownloadConditions conditions)
125-
throws Exception {
126-
CustomModel localModel = sharedPreferencesUtil.getCustomModelDetails(modelName);
127-
if (localModel == null) {
124+
@Nullable CustomModelDownloadConditions conditions) {
125+
CustomModel localModelDetails = getLocalModelDetails(modelName);
126+
if (localModelDetails == null) {
128127
// no local model - get latest.
129128
return getCustomModelTask(modelName, conditions);
130129
}
131130

132131
switch (downloadType) {
133132
case LOCAL_MODEL:
134-
return Tasks.forResult(localModel);
133+
return getCompletedLocalCustomModelTask(localModelDetails);
135134
case LATEST_MODEL:
136135
// check for latest model, wait for download if newer model exists
137-
return getCustomModelTask(modelName, conditions, localModel.getModelHash());
136+
return getCustomModelTask(modelName, conditions, localModelDetails.getModelHash());
138137
case LOCAL_MODEL_UPDATE_IN_BACKGROUND:
139-
// start download in back ground, return local model
140-
getCustomModelTask(modelName, conditions, localModel.getModelHash());
141-
return Tasks.forResult(localModel);
138+
// start download in background, if newer model exists
139+
getCustomModelTask(modelName, conditions, localModelDetails.getModelHash());
140+
return getCompletedLocalCustomModelTask(localModelDetails);
142141
}
143-
throw new IllegalArgumentException(
144-
"Unsupported downloadType, please chose LOCAL_MODEL, LATEST_MODEL, or LOCAL_MODEL_UPDATE_IN_BACKGROUND");
142+
return Tasks.forException(
143+
new FirebaseMlException(
144+
"Unsupported downloadType, please chose LOCAL_MODEL, LATEST_MODEL, or LOCAL_MODEL_UPDATE_IN_BACKGROUND",
145+
FirebaseMlException.INVALID_ARGUMENT));
146+
}
147+
148+
/**
149+
* Checks the local model, if a completed download exists - returns this model. Else if a download
150+
* is in progress returns the downloading model version. Otherwise, this model is in a bad state -
151+
* clears the model and return null
152+
*
153+
* @param modelName - name of the model
154+
* @return the local model with file downloaded details or null if no local model.
155+
*/
156+
@Nullable
157+
private CustomModel getLocalModelDetails(@NonNull String modelName) {
158+
CustomModel localModel = sharedPreferencesUtil.getCustomModelDetails(modelName);
159+
if (localModel == null) {
160+
return null;
161+
}
162+
163+
// valid model file exists when local file path is set
164+
if (localModel.getLocalFilePath() != null && localModel.isModelFilePresent()) {
165+
return localModel;
166+
}
167+
168+
// download is in progress - return downloading model details
169+
if (localModel.getDownloadId() != 0) {
170+
return sharedPreferencesUtil.getDownloadingCustomModelDetails(modelName);
171+
}
172+
173+
// bad model state - delete all existing details and return null
174+
deleteModelDetails(localModel.getName());
175+
return null;
176+
}
177+
178+
// Given a model, if the local file path is present, return model.
179+
// Else if there is a file download is in progress, returns the download task.
180+
// Otherwise reset model and return null - this should not happen.
181+
private Task<CustomModel> getCompletedLocalCustomModelTask(@NonNull CustomModel model) {
182+
// model file exists - use this
183+
if (model.isModelFilePresent()) {
184+
return Tasks.forResult(model);
185+
}
186+
187+
// download in progress - return the downloading task.
188+
if (model.getDownloadId() != 0) {
189+
190+
// download in progress - find existing download task and wait for it to complete.
191+
Task<Void> downloadInProgressTask =
192+
fileDownloadService.getExistingDownloadTask(model.getDownloadId());
193+
194+
if (downloadInProgressTask != null) {
195+
return downloadInProgressTask.continueWithTask(
196+
executor,
197+
downloadTask -> {
198+
if (downloadTask.isSuccessful()) {
199+
return finishModelDownload(model.getName());
200+
} else if (downloadTask.getException() instanceof FirebaseMlException) {
201+
return Tasks.forException((FirebaseMlException) downloadTask.getException());
202+
}
203+
return Tasks.forException(
204+
new FirebaseMlException(
205+
"Model download failed for " + model.getName(),
206+
FirebaseMlException.INTERNAL));
207+
});
208+
}
209+
210+
// maybe download just completed - fetch latest model to check.
211+
CustomModel latestModel = sharedPreferencesUtil.getCustomModelDetails(model.getName());
212+
if (latestModel != null && latestModel.isModelFilePresent()) {
213+
return Tasks.forResult(latestModel);
214+
}
215+
}
216+
217+
// bad model state - delete all existing model details and return exception
218+
return deleteDownloadedModel(model.getName())
219+
.continueWithTask(
220+
executor,
221+
deletionTask ->
222+
Tasks.forException(
223+
new FirebaseMlException(
224+
"Model download in bad state - please retry",
225+
FirebaseMlException.INTERNAL)));
145226
}
146227

147228
// This version of getCustomModelTask will always call the modelDownloadService and upon
148229
// success will then trigger file download.
149230
private Task<CustomModel> getCustomModelTask(
150-
@NonNull String modelName, @Nullable CustomModelDownloadConditions conditions)
151-
throws Exception {
231+
@NonNull String modelName, @Nullable CustomModelDownloadConditions conditions) {
152232
return getCustomModelTask(modelName, conditions, null);
153233
}
154234

@@ -157,8 +237,12 @@ private Task<CustomModel> getCustomModelTask(
157237
private Task<CustomModel> getCustomModelTask(
158238
@NonNull String modelName,
159239
@Nullable CustomModelDownloadConditions conditions,
160-
@Nullable String modelHash)
161-
throws Exception {
240+
@Nullable String modelHash) {
241+
CustomModel currentModel = sharedPreferencesUtil.getCustomModelDetails(modelName);
242+
if (currentModel == null && modelHash != null) {
243+
// todo(annzimmer) log something about mismatched state and use hash = null
244+
modelHash = null;
245+
}
162246
Task<CustomModel> incomingModelDetails =
163247
modelDownloadService.getCustomModelDetails(
164248
firebaseOptions.getProjectId(), modelName, modelHash);
@@ -167,10 +251,22 @@ private Task<CustomModel> getCustomModelTask(
167251
executor,
168252
incomingModelDetailTask -> {
169253
if (incomingModelDetailTask.isSuccessful()) {
170-
CustomModel currentModel = sharedPreferencesUtil.getCustomModelDetails(modelName);
171-
// null means we have the latest model
254+
// null means we have the latest model or we failed to connect.
172255
if (incomingModelDetailTask.getResult() == null) {
173-
return Tasks.forResult(currentModel);
256+
if (currentModel != null) {
257+
return getCompletedLocalCustomModelTask(currentModel);
258+
}
259+
// double check due to timing.
260+
CustomModel updatedModel = sharedPreferencesUtil.getCustomModelDetails(modelName);
261+
if (updatedModel != null) {
262+
return getCompletedLocalCustomModelTask(updatedModel);
263+
}
264+
// clean up model internally
265+
deleteModelDetails(currentModel.getName());
266+
return Tasks.forException(
267+
new FirebaseMlException(
268+
"Possible caching issues: no model associated with " + modelName + ".",
269+
FirebaseMlException.INTERNAL));
174270
}
175271

176272
// if modelHash matches current local model just return local model.
@@ -183,7 +279,7 @@ private Task<CustomModel> getCustomModelTask(
183279
&& currentModel.getLocalFilePath() != null
184280
&& !currentModel.getLocalFilePath().isEmpty()
185281
&& new File(currentModel.getLocalFilePath()).exists()) {
186-
return Tasks.forResult(currentModel);
282+
return getCompletedLocalCustomModelTask(currentModel);
187283
}
188284

189285
// is download already in progress for this hash?
@@ -193,12 +289,14 @@ && new File(currentModel.getLocalFilePath()).exists()) {
193289
if (downloadingModel != null
194290
&& downloadingModel
195291
.getModelHash()
196-
.equals(incomingModelDetails.getResult().getModelHash()))
292+
.equals(incomingModelDetails.getResult().getModelHash())) {
197293
return Tasks.forResult(downloadingModel);
294+
}
295+
// todo(annzimmer) this shouldn't happen unless they are calling the sdk with
296+
// multiple
297+
// sets of download types/conditions.
298+
// this should be a download in progress - add appropriate handling.
198299
}
199-
// todo(annzimmer) this shouldn't happen unless they are calling the sdk with multiple
200-
// sets of download types/conditions.
201-
// this should be a download in progress - add appropriate handling.
202300
}
203301

204302
// start download
@@ -208,24 +306,7 @@ && new File(currentModel.getLocalFilePath()).exists()) {
208306
executor,
209307
downloadTask -> {
210308
if (downloadTask.isSuccessful()) {
211-
// read the updated model
212-
CustomModel updatedModel =
213-
sharedPreferencesUtil.getDownloadingCustomModelDetails(modelName);
214-
if (updatedModel == null) {
215-
// either download failed or it completed really fast.
216-
return Tasks.forResult(
217-
sharedPreferencesUtil.getCustomModelDetails(modelName));
218-
}
219-
// trigger the file to be moved to permanent location
220-
// This handles immediate download and completion.
221-
fileDownloadService.loadNewlyDownloadedModelFile(updatedModel);
222-
updatedModel =
223-
sharedPreferencesUtil.getDownloadingCustomModelDetails(modelName);
224-
// download complete - get current model.
225-
if (updatedModel == null) {
226-
updatedModel = sharedPreferencesUtil.getCustomModelDetails(modelName);
227-
}
228-
return Tasks.forResult(updatedModel);
309+
return finishModelDownload(modelName);
229310
} else {
230311
return retryExpiredUrlDownload(modelName, conditions, downloadTask, 2);
231312
}
@@ -239,13 +320,12 @@ private Task<CustomModel> retryExpiredUrlDownload(
239320
@NonNull String modelName,
240321
@Nullable CustomModelDownloadConditions conditions,
241322
Task<Void> downloadTask,
242-
int retryCounter)
243-
throws Exception {
323+
int retryCounter) {
244324
if (downloadTask.getException().getMessage().contains("Retry: Expired URL")) {
245-
// this is likely an expired url - retry once.
325+
// this is likely an expired url - retry.
246326
Task<CustomModel> retryModelDetails =
247-
modelDownloadService.getCustomModelDetails(
248-
firebaseOptions.getProjectId(), modelName, null);
327+
modelDownloadService.getNewDownloadUrlWithExpiry(
328+
firebaseOptions.getProjectId(), modelName);
249329
// no local model - start download.
250330
return retryModelDetails.continueWithTask(
251331
executor,
@@ -258,13 +338,7 @@ private Task<CustomModel> retryExpiredUrlDownload(
258338
executor,
259339
retryDownloadTask -> {
260340
if (retryDownloadTask.isSuccessful()) {
261-
// read the updated model
262-
CustomModel downloadedModel =
263-
sharedPreferencesUtil.getCustomModelDetails(modelName);
264-
// TODO(annz) trigger file move here as well... right
265-
// now it's temp
266-
// call loadNewlyDownloadedModelFile
267-
return Tasks.forResult(downloadedModel);
341+
return finishModelDownload(modelName);
268342
}
269343
if (retryCounter > 1) {
270344
return retryExpiredUrlDownload(
@@ -280,6 +354,24 @@ private Task<CustomModel> retryExpiredUrlDownload(
280354
return Tasks.forException(new Exception("File download failed."));
281355
}
282356

357+
private Task<CustomModel> finishModelDownload(@NonNull String modelName) {
358+
// read the updated model
359+
CustomModel downloadedModel = sharedPreferencesUtil.getDownloadingCustomModelDetails(modelName);
360+
if (downloadedModel == null) {
361+
// check if latest download completed - if so use current.
362+
downloadedModel = sharedPreferencesUtil.getCustomModelDetails(modelName);
363+
if (downloadedModel == null) {
364+
return Tasks.forException(
365+
new Exception(
366+
"Model (" + modelName + ") expected and not found during download completion."));
367+
}
368+
}
369+
// trigger the file to be moved to permanent location.
370+
fileDownloadService.loadNewlyDownloadedModelFile(downloadedModel);
371+
downloadedModel = sharedPreferencesUtil.getCustomModelDetails(modelName);
372+
return Tasks.forResult(downloadedModel);
373+
}
374+
283375
/**
284376
* Triggers the move to permanent storage of successful model downloads and lists all models
285377
* downloaded to device.
@@ -290,11 +382,7 @@ private Task<CustomModel> retryExpiredUrlDownload(
290382
@NonNull
291383
public Task<Set<CustomModel>> listDownloadedModels() {
292384
// trigger completion of file moves for download files.
293-
try {
294-
fileDownloadService.maybeCheckDownloadingComplete();
295-
} catch (Exception ex) {
296-
System.out.println("Error checking for in progress downloads: " + ex.getMessage());
297-
}
385+
fileDownloadService.maybeCheckDownloadingComplete();
298386

299387
TaskCompletionSource<Set<CustomModel>> taskCompletionSource = new TaskCompletionSource<>();
300388
executor.execute(
@@ -314,13 +402,17 @@ public Task<Void> deleteDownloadedModel(@NonNull String modelName) {
314402
executor.execute(
315403
() -> {
316404
// remove all files associated with this model and then clean up model references.
317-
fileManager.deleteAllModels(modelName);
318-
sharedPreferencesUtil.clearModelDetails(modelName);
405+
deleteModelDetails(modelName);
319406
taskCompletionSource.setResult(null);
320407
});
321408
return taskCompletionSource.getTask();
322409
}
323410

411+
private void deleteModelDetails(@NonNull String modelName) {
412+
fileManager.deleteAllModels(modelName);
413+
sharedPreferencesUtil.clearModelDetails(modelName);
414+
}
415+
324416
/**
325417
* Update the settings which allow logging to firelog.
326418
*

0 commit comments

Comments
 (0)