1212import com .parse .http .ParseHttpRequest ;
1313import java .io .File ;
1414import java .io .IOException ;
15+ import java .util .ArrayList ;
16+ import java .util .List ;
1517import java .util .concurrent .CancellationException ;
1618import org .json .JSONObject ;
1719
@@ -21,6 +23,8 @@ class ParseFileController {
2123 private final Object lock = new Object ();
2224 private final ParseHttpClient restClient ;
2325 private final File cachePath ;
26+ private final List <String > currentlyDownloadedFilesNames = new ArrayList <>();
27+
2428
2529 private ParseHttpClient fileClient ;
2630
@@ -168,64 +172,79 @@ public Task<File> fetchAsync(
168172 if (cancellationToken != null && cancellationToken .isCancelled ()) {
169173 return Task .cancelled ();
170174 }
171- final File cacheFile = getCacheFile (state );
172- return Task .call (cacheFile ::exists , ParseExecutors .io ())
173- .continueWithTask (
174- task -> {
175- boolean result = task .getResult ();
176- if (result ) {
177- return Task .forResult (cacheFile );
178- }
179- if (cancellationToken != null && cancellationToken .isCancelled ()) {
180- return Task .cancelled ();
181- }
175+ return Task .call (() -> {
176+ final File cacheFile = getCacheFile (state );
177+
178+ synchronized (lock ) {
179+ if (currentlyDownloadedFilesNames .contains (state .name ())) {
180+ while (currentlyDownloadedFilesNames .contains (state .name ())) {
181+ lock .wait ();
182+ }
183+ }
184+
185+ if (cacheFile .exists ()) {
186+ return cacheFile ;
187+ } else {
188+ currentlyDownloadedFilesNames .add (state .name ());
189+ }
190+ }
191+
192+ try {
193+ if (cancellationToken != null && cancellationToken .isCancelled ()) {
194+ throw new CancellationException ();
195+ }
196+
197+ // Generate the temp file path for caching ParseFile content based on
198+ // ParseFile's url
199+ // The reason we do not write to the cacheFile directly is because there
200+ // is no way we can
201+ // verify if a cacheFile is complete or not. If download is interrupted
202+ // in the middle, next
203+ // time when we download the ParseFile, since cacheFile has already
204+ // existed, we will return
205+ // this incomplete cacheFile
206+ final File tempFile = getTempFile (state );
207+
208+ // network
209+ final ParseFileRequest request =
210+ new ParseFileRequest (
211+ ParseHttpRequest .Method .GET , state .url (), tempFile );
212+
213+ // We do not need to delete the temp file since we always try to
214+ // overwrite it
215+ Task <Void > downloadTask = request .executeAsync (
216+ fileClient (),
217+ null ,
218+ downloadProgressCallback ,
219+ cancellationToken
220+ );
221+ ParseTaskUtils .wait (downloadTask );
222+
223+ // If the top-level task was cancelled, don't
224+ // actually set the data -- just move on.
225+ if (cancellationToken != null && cancellationToken .isCancelled ()) {
226+ throw new CancellationException ();
227+ }
228+ if (downloadTask .isFaulted ()) {
229+ ParseFileUtils .deleteQuietly (tempFile );
230+ throw new RuntimeException (downloadTask .getError ());
231+ }
232+
233+ // Since we give the cacheFile pointer to
234+ // developers, it is not safe to guarantee
235+ // cacheFile always does not exist here, so it is
236+ // better to delete it manually,
237+ // otherwise moveFile may throw an exception.
238+ ParseFileUtils .deleteQuietly (cacheFile );
239+ ParseFileUtils .moveFile (tempFile , cacheFile );
240+ return cacheFile ;
241+ } finally {
242+ synchronized (lock ) {
243+ currentlyDownloadedFilesNames .remove (state .name ());
244+ lock .notifyAll ();
245+ }
246+ }
182247
183- // Generate the temp file path for caching ParseFile content based on
184- // ParseFile's url
185- // The reason we do not write to the cacheFile directly is because there
186- // is no way we can
187- // verify if a cacheFile is complete or not. If download is interrupted
188- // in the middle, next
189- // time when we download the ParseFile, since cacheFile has already
190- // existed, we will return
191- // this incomplete cacheFile
192- final File tempFile = getTempFile (state );
193-
194- // network
195- final ParseFileRequest request =
196- new ParseFileRequest (
197- ParseHttpRequest .Method .GET , state .url (), tempFile );
198-
199- // We do not need to delete the temp file since we always try to
200- // overwrite it
201- return request .executeAsync (
202- fileClient (),
203- null ,
204- downloadProgressCallback ,
205- cancellationToken )
206- .continueWithTask (
207- task1 -> {
208- // If the top-level task was cancelled, don't
209- // actually set the data -- just move on.
210- if (cancellationToken != null
211- && cancellationToken .isCancelled ()) {
212- throw new CancellationException ();
213- }
214- if (task1 .isFaulted ()) {
215- ParseFileUtils .deleteQuietly (tempFile );
216- return task1 .cast ();
217- }
218-
219- // Since we give the cacheFile pointer to
220- // developers, it is not safe to guarantee
221- // cacheFile always does not exist here, so it is
222- // better to delete it manually,
223- // otherwise moveFile may throw an exception.
224- ParseFileUtils .deleteQuietly (cacheFile );
225- ParseFileUtils .moveFile (tempFile , cacheFile );
226- return Task .forResult (cacheFile );
227- },
228- ParseExecutors .io ());
229- });
248+ }, ParseExecutors .io ());
230249 }
231250}
0 commit comments