diff --git a/odk/collect/collect_app/src/main/java/org/odk/collect/android/formmanagement/FormMediaDownloader.kt b/odk/collect/collect_app/src/main/java/org/odk/collect/android/formmanagement/FormMediaDownloader.kt index 65c443e196b..3b6b363e81e 100644 --- a/odk/collect/collect_app/src/main/java/org/odk/collect/android/formmanagement/FormMediaDownloader.kt +++ b/odk/collect/collect_app/src/main/java/org/odk/collect/android/formmanagement/FormMediaDownloader.kt @@ -3,20 +3,19 @@ package org.odk.collect.android.formmanagement import org.odk.collect.android.utilities.FileUtils.copyFile import org.odk.collect.android.utilities.FileUtils.interuptablyWriteFile import org.odk.collect.async.OngoingWorkListener -import org.odk.collect.forms.Form -import org.odk.collect.forms.FormSource -import org.odk.collect.forms.FormSourceException -import org.odk.collect.forms.FormsRepository -import org.odk.collect.forms.MediaFile +import org.odk.collect.forms.* import org.odk.collect.shared.strings.Md5.getMd5Hash import java.io.File import java.io.IOException +import java.util.concurrent.locks.Lock +import java.util.concurrent.locks.ReentrantLock class FormMediaDownloader( private val formsRepository: FormsRepository, private val formSource: FormSource ) { + private val lock: Lock = ReentrantLock() @Throws(IOException::class, FormSourceException::class, InterruptedException::class) fun download( formToDownload: ServerFormDetails, @@ -39,7 +38,9 @@ class FormMediaDownloader( } else { atLeastOneNewMediaFileDetected = true val file = formSource.fetchMediaFile(mediaFile.downloadUrl) + lock.lock() interuptablyWriteFile(file, tempMediaFile, tempDir, stateListener) + lock.unlock() } } } diff --git a/odk/collect/collect_app/src/main/java/org/odk/collect/android/formmanagement/ServerFormDownloader.java b/odk/collect/collect_app/src/main/java/org/odk/collect/android/formmanagement/ServerFormDownloader.java index eda34cf5aa9..4aec842f4c7 100644 --- a/odk/collect/collect_app/src/main/java/org/odk/collect/android/formmanagement/ServerFormDownloader.java +++ b/odk/collect/collect_app/src/main/java/org/odk/collect/android/formmanagement/ServerFormDownloader.java @@ -22,6 +22,8 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.Supplier; import javax.annotation.Nullable; @@ -46,6 +48,8 @@ public ServerFormDownloader(FormSource formSource, FormsRepository formsReposito this.clock = clock; } + private static Lock lock = new ReentrantLock(); + @Override public void downloadForm(ServerFormDetails form, @Nullable ProgressReporter progressReporter, @Nullable Supplier isCancelled) throws FormDownloadException { Form formOnDevice; @@ -94,8 +98,8 @@ private void processOneForm(ServerFormDetails fd, OngoingWorkListener stateListe // get the xml file // if we've downloaded a duplicate, this gives us the file fileResult = downloadXform(fd.getFormName(), fd.getDownloadUrl(), stateListener, tempDir, formsDirPath); - // download media files if there are any + lock.unlock(); if (fd.getManifest() != null && !fd.getManifest().getMediaFiles().isEmpty()) { FormMediaDownloader mediaDownloader = new FormMediaDownloader(formsRepository, formSource); newAttachmentsDetected = mediaDownloader.download(fd, fd.getManifest().getMediaFiles(), tempMediaPath, tempDir, stateListener); @@ -103,8 +107,10 @@ private void processOneForm(ServerFormDetails fd, OngoingWorkListener stateListe } catch (FormDownloadException.DownloadingInterrupted | InterruptedException e) { Timber.i(e); cleanUp(fileResult, tempMediaPath); + lock.unlock(); throw new FormDownloadException.DownloadingInterrupted(); } catch (IOException e) { + lock.unlock(); throw new FormDownloadException.DiskError(); } @@ -137,9 +143,12 @@ private void processOneForm(ServerFormDetails fd, OngoingWorkListener stateListe } try { + lock.lock(); installEverything(tempMediaPath, fileResult, parsedFields, formsDirPath, newAttachmentsDetected); + lock.unlock(); } catch (FormDownloadException.DiskError e) { cleanUp(fileResult, tempMediaPath); + lock.unlock(); throw e; } } @@ -244,7 +253,7 @@ private Form saveNewForm(Map formInfo, File formFile, String med */ private FileResult downloadXform(String formName, String url, OngoingWorkListener stateListener, File tempDir, String formsDirPath) throws FormSourceException, IOException, FormDownloadException.DownloadingInterrupted, InterruptedException { InputStream xform = formSource.fetchForm(url); - + lock.lock(); String fileName = getFormFileName(formName, formsDirPath); File tempFormFile = new File(tempDir + File.separator + fileName); interuptablyWriteFile(xform, tempFormFile, tempDir, stateListener); diff --git a/odk/collect/collect_app/src/main/java/org/odk/collect/android/tasks/DownloadFormsTask.java b/odk/collect/collect_app/src/main/java/org/odk/collect/android/tasks/DownloadFormsTask.java index f5c25ed43a9..4bc8390e256 100644 --- a/odk/collect/collect_app/src/main/java/org/odk/collect/android/tasks/DownloadFormsTask.java +++ b/odk/collect/collect_app/src/main/java/org/odk/collect/android/tasks/DownloadFormsTask.java @@ -18,6 +18,8 @@ import static java.util.Collections.emptyMap; import android.os.AsyncTask; +import android.os.Handler; +import android.os.Looper; import org.odk.collect.android.R; import org.odk.collect.android.application.Collect; @@ -30,6 +32,9 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; /** * Background task for downloading a given list of forms. We assume right now that the forms are @@ -53,34 +58,54 @@ public DownloadFormsTask(FormDownloader formDownloader) { protected Map doInBackground(ArrayList... values) { HashMap results = new HashMap<>(); + ExecutorService executorService = Executors.newCachedThreadPool(); int index = 1; for (ServerFormDetails serverFormDetails : values[0]) { - try { - String currentFormNumber = String.valueOf(index); - String totalForms = String.valueOf(values[0].size()); - publishProgress(serverFormDetails.getFormName(), currentFormNumber, totalForms); - - formDownloader.downloadForm(serverFormDetails, count -> { - String message = getLocalizedString(Collect.getInstance(), R.string.form_download_progress, - serverFormDetails.getFormName(), - String.valueOf(count), - String.valueOf(serverFormDetails.getManifest().getMediaFiles().size()) - ); - - publishProgress(message, currentFormNumber, totalForms); - }, this::isCancelled); - - results.put(serverFormDetails, null); - FormEventBus.INSTANCE.formDownloaded(serverFormDetails.getFormId()); - } catch (FormDownloadException.DownloadingInterrupted e) { - return emptyMap(); - } catch (FormDownloadException e) { - results.put(serverFormDetails, e); - FormEventBus.INSTANCE.formDownloadFailed(serverFormDetails.getFormId(), e.getMessage()); - } + String currentFormNumber = String.valueOf(index); + String totalForms = String.valueOf(values[0].size()); + publishProgress(serverFormDetails.getFormName(), currentFormNumber, totalForms); + + Thread thread = new Thread() { + public void run() { + + try { + formDownloader.downloadForm(serverFormDetails, count -> { + String message = getLocalizedString(Collect.getInstance(), R.string.form_download_progress, + serverFormDetails.getFormName(), + String.valueOf(count), + String.valueOf(serverFormDetails.getManifest().getMediaFiles().size()) + ); + + publishProgress(message, currentFormNumber, totalForms); + + }, DownloadFormsTask.this::isCancelled); + } catch (FormDownloadException e) { + results.put(serverFormDetails, e); + FormEventBus.INSTANCE.formDownloadFailed(serverFormDetails.getFormId(), e.getMessage()); + throw new RuntimeException(e); + } + + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + results.put(serverFormDetails, null); + FormEventBus.INSTANCE.formDownloaded(serverFormDetails.getFormId()); + } + }); + } + }; + + executorService.submit(thread); index++; } + executorService.shutdown(); + // Wait for all tasks to complete + try { + executorService.awaitTermination(1, TimeUnit.MINUTES); + } catch (InterruptedException e) { + e.printStackTrace(); + } return results; }