Skip to content

Commit 6fd2864

Browse files
authored
Clean up/remove failed or completed download attempts. (#2298)
* 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
1 parent 48b41ff commit 6fd2864

File tree

2 files changed

+316
-49
lines changed

2 files changed

+316
-49
lines changed

firebase-ml-modeldownloader/src/main/java/com/google/firebase/ml/modeldownloader/internal/ModelFileDownloadService.java

Lines changed: 112 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import android.os.Build.VERSION;
2727
import android.os.Build.VERSION_CODES;
2828
import android.os.ParcelFileDescriptor;
29+
import android.util.Log;
2930
import android.util.LongSparseArray;
3031
import androidx.annotation.GuardedBy;
3132
import androidx.annotation.NonNull;
@@ -57,6 +58,9 @@
5758
*/
5859
public class ModelFileDownloadService {
5960

61+
private static final String TAG = "ModelFileDownloadSer";
62+
private static final int COMPLETION_BUFFER_IN_MS = 60 * 5 * 1000;
63+
6064
private final DownloadManager downloadManager;
6165
private final Context context;
6266
private final ModelFileManager fileManager;
@@ -125,10 +129,30 @@ public Task<Void> download(
125129

126130
@VisibleForTesting
127131
Task<Void> ensureModelDownloaded(CustomModel customModel) {
128-
// todo check model not already in progress of being downloaded
129-
130-
// todo remove any failed download attempts
131-
132+
// todo add logging for explicitly requested
133+
// check model download already in progress
134+
CustomModel downloadingModel =
135+
sharedPreferencesUtil.getDownloadingCustomModelDetails(customModel.getName());
136+
if (downloadingModel != null && downloadingModel.getDownloadId() != 0) {
137+
Integer statusCode = getDownloadingModelStatusCode(downloadingModel.getDownloadId());
138+
Date now = new Date();
139+
140+
// check if download has completed or still has time to finish.
141+
// Give a buffer above url expiry to continue if in progress.
142+
if (statusCode != null
143+
&& (statusCode == DownloadManager.STATUS_SUCCESSFUL
144+
|| statusCode == DownloadManager.STATUS_FAILED
145+
|| (customModel.getDownloadUrlExpiry()
146+
> (now.getTime() - COMPLETION_BUFFER_IN_MS)))) {
147+
// download in progress for this hash already - return this result.
148+
return registerReceiverForDownloadId(
149+
downloadingModel.getDownloadId(), customModel.getName());
150+
}
151+
}
152+
if (downloadingModel != null) {
153+
// remove failed download attempts
154+
removeOrCancelDownload(downloadingModel.getName(), downloadingModel.getDownloadId());
155+
}
132156
// schedule new download of model file
133157
Long newDownloadId = scheduleModelDownload(customModel);
134158
if (newDownloadId == null) {
@@ -138,18 +162,30 @@ Task<Void> ensureModelDownloaded(CustomModel customModel) {
138162
return registerReceiverForDownloadId(newDownloadId, customModel.getName());
139163
}
140164

165+
/** Removes or cancels the downloading model if exists. */
166+
synchronized void removeOrCancelDownload(String modelName, Long downloadId) {
167+
if (downloadManager != null && downloadId != 0) {
168+
downloadManager.remove(downloadId);
169+
}
170+
sharedPreferencesUtil.setFailedUploadedCustomModelDetails(modelName);
171+
}
172+
141173
private synchronized DownloadBroadcastReceiver getReceiverInstance(
142174
long downloadId, String modelName) {
143-
DownloadBroadcastReceiver receiver = receiverMaps.get(downloadId);
175+
DownloadBroadcastReceiver receiver = this.receiverMaps.get(downloadId);
144176
if (receiver == null) {
145177
receiver =
146178
new DownloadBroadcastReceiver(
147179
downloadId, modelName, getTaskCompletionSourceInstance(downloadId));
148-
receiverMaps.put(downloadId, receiver);
180+
this.receiverMaps.put(downloadId, receiver);
149181
}
150182
return receiver;
151183
}
152184

185+
private synchronized void removeReceiverInstance(long downloadId) {
186+
this.receiverMaps.remove(downloadId);
187+
}
188+
153189
private Task<Void> registerReceiverForDownloadId(long downloadId, String modelName) {
154190
BroadcastReceiver broadcastReceiver = getReceiverInstance(downloadId, modelName);
155191
// It is okay to always register here. Since the broadcast receiver is the same via the lookup
@@ -162,15 +198,25 @@ private Task<Void> registerReceiverForDownloadId(long downloadId, String modelNa
162198

163199
@VisibleForTesting
164200
synchronized TaskCompletionSource<Void> getTaskCompletionSourceInstance(long downloadId) {
165-
TaskCompletionSource<Void> taskCompletionSource = taskCompletionSourceMaps.get(downloadId);
201+
TaskCompletionSource<Void> taskCompletionSource = this.taskCompletionSourceMaps.get(downloadId);
166202
if (taskCompletionSource == null) {
167203
taskCompletionSource = new TaskCompletionSource<>();
168-
taskCompletionSourceMaps.put(downloadId, taskCompletionSource);
204+
this.taskCompletionSourceMaps.put(downloadId, taskCompletionSource);
169205
}
170206

171207
return taskCompletionSource;
172208
}
173209

210+
@VisibleForTesting
211+
synchronized boolean existTaskCompletionSourceInstance(long downloadId) {
212+
TaskCompletionSource<Void> taskCompletionSource = this.taskCompletionSourceMaps.get(downloadId);
213+
return (taskCompletionSource != null);
214+
}
215+
216+
private synchronized void removeTaskCompletionSourceInstance(long downloadId) {
217+
this.taskCompletionSourceMaps.remove(downloadId);
218+
}
219+
174220
@VisibleForTesting
175221
synchronized Long scheduleModelDownload(@NonNull CustomModel customModel) {
176222
if (downloadManager == null) {
@@ -253,8 +299,8 @@ private synchronized ParcelFileDescriptor getDownloadedFile(Long downloadingId)
253299
try {
254300
fileDescriptor = downloadManager.openDownloadedFile(downloadingId);
255301
} catch (FileNotFoundException e) {
256-
// todo(annz)
257-
System.out.println("Downloaded file is not found");
302+
// todo replace with FirebaseMlException
303+
Log.d(TAG, "Downloaded file is not found: " + e);
258304
}
259305
return fileDescriptor;
260306
}
@@ -286,12 +332,18 @@ public File loadNewlyDownloadedModelFile(CustomModel model) throws FirebaseMlExc
286332
String downloadingModelHash = model.getModelHash();
287333

288334
if (downloadingId == 0 || downloadingModelHash.isEmpty()) {
289-
// no downloading model file or incomplete info.
335+
// Clear the downloading info completely.
336+
// It handles the case: developer clear the app cache but downloaded model file in
337+
// DownloadManager's cache would not be cleared.
338+
removeOrCancelDownload(model.getName(), model.getDownloadId());
290339
return null;
291340
}
292341

293342
Integer statusCode = getDownloadingModelStatusCode(downloadingId);
294343
if (statusCode == null) {
344+
Log.d(TAG, "Download failed - no download status available.");
345+
// No status code, it may mean no such download or no download manager.
346+
removeOrCancelDownload(model.getName(), model.getDownloadId());
295347
return null;
296348
}
297349

@@ -301,16 +353,21 @@ public File loadNewlyDownloadedModelFile(CustomModel model) throws FirebaseMlExc
301353
if (fileDescriptor == null) {
302354
// reset original model - removing download id.
303355
sharedPreferencesUtil.setFailedUploadedCustomModelDetails(model.getName());
304-
// todo call the download register?
356+
removeOrCancelDownload(model.getName(), model.getDownloadId());
357+
// todo log this?
305358
return null;
306359
}
307360

308361
// Try to move it to destination folder.
309-
File newModelFile = fileManager.moveModelToDestinationFolder(model, fileDescriptor);
362+
File newModelFile;
363+
try {
364+
// TODO add logging
365+
newModelFile = fileManager.moveModelToDestinationFolder(model, fileDescriptor);
366+
} finally {
367+
removeOrCancelDownload(model.getName(), model.getDownloadId());
368+
}
310369

311370
if (newModelFile == null) {
312-
// reset original model - removing download id.
313-
// todo call the download register?
314371
sharedPreferencesUtil.setFailedUploadedCustomModelDetails(model.getName());
315372
return null;
316373
}
@@ -323,9 +380,9 @@ public File loadNewlyDownloadedModelFile(CustomModel model) throws FirebaseMlExc
323380
// todo(annzimmer) Cleans up the old files if it is the initial creation.
324381
return newModelFile;
325382
} else if (statusCode == DownloadManager.STATUS_FAILED) {
326-
// reset original model - removing download id.
383+
// reset original model - removing downloading details.
327384
sharedPreferencesUtil.setFailedUploadedCustomModelDetails(model.getName());
328-
// todo - determine if the temp files need to be clean up? Does one exist?
385+
removeOrCancelDownload(model.getName(), model.getDownloadId());
329386
}
330387
// Other cases, return as null and wait for download finish.
331388
return null;
@@ -398,6 +455,13 @@ public void onReceive(Context context, Intent intent) {
398455
}
399456

400457
Integer statusCode = getDownloadingModelStatusCode(downloadId);
458+
// check to prevent DuplicateTaskCompletionException - this was already updated and removed.
459+
// Just return.
460+
if (!existTaskCompletionSourceInstance(downloadId)) {
461+
removeReceiverInstance(downloadId);
462+
return;
463+
}
464+
401465
synchronized (ModelFileDownloadService.this) {
402466
try {
403467
context.getApplicationContext().unregisterReceiver(this);
@@ -409,31 +473,45 @@ public void onReceive(Context context, Intent intent) {
409473
// move on.
410474
}
411475

412-
receiverMaps.remove(downloadId);
413-
taskCompletionSourceMaps.remove(downloadId);
476+
removeReceiverInstance(downloadId);
477+
removeTaskCompletionSourceInstance(downloadId);
414478
}
415479

416480
if (statusCode != null) {
417481
if (statusCode == DownloadManager.STATUS_FAILED) {
418-
eventLogger.logDownloadFailureWithReason(
419-
sharedPreferencesUtil.getDownloadingCustomModelDetails(modelName),
420-
false,
421-
getFailureReason(id));
422-
if (checkErrorCausedByExpiry(id, modelName)) {
423-
// retry as a new download
424-
// todo change to FirebaseMlException retry error.
425-
taskCompletionSource.setException(new Exception("Retry: Expired URL"));
426-
return;
482+
int failureReason = getFailureReason(id);
483+
CustomModel downloadingModel =
484+
sharedPreferencesUtil.getDownloadingCustomModelDetails(modelName);
485+
if (downloadingModel != null) {
486+
eventLogger.logDownloadFailureWithReason(downloadingModel, false, failureReason);
487+
if (checkErrorCausedByExpiry(downloadingModel.getDownloadUrlExpiry(), failureReason)) {
488+
// retry as a new download
489+
// todo change to FirebaseMlException retry error - or whatever we decide is
490+
// appropriate.
491+
taskCompletionSource.setException(new Exception("Retry: Expired URL"));
492+
return;
493+
}
427494
}
428495
taskCompletionSource.setException(getExceptionAccordingToDownloadManager(id));
429496
return;
430497
}
431498

432499
if (statusCode == DownloadManager.STATUS_SUCCESSFUL) {
500+
CustomModel model = sharedPreferencesUtil.getDownloadingCustomModelDetails(modelName);
501+
if (model == null) {
502+
// model might have been updated already get the downloaded model.
503+
model = sharedPreferencesUtil.getCustomModelDetails(modelName);
504+
if (model == null) {
505+
// todo add logging here.
506+
taskCompletionSource.setException(
507+
new FirebaseMlException(
508+
"No model associated with name: " + modelName,
509+
FirebaseMlException.INVALID_ARGUMENT));
510+
return;
511+
}
512+
}
433513
eventLogger.logDownloadEventWithExactDownloadTime(
434-
sharedPreferencesUtil.getDownloadingCustomModelDetails(modelName),
435-
ErrorCode.NO_ERROR,
436-
DownloadStatus.SUCCEEDED);
514+
model, ErrorCode.NO_ERROR, DownloadStatus.SUCCEEDED);
437515
taskCompletionSource.setResult(null);
438516
return;
439517
}
@@ -443,25 +521,11 @@ public void onReceive(Context context, Intent intent) {
443521
taskCompletionSource.setException(new Exception("Model downloading failed"));
444522
}
445523

446-
private boolean checkErrorCausedByExpiry(Long downloadId, String modelName) {
447-
CustomModel model = sharedPreferencesUtil.getCustomModelDetails(modelName);
448-
449-
if (model == null) {
450-
return false;
451-
}
452-
524+
private boolean checkErrorCausedByExpiry(long downloadUrlExpiry, int failureReason) {
453525
final Date time = new Date();
454526

455-
if (model.getDownloadUrlExpiry() < time.getTime()) {
456-
Cursor cursor =
457-
(downloadManager == null || downloadId == null)
458-
? null
459-
: downloadManager.query(new Query().setFilterById(downloadId));
460-
if (cursor != null && cursor.moveToFirst()) {
461-
int reason = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_REASON));
462-
// 400 implies possibility of url expiry
463-
return (reason == 400);
464-
}
527+
if (failureReason == 400 && downloadUrlExpiry < time.getTime()) {
528+
return true;
465529
}
466530
return false;
467531
}

0 commit comments

Comments
 (0)